diff --git a/external/cfitsio/License.txt b/external/cfitsio/License.txt new file mode 100644 index 0000000..2f5f48d --- /dev/null +++ b/external/cfitsio/License.txt @@ -0,0 +1,25 @@ +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. diff --git a/external/cfitsio/Makefile.in b/external/cfitsio/Makefile.in new file mode 100644 index 0000000..cf105ca --- /dev/null +++ b/external/cfitsio/Makefile.in @@ -0,0 +1,166 @@ +# +# Makefile for cfitsio library: +# libcfits.a +# +# Oct-96 : original version by +# +# JDD/WDP +# NASA GSFC +# Oct 1996 +# +# 25-Jan-01 : removed conditional drvrsmem.c compilation because this +# is now handled within the source file itself. +# 09-Mar-98 : modified to conditionally compile drvrsmem.c. Also +# changes to target all (deleted clean), added DEFS, LIBS, added +# DEFS to .c.o, added SOURCES_SHMEM and MY_SHMEM, expanded getcol* +# and putcol* in SOURCES, modified OBJECTS, mv changed to /bin/mv +# (to bypass aliasing), cp changed to /bin/cp, add smem and +# testprog targets. See also changes and comments in configure.in +# + +prefix = @prefix@ +exec_prefix = @exec_prefix@ +DESTDIR = +CFITSIO_LIB = ${DESTDIR}@libdir@ +CFITSIO_INCLUDE = ${DESTDIR}@includedir@ +INSTALL_DIRS = @INSTALL_ROOT@ ${CFITSIO_INCLUDE} ${CFITSIO_LIB} ${CFITSIO_LIB}/pkgconfig + + +SHELL = /bin/sh +RANLIB = @RANLIB@ +CC = @CC@ +CFLAGS = @CFLAGS@ +SSE_FLAGS = @SSE_FLAGS@ +FC = @FC@ +LDFLAGS = $(CFLAGS) +DEFS = @DEFS@ +LIBS = @LIBS@ +FLEX = flex +BISON = bison + +SHLIB_LD = @SHLIB_LD@ +SHLIB_SUFFIX = @SHLIB_SUFFIX@ + + +CORE_SOURCES = buffers.c cfileio.c checksum.c drvrfile.c drvrmem.c \ + drvrnet.c drvrsmem.c drvrgsiftp.c editcol.c edithdu.c eval_l.c \ + eval_y.c eval_f.c fitscore.c getcol.c getcolb.c getcold.c getcole.c \ + getcoli.c getcolj.c getcolk.c getcoll.c getcols.c getcolsb.c \ + getcoluk.c getcolui.c getcoluj.c getkey.c group.c grparser.c \ + histo.c iraffits.c \ + modkey.c putcol.c putcolb.c putcold.c putcole.c putcoli.c \ + putcolj.c putcolk.c putcoluk.c putcoll.c putcols.c putcolsb.c \ + putcolu.c putcolui.c putcoluj.c putkey.c region.c scalnull.c \ + swapproc.c wcssub.c wcsutil.c imcompress.c quantize.c ricecomp.c \ + pliocomp.c fits_hcompress.c fits_hdecompress.c zuncompress.c zcompress.c \ + adler32.c crc32.c inffast.c inftrees.c trees.c zutil.c \ + deflate.c infback.c inflate.c uncompr.c \ + +SOURCES = ${CORE_SOURCES} @F77_WRAPPERS@ + +OBJECTS = ${SOURCES:.c=.o} + +CORE_OBJECTS = ${CORE_SOURCES:.c=.o} + + +FITSIO_SRC = f77_wrap1.c f77_wrap2.c f77_wrap3.c f77_wrap4.c + +# ============ description of all targets ============= +# - <<-- ignore error code + +all: + @if [ "x${FC}" = x ]; then \ + ${MAKE} all-nofitsio; \ + else \ + ${MAKE} stand_alone; \ + fi + +all-nofitsio: + ${MAKE} stand_alone "FITSIO_SRC=" + +stand_alone: libcfitsio.a + +libcfitsio.a: ${OBJECTS} + ar rv libcfitsio.a ${OBJECTS}; \ + ${RANLIB} libcfitsio.a; + +shared: libcfitsio${SHLIB_SUFFIX} + +libcfitsio${SHLIB_SUFFIX}: ${OBJECTS} + ${SHLIB_LD} ${LDFLAGS} -o $@ ${OBJECTS} -lm ${LIBS} + +install: libcfitsio.a $(INSTALL_DIRS) + @if [ -f libcfitsio.a ]; then \ + /bin/mv libcfitsio.a ${CFITSIO_LIB}; \ + fi; \ + if [ -f libcfitsio${SHLIB_SUFFIX} ]; then \ + /bin/mv libcfitsio${SHLIB_SUFFIX} ${CFITSIO_LIB}; \ + fi; \ + /bin/cp fitsio.h fitsio2.h longnam.h drvrsmem.h ${CFITSIO_INCLUDE}/; \ + /bin/cp cfitsio.pc ${CFITSIO_LIB}/pkgconfig + +.c.o: + $(CC) -c $(CFLAGS) $(DEFS) $< + +swapproc.o: swapproc.c + $(CC) -c $(CFLAGS) $(SSE_FLAGS) $(DEFS) $< + +smem: smem.o libcfitsio.a ${OBJECTS} + ${CC} $(CFLAGS) $(DEFS) -o smem smem.o -L. -lcfitsio -lm + +testprog: testprog.o libcfitsio.a ${OBJECTS} + ${CC} $(CFLAGS) $(DEFS) -o testprog testprog.o -L. -lcfitsio -lm ${LIBS} + +fpack: fpack.o fpackutil.o libcfitsio.a ${OBJECTS} + ${CC} $(CFLAGS) $(DEFS) -o fpack fpack.o fpackutil.o libcfitsio.a -lm ${LIBS} + +funpack: funpack.o fpackutil.o libcfitsio.a ${OBJECTS} + ${CC} $(CFLAGS) $(DEFS) -o funpack funpack.o fpackutil.o libcfitsio.a -lm ${LIBS} + +fitscopy: fitscopy.o libcfitsio.a ${OBJECTS} + ${CC} $(CFLAGS) $(DEFS) -o fitscopy fitscopy.o -L. -lcfitsio -lm ${LIBS} + +speed: speed.o libcfitsio.a ${OBJECTS} + ${CC} $(CFLAGS) $(DEFS) -o speed speed.o -L. -lcfitsio -lm ${LIBS} + +imcopy: imcopy.o libcfitsio.a ${OBJECTS} + ${CC} $(CFLAGS) $(DEFS) -o imcopy imcopy.o -L. -lcfitsio -lm ${LIBS} + +listhead: listhead.o libcfitsio.a ${OBJECTS} + ${CC} $(CFLAGS) $(DEFS) -o listhead listhead.o -L. -lcfitsio -lm ${LIBS} + +cookbook: cookbook.o libcfitsio.a ${OBJECTS} + ${CC} $(CFLAGS) $(DEFS) -o cookbook cookbook.o -L. -lcfitsio -lm ${LIBS} + +eval: # Rebuild eval_* files from flex/bison source + $(FLEX) -t eval.l > eval_l.c1 + /bin/sed -e 's/yy/ff/g' -e 's/YY/FF/g' eval_l.c1 > eval_l.c + /bin/rm -f eval_l.c1 + $(BISON) -d -v -y eval.y + /bin/sed -e 's/yy/ff/g' -e 's/YY/FF/g' y.tab.c > eval_y.c + /bin/sed -e 's/yy/ff/g' -e 's/YY/FF/g' y.tab.h > eval_tab.h + /bin/rm -f y.tab.c y.tab.h + +clean: + - /bin/rm -f *.o libcfitsio.a libcfitsio${SHLIB_SUFFIX} \ + smem testprog y.output + +distclean: clean + - /bin/rm -f Makefile cfitsio.pc config.* configure.lineno + +# Make target which outputs the list of the .o contained in the cfitsio lib +# usefull to build a single big shared library containing Tcl/Tk and other +# extensions. used for the Tcl Plugin. + +cfitsioLibObjs: + @echo ${CORE_OBJECTS} + +cfitsioLibSrcs: + @echo ${SOURCES} + +# This target actually builds the objects needed for the lib in the above +# case +objs: ${CORE_OBJECTS} + +$(INSTALL_DIRS): + @if [ ! -d $@ ]; then mkdir -p $@; fi diff --git a/external/cfitsio/README b/external/cfitsio/README new file mode 100644 index 0000000..201d53f --- /dev/null +++ b/external/cfitsio/README @@ -0,0 +1,144 @@ + CFITSIO Interface Library + +CFITSIO is a library of ANSI C routines for reading and writing FITS +format data files. A set of Fortran-callable wrapper routines are also +included for the convenience of Fortran programmers. This README file +gives a brief summary of how to build and test CFITSIO, but the CFITSIO +User's Guide, found in the files cfitsio.doc (plain text), cfitsio.tex +(LaTeX source file), cfitsio.ps, or cfitsio.pdf should be +referenced for the latest and most complete information. + +BUILDING CFITSIO +---------------- + +The CFITSIO code is contained in about 40 *.c source files and several *.h +header files. The CFITSIO library is built on Unix systems by typing: + + > ./configure [--prefix=/target/installation/path] + > make (or 'make shared') + > make install (this step is optional) + +at the operating system prompt. The configure command customizes the +Makefile for the particular system, then the `make' command compiles the +source files and builds the library. Type `./configure' and not simply +`configure' to ensure that the configure script in the current directory +is run and not some other system-wide configure script. The optional +'prefix' argument to configure gives the path to the directory where +the CFITSIO library and include files should be installed via the later +'make install' command. For example, + + > ./configure --prefix=/usr1/local + +will cause the 'make install' command to copy the CFITSIO libcfitsio file +to /usr1/local/lib and the necessary include files to /usr1/local/include +(assuming of course that the process has permission to write to these +directories). + +On VAX/VMS and ALPHA/VMS systems the make.com command file may be used +to build the cfitsio.olb object library using the default G-floating +point option for double variables. The make\_dfloat.com and make\_ieee.com +files may be used instead to build the library with the other floating +point options. + +A precompiled DLL version of CFITSIO is available for IBM-PC users of +the Borland or Microsoft Visual C++ compilers in the files +cfitsiodll_xxxx_borland.zip and cfitsiodll_xxxx_vcc.zip, where 'xxxx' +represents the current release number. These zip archives also +contains other files and instructions on how to use the CFITSIO DLL +library. The CFITSIO library may also be built from the source code +using the makefile.bc or makefile.vcc files. Finally, the makepc.bat +file gives an example of building CFITSIO with the Borland C++ v4.5 +compiler using simpler DOS commands. + +When building on Mac OS-X, users should follow the Unix instructions, +above. Previous MacOS versions of the cfitsio library can be built by +(1) un binhex and unstuff cfitsio_mac.sit.hqx, (2) put CFitsioPPC.mcp +in the cfitsio directory, and (3) load CFitsioPPC.mcp into CodeWarrior +Pro 5 and make. This builds the cfitsio library for PPC. There are +also targets for both the test program and the speed test program. + +To use the MacOS port you can add Cfitsio PPC.lib to your Codewarrior +Pro 5 project. Note that this only has been tested for the PPC and +probably won't work + on 68k macs. + +TESTING CFITSIO +--------------- + +The CFITSIO library should be tested by building and running +the testprog.c program that is included with the release. +On Unix systems, type: +- + % make testprog + % testprog > testprog.lis + % diff testprog.lis testprog.out + % cmp testprog.fit testprog.std +- + On VMS systems, +(assuming cc is the name of the C compiler command), type: +- + $ cc testprog.c + $ link testprog, cfitsio/lib + $ run testprog +- +The testprog program should produce a FITS file called `testprog.fit' +that is identical to the testprog.std FITS file included in this +release. The diagnostic messages (which were piped to the file +testprog.lis in the Unix example) should be identical to the listing +contained in the file testprog.out. The 'diff' and 'cmp' commands +shown above should not report any differences in the files. + +USING CFITSIO +------------- + +The CFITSIO User's Guide, contained in the files cfitsio.doc (plain +text file) and cfitsio.ps (postscript file), provides detailed +documentation about how to build and use the CFITSIO library. +It contains a description of every user-callable routine in the +CFITSIO interface. + +The cookbook.c file provides some sample routines for performing common +operations on various types of FITS files. Programmers are urged to +examine these routines for recommended programming practices when using +CFITSIO. Users are free to copy or modify these routines for their own +purposes. + +SUPPORTED PLATFORMS +------------------- + +CFITSIO has currently been tested on the following platforms: + + Operating System Compiler + ---------------- -------- + OPERATING SYSTEM COMPILER + Sun OS gcc and cc (3.0.1) + Sun Solaris gcc and cc + Silicon Graphics IRIX gcc and cc + Silicon Graphics IRIX64 MIPS + Dec Alpha OSF/1 gcc and cc + DECstation Ultrix gcc + Dec Alpha OpenVMS cc + DEC VAX/VMS gcc and cc + HP-UX gcc + IBM AIX gcc + Linux gcc + MkLinux DR3 + Windows 95/98/NT Borland C++ V4.5 + Windows 95/98/NT/ME/XP Microsoft/Compaq Visual C++ v5.0, v6.0 + Windows 95/98/NT Cygwin gcc + OS/2 gcc + EMX + Mac OS 7.1 or greater Metrowerks 10.+ + Mac OS-X 10.1 or greater cc (gcc) + +CFITSIO will probably run on most other Unix platforms without +modification. Cray supercomputers and IBM mainframe computers are +currently not supported. + +Reports of any success or failure to run CFITSIO on other platforms +would be appreciated. Any problem reports or suggestions for +improvements are also welcome and should be sent to the primary author. + +------------------------------------------------------------------------- +William D. Pence +HEASARC, NASA/GSFC +email: William.D.Pence@nasa.gov diff --git a/external/cfitsio/README.MacOS b/external/cfitsio/README.MacOS new file mode 100644 index 0000000..9f73287 --- /dev/null +++ b/external/cfitsio/README.MacOS @@ -0,0 +1,70 @@ +To build CFITSIO library on an Intel Mac as a Universal Binary + +Unzip the library: +- tar xzf cfitsio3060.tar.gz (or whatever version this is) + +- cd cfitsio/ + +- copy the cfitsio-xcodeproj.zip file here + +- unzip cfitsio-xcodeproj.zip + +- start Xcode and open cfitsio.xcodeproj + +- expand the "Targets" menu under "Groups & Files" + +- choose one of the following build options: + + * right-click on Build PPC -> Build "Build PPC" + + * right-click on Build i386 -> Build "Build i386" + + * right-click on Build x86_64 -> Build "Build x86_64" + + * right-click on Build Universal -> Build "Build Universal" + (Builds all three of the above options, i.e. a Universal Binary + usable on ppc, i386, and x86_64 architectures) + + (For some reason clicking on the menu "Build" icon doesn't seem to + work correctly, but the right-click menus do). + +------------------------------------------------------- + +Another way to build the universal binary: + +- unpack the cfitsio source code tar file +- cd cfitsio + +Set the CFLAGS environment variable for building a Universal Binary: + + C-Shell variants: + setenv CFLAGS "-arch ppc -arch i386 -arch x86_64 -g -O2" + + Bourne Shell variants: + export CFLAGS="-arch ppc -arch i386 -arch x86_64 -g -O2" + +Then proceed with the standard cfitsio build, i.e.: + +- ./configure +- make +- make install + + +------------------------------------------------------- + +Below, are the old (and now obsolete) instructions for building CFITSIO +on classic Mac OS-9 or earlier versions: + +1. Un binhex and unstuff cfitsio_mac.sit.hqx +2. put CFitsioPPC.mcp in the cfitsio directory. +2. Load CFitsioPPC.mcp into CodeWarrior Pro 5 and make. + This builds the cfitsio library for PPC. There are also targets for both + the test program and the speed test program. + +To use the MacOS port you can add Cfitsio PPC.lib to your Codewarrior Pro 5 +project. Note that this only has been tested for the PPC. It probably +won't work on 68k macs. Also note that the fortran bindings aren't +included. I haven't worked with the codewarrior f2c plugin so I don't know +how these would work. If one is interested, please write and I can look +into this. + diff --git a/external/cfitsio/README.win32 b/external/cfitsio/README.win32 new file mode 100644 index 0000000..aaca099 --- /dev/null +++ b/external/cfitsio/README.win32 @@ -0,0 +1,74 @@ + Instructions on using CFITSIO on Windows platforms for C programmers + +These instructions use a simple DOS-style command window. It is also possible +to build and use CFITSIO within a GUI programming environment such as Visual +Studio, but this is not supported here. + +=============================================================================== +1. Build the CFITSIO dll library + +This step will create the cfitsio.def, cfitsio.dll, and cfitsio.lib files. +(If you downloaded the CFITSIO .zip file that contains the pre-built binary +.dll file, then SKIP THIS STEP). + + A. With Microsoft Visual C++: + + 1. Open a DOS command window and execute the vcvars32.bat file that + is distributed with older versions of Visual C++, or simply open + the Visual C++ command window (e.g., when using Visual Studio 2010). + + 2. Unpack the CFITSIO source files (cfitxxxx.zip) into a new empty directory + + 3. In the DOS command window, cd to that directory and enter the + following commands: + + nmake winDumpExts.mak + nmake makefile.vcc + (ignore the compiler warning messages) + + B: With Borland C++: + + First, follow the instructions provided by Borland to set up + the proper environment variables and configure files for the compiler. + + Unpack the cfitsio.zip source file distribution into a suitable directory. + + In a DOS command window, cd to that directory and then execute the + makepc.bat batch file on the command line to build the CFITSIO library, + and the testprog and cookbook sample programs. + +=============================================================================== +2. Test the CFITSIO library with Visual C++ + + Compile and link the testprog.c test program. When using Visual Studio, + the command is: + + cl /MD testprog.c cfitsio.lib + + + This will create the testprog.exe executable program. Running this + program should print out a long series of diagnostic messages + that should end with "Status = 0; OK - no error" + +=============================================================================== +3. Compile and link an application program that calls CFITSIO routines + with Visual C++ + + Include the fitsio.h and longnam.h header files in the C source code. + + Link the program with the cfitsio.lib file: + + cl /MD your_program.c cfitsio.lib + + + NOTE: The /MD command line switch must be specified on the cl + command line to force the compiler/linker to use the + appropriete runtime library. If this switch is omitted, then + the fits_report_error function in CFITSIO will likely crash. + + When building programs in the Visual Studio environment, one + can force the equivalent of the /MD switch by selecting + 'Settings...' under the 'Project' menu, then click on the C/C++ + tab and select the 'Code Generator' category. Then under 'User + Run-time Library' select 'Multithreaded DLL'. + diff --git a/external/cfitsio/adler32.c b/external/cfitsio/adler32.c new file mode 100644 index 0000000..172de60 --- /dev/null +++ b/external/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/external/cfitsio/buffers.c b/external/cfitsio/buffers.c new file mode 100644 index 0000000..8d80f46 --- /dev/null +++ b/external/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 +#include +#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/external/cfitsio/cfileio.c b/external/cfitsio/cfileio.c new file mode 100644 index 0000000..168ff59 --- /dev/null +++ b/external/cfitsio/cfileio.c @@ -0,0 +1,6965 @@ +/* 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 +#include +#include +#include +#include +#include /* 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], compspec[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'; + compspec[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. */ + + /* call the newer version of this parsing routine that supports 'compspec' */ + ffifile2(url, urltype, infile, outfile, extspec, + rowfilter, binspec, colspec, pixfilter, compspec, 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); + } + 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); + } + } + + /* parse and save image compression specification, if given */ + if (*compspec) { + ffparsecompspec(*fptr, compspec, 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, inparen; + 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 */ + + /* as of July 2012, the CFITSIO column filter syntax was modified */ + /* so that commas may be used to separate clauses, as well as semi-colons. */ + /* This was done because users cannot enter the semi-colon in the HEASARC's */ + /* Hera on-line data processing system due for computer security reasons. */ + /* Therefore, we must convert those commas back to semi-colons here, but we */ + /* must not convert any columns that occur within parenthesies. */ + + cptr2 = cptr; + inparen = 0; /* flag to indicate we are within a parenthetical clause */ + + while (*cptr2) { + if (*cptr2 == '(') { + inparen++; /* increment the nested-parenthesis counter */ + } else if (*cptr2 == ')') { + inparen--; /* decrement the nested-parenthesis counter */ + } else if (*cptr2 == ',' && !inparen) { + *cptr2 = ';'; /* replace this comma with a semi-colon */ + } + cptr2++; + } + + /* 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 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 does not support the pixfilter or compspec components. +*/ +{ + return ffifile2(url, urltype, infilex, outfile, + extspec, rowfilterx, binspec, colspec, 0, 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 does not support the compspec component. +*/ +{ + return ffifile2(url, urltype, infilex, outfile, + extspec, rowfilterx, binspec, colspec, pixfilter, 0, status); + +} +/*--------------------------------------------------------------------------*/ +int ffifile2(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 */ + char *compspec, /* image compression specification */ + 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, compStart; + + + /* 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'; + if (compspec) *compspec = '\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 a image compression 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")] + + [compress Rice] + + 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; + compStart = 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, "compress ", 9) || + !strncasecmp(tmptr, "compress]", 9) ) + compStart = 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 || + compStart || + (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 */ + } + + /* ------------------------------------------------------------ */ + /* does the filter contain an image compression specification? */ + /* ------------------------------------------------------------ */ + + ptr1 = strstr(rowfilter, "[compress"); + + if (ptr1) + { + ptr2 = ptr1 + 9; /* end of the '[compress' string */ + + if ( *ptr2 != ' ' && *ptr2 != ']') + ptr1 = NULL; /* compress string must be followed by space or ] */ + } + + if (ptr1) + { + /* found the compress string */ + if (compspec) + { + if (strlen(ptr1 +1) > FLEN_FILENAME - 1) + { + free(infile); + return(*status = URL_PARSE_ERROR); + } + + strcpy(compspec, ptr1 + 1); + ptr2 = strchr(compspec, ']'); + + 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 compression spec from the row filter string */ + ptr2 = strchr(ptr1, ']'); + strcpy(tmpstr, ptr2+1); /* copy any chars after the binspec */ + 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/external/cfitsio/cfitsio.doc b/external/cfitsio/cfitsio.doc new file mode 100644 index 0000000..d22704b --- /dev/null +++ b/external/cfitsio/cfitsio.doc @@ -0,0 +1,9535 @@ + CFITSIO - An Interface to FITS Format Files for C Programmers + + William D Pence, HEASARC, NASA/GSFC + Version 3.0 + + +[Note: This file contains various formatting command symbols ('*', '-') +in the first column which are used when generating the LATeX version of +this document.] + +*I. Introduction + +**A. A Brief Overview + +CFITSIO is a machine-independent library of routines for reading and +writing data files in the FITS (Flexible Image Transport System) data +format. It can also read IRAF format image files and raw binary data +arrays by converting them on the fly into a virtual FITS format file. +This library is written in ANSI C and provides a powerful yet simple +interface for accessing FITS files which will run on most commonly used +computers and workstations. CFITSIO supports all the features +described in the official NOST definition of the FITS format and can +read and write all the currently defined types of extensions, including +ASCII tables (TABLE), Binary tables (BINTABLE) and IMAGE extensions. +The CFITSIO routines insulate the programmer from having to deal with +the complicated formatting details in the FITS file, however, it is +assumed that users have a general knowledge about the structure and +usage of FITS files. + +CFITSIO also contains a set of Fortran callable wrapper routines which +allow Fortran programs to call the CFITSIO routines. See the companion +``FITSIO User's Guide'' for the definition of the Fortran subroutine +calling sequences. These wrappers replace the older Fortran FITSIO +library which is no longer supported. + +The CFITSIO package was initially developed by the HEASARC (High Energy +Astrophysics Science Archive Research Center) at the NASA Goddard Space +Flight Center to convert various existing and newly acquired +astronomical data sets into FITS format and to further analyze data +already in FITS format. New features continue to be added to CFITSIO +in large part due to contributions of ideas or actual code from +users of the package. The Integral Science Data Center in Switzerland, +and the XMM/ESTEC project in The Netherlands made especially significant +contributions that resulted in many of the new features that appeared +in v2.0 of CFITSIO. + +**B. Sources of FITS Software and Information + +The latest version of the CFITSIO source code, +documentation, and example programs are available on the World-Wide +Web or via anonymous ftp from: +- + http://heasarc.gsfc.nasa.gov/fitsio + ftp://legacy.gsfc.nasa.gov/software/fitsio/c +- + +Any questions, bug reports, or suggested enhancements related to the CFITSIO +package should be sent to the primary author: +- + Dr. William Pence Telephone: (301) 286-4599 + HEASARC, Code 662 E-mail: William.D.Pence@nasa.gov + NASA/Goddard Space Flight Center + Greenbelt, MD 20771, USA +- +This User's Guide assumes that readers already have a general +understanding of the definition and structure of FITS format files. +Further information about FITS formats is available from the FITS Support +Office at {\tt http://fits.gsfc.nasa.gov}. In particular, the +'NOST FITS Standard' gives the authoritative definition of the FITS data +format, and the `FITS User's Guide' provides additional historical background +and practical advice on using FITS files. + +The HEASARC also provides a very sophisticated FITS file analysis +program called `Fv' which can be used to display and edit the contents +of any FITS file as well as construct new FITS files from scratch. The +display functions in Fv allow users to interactively adjust the +brightness and contrast of images, pan, zoom, and blink images, and +measure the positions and brightnesses of objects within images. FITS +tables can be displayed like a spread sheet, and then modified using +powerful calculator and sorting functions. Fv is freely available for +most Unix platforms, Mac PCs, and Windows PCs. +CFITSIO users may also be interested in the FTOOLS package of programs +that can be used to manipulate and analyze FITS format files. +Fv and FTOOLS are available from their respective Web sites at: +- + http://fv.gsfc.nasa.gov + http://heasarc.gsfc.nasa.gov/ftools +- + +**C. Acknowledgments + +The development of the many powerful features in CFITSIO was made +possible through collaborations with many people or organizations from +around the world. The following in particular have made especially +significant contributions: + +Programmers from the Integral Science Data Center, Switzerland (namely, +Jurek Borkowski, Bruce O'Neel, and Don Jennings), designed the concept +for the plug-in I/O drivers that was introduced with CFITSIO 2.0. The +use of `drivers' greatly simplified the low-level I/O, which in turn +made other new features in CFITSIO (e.g., support for compressed FITS +files and support for IRAF format image files) much easier to +implement. Jurek Borkowski wrote the Shared Memory driver, and Bruce +O'Neel wrote the drivers for accessing FITS files over the network +using the FTP, HTTP, and ROOT protocols. Also, in 2009, Bruce O'Neel +was the key developer of the thread-safe version of CFITSIO. + +The ISDC also provided the template parsing routines (written by Jurek +Borkowski) and the hierarchical grouping routines (written by Don +Jennings). The ISDC DAL (Data Access Layer) routines are layered on +top of CFITSIO and make extensive use of these features. + +Giuliano Taffoni and Andrea Barisani, at INAF, University of Trieste, +Italy, implemented the I/O driver routines for accessing FITS files +on the computational grids using the gridftp protocol. + +Uwe Lammers (XMM/ESA/ESTEC, The Netherlands) designed the +high-performance lexical parsing algorithm that is used to do +on-the-fly filtering of FITS tables. This algorithm essentially +pre-compiles the user-supplied selection expression into a form that +can be rapidly evaluated for each row. Peter Wilson (RSTX, NASA/GSFC) +then wrote the parsing routines used by CFITSIO based on Lammers' +design, combined with other techniques such as the CFITSIO iterator +routine to further enhance the data processing throughput. This effort +also benefited from a much earlier lexical parsing routine that was +developed by Kent Blackburn (NASA/GSFC). More recently, Craig Markwardt +(NASA/GSFC) implemented additional functions (median, average, stddev) +and other enhancements to the lexical parser. + +The CFITSIO iterator function is loosely based on similar ideas +developed for the XMM Data Access Layer. + +Peter Wilson (RSTX, NASA/GSFC) wrote the complete set of +Fortran-callable wrappers for all the CFITSIO routines, which in turn +rely on the CFORTRAN macro developed by Burkhard Burow. + +The syntax used by CFITSIO for filtering or binning input FITS files is +based on ideas developed for the AXAF Science Center Data Model by +Jonathan McDowell, Antonella Fruscione, Aneta Siemiginowska and Bill +Joye. See http://heasarc.gsfc.nasa.gov/docs/journal/axaf7.html for +further description of the AXAF Data Model. + +The file decompression code were taken directly from the gzip (GNU zip) +program developed by Jean-loup Gailly and others. + +The new compressed image data format (where the image is tiled and +the compressed byte stream from each tile is stored in a binary table) +was implemented in collaboration with Richard White (STScI), Perry +Greenfield (STScI) and Doug Tody (NOAO). + +Doug Mink (SAO) provided the routines for converting IRAF format +images into FITS format. + +Martin Reinecke (Max Planck Institute, Garching)) provided the modifications to +cfortran.h that are necessary to support 64-bit integer values when calling +C routines from fortran programs. The cfortran.h macros were originally developed +by Burkhard Burow (CERN). + +Julian Taylor (ESO, Garching) provided the fast byte-swapping algorithms +that use the SSE2 and SSSE3 machine instructions available on x86\_64 CPUs. + +In addition, many other people have made valuable contributions to the +development of CFITSIO. These include (with apologies to others that may +have inadvertently been omitted): + +Steve Allen, Carl Akerlof, Keith Arnaud, Morten Krabbe Barfoed, Kent +Blackburn, G Bodammer, Romke Bontekoe, Lucio Chiappetti, Keith Costorf, +Robin Corbet, John Davis, Richard Fink, Ning Gan, Emily Greene, Gretchen +Green, Joe Harrington, Cheng Ho, Phil Hodge, Jim Ingham, Yoshitaka +Ishisaki, Diab Jerius, Mark Levine, Todd Karakaskian, Edward King, +Scott Koch, Claire Larkin, Rob Managan, Eric Mandel, Richard Mathar, +John Mattox, Carsten Meyer, Emi Miyata, Stefan Mochnacki, Mike Noble, +Oliver Oberdorf, Clive Page, Arvind Parmar, Jeff Pedelty, Tim Pearson, +Philippe Prugniel, Maren Purves, Scott Randall, Chris Rogers, Arnold Rots, +Rob Seaman, Barry Schlesinger, Robin Stebbins, Andrew Szymkowiak, Allyn Tennant, +Peter Teuben, James Theiler, Doug Tody, Shiro Ueno, Steve Walton, Archie +Warnock, Alan Watson, Dan Whipple, Wim Wimmers, Peter Young, Jianjun Xu, +and Nelson Zarate. + +**D. Legal Stuff + +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." + +*II. Creating the CFITSIO Library + +**A. Building the Library + +The CFITSIO code is contained in about 40 C source files (*.c) and header +files (*.h). On VAX/VMS systems 2 assembly-code files (vmsieeed.mar and +vmsieeer.mar) are also needed. + +CFITSIO has currently been tested on the following platforms (not up-to-date): +- + OPERATING SYSTEM COMPILER + Sun OS gcc and cc (3.0.1) + Sun Solaris gcc and cc + Silicon Graphics IRIX gcc and cc + Silicon Graphics IRIX64 MIPS + Dec Alpha OSF/1 gcc and cc + DECstation Ultrix gcc + Dec Alpha OpenVMS cc + DEC VAX/VMS gcc and cc + HP-UX gcc + IBM AIX gcc + Linux gcc + MkLinux DR3 + Windows 95/98/NT Borland C++ V4.5 + Windows 95/98/NT/ME/XP Microsoft/Compaq Visual C++ v5.0, v6.0 + Windows 95/98/NT Cygwin gcc + MacOS 7.1 or greater Metrowerks 10.+ + MacOS-X 10.1 or greater cc (gcc) +- +CFITSIO will probably run on most other Unix platforms. Cray +supercomputers are currently not supported. + +***1. Unix Systems + +The CFITSIO library is built on Unix systems by typing: +- + > ./configure [--prefix=/target/installation/path] [--enable-reentrant] + [--enable-sse2] [--enable-ssse3] + > make (or 'make shared') + > make install (this step is optional) +- +at the operating system prompt. The configure command customizes the +Makefile for the particular system, then the `make' command compiles the +source files and builds the library. Type `./configure' and not simply +`configure' to ensure that the configure script in the current directory +is run and not some other system-wide configure script. The optional +'prefix' argument to configure gives the path to the directory where +the CFITSIO library and include files should be installed via the later +'make install' command. For example, +- + > ./configure --prefix=/usr1/local +- +will cause the 'make install' command to copy the CFITSIO libcfitsio file +to /usr1/local/lib and the necessary include files to /usr1/local/include +(assuming of course that the process has permission to write to these +directories). + +The optional --enable-reentrant flag will attempt to configure CFITSIO +so that it can be used in multi-threaded programs. See the "Using CFITSIO in Multi-threaded Environments" section, below, for more details. + +The optional --enable-sse2 and --enable-ssse3 flags will cause configure to +attempt to build CFITSIO using faster byte-swapping algorithms. +See the "Optimizing Programs" chapter of this manual for +more information about these options. + +The 'make shared' option builds a shared or dynamic version of the +CFITSIO library. When using the shared library the executable code is +not copied into your program at link time and instead the program +locates the necessary library code at run time, normally through +LD\_LIBRARY\_PATH or some other method. The advantages of using a shared +library are: +- + 1. Less disk space if you build more than 1 program + 2. Less memory if more than one copy of a program using the shared + library is running at the same time since the system is smart + enough to share copies of the shared library at run time. + 3. Possibly easier maintenance since a new version of the shared + library can be installed without relinking all the software + that uses it (as long as the subroutine names and calling + sequences remain unchanged). + 4. No run-time penalty. +- +The disadvantages are: +- + 1. More hassle at runtime. You have to either build the programs + specially or have LD_LIBRARY_PATH set right. + 2. There may be a slight start up penalty, depending on where you are + reading the shared library and the program from and if your CPU is + either really slow or really heavily loaded. +- + +On Mac OS X platforms the 'make shared' command works like on other +UNIX platforms, but a .dylib file will be created instead of .so. If +installed in a nonstandard location, add its location to the +DYLD\_LIBRARY\_PATH environment variable so that the library can be found +at run time. + +On HP/UX systems, the environment variable CFLAGS should be set +to -Ae before running configure to enable "extended ANSI" features. + +By default, a set of Fortran-callable wrapper routines are +also built and included in the CFITSIO library. If these wrapper +routines are not needed (i.e., the CFITSIO library will not +be linked to any Fortran applications which call FITSIO subroutines) +then they may be omitted from the build by typing 'make all-nofitsio' +instead of simply typing 'make'. This will reduce the size +of the CFITSIO library slightly. + +It may not be possible to statically link programs that use CFITSIO on +some platforms (namely, on Solaris 2.6) due to the network drivers +(which provide FTP and HTTP access to FITS files). It is possible to +make both a dynamic and a static version of the CFITSIO library, but +network file access will not be possible using the static version. + +***2. VMS + +On VAX/VMS and ALPHA/VMS systems the make\_gfloat.com command file may +be executed to build the cfitsio.olb object library using the default +G-floating point option for double variables. The make\_dfloat.com and +make\_ieee.com files may be used instead to build the library with the +other floating point options. Note that the getcwd function that is +used in the group.c module may require that programs using CFITSIO be +linked with the ALPHA\$LIBRARY:VAXCRTL.OLB library. See the example +link line in the next section of this document. + +***3. Windows PCs + +A precompiled DLL version of CFITSIO is available for IBM-PC users of +the Borland or Microsoft Visual C++ compilers in the files +cfitsiodll\_3xxx\_borland.zip and cfitsiodll\_3xxx\_vcc.zip, where +'3xxx' represents the current release number. These zip archives also +contains other files and instructions on how to use the CFITSIO DLL +library. + +The CFITSIO library may also be built from the source code using the +makefile.bc or makefile.vcc files. Finally, the makepc.bat file gives +an example of building CFITSIO with the Borland C++ v4.5 or v5.5 compiler +using older DOS commands. + +***4. Macintosh PCs + +When building on Mac OS-X, users should follow the Unix instructions, +above. See the README.MacOS file for instructions on building a Universal +Binary that supports both Intel and PowerPC CPUs. + +**B. Testing the Library + +The CFITSIO library should be tested by building and running +the testprog.c program that is included with the release. +On Unix systems, type: +- + % make testprog + % testprog > testprog.lis + % diff testprog.lis testprog.out + % cmp testprog.fit testprog.std +- + On VMS systems, +(assuming cc is the name of the C compiler command), type: +- + $ cc testprog.c + $ link testprog, cfitsio/lib, alpha$library:vaxcrtl/lib + $ run testprog +- +The test program should produce a FITS file called `testprog.fit' +that is identical to the `testprog.std' FITS file included with this +release. The diagnostic messages (which were piped to the file +testprog.lis in the Unix example) should be identical to the listing +contained in the file testprog.out. The 'diff' and 'cmp' commands +shown above should not report any differences in the files. (There +may be some minor format differences, such as the presence or +absence of leading zeros, or 3 digit exponents in numbers, +which can be ignored). + +The Fortran wrappers in CFITSIO may be tested with the testf77 +program on Unix systems with: +- + % f77 -o testf77 testf77.f -L. -lcfitsio -lnsl -lsocket + or + % f77 -f -o testf77 testf77.f -L. -lcfitsio (under SUN O/S) + or + % f77 -o testf77 testf77.f -Wl,-L. -lcfitsio -lm -lnsl -lsocket (HP/UX) + + % testf77 > testf77.lis + % diff testf77.lis testf77.out + % cmp testf77.fit testf77.std +- +On machines running SUN O/S, Fortran programs must be compiled with the +'-f' option to force double precision variables to be aligned on 8-byte +boundarys to make the fortran-declared variables compatible with C. A +similar compiler option may be required on other platforms. Failing to +use this option may cause the program to crash on FITSIO routines that +read or write double precision variables. + +Also note that on some systems, the output listing of the testf77 +program may differ slightly from the testf77.std template, if leading +zeros are not printed by default before the decimal point when using F +format. + +A few other utility programs are included with CFITSIO; the first four +of this programs can be compiled an linked by typing `make +program\_name' where `program\_name' is the actual name of the program: +- + speed - measures the maximum throughput (in MB per second) + for writing and reading FITS files with CFITSIO. + + listhead - lists all the header keywords in any FITS file + + fitscopy - copies any FITS file (especially useful in conjunction + with the CFITSIO's extended input filename syntax). + + cookbook - a sample program that performs common read and + write operations on a FITS file. + + iter_a, iter_b, iter_c - examples of the CFITSIO iterator routine +- + +**C. Linking Programs with CFITSIO + +When linking applications software with the CFITSIO library, several +system libraries usually need to be specified on the link command +line. On Unix systems, the most reliable way to determine what +libraries are required is to type 'make testprog' and see what +libraries the configure script has added. The typical libraries that +need to be added are -lm (the math library) and -lnsl and -lsocket +(needed only for FTP and HTTP file access). These latter 2 libraries +are not needed on VMS and Windows platforms, because FTP file access is +not currently supported on those platforms. + +Note that when upgrading to a newer version of CFITSIO it is usually +necessary to recompile, as well as relink, the programs that use CFITSIO, +because the definitions in fitsio.h often change. + +**D. Using CFITSIO in Multi-threaded Environments + +CFITSIO can be used either with the +POSIX pthreads interface or the OpenMP interface for multi-threaded +parallel programs. When used in a multi-threaded environment, +the CFITSIO library *must* be built using +the -D\_REENTRANT compiler directive. This can be done using the following +build commands: +- + >./configure --enable-reentrant + > make +- +A function called fits\_is\_reentrant is available to test +whether or not CFITSIO was compiled with the -D\_REENTRANT +directive. When this feature is enabled, multiple threads can +call any of the CFITSIO routines +to simultaneously read or write separate +FITS files. Multiple threads can also read data from +the same FITS file simultaneously, as long as the file +was opened independently by each thread. This relies on +the operating system to correctly deal with reading the +same file by multiple processes. Different threads should +not share the same 'fitsfile' pointer to read an opened +FITS file, unless locks are placed around the calls to +the CFITSIO reading routines. +Different threads should never try to write to the same +FITS file. + +**E. Getting Started with CFITSIO + +In order to effectively use the CFITSIO library it is recommended that +new users begin by reading the ``CFITSIO Quick Start Guide''. It +contains all the basic information needed to write programs that +perform most types of operations on FITS files. The set of example +FITS utility programs that are available from the CFITSIO web site are +also very useful for learning how to use CFITSIO. To learn even more +about the capabilities of the CFITSIO library the following steps are +recommended: + +1. Read the following short `FITS Primer' chapter for an overview of +the structure of FITS files. + +2. Review the Programming Guidelines in Chapter 4 to become familiar +with the conventions used by the CFITSIO interface. + +3. Refer to the cookbook.c, listhead.c, and fitscopy.c programs that +are included with this release for examples of routines that perform +various common FITS file operations. Type 'make program\_name' to +compile and link these programs on Unix systems. + +4. Write a simple program to read or write a FITS file using the Basic +Interface routines described in Chapter 5. + +5. Scan through the more specialized routines that are described in +the following chapters to become familiar with the functionality that +they provide. + +**F. Example Program + +The following listing shows an example of how to use the CFITSIO +routines in a C program. Refer to the cookbook.c program that is +included with the CFITSIO distribution for other example routines. + +This program creates a new FITS file, containing a FITS image. An +`EXPOSURE' keyword is written to the header, then the image data are +written to the FITS file before closing the FITS file. +- +#include "fitsio.h" /* required by every program that uses CFITSIO */ +main() +{ + fitsfile *fptr; /* pointer to the FITS file; defined in fitsio.h */ + int status, ii, jj; + long fpixel = 1, naxis = 2, nelements, exposure; + long naxes[2] = { 300, 200 }; /* image is 300 pixels wide by 200 rows */ + short array[200][300]; + + status = 0; /* initialize status before calling fitsio routines */ + fits_create_file(&fptr, "testfile.fits", &status); /* create new file */ + + /* Create the primary array image (16-bit short integer pixels */ + fits_create_img(fptr, SHORT_IMG, naxis, naxes, &status); + + /* Write a keyword; must pass the ADDRESS of the value */ + exposure = 1500.; + fits_update_key(fptr, TLONG, "EXPOSURE", &exposure, + "Total Exposure Time", &status); + + /* Initialize the values in the image with a linear ramp function */ + for (jj = 0; jj < naxes[1]; jj++) + for (ii = 0; ii < naxes[0]; ii++) + array[jj][ii] = ii + jj; + + nelements = naxes[0] * naxes[1]; /* number of pixels to write */ + + /* Write the array of integers to the image */ + fits_write_img(fptr, TSHORT, fpixel, nelements, array[0], &status); + + fits_close_file(fptr, &status); /* close the file */ + + fits_report_error(stderr, status); /* print out any error messages */ + return( status ); +} +- + +*III. A FITS Primer + +This section gives a brief overview of the structure of FITS files. +Users should refer to the documentation available from the NOST, as +described in the introduction, for more detailed information on FITS +formats. + +FITS was first developed in the late 1970's as a standard data +interchange format between various astronomical observatories. Since +then FITS has become the standard data format supported by most +astronomical data analysis software packages. + +A FITS file consists of one or more Header + Data Units (HDUs), where +the first HDU is called the `Primary HDU', or `Primary Array'. The +primary array contains an N-dimensional array of pixels, such as a 1-D +spectrum, a 2-D image, or a 3-D data cube. Six different primary +data types are supported: Unsigned 8-bit bytes, 16-bit, 32-bit, and 64-bit signed +integers, and 32 and 64-bit floating point reals. FITS also has a +convention for storing 16 and 32-bit unsigned integers (see the later +section entitled `Unsigned Integers' for more details). The primary HDU +may also consist of only a header with a null array containing no +data pixels. + +Any number of additional HDUs may follow the primary array; these +additional HDUs are called FITS `extensions'. There are currently 3 +types of extensions defined by the FITS standard: + +\begin{itemize} +\item + Image Extension - a N-dimensional array of pixels, like in a primary array +\item + ASCII Table Extension - rows and columns of data in ASCII character format +\item + Binary Table Extension - rows and columns of data in binary representation +\end{itemize} + +In each case the HDU consists of an ASCII Header Unit followed by an optional +Data Unit. For historical reasons, each Header or Data unit must be an +exact multiple of 2880 8-bit bytes long. Any unused space is padded +with fill characters (ASCII blanks or zeros). + +Each Header Unit consists of any number of 80-character keyword records +or `card images' which have the +general form: +- + KEYNAME = value / comment string + NULLKEY = / comment: This keyword has no value +- +The keyword names may be up to 8 characters long and can only contain +uppercase letters, the digits 0-9, the hyphen, and the underscore +character. The keyword name is (usually) followed by an equals sign and +a space character (= ) in columns 9 - 10 of the record, followed by the +value of the keyword which may be either an integer, a floating point +number, a character string (enclosed in single quotes), or a boolean +value (the letter T or F). A keyword may also have a null or undefined +value if there is no specified value string, as in the second example, above + +The last keyword in the header is always the `END' keyword which has no +value or comment fields. There are many rules governing the exact +format of a keyword record (see the NOST FITS Standard) so it is better +to rely on standard interface software like CFITSIO to correctly +construct or to parse the keyword records rather than try to deal +directly with the raw FITS formats. + +Each Header Unit begins with a series of required keywords which depend +on the type of HDU. These required keywords specify the size and +format of the following Data Unit. The header may contain other +optional keywords to describe other aspects of the data, such as the +units or scaling values. Other COMMENT or HISTORY keywords are also +frequently added to further document the data file. + +The optional Data Unit immediately follows the last 2880-byte block in +the Header Unit. Some HDUs do not have a Data Unit and only consist of +the Header Unit. + +If there is more than one HDU in the FITS file, then the Header Unit of +the next HDU immediately follows the last 2880-byte block of the +previous Data Unit (or Header Unit if there is no Data Unit). + +The main required keywords in FITS primary arrays or image extensions are: +\begin{itemize} +\item +BITPIX -- defines the data type of the array: 8, 16, 32, 64, -32, -64 for +unsigned 8--bit byte, 16--bit signed integer, 32--bit signed integer, +32--bit IEEE floating point, and 64--bit IEEE double precision floating +point, respectively. +\item +NAXIS -- the number of dimensions in the array, usually 0, 1, 2, 3, or 4. +\item +NAXISn -- (n ranges from 1 to NAXIS) defines the size of each dimension. +\end{itemize} + +FITS tables start with the keyword XTENSION = `TABLE' (for ASCII +tables) or XTENSION = `BINTABLE' (for binary tables) and have the +following main keywords: +\begin{itemize} +\item +TFIELDS -- number of fields or columns in the table +\item +NAXIS2 -- number of rows in the table +\item +TTYPEn -- for each column (n ranges from 1 to TFIELDS) gives the +name of the column +\item +TFORMn -- the data type of the column +\item +TUNITn -- the physical units of the column (optional) +\end{itemize} + +Users should refer to the FITS Support Office at {\tt http://fits.gsfc.nasa.gov} +for further information about the FITS format and related software +packages. + + +*IV. Programming Guidelines + +**A. CFITSIO Definitions + +Any program that uses the CFITSIO interface must include the fitsio.h +header file with the statement +- + #include "fitsio.h" +- +This header file contains the prototypes for all the CFITSIO user +interface routines as well as the definitions of various constants used +in the interface. It also defines a C structure of type `fitsfile' +that is used by CFITSIO to store the relevant parameters that define +the format of a particular FITS file. Application programs must define +a pointer to this structure for each FITS file that is to be opened. +This structure is initialized (i.e., memory is allocated for the +structure) when the FITS file is first opened or created with the +fits\_open\_file or fits\_create\_file routines. This fitsfile pointer +is then passed as the first argument to every other CFITSIO routine +that operates on the FITS file. Application programs must not directly +read or write elements in this fitsfile structure because the +definition of the structure may change in future versions of CFITSIO. + +A number of symbolic constants are also defined in fitsio.h for the +convenience of application programmers. Use of these symbolic +constants rather than the actual numeric value will help to make the +source code more readable and easier for others to understand. +- +String Lengths, for use when allocating character arrays: + + #define FLEN_FILENAME 1025 /* max length of a filename */ + #define FLEN_KEYWORD 72 /* max length of a keyword */ + #define FLEN_CARD 81 /* max 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 CFITSIO error message */ + #define FLEN_STATUS 31 /* max length of a CFITSIO status text string */ + + Note that FLEN_KEYWORD is longer than the nominal 8-character keyword + name length because the HIERARCH convention supports longer keyword names. + +Access modes when opening a FITS file: + + #define READONLY 0 + #define READWRITE 1 + +BITPIX data type code values for FITS images: + + #define BYTE_IMG 8 /* 8-bit unsigned integers */ + #define SHORT_IMG 16 /* 16-bit signed integers */ + #define LONG_IMG 32 /* 32-bit signed integers */ + #define LONGLONG_IMG 64 /* 64-bit signed integers */ + #define FLOAT_IMG -32 /* 32-bit single precision floating point */ + #define DOUBLE_IMG -64 /* 64-bit double precision floating point */ + + The following 4 data type codes are also supported by CFITSIO: + #define SBYTE_IMG 10 /* 8-bit signed integers, equivalent to */ + /* BITPIX = 8, BSCALE = 1, BZERO = -128 */ + #define USHORT_IMG 20 /* 16-bit unsigned integers, equivalent to */ + /* BITPIX = 16, BSCALE = 1, BZERO = 32768 */ + #define ULONG_IMG 40 /* 32-bit unsigned integers, equivalent to */ + /* BITPIX = 32, BSCALE = 1, BZERO = 2147483648 */ + +Codes for the data type of binary table columns and/or for the +data type of variables when reading or writing keywords or data: + + DATATYPE TFORM CODE + #define TBIT 1 /* 'X' */ + #define TBYTE 11 /* 8-bit unsigned byte, 'B' */ + #define TLOGICAL 14 /* logicals (int for keywords */ + /* and char for table cols 'L' */ + #define TSTRING 16 /* ASCII string, 'A' */ + #define TSHORT 21 /* signed short, 'I' */ + #define TLONG 41 /* signed long, */ + #define TLONGLONG 81 /* 64-bit long signed integer 'K' */ + #define TFLOAT 42 /* single precision float, 'E' */ + #define TDOUBLE 82 /* double precision float, 'D' */ + #define TCOMPLEX 83 /* complex (pair of floats) 'C' */ + #define TDBLCOMPLEX 163 /* double complex (2 doubles) 'M' */ + + The following data type codes are also supported by CFITSIO: + #define TINT 31 /* int */ + #define TSBYTE 12 /* 8-bit signed byte, 'S' */ + #define TUINT 30 /* unsigned int 'V' */ + #define TUSHORT 20 /* unsigned short 'U' */ + #define TULONG 40 /* unsigned long */ + + The following data type code is only for use with fits\_get\_coltype + #define TINT32BIT 41 /* signed 32-bit int, 'J' */ + + +HDU type code values (value returned when moving to new HDU): + + #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 type of HDU */ + +Column name and string matching case-sensitivity: + + #define CASESEN 1 /* do case-sensitive string match */ + #define CASEINSEN 0 /* do case-insensitive string match */ + +Logical states (if TRUE and FALSE are not already defined): + + #define TRUE 1 + #define FALSE 0 + +Values to represent undefined floating point numbers: + + #define FLOATNULLVALUE -9.11912E-36F + #define DOUBLENULLVALUE -9.1191291391491E-36 + +Image compression algorithm definitions + + #define RICE_1 11 + #define GZIP_1 21 + #define PLIO_1 31 + #define HCOMPRESS_1 41 +- + +**B. Current Header Data Unit (CHDU) + +The concept of the Current Header and Data Unit, or CHDU, is +fundamental to the use of the CFITSIO library. A simple FITS image may +only contain a single Header and Data unit (HDU), but in general FITS +files can contain multiple Header Data Units (also known as +`extensions'), concatenated one after the other in the file. The user +can specify which HDU should be initially opened at run time by giving +the HDU name or number after the root file name. For example, +'myfile.fits[4]' opens the 5th HDU in the file (note that the numbering +starts with 0), and 'myfile.fits[EVENTS] opens the HDU with the name +'EVENTS' (as defined by the EXTNAME or HDUNAME keywords). If no HDU is +specified then CFITSIO opens the first HDU (the primary array) by +default. The CFITSIO routines which read and write data only operate +within the opened HDU, Other CFITSIO routines are provided to move to +and open any other existing HDU within the FITS file or to append or +insert new HDUs in the FITS file. + +**C. Function Names and Variable Datatypes + +Most of the CFITSIO routines have both a short name as well as a +longer descriptive name. The short name is only 5 or 6 characters long +and is similar to the subroutine name in the Fortran-77 version of +FITSIO. The longer name is more descriptive and it is recommended that +it be used instead of the short name to more clearly document the +source code. + +Many of the CFITSIO routines come in families which differ only in the +data type of the associated parameter(s). The data type of these +routines is indicated by the suffix of the routine name. The short +routine names have a 1 or 2 character suffix (e.g., 'j' in 'ffpkyj') +while the long routine names have a 4 character or longer suffix +as shown in the following table: +- + Long Short Data + Names Names Type + ----- ----- ---- + _bit x bit + _byt b unsigned byte + _sbyt sb signed byte + _sht i short integer + _lng j long integer + _lnglng jj 8-byte LONGLONG integer (see note below) + _usht ui unsigned short integer + _ulng uj unsigned long integer + _uint uk unsigned int integer + _int k int integer + _flt e real exponential floating point (float) + _fixflt f real fixed-decimal format floating point (float) + _dbl d double precision real floating-point (double) + _fixdbl g double precision fixed-format floating point (double) + _cmp c complex reals (pairs of float values) + _fixcmp fc complex reals, fixed-format floating point + _dblcmp m double precision complex (pairs of double values) + _fixdblcmp fm double precision complex, fixed-format floating point + _log l logical (int) + _str s character string +- + +The logical data type corresponds to `int' for logical keyword values, +and `byte' for logical binary table columns. In other words, the value +when writing a logical keyword must be stored in an `int' variable, and +must be stored in a `char' array when reading or writing to `L' columns +in a binary table. Implicit data type conversion is not supported for +logical table columns, but is for keywords, so a logical keyword may be +read and cast to any numerical data type; a returned value = 0 +indicates false, and any other value = true. + +The `int' data type may be 2 bytes long on some old PC compilers, +but otherwise it is nearly always 4 bytes long. Some 64-bit +machines, like the Alpha/OSF, define the `short', `int', +and `long' integer data types to be 2, 4, and 8 bytes long, +respectively. + +Because there is no universal C compiler standard for the name of the +8-byte integer datatype, the fitsio.h include file typedef's +'LONGLONG' to be equivalent to an +appropriate 8-byte integer data type on each supported platform. +For maximum software portability it is recommended that +this LONGLONG datatype be used to define 8-byte integer variables +rather than using the native data type name on a particular +platform. On most +32-bit Unix and Mac OS-X operating systems LONGLONG is equivalent to the +intrinsic 'long long' 8-byte integer datatype. On 64-bit systems (which currently +includes Alpha OSF/1, 64-bit Sun Solaris, 64-bit SGI MIPS, and 64-bit +Itanium and Opteron PC systems), LONGLONG is simply typedef'ed to be +equivalent to 'long'. Microsoft Visual C++ Version 6.0 does not define +a 'long long' data type, so LONGLONG is typedef'ed to be equivalent to +the '\_\_int64' data type on 32-bit windows systems when using Visual C++. + +A related issue that affects the portability of software is how to print +out the value of a 'LONGLONG' variable with printf. Developers may +find it convenient to use the following preprocessing statements +in their C programs to handle this in a machine-portable manner: + +- +#if defined(_MSC_VER) /* Microsoft Visual C++ */ + printf("%I64d", longlongvalue); + +#elif (USE_LL_SUFFIX == 1) + printf("%lld", longlongvalue); + +#else + printf("%ld", longlongvalue); +#endif +- + +Similarly, the name of the C utility routine that converts a character +string of digits into a 8-byte integer value is platform dependent: + +- +#if defined(_MSC_VER) /* Microsoft Visual C++ */ + /* VC++ 6.0 does not seem to have an 8-byte conversion routine */ + +#elif (USE_LL_SUFFIX == 1) + longlongvalue = atoll(*string); + +#else + longlongvalue = atol(*string); +#endif +- + +When dealing with the FITS byte data type it is important to remember +that the raw values (before any scaling by the BSCALE and BZERO, or +TSCALn and TZEROn keyword values) in byte arrays (BITPIX = 8) or byte +columns (TFORMn = 'B') are interpreted as unsigned bytes with values +ranging from 0 to 255. Some C compilers define a 'char' variable as +signed, so it is important to explicitly declare a numeric char +variable as 'unsigned char' to avoid any ambiguity + +One feature of the CFITSIO routines is that they can operate on a `X' +(bit) column in a binary table as though it were a `B' (byte) column. +For example a `11X' data type column can be interpreted the same as a +`2B' column (i.e., 2 unsigned 8-bit bytes). In some instances, it can +be more efficient to read and write whole bytes at a time, rather than +reading or writing each individual bit. + +The complex and double precision complex data types are not directly +supported in ANSI C so these data types should be interpreted as pairs +of float or double values, respectively, where the first value in each +pair is the real part, and the second is the imaginary part. + +**D. Support for Unsigned Integers and Signed Bytes + +Although FITS does not directly support unsigned integers as one of its +fundamental data types, FITS can still be used to efficiently store +unsigned integer data values in images and binary tables. The +convention used in FITS files is to store the unsigned integers as +signed integers with an associated offset (specified by the BZERO or +TZEROn keyword). For example, to store unsigned 16-bit integer values +in a FITS image the image would be defined as a signed 16-bit integer +(with BITPIX keyword = SHORT\_IMG = 16) with the keywords BSCALE = 1.0 +and BZERO = 32768. Thus the unsigned values of 0, 32768, and 65535, +for example, are physically stored in the FITS image as -32768, 0, and +32767, respectively; CFITSIO automatically adds the BZERO offset to +these values when they are read. Similarly, in the case of unsigned +32-bit integers the BITPIX keyword would be equal to LONG\_IMG = 32 and +BZERO would be equal to 2147483648 (i.e. 2 raised to the 31st power). + +The CFITSIO interface routines will efficiently and transparently apply +the appropriate offset in these cases so in general application +programs do not need to be concerned with how the unsigned values are +actually stored in the FITS file. As a convenience for users, CFITSIO +has several predefined constants for the value of BITPIX (USHORT\_IMG, +ULONG\_IMG) and for the TFORMn value in the case of binary tables (`U' +and `V') which programmers can use when creating FITS files containing +unsigned integer values. The following code fragment illustrates how +to write a FITS 1-D primary array of unsigned 16-bit integers: +- + unsigned short uarray[100]; + int naxis, status; + long naxes[10], group, firstelem, nelements; + ... + status = 0; + naxis = 1; + naxes[0] = 100; + fits_create_img(fptr, USHORT_IMG, naxis, naxes, &status); + + firstelem = 1; + nelements = 100; + fits_write_img(fptr, TUSHORT, firstelem, nelements, + uarray, &status); + ... +- +In the above example, the 2nd parameter in fits\_create\_img tells +CFITSIO to write the header keywords appropriate for an array of 16-bit +unsigned integers (i.e., BITPIX = 16 and BZERO = 32768). Then the +fits\_write\_img routine writes the array of unsigned short integers +(uarray) into the primary array of the FITS file. Similarly, a 32-bit +unsigned integer image may be created by setting the second parameter +in fits\_create\_img equal to `ULONG\_IMG' and by calling the +fits\_write\_img routine with the second parameter = TULONG to write +the array of unsigned long image pixel values. + +An analogous set of routines are available for reading or writing unsigned +integer values and signed byte values in a FITS binary table extension. +When specifying the TFORMn keyword value which defines the format of a +column, CFITSIO recognized 3 additional data type codes besides those +already defined in the FITS standard: `U' meaning a 16-bit unsigned +integer column, `V' for a 32-bit unsigned integer column, and 'S' +for a signed byte column. These non-standard data type codes are not +actually written into the FITS file but instead are just used internally +within CFITSIO. The following code fragment illustrates how to use +these features: +- + unsigned short uarray[100]; + unsigned int varray[100]; + + int colnum, tfields, status; + long nrows, firstrow, firstelem, nelements, pcount; + + char extname[] = "Test_table"; /* extension name */ + + /* define the name, data type, and physical units for the 2 columns */ + char *ttype[] = { "Col_1", "Col_2", "Col_3" }; + char *tform[] = { "1U", "1V", "1S"}; /* special CFITSIO codes */ + char *tunit[] = { " ", " ", " " }; + ... + + /* write the header keywords */ + status = 0; + nrows = 1; + tfields = 3 + pcount = 0; + fits_create_tbl(fptr, BINARY_TBL, nrows, tfields, ttype, tform, + tunit, extname, &status); + + /* write the unsigned shorts to the 1st column */ + colnum = 1; + firstrow = 1; + firstelem = 1; + nelements = 100; + fits_write_col(fptr, TUSHORT, colnum, firstrow, firstelem, + nelements, uarray, &status); + + /* now write the unsigned longs to the 2nd column */ + colnum = 2; + fits_write_col(fptr, TUINT, colnum, firstrow, firstelem, + nelements, varray, &status); + ... +- +Note that the non-standard TFORM values for the 3 columns, `U' and `V', +tell CFITSIO to write the keywords appropriate for unsigned 16-bit and +unsigned 32-bit integers, respectively (i.e., TFORMn = '1I' and TZEROn += 32678 for unsigned 16-bit integers, and TFORMn = '1J' and TZEROn = +2147483648 for unsigned 32-bit integers). The 'S' TFORMn value tells +CFITSIO to write the keywords appropriate for a signed 8-bit byte column +with TFORMn = '1B' and TZEROn = -128. The calls to fits\_write\_col +then write the arrays of unsigned integer values to the columns. + +**E. Dealing with Character Strings + +The character string values in a FITS header or in an ASCII column in a +FITS table extension are generally padded out with non-significant +space characters (ASCII 32) to fill up the header record or the column +width. When reading a FITS string value, the CFITSIO routines will +strip off these non-significant trailing spaces and will return a +null-terminated string value containing only the significant +characters. Leading spaces in a FITS string are considered +significant. If the string contains all blanks, then CFITSIO will +return a single blank character, i.e, the first blank is considered to +be significant, since it distinguishes the string from a null or +undefined string, but the remaining trailing spaces are not +significant. + +Similarly, when writing string values to a FITS file the +CFITSIO routines expect to get a null-terminated string as input; +CFITSIO will pad the string with blanks if necessary when writing it +to the FITS file. + +When calling CFITSIO routines that return a character string it is +vital that the size of the char array be large enough to hold the +entire string of characters, otherwise CFITSIO will overwrite whatever +memory locations follow the char array, possibly causing the program to +execute incorrectly. This type of error can be difficult to debug, so +programmers should always ensure that the char arrays are allocated +enough space to hold the longest possible string, {\bf including} the +terminating NULL character. The fitsio.h file contains the following +defined constants which programmers are strongly encouraged to use +whenever they are allocating space for char arrays: +- +#define FLEN_FILENAME 1025 /* max length of a filename */ +#define FLEN_KEYWORD 72 /* max length of a keyword */ +#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 CFITSIO error message */ +#define FLEN_STATUS 31 /* max length of a CFITSIO status text string */ +- +For example, when declaring a char array to hold the value string +of FITS keyword, use the following statement: +- + char value[FLEN_VALUE]; +- +Note that FLEN\_KEYWORD is longer than needed for the nominal 8-character +keyword name because the HIERARCH convention supports longer keyword names. + +**F. Implicit Data Type Conversion + +The CFITSIO routines that read and write numerical data can perform +implicit data type conversion. This means that the data type of the +variable or array in the program does not need to be the same as the +data type of the value in the FITS file. Data type conversion is +supported for numerical and string data types (if the string contains a +valid number enclosed in quotes) when reading a FITS header keyword +value and for numeric values when reading or writing values in the +primary array or a table column. CFITSIO returns status = +NUM\_OVERFLOW if the converted data value exceeds the range of the +output data type. Implicit data type conversion is not supported +within binary tables for string, logical, complex, or double complex +data types. + +In addition, any table column may be read as if it contained string values. +In the case of numeric columns the returned string will be formatted +using the TDISPn display format if it exists. + +**G. Data Scaling + +When reading numerical data values in the primary array or a +table column, the values will be scaled automatically by the BSCALE and +BZERO (or TSCALn and TZEROn) header values if they are +present in the header. The scaled data that is returned to the reading +program will have +- + output value = (FITS value) * BSCALE + BZERO +- +(a corresponding formula using TSCALn and TZEROn is used when reading +from table columns). In the case of integer output values the floating +point scaled value is truncated to an integer (not rounded to the +nearest integer). The fits\_set\_bscale and fits\_set\_tscale routines +(described in the `Advanced' chapter) may be used to override the +scaling parameters defined in the header (e.g., to turn off the scaling +so that the program can read the raw unscaled values from the FITS +file). + +When writing numerical data to the primary array or to a table column +the data values will generally be automatically inversely scaled by the +value of the BSCALE and BZERO (or TSCALn and TZEROn) keyword values if +they they exist in the header. These keywords must have been written +to the header before any data is written for them to have any immediate +effect. One may also use the fits\_set\_bscale and fits\_set\_tscale +routines to define or override the scaling keywords in the header +(e.g., to turn off the scaling so that the program can write the raw +unscaled values into the FITS file). If scaling is performed, the +inverse scaled output value that is written into the FITS file will +have +- + FITS value = ((input value) - BZERO) / BSCALE +- +(a corresponding formula using TSCALn and TZEROn is used when +writing to table columns). Rounding to the nearest integer, rather +than truncation, is performed when writing integer data types to the +FITS file. + +**H. Support for IEEE Special Values + +The ANSI/IEEE-754 floating-point number standard defines certain +special values that are used to represent such quantities as +Not-a-Number (NaN), denormalized, underflow, overflow, and infinity. +(See the Appendix in the NOST FITS standard or the NOST FITS User's +Guide for a list of these values). The CFITSIO routines that read +floating point data in FITS files recognize these IEEE special values +and by default interpret the overflow and infinity values as being +equivalent to a NaN, and convert the underflow and denormalized values +into zeros. In some cases programmers may want access to the raw IEEE +values, without any modification by CFITSIO. This can be done by +calling the fits\_read\_img or fits\_read\_col routines while +specifying 0.0 as the value of the NULLVAL parameter. This will force +CFITSIO to simply pass the IEEE values through to the application +program without any modification. This is not fully supported on +VAX/VMS machines, however, where there is no easy way to bypass the +default interpretation of the IEEE special values. This is also not +supported when reading floating-point images that have been compressed +with the FITS tiled image compression convention that is discussed in +section 5.6; the pixels values in tile compressed images are +represented by scaled integers, and a reserved integer value +(not a NaN) is used to represent undefined pixels. + +**I. Error Status Values and the Error Message Stack + +Nearly all the CFITSIO routines return an error status value +in 2 ways: as the value of the last parameter in the function call, +and as the returned value of the function itself. This provides +some flexibility in the way programmers can test if an error +occurred, as illustrated in the following 2 code fragments: +- + if ( fits_write_record(fptr, card, &status) ) + printf(" Error occurred while writing keyword."); + +or, + + fits_write_record(fptr, card, &status); + if ( status ) + printf(" Error occurred while writing keyword."); +- +A listing of all the CFITSIO status code values is given at the end of +this document. Programmers are encouraged to use the symbolic +mnemonics (defined in fitsio.h) rather than the actual integer status +values to improve the readability of their code. + +The CFITSIO library uses an `inherited status' convention for the +status parameter which means that if a routine is called with a +positive input value of the status parameter as input, then the routine +will exit immediately without changing the value of the status +parameter. Thus, if one passes the status value returned from each +CFITSIO routine as input to the next CFITSIO routine, then whenever an +error is detected all further CFITSIO processing will cease. This +convention can simplify the error checking in application programs +because it is not necessary to check the value of the status parameter +after every single CFITSIO routine call. If a program contains a +sequence of several CFITSIO calls, one can just check the status value +after the last call. Since the returned status values are generally +distinctive, it should be possible to determine which routine +originally returned the error status. + +CFITSIO also maintains an internal stack of error messages +(80-character maximum length) which in many cases provide a more +detailed explanation of the cause of the error than is provided by the +error status number alone. It is recommended that the error message +stack be printed out whenever a program detects a CFITSIO error. The +function fits\_report\_error will print out the entire error message +stack, or alternatively one may call fits\_read\_errmsg to get the +error messages one at a time. + +**J. Variable-Length Arrays in Binary Tables + +CFITSIO provides easy-to-use support for reading and writing data in +variable length fields of a binary table. The variable length columns +have TFORMn keyword values of the form `1Pt(len)' where `t' is the +data type code (e.g., I, J, E, D, etc.) and `len' is an integer +specifying the maximum length of the vector in the table. (CFITSIO also +supports the experimental 'Q' datatype, which is identical to the 'P' type +except that is supports is a 64-bit address space and hence much larger +data structures). If the value +of `len' is not specified when the table is created (e.g., if the TFORM +keyword value is simply specified as '1PE' instead of '1PE(400) ), then +CFITSIO will automatically scan the table when it is closed to +determine the maximum length of the vector and will append this value +to the TFORMn value. + +The same routines that read and write data in an ordinary fixed length +binary table extension are also used for variable length fields, +however, the routine parameters take on a slightly different +interpretation as described below. + +All the data in a variable length field is written into an area called +the `heap' which follows the main fixed-length FITS binary table. The +size of the heap, in bytes, is specified by the PCOUNT keyword in the +FITS header. When creating a new binary table, the initial value of +PCOUNT should usually be set to zero. CFITSIO will recompute the size +of the heap as the data is written and will automatically update the +PCOUNT keyword value when the table is closed. When writing variable +length data to a table, CFITSIO will automatically extend the size +of the heap area if necessary, so that any following HDUs do not +get overwritten. + +By default the heap data area starts immediately after the last row of +the fixed-length table. This default starting location may be +overridden by the THEAP keyword, but this is not recommended. +If additional rows of data are added to the table, CFITSIO will +automatically shift the the heap down to make room for the new +rows, but it is obviously be more efficient to initially +create the table with the necessary number of blank rows, so that +the heap does not needed to be constantly moved. + +When writing row of data to a variable length field the entire array of values for +a given row of the table must be written with a single call to +fits\_write\_col. +The total length of the array is given by nelements ++ firstelem - 1. Additional elements cannot be appended to an existing +vector at a later time since any attempt to do so will simply overwrite +all the previously written data and the new data will be +written to a new area of the heap. The fits\_compress\_heap routine +is provided to compress the heap and recover any unused space. +To avoid having to deal with this issue, it is recommended +that rows in a variable length field should only be written once. +An exception to +this general rule occurs when setting elements of an array as +undefined. It is allowed to first write a dummy value into the array with +fits\_write\_col, and then call fits\_write\_col\_nul to flag the +desired elements as undefined. Note that the rows of a table, +whether fixed or variable length, do not have to be written +consecutively and may be written in any order. + +When writing to a variable length ASCII character field (e.g., TFORM = +'1PA') only a single character string can be written. The `firstelem' +and `nelements' parameter values in the fits\_write\_col routine are +ignored and the number of characters to write is simply determined by +the length of the input null-terminated character string. + +The fits\_write\_descript routine is useful in situations where +multiple rows of a variable length column have the identical array of +values. One can simply write the array once for the first row, and +then use fits\_write\_descript to write the same descriptor values into +the other rows; all the rows will then point to the same storage +location thus saving disk space. + +When reading from a variable length array field one can only read as +many elements as actually exist in that row of the table; reading does +not automatically continue with the next row of the table as occurs +when reading an ordinary fixed length table field. Attempts to read +more than this will cause an error status to be returned. One can +determine the number of elements in each row of a variable column with +the fits\_read\_descript routine. + +**K. Multiple Access to the Same FITS File + +CFITSIO supports simultaneous read and write access to multiple HDUs in +the same FITS file. Thus, one can open the same FITS file twice within +a single program and move to 2 different HDUs in the file, and then +read and write data or keywords to the 2 extensions just as if one were +accessing 2 completely separate FITS files. Since in general it is +not possible to physically open the same file twice and then expect to +be able to simultaneously (or in alternating succession) write to 2 +different locations in the file, CFITSIO recognizes when the file to be +opened (in the call to fits\_open\_file) has already been opened and +instead of actually opening the file again, just logically links the +new file to the old file. (This of course does not prevent the same +file from being simultaneously opened by more than one program). Then +before CFITSIO reads or writes to either (logical) file, it makes sure +that any modifications made to the other file have been completely +flushed from the internal buffers to the file. Thus, in principle, one +could open a file twice, in one case pointing to the first extension +and in the other pointing to the 2nd extension and then write data to +both extensions, in any order, without danger of corrupting the file. +There may be some efficiency penalties in doing this however, since +CFITSIO has to flush all the internal buffers related to one file +before switching to the other, so it would still be prudent to +minimize the number of times one switches back and forth between doing +I/O to different HDUs in the same file. + +Some restriction apply: a FITS file cannot be opened the first time +with READONLY access, and then opened a second time with READWRITE access, +because this may be phyically impossible (e.g., if the file resides +on read-only media such as a CDROM). Also, in multi-threaded environoments, +one should never open the same file with write access in different threads. + +**L. When the Final Size of the FITS HDU is Unknown + +It is not required to know the total size of a FITS data array or table +before beginning to write the data to the FITS file. In the case of +the primary array or an image extension, one should initially create +the array with the size of the highest dimension (largest NAXISn +keyword) set to a dummy value, such as 1. Then after all the data have +been written and the true dimensions are known, then the NAXISn value +should be updated using the fits\_update\_key routine before moving to +another extension or closing the FITS file. + +When writing to FITS tables, CFITSIO automatically keeps track of the +highest row number that is written to, and will increase the size of +the table if necessary. CFITSIO will also automatically insert space +in the FITS file if necessary, to ensure that the data 'heap', if it +exists, and/or any additional HDUs that follow the table do not get +overwritten as new rows are written to the table. + +As a general rule it is best to specify the initial number of rows = 0 +when the table is created, then let CFITSIO keep track of the number of +rows that are actually written. The application program should not +manually update the number of rows in the table (as given by the NAXIS2 +keyword) since CFITSIO does this automatically. If a table is +initially created with more than zero rows, then this will usually be +considered as the minimum size of the table, even if fewer rows are +actually written to the table. Thus, if a table is initially created +with NAXIS2 = 20, and CFITSIO only writes 10 rows of data before +closing the table, then NAXIS2 will remain equal to 20. If however, 30 +rows of data are written to this table, then NAXIS2 will be increased +from 20 to 30. The one exception to this automatic updating of the +NAXIS2 keyword is if the application program directly modifies the +value of NAXIS2 (up or down) itself just before closing the table. In this +case, CFITSIO does not update NAXIS2 again, since it assumes that the +application program must have had a good reason for changing the value +directly. This is not recommended, however, and is only provided for +backward compatibility with software that initially creates a table +with a large number of rows, than decreases the NAXIS2 value to the +actual smaller value just before closing the table. + +**M. CFITSIO Size Limitations + +CFITSIO places very few restrictions on the size of FITS files that it +reads or writes. There are a few limits, however, that may affect +some extreme cases: + +1. The maximum number of FITS files that may be simultaneously opened +by CFITSIO is set by NMAXFILES as defined in fitsio2.h. It is currently +set = 300 by default. CFITSIO will allocate about 80 * NMAXFILES bytes +of memory for internal use. Note that the underlying C compiler or +operating system, may have a smaller limit on the number of opened files. +The C symbolic constant FOPEN\_MAX is intended to define the maximum +number of files that may open at once (including any other text or +binary files that may be open, not just FITS files). On some systems it +has been found that gcc supports a maximum of 255 opened files. + +2. It used to be common for computer systems to only support disk files up +to 2**31 bytes = 2.1 GB in size, but most systems now support larger files. +CFITSIO can optionally read and write these so-called 'large files' that +are greater than 2.1 GB on +platforms where they are supported, but this +usually requires that special compiler option flags be specified to turn +on this option. On linux and solaris systems the compiler flags are +'-D\_LARGEFILE\_SOURCE' and `-D\_FILE\_OFFSET\_BITS=64'. These flags +may also work on other platforms but this has not been tested. Starting +with version 3.0 of CFITSIO, the default Makefile that is distributed +with CFITSIO will include these 2 compiler flags when building on Solaris +and Linux PC systems. Users on other platforms will need to add these +compiler flags manually if they want to support large files. In most +cases it appears that it is not necessary to include these compiler +flags when compiling application code that call the CFITSIO library +routines. + +When CFITSIO is built with large file support (e.g., on Solaris and +Linux PC system by default) then it can read and write FITS data files +on disk that have any of these conditions: + +\begin{itemize} +\item +FITS files larger than 2.1 GB in size +\item +FITS images containing greater than 2.1 G pixels +\item +FITS images that have one dimension with more than 2.1 G pixels +(as given by one of the NAXISn keyword) +\item +FITS tables containing more than 2.1E09 rows (given by the NAXIS2 keyword), +or with rows that are more than 2.1 GB wide (given by the NAXIS1 keyword) +\item +FITS binary tables with a variable-length array heap that is larger +than 2.1 GB (given by the PCOUNT keyword) +\end{itemize} + +The current maximum FITS file size supported by CFITSIO +is about 6 terabytes (containing +2**31 FITS blocks, each 2880 bytes in size). Currently, support for large +files in CFITSIO has been tested on the Linux, Solaris, and IBM AIX +operating systems. + +Note that when writing application programs that are intended to support +large files it is important to use 64-bit integer variables +to store quantities such as the dimensions of images, or the number of +rows in a table. These programs must also call the special versions +of some of the CFITSIO routines that have been adapted to +support 64-bit integers. The names of these routines end in +'ll' ('el' 'el') to distinguish them from the 32-bit integer +version (e.g., fits\_get\_num\_rowsll). + + +*V. Basic CFITSIO Interface Routines + +This chapter describes the basic routines in the CFITSIO user interface +that provide all the functions normally needed to read and write most +FITS files. It is recommended that these routines be used for most +applications and that the more advanced routines described in the +next chapter only be used in special circumstances when necessary. + +The following conventions are used in this chapter in the description +of each function: + +1. Most functions have 2 names: a long descriptive name and a short +concise name. Both names are listed on the first line of the following +descriptions, separated by a slash (/) character. Programmers may use +either name in their programs but the long names are recommended to +help document the code and make it easier to read. + +2. A right arrow symbol ($>$) is used in the function descriptions to +separate the input parameters from the output parameters in the +definition of each routine. This symbol is not actually part of the C +calling sequence. + +3. The function parameters are defined in more detail in the +alphabetical listing in Appendix B. + +4. The first argument in almost all the functions is a pointer to a +structure of type `fitsfile'. Memory for this structure is allocated +by CFITSIO when the FITS file is first opened or created and is freed +when the FITS file is closed. + +5. The last argument in almost all the functions is the error status +parameter. It must be equal to 0 on input, otherwise the function will +immediately exit without doing anything. A non-zero output value +indicates that an error occurred in the function. In most cases the +status value is also returned as the value of the function itself. + +**A. CFITSIO Error Status Routines + +>1 Return a descriptive text string (30 char max.) corresponding to +> a CFITSIO error status code.\label{ffgerr} +- + void fits_get_errstatus / ffgerr (int status, > char *err_text) +- +>2 Return the top (oldest) 80-character error message from the + internal CFITSIO stack of error messages and shift any remaining + messages on the stack up one level. Call this routine + repeatedly to get each message in sequence. The function returns + a value = 0 and a null error message when the error stack is empty. +>\label{ffgmsg} +- + int fits_read_errmsg / ffgmsg (char *err_msg) +- +>3 Print out the error message corresponding to the input status + value and all the error messages on the CFITSIO stack to the specified + file stream (normally to stdout or stderr). If the input + status value = 0 then this routine does nothing. +>\label{ffrprt} +- + void fits_report_error / ffrprt (FILE *stream, status) +- +>4 The fits\_write\_errmark routine puts an invisible marker on the + CFITSIO error stack. The fits\_clear\_errmark routine can then be + used to delete any more recent error messages on the stack, back to + the position of the marker. This preserves any older error messages + on the stack. The fits\_clear\_errmsg routine simply clears all the + messages (and marks) from the stack. These routines are called + without any arguments. +>\label{ffpmrk} \label{ffcmsg} +- + void fits_write_errmark / ffpmrk (void) + void fits_clear_errmark / ffcmrk (void) + void fits_clear_errmsg / ffcmsg (void) +- + +**B. FITS File Access Routines + +>1 Open an existing data file. \label{ffopen} + +- +int fits_open_file / ffopen + (fitsfile **fptr, char *filename, int iomode, > int *status) + +int fits_open_diskfile / ffdkopen + (fitsfile **fptr, char *filename, int iomode, > int *status) + +int fits_open_data / ffdopn + (fitsfile **fptr, char *filename, int iomode, > int *status) + +int fits_open_table / fftopn + (fitsfile **fptr, char *filename, int iomode, > int *status) + +int fits_open_image / ffiopn + (fitsfile **fptr, char *filename, int iomode, > int *status) +- + +The iomode parameter determines the read/write access allowed in the +file and can have values of READONLY (0) or READWRITE (1). The filename +parameter gives the name of the file to be opened, followed by an +optional argument giving the name or index number of the extension +within the FITS file that should be moved to and opened (e.g., +\verb-myfile.fits+3- or \verb-myfile.fits[3]- moves to the 3rd extension within +the file, and \verb-myfile.fits[events]- moves to the extension with the +keyword EXTNAME = 'EVENTS'). + +The fits\_open\_diskfile routine is similar to the fits\_open\_file routine +except that it does not support the extended filename syntax in the input +file name. This routine simply tries to open the specified input file +on magnetic disk. This routine is mainly for use in cases where the +filename (or directory path) contains square or curly bracket characters +that would confuse the extended filename parser. + +The fits\_open\_data routine is similar to the fits\_open\_file routine +except that it will move to the first HDU containing significant data, +if a HDU name or number to open was not explicitly specified as +part of the filename. In this case, it will look for the first +IMAGE HDU with NAXIS greater than 0, or the first table that does not contain the +strings `GTI' (Good Time Interval extension) or `OBSTABLE' in the +EXTNAME keyword value. + +The fits\_open\_table and fits\_open\_image routines are similar to +fits\_open\_data except they will move to the first significant table +HDU or image HDU in the file, respectively, if a HDU name or +number is not specified as part of the filename. + +IRAF images (.imh format files) and raw binary data arrays may also be +opened with READONLY access. CFITSIO will automatically test if the +input file is an IRAF image, and if, so will convert it on the fly into +a virtual FITS image before it is opened by the application program. +If the input file is a raw binary data array of numbers, then the data type +and dimensions of the array must be specified in square brackets +following the name of the file (e.g. 'rawfile.dat[i512,512]' opens a +512 x 512 short integer image). See the `Extended File Name Syntax' +chapter for more details on how to specify the raw file name. The raw +file is converted on the fly into a virtual FITS image in memory that +is then opened by the application program with READONLY access. + +Programs can read the input file from the 'stdin' file stream if a dash +character ('-') is given as the filename. Files can also be opened over +the network using FTP or HTTP protocols by supplying the appropriate URL +as the filename. + +The input file can be modified in various ways to create a virtual file +(usually stored in memory) that is then opened by the application +program by supplying a filtering or binning specifier in square brackets +following the filename. Some of the more common filtering methods are +illustrated in the following paragraphs, but users should refer to the +'Extended File Name Syntax' chapter for a complete description of +the full file filtering syntax. + +When opening an image, a rectangular subset of the physical image may be +opened by listing the first and last pixel in each dimension (and +optional pixel skipping factor): +- +myimage.fits[101:200,301:400] +- +will create and open a 100x100 pixel virtual image of that section of +the physical image, and \verb+myimage.fits[*,-*]+ opens a virtual image +that is the same size as the physical image but has been flipped in +the vertical direction. + +When opening a table, the filtering syntax can be used to add or delete +columns or keywords in the virtual table: +\verb-myfile.fits[events][col !time; PI = PHA*1.2]- opens a virtual table in which the TIME column +has been deleted and a new PI column has been added with a value 1.2 +times that of the PHA column. Similarly, one can filter a table to keep +only those rows that satisfy a selection criterion: +\verb-myfile.fits[events][pha > 50]- creates and opens a virtual table +containing only those rows with a PHA value greater than 50. A large +number of boolean and mathematical operators can be used in the +selection expression. One can also filter table rows using 'Good Time +Interval' extensions, and spatial region filters as in +\verb-myfile.fits[events][gtifilter()]- and +\verb-myfile.fits[events][regfilter( "stars.rng")]-. + +Finally, table columns may be binned or histogrammed to generate a +virtual image. For example, \verb-myfile.fits[events][bin (X,Y)=4]- will +result in a 2-dimensional image calculated by binning the X and Y +columns in the event table with a bin size of 4 in each dimension. The +TLMINn and TLMAXn keywords will be used by default to determine the +range of the image. + +A single program can open the same FITS file more than once and then +treat the resulting fitsfile pointers as though they were completely +independent FITS files. Using this facility, a program can open a FITS +file twice, move to 2 different extensions within the file, and then +> read and write data in those extensions in any order. + +>2 Create and open a new empty output FITS file. \label{ffinit} + +- +int fits_create_file / ffinit + (fitsfile **fptr, char *filename, > int *status) + +int fits_create_diskfile / ffdkinit + (fitsfile **fptr, char *filename, > int *status) +- + +An error will be returned if the specified file already exists, unless +the filename is prefixed with an exclamation point (!). In that case +CFITSIO will overwrite (delete) any existing file with the same name. +Note that the exclamation point is a special UNIX character so if +it is used on the command line it must be preceded by a backslash to +force the UNIX shell to accept the character as part of the filename. + +The output file will be written to the 'stdout' file stream if a dash +character ('-') or the string 'stdout' is given as the filename. Similarly, +'-.gz' or 'stdout.gz' will cause the file to be gzip compressed before +it is written out to the stdout stream. + +Optionally, the name of a template file that is used to define the +structure of the new file may be specified in parentheses following the +output file name. The template file may be another FITS file, in which +case the new file, at the time it is opened, will be an exact copy of +the template file except that the data structures (images and tables) +will be filled with zeros. Alternatively, the template file may be an +ASCII format text file containing directives that define the keywords to be +created in each HDU of the file. See the 'Extended File Name Syntax' + section for a complete description of the template file syntax. + +The fits\_create\_diskfile routine is similar to the fits\_create\_file routine +except that it does not support the extended filename syntax in the input +file name. This routine simply tries to create the specified file +on magnetic disk. This routine is mainly for use in cases where the +filename (or directory path) contains square or curly bracket characters +> that would confuse the extended filename parser. + + +>3 Close a previously opened FITS file. The first routine simply +closes the file, whereas the second one also DELETES THE FILE, which +can be useful in cases where a FITS file has been partially created, +but then an error occurs which prevents it from being completed. +> \label{ffclos} \label{ffdelt} +- + int fits_close_file / ffclos (fitsfile *fptr, > int *status) + + int fits_delete_file / ffdelt (fitsfile *fptr, > int *status) +- +>4 Return the name, I/O mode (READONLY or READWRITE), and/or the file +type (e.g. 'file://', 'ftp://') of the opened FITS file. \label{ffflnm} +> \label{ffflmd} \label{ffurlt} +- + int fits_file_name / ffflnm (fitsfile *fptr, > char *filename, int *status) + + int fits_file_mode / ffflmd (fitsfile *fptr, > int *iomode, int *status) + + int fits_url_type / ffurlt (fitsfile *fptr, > char *urltype, int *status) +- +**C. HDU Access Routines + +The following functions perform operations on Header-Data Units (HDUs) +as a whole. + +>1 Move to a different HDU in the file. The first routine moves to a + specified absolute HDU number (starting with 1 for the primary + array) in the FITS file, and the second routine moves a relative + number HDUs forward or backward from the current HDU. A null + pointer may be given for the hdutype parameter if it's value is not + needed. The third routine moves to the (first) HDU which has the + specified extension type and EXTNAME and EXTVER keyword values (or + HDUNAME and HDUVER keywords). The hdutype parameter may have a + value of IMAGE\_HDU, ASCII\_TBL, BINARY\_TBL, or ANY\_HDU where + ANY\_HDU means that only the extname and extver values will be used + to locate the correct extension. If the input value of extver is 0 + then the EXTVER keyword is ignored and the first HDU with a + matching EXTNAME (or HDUNAME) keyword will be found. If no + matching HDU is found in the file then the current HDU will remain + unchanged and a status = BAD\_HDU\_NUM will be returned. +> \label{ffmahd} \label{ffmrhd} \label{ffmnhd} +- + int fits_movabs_hdu / ffmahd + (fitsfile *fptr, int hdunum, > int *hdutype, int *status) + + int fits_movrel_hdu / ffmrhd + (fitsfile *fptr, int nmove, > int *hdutype, int *status) + + int fits_movnam_hdu / ffmnhd + (fitsfile *fptr, int hdutype, char *extname, int extver, > int *status) +- +>2 Return the total number of HDUs in the FITS file. This returns the +number of completely defined HDUs in the file. If a new HDU has just been added to +the FITS file, then that last HDU will only be counted if it has been closed, +or if data has been written to the HDU. +> The current HDU remains unchanged by this routine. \label{ffthdu} +- + int fits_get_num_hdus / ffthdu + (fitsfile *fptr, > int *hdunum, int *status) +- +>3 Return the number of the current HDU (CHDU) in the FITS file (where + the primary array = 1). This function returns the HDU number +> rather than a status value. \label{ffghdn} +- + int fits_get_hdu_num / ffghdn + (fitsfile *fptr, > int *hdunum) +- +>4 Return the type of the current HDU in the FITS file. The possible +> values for hdutype are: IMAGE\_HDU, ASCII\_TBL, or BINARY\_TBL. \label{ffghdt} +- + int fits_get_hdu_type / ffghdt + (fitsfile *fptr, > int *hdutype, int *status) +- +>5 Copy all or part of the HDUs in the FITS file associated with infptr + and append them to the end of the FITS file associated with + outfptr. If 'previous' is true (not 0), then any HDUs preceding + the current HDU in the input file will be copied to the output + file. Similarly, 'current' and 'following' determine whether the + current HDU, and/or any following HDUs in the input file will be + copied to the output file. Thus, if all 3 parameters are true, then the + entire input file will be copied. On exit, the current HDU in + the input file will be unchanged, and the last HDU in the output +> file will be the current HDU. \label{ffcpfl} +- + int fits_copy_file / ffcpfl + (fitsfile *infptr, fitsfile *outfptr, int previous, int current, + int following, > int *status) +- +>6 Copy the current HDU from the FITS file associated with infptr and append it + to the end of the FITS file associated with outfptr. Space may be +> reserved for MOREKEYS additional keywords in the output header. \label{ffcopy} +- + int fits_copy_hdu / ffcopy + (fitsfile *infptr, fitsfile *outfptr, int morekeys, > int *status) +- +>7 Write the current HDU in the input FITS file to the +> output FILE stream (e.g., to stdout). \label{ffwrhdu} +- + int fits_write_hdu / ffwrhdu + (fitsfile *infptr, FILE *stream, > int *status) +- +>8 Copy the header (and not the data) from the CHDU associated with infptr + to the CHDU associated with outfptr. If the current output HDU + is not completely empty, then the CHDU will be closed and a new + HDU will be appended to the output file. An empty output data unit +> will be created with all values initially = 0). \label{ffcphd} +- + int fits_copy_header / ffcphd + (fitsfile *infptr, fitsfile *outfptr, > int *status) +- +>9 Delete the CHDU in the FITS file. Any following HDUs will be shifted + forward in the file, to fill in the gap created by the deleted + HDU. In the case of deleting the primary array (the first HDU in + the file) then the current primary array will be replace by a null + primary array containing the minimum set of required keywords and + no data. If there are more extensions in the file following the + one that is deleted, then the the CHDU will be redefined to point + to the following extension. If there are no following extensions + then the CHDU will be redefined to point to the previous HDU. The + output hdutype parameter returns the type of the new CHDU. A null + pointer may be given for +> hdutype if the returned value is not needed. \label{ffdhdu} +- + int fits_delete_hdu / ffdhdu + (fitsfile *fptr, > int *hdutype, int *status) +- +**D. Header Keyword Read/Write Routines + +These routines read or write keywords in the Current Header Unit +(CHU). Wild card characters (*, ?, or \#) may be used when specifying +the name of the keyword to be read: a '?' will match any single +character at that position in the keyword name and a '*' will match any +length (including zero) string of characters. The '\#' character will +match any consecutive string of decimal digits (0 - 9). When a wild +card is used the routine will only search for a match from the current +header position to the end of the header and will not resume the search +from the top of the header back to the original header position as is +done when no wildcards are included in the keyword name. The +fits\_read\_record routine may be used to set the starting position +when doing wild card searches. A status value of KEY\_NO\_EXIST is +returned if the specified keyword to be read is not found in the +header. + +***1. Keyword Reading Routines + +>1 Return the number of existing keywords (not counting the + END keyword) and the amount of space currently available for more + keywords. It returns morekeys = -1 if the header has not yet been + closed. Note that CFITSIO will dynamically add space if required + when writing new keywords to a header so in practice there is no + limit to the number of keywords that can be added to a header. A + null pointer may be entered for the morekeys parameter if it's +> value is not needed. \label{ffghsp} +- + int fits_get_hdrspace / ffghsp + (fitsfile *fptr, > int *keysexist, int *morekeys, int *status) +- +>2 Return the specified keyword. In the first routine, + the datatype parameter specifies the desired returned data type of the + keyword value and can have one of the following symbolic constant + values: TSTRING, TLOGICAL (== int), TBYTE, TSHORT, TUSHORT, TINT, + TUINT, TLONG, TULONG, TLONGLONG, TFLOAT, TDOUBLE, TCOMPLEX, and TDBLCOMPLEX. + Within the context of this routine, TSTRING corresponds to a + 'char*' data type, i.e., a pointer to a character array. Data type + conversion will be performed for numeric values if the keyword + value does not have the same data type. If the value of the keyword + is undefined (i.e., the value field is blank) then an error status + = VALUE\_UNDEFINED will be returned. + + The second routine returns the keyword value as a character string + (a literal copy of what is in the value field) regardless of the + intrinsic data type of the keyword. The third routine returns + the entire 80-character header record of the keyword, with any + trailing blank characters stripped off. The fourth routine returns + the (next) header record that contains the literal string of characters + specified by the 'string' argument. + + If a NULL comment pointer is supplied then the comment string +> will not be returned. \label{ffgky} \label{ffgkey} \label{ffgcrd} +- + int fits_read_key / ffgky + (fitsfile *fptr, int datatype, char *keyname, > DTYPE *value, + char *comment, int *status) + + int fits_read_keyword / ffgkey + (fitsfile *fptr, char *keyname, > char *value, char *comment, + int *status) + + int fits_read_card / ffgcrd + (fitsfile *fptr, char *keyname, > char *card, int *status) + + int fits_read_str / ffgstr + (fitsfile *fptr, char *string, > char *card, int *status) +- +>3 Return the nth header record in the CHU. The first keyword + in the header is at keynum = 1; if keynum = 0 then these routines + simply reset the internal CFITSIO pointer to the beginning of the header + so that subsequent keyword operations will start at the top of the + header (e.g., prior to searching for keywords using wild cards in + the keyword name). The first routine returns the entire + 80-character header record (with trailing blanks truncated), + while the second routine parses the record and returns the name, + value, and comment fields as separate (blank truncated) + character strings. If a NULL comment pointer is given on input, + then the comment string will not be +> returned. \label{ffgrec} \label{ffgkyn} +- + int fits_read_record / ffgrec + (fitsfile *fptr, int keynum, > char *card, int *status) + + int fits_read_keyn / ffgkyn + (fitsfile *fptr, int keynum, > char *keyname, char *value, + char *comment, int *status) +- +>4 Return the next keyword whose name matches one of the strings in + 'inclist' but does not match any of the strings in 'exclist'. + The strings in inclist and exclist may contain wild card characters + (*, ?, and \#) as described at the beginning of this section. + This routine searches from the current header position to the + end of the header, only, and does not continue the search from + the top of the header back to the original position. The current + header position may be reset with the ffgrec routine. Note + that nexc may be set = 0 if there are no keywords to be excluded. + This routine returns status = KEY\_NO\_EXIST if a matching +> keyword is not found. \label{ffgnxk} +- + int fits_find_nextkey / ffgnxk + (fitsfile *fptr, char **inclist, int ninc, char **exclist, + int nexc, > char *card, int *status) +- +>5 Return the physical units string from an existing keyword. This + routine uses a local convention, shown in the following example, + in which the keyword units are enclosed in square brackets in the + beginning of the keyword comment field. A null string is returned +> if no units are defined for the keyword. \label{ffgunt} +- + VELOCITY= 12.3 / [km/s] orbital speed + + int fits_read_key_unit / ffgunt + (fitsfile *fptr, char *keyname, > char *unit, int *status) +- +>6 Concatenate the header keywords in the CHDU into a single long + string of characters. This provides a convenient way of passing + all or part of the header information in a FITS HDU to other subroutines. + Each 80-character fixed-length keyword record is appended to the + output character string, in order, with no intervening separator or + terminating characters. The last header record is terminated with + a NULL character. These routine allocates memory for the returned + character array, so the calling program must free the memory when + finished. The cleanest way to do this is to + call the fits\_free\_memory routine. + + There are 2 related routines: fits\_hdr2str simply concatenates all + the existing keywords in the header; fits\_convert\_hdr2str is similar, + except that if the CHDU is a tile compressed image (stored in a binary + table) then it will first convert that header back to that of the corresponding + normal FITS image before concatenating the keywords. + + Selected keywords may be excluded from the returned character string. + If the second parameter (nocomments) is TRUE (nonzero) then any + COMMENT, HISTORY, or blank keywords in the header will not be copied + to the output string. + + The 'exclist' parameter may be used to supply a list of keywords + that are to be excluded from the output character string. Wild card + characters (*, ?, and \#) may be used in the excluded keyword names. + If no additional keywords are to be excluded, then set nexc = 0 and +> specify NULL for the the **exclist parameter. \label{ffhdr2str} +- + int fits_hdr2str / ffhdr2str + (fitsfile *fptr, int nocomments, char **exclist, int nexc, + > char **header, int *nkeys, int *status) + + int fits_convert_hdr2str / ffcnvthdr2str + (fitsfile *fptr, int nocomments, char **exclist, int nexc, + > char **header, int *nkeys, int *status) +- + +***2. Keyword Writing Routines + +>1 Write a keyword of the appropriate data type into the + CHU. The first routine simply appends a new keyword whereas the + second routine will update the value and comment fields of the + keyword if it already exists, otherwise it appends a new + keyword. Note that the address to the value, and not the value + itself, must be entered. The datatype parameter specifies the + data type of the keyword value with one of the following values: + TSTRING, TLOGICAL (== int), TBYTE, TSHORT, TUSHORT, TINT, TUINT, + TLONG, TLONGLONG, TULONG, TFLOAT, TDOUBLE. Within the context of this + routine, TSTRING corresponds to a 'char*' data type, i.e., a pointer + to a character array. A null pointer may be entered for the + comment parameter in which case the keyword comment +> field will be unmodified or left blank. \label{ffpky} \label{ffuky} +- + int fits_write_key / ffpky + (fitsfile *fptr, int datatype, char *keyname, DTYPE *value, + char *comment, > int *status) + + int fits_update_key / ffuky + (fitsfile *fptr, int datatype, char *keyname, DTYPE *value, + char *comment, > int *status) +- +>2 Write a keyword with a null or undefined value (i.e., the + value field in the keyword is left blank). The first routine + simply appends a new keyword whereas the second routine will update + the value and comment fields of the keyword if it already exists, + otherwise it appends a new keyword. A null pointer may be + entered for the comment parameter in which case the keyword + comment +> field will be unmodified or left blank. \label{ffpkyu} \label{ffukyu} +- + int fits_write_key_null / ffpkyu + (fitsfile *fptr, char *keyname, char *comment, > int *status) + + int fits_update_key_null / ffukyu + (fitsfile *fptr, char *keyname, char *comment, > int *status) +- +>3 Write (append) a COMMENT or HISTORY keyword to the CHU. The comment or + history string will be continued over multiple keywords if it is longer +> than 70 characters. \label{ffpcom} \label{ffphis} +- + int fits_write_comment / ffpcom + (fitsfile *fptr, char *comment, > int *status) + + int fits_write_history / ffphis + (fitsfile *fptr, char *history, > int *status) +- +>4 Write the DATE keyword to the CHU. The keyword value will contain + the current system date as a character string in 'yyyy-mm-ddThh:mm:ss' + format. If a DATE keyword already exists in the header, then this + routine will simply update the keyword value with the current date. +> \label{ffpdat} +- + int fits_write_date / ffpdat + (fitsfile *fptr, > int *status) +- +>5 Write a user specified keyword record into the CHU. This is + a low--level routine which can be used to write any arbitrary + record into the header. The record must conform to the all +> the FITS format requirements. \label{ffprec} +- + int fits_write_record / ffprec + (fitsfile *fptr, char *card, > int *status) +- +>6 Update an 80-character record in the CHU. If a keyword with the input + name already exists, then it is overwritten by the value of card. This + could modify the keyword name as well as the value and comment fields. + If the keyword doesn't already exist then a new keyword card is appended +> to the header. \label{ffucrd} +- + int fits_update_card / ffucrd + (fitsfile *fptr, char *keyname, char *card, > int *status) +- + +>>7 Modify (overwrite) the comment field of an existing keyword. \label{ffmcom} +- + int fits_modify_comment / ffmcom + (fitsfile *fptr, char *keyname, char *comment, > int *status) +- + +>8 Write the physical units string into an existing keyword. This + routine uses a local convention, shown in the following example, + in which the keyword units are enclosed in square brackets in the +> beginning of the keyword comment field. \label{ffpunt} +- + VELOCITY= 12.3 / [km/s] orbital speed + + int fits_write_key_unit / ffpunt + (fitsfile *fptr, char *keyname, char *unit, > int *status) +- +>9 Rename an existing keyword, preserving the current value +> and comment fields. \label{ffmnam} +- + int fits_modify_name / ffmnam + (fitsfile *fptr, char *oldname, char *newname, > int *status) +- +>10 Delete a keyword record. The space occupied by + the keyword is reclaimed by moving all the following header records up + one row in the header. The first routine deletes a keyword at a + specified position in the header (the first keyword is at position 1), + whereas the second routine deletes a specifically named keyword. + Wild card characters may be used when specifying the name of the keyword + to be deleted. The third routine deletes the (next) keyword that contains + the literal character string specified by the 'string' +> argument.\label{ffdrec} \label{ffdkey} +- + int fits_delete_record / ffdrec + (fitsfile *fptr, int keynum, > int *status) + + int fits_delete_key / ffdkey + (fitsfile *fptr, char *keyname, > int *status) + + int fits_delete_str / ffdstr + (fitsfile *fptr, char *string, > int *status) +- +**E. Primary Array or IMAGE Extension I/O Routines + +These routines read or write data values in the primary data array (i.e., +the first HDU in a FITS file) or an IMAGE extension. There are also +routines to get information about the data type and size of the image. +Users should also read the following chapter on the CFITSIO iterator +function which provides a more `object oriented' method of reading and +writing images. The iterator function is a little more complicated to +use, but the advantages are that it usually takes less code to perform +the same operation, and the resulting program often runs faster because +the FITS files are read and written using the most efficient block size. + +C programmers should note that the ordering of arrays in FITS files, and +hence in all the CFITSIO calls, is more similar to the dimensionality +of arrays in Fortran rather than C. For instance if a FITS image has +NAXIS1 = 100 and NAXIS2 = 50, then a 2-D array just large enough to hold +the image should be declared as array[50][100] and not as array[100][50]. + +The `datatype' parameter specifies the data type of the `nulval' and +`array' pointers and can have one of the following values: TBYTE, +TSBYTE, TSHORT, TUSHORT, TINT, TUINT, TLONG, TLONGLONG, TULONG, TFLOAT, +TDOUBLE. Automatic data type conversion is performed if the data type +of the FITS array (as defined by the BITPIX keyword) differs from that +specified by 'datatype'. The data values are also automatically scaled +by the BSCALE and BZERO keyword values as they are being read or written +in the FITS array. + +>1 Get the data type or equivalent data type of the image. The + first routine returns the physical data type of the FITS image, as + given by the BITPIX keyword, with allowed values of BYTE\_IMG (8), + SHORT\_IMG (16), LONG\_IMG (32), LONGLONG\_IMG (64), + FLOAT\_IMG (-32), and DOUBLE\_IMG + (-64). + The second routine is similar, except that if the image pixel + values are scaled, with non-default values for the BZERO and BSCALE + keywords, then the routine will return the 'equivalent' data type + that is needed to store the scaled values. For example, if BITPIX + = 16 and BSCALE = 0.1 then the equivalent data type is FLOAT\_IMG. + Similarly if BITPIX = 16, BSCALE = 1, and BZERO = 32768, then the + the pixel values span the range of an unsigned short integer and +> the returned data type will be USHORT\_IMG. \label{ffgidt} +- + int fits_get_img_type / ffgidt + (fitsfile *fptr, > int *bitpix, int *status) + + int fits_get_img_equivtype / ffgiet + (fitsfile *fptr, > int *bitpix, int *status) +- +>2 Get the number of dimensions, and/or the size of + each dimension in the image . The number of axes in the image is + given by naxis, and the size of each dimension is given by the + naxes array (a maximum of maxdim dimensions will be returned). +> \label{ffgidm} \label{ffgisz} \label{ffgipr} +- + int fits_get_img_dim / ffgidm + (fitsfile *fptr, > int *naxis, int *status) + + int fits_get_img_size / ffgisz + (fitsfile *fptr, int maxdim, > long *naxes, int *status) + + int fits_get_img_sizell / ffgiszll + (fitsfile *fptr, int maxdim, > LONGLONG *naxes, int *status) + + int fits_get_img_param / ffgipr + (fitsfile *fptr, int maxdim, > int *bitpix, int *naxis, long *naxes, + int *status) + + int fits_get_img_paramll / ffgiprll + (fitsfile *fptr, int maxdim, > int *bitpix, int *naxis, LONGLONG *naxes, + int *status) +- +>3 Create a new primary array or IMAGE extension with a specified + data type and size. If the FITS file is currently empty then a + primary array is created, otherwise a new IMAGE extension is +> appended to the file. \label{ffcrim} +- + int fits_create_img / ffcrim + ( fitsfile *fptr, int bitpix, int naxis, long *naxes, > int *status) + + int fits_create_imgll / ffcrimll + ( fitsfile *fptr, int bitpix, int naxis, LONGLONG *naxes, > int *status) +- +>4 Copy an n-dimensional image in a particular row and column of a + binary table (in a vector column) + to or from a primary array or image extension. + + The 'cell2image' routine + will append a new image extension (or primary array) to the output file. + Any WCS keywords associated with the input column image will be translated + into the appropriate form for an image extension. Any other keywords + in the table header that are not specifically related to defining the + binary table structure or to other columns in the table + will also be copied to the header of the output image. + + The 'image2cell' routine will copy the input image into the specified row + and column of the current binary table in the output file. The binary table + HDU must exist before calling this routine, but it + may be empty, with no rows or columns of data. The specified column + (and row) will be created if it does not already exist. The 'copykeyflag' + parameter controls which keywords are copied from the input + image to the header of the output table: 0 = no keywords will be copied, + 1 = all keywords will be copied (except those keywords that would be invalid in +> the table header), and 2 = copy only the WCS keywords. \label{copycell} +- + int fits_copy_cell2image + (fitsfile *infptr, fitsfile *outfptr, char *colname, long rownum, + > int *status) + + int fits_copy_image2cell + (fitsfile *infptr, fitsfile *outfptr, char *colname, long rownum, + int copykeyflag > int *status) +- + +>5 Write a rectangular subimage (or the whole image) to the FITS data + array. The fpixel and lpixel arrays give the coordinates of the + first (lower left corner) and last (upper right corner) pixels in +> FITS image to be written to. \label{ffpss} +- + int fits_write_subset / ffpss + (fitsfile *fptr, int datatype, long *fpixel, long *lpixel, + DTYPE *array, > int *status) +- +>6 Write pixels into the FITS data array. 'fpixel' is an array of + length NAXIS which gives the coordinate of the starting pixel to be + written to, such that fpixel[0] is in the range 1 to NAXIS1, + fpixel[1] is in the range 1 to NAXIS2, etc. The first pair of routines + simply writes the array of pixels to the FITS file (doing data type + conversion if necessary) whereas the second routines will substitute + the appropriate FITS null value for any elements which are equal to + the input value of nulval (note that this parameter gives the + address of the null value, not the null value itself). For integer + FITS arrays, the FITS null value is defined by the BLANK keyword (an + error is returned if the BLANK keyword doesn't exist). For floating + point FITS arrays the special IEEE NaN (Not-a-Number) value will be + written into the FITS file. If a null pointer is entered for + nulval, then the null value is ignored and this routine behaves +> the same as fits\_write\_pix. \label{ffppx} \label{ffppxn} +- + int fits_write_pix / ffppx + (fitsfile *fptr, int datatype, long *fpixel, LONGLONG nelements, + DTYPE *array, int *status); + + int fits_write_pixll / ffppxll + (fitsfile *fptr, int datatype, LONGLONG *fpixel, LONGLONG nelements, + DTYPE *array, int *status); + + int fits_write_pixnull / ffppxn + (fitsfile *fptr, int datatype, long *fpixel, LONGLONG nelements, + DTYPE *array, DTYPE *nulval, > int *status); + + int fits_write_pixnullll / ffppxnll + (fitsfile *fptr, int datatype, LONGLONG *fpixel, LONGLONG nelements, + DTYPE *array, DTYPE *nulval, > int *status); +- +>7 Set FITS data array elements equal to the appropriate null pixel + value. For integer FITS arrays, the FITS null value is defined by + the BLANK keyword (an error is returned if the BLANK keyword + doesn't exist). For floating point FITS arrays the special IEEE NaN + (Not-a-Number) value will be written into the FITS file. Note that + 'firstelem' is a scalar giving the offset to the first pixel to be +> written in the equivalent 1-dimensional array of image pixels. \label{ffpprn} +- + int fits_write_null_img / ffpprn + (fitsfile *fptr, LONGLONG firstelem, LONGLONG nelements, > int *status) +- +>8 Read a rectangular subimage (or the whole image) from the FITS + data array. The fpixel and lpixel arrays give the coordinates of + the first (lower left corner) and last (upper right corner) pixels + to be read from the FITS image. Undefined FITS array elements will + be returned with a value = *nullval, (note that this parameter + gives the address of the null value, not the null value itself) + unless nulval = 0 or *nulval = 0, in which case no checks for +> undefined pixels will be performed. \label{ffgsv} +- + int fits_read_subset / ffgsv + (fitsfile *fptr, int datatype, long *fpixel, long *lpixel, long *inc, + DTYPE *nulval, > DTYPE *array, int *anynul, int *status) +- +>9 Read pixels from the FITS data array. 'fpixel' is the starting + pixel location and is an array of length NAXIS such that fpixel[0] + is in the range 1 to NAXIS1, fpixel[1] is in the range 1 to NAXIS2, + etc. The nelements parameter specifies the number of pixels to + read. If fpixel is set to the first pixel, and nelements is set + equal to the NAXIS1 value, then this routine would read the first + row of the image. Alternatively, if nelements is set equal to + NAXIS1 * NAXIS2 then it would read an entire 2D image, or the first + plane of a 3-D datacube. + + The first 2 routines will return any undefined pixels in the FITS array + equal to the value of *nullval (note that this parameter gives the + address of the null value, not the null value itself) unless nulval + = 0 or *nulval = 0, in which case no checks for undefined pixels + will be performed. The second 2 routines are similar except that any + undefined pixels will have the corresponding nullarray element set +> equal to TRUE (= 1). \label{ffgpxv} \label{ffgpxf} +- + int fits_read_pix / ffgpxv + (fitsfile *fptr, int datatype, long *fpixel, LONGLONG nelements, + DTYPE *nulval, > DTYPE *array, int *anynul, int *status) + + int fits_read_pixll / ffgpxvll + (fitsfile *fptr, int datatype, LONGLONG *fpixel, LONGLONG nelements, + DTYPE *nulval, > DTYPE *array, int *anynul, int *status) + + int fits_read_pixnull / ffgpxf + (fitsfile *fptr, int datatype, long *fpixel, LONGLONG nelements, + > DTYPE *array, char *nullarray, int *anynul, int *status) + + int fits_read_pixnullll / ffgpxfll + (fitsfile *fptr, int datatype, LONGLONG *fpixel, LONGLONG nelements, + > DTYPE *array, char *nullarray, int *anynul, int *status) +- +>10 Copy a rectangular section of an image and write it to a new + FITS primary image or image extension. The new image HDU is appended + to the end of the output file; all the keywords in the input image + will be copied to the output image. The common WCS keywords will + be updated if necessary to correspond to the coordinates of the section. + The format of the section expression is + same as specifying an image section using the extended file name + syntax (see "Image Section" in Chapter 10). + (Examples: "1:100,1:200", "1:100:2, 1:*:2", "*, -*"). +> \label{ffcpimg} +- + int fits_copy_image_section / ffcpimg + (fitsfile *infptr, fitsfile *outfptr, char *section, int *status) +- + +**F. Image Compression + +CFITSIO transparently supports the 2 methods of image compression described +below. + +1) The entire FITS file may be externally compressed with the gzip or Unix +compress utility programs, producing a *.gz or *.Z file, respectively. When reading +compressed files of this type, CFITSIO first uncompresses the entire file +into memory before performing the requested read operations. Output files +can be directly written in the gzip compressed format if the user-specified +filename ends with `.gz'. In this case, CFITSIO initially writes the +uncompressed file in memory and then compresses it and writes it to disk +when the FITS file is closed, thus saving user disk space. Read and write +access to these compressed FITS files is generally quite fast since all the +I/O is performed in memory; the main limitation with this technique is that +there must be enough available memory (or swap space) to hold the entire +uncompressed FITS file. + +2) CFITSIO also supports the FITS tiled image compression convention in +which the image is subdivided into a grid of rectangular tiles, and each +tile of pixels is individually compressed. The details of this FITS +compression convention are described at the FITS Support Office web site at +http://fits.gsfc.nasa.gov/fits\_registry.html Basically, the compressed +image tiles are stored in rows of a variable length array column in a FITS +binary table, however CFITSIO recognizes that this binary table extension +contains an image and treats it as if it were an IMAGE extension. This +tile-compressed format is especially well suited for compressing very large +images because a) the FITS header keywords remain uncompressed for rapid +read access, and because b) it is possible to extract and uncompress +sections of the image without having to uncompress the entire image. This +format is also much more effective in compressing floating point images +than simply compressing the image using gzip or compress because it +approximates the floating point values with scaled integers which can then +be compressed more efficiently. + +Currently CFITSIO supports 3 general purpose compression algorithms plus +one other special-purpose compression technique that is designed for data +masks with positive integer pixel values. The 3 general purpose algorithms +are GZIP, Rice, and HCOMPRESS, and the special purpose algorithm is the +IRAF pixel list compression technique (PLIO). In principle, any number of +other compression algorithms could also be supported by the FITS tiled +image compression convention. + +The FITS image can be subdivided into any desired rectangular grid of +compression tiles. With the GZIP, Rice, and PLIO algorithms, the default +is to take each row of the image as a tile. The HCOMPRESS algorithm is +inherently 2-dimensional in nature, so the default in this case is to take +16 rows of the image per tile. In most cases it makes little difference what +tiling pattern is used, so the default tiles are usually adequate. In the +case of very small images, it could be more efficient to compress the whole +image as a single tile. Note that the image dimensions are not required to +be an integer multiple of the tile dimensions; if not, then the tiles at the +edges of the image will be smaller than the other tiles. + +The 4 supported image compression algorithms are all 'loss-less' when +applied to integer FITS images; the pixel values are preserved exactly with +no loss of information during the compression and uncompression process. In +addition, the HCOMPRESS algorithm supports a 'lossy' compression mode that +will produce +larger amount of image compression. This is achieved by specifying a non-zero +value for the HCOMPRESS ``scale'' +parameter. Since the amount of compression that is achieved depends directly +on the RMS noise in the image, it is usually more convention +to specify the HCOMPRESS scale factor relative to the RMS noise. +Setting s = 2.5 means use a scale factor that is 2.5 times the calculated RMS noise +in the image tile. In some cases +it may be desirable to specify the exact scaling to be used, +instead of specifying it relative to the calculated noise value. This may +be done by specifying the negative of desired scale value (typically +in the range -2 to -100). + +Very high compression factors (of 100 or more) can be +achieved by using large HCOMPRESS scale values, however, this can produce undesirable +``blocky'' artifacts in the compressed image. A variation of the HCOMPRESS +algorithm (called HSCOMPRESS) can be used in this case to apply a small +amount of smoothing of the image when it is uncompressed to help cover up +these artifacts. This smoothing is purely cosmetic and does not cause any +significant change to the image pixel values. + +Floating point FITS images (which have BITPIX = -32 or -64) usually contain +too much ``noise'' in the least significant bits of the mantissa of the +pixel values to be effectively compressed with any lossless algorithm. +Consequently, floating point images are first quantized into scaled integer +pixel values (and thus throwing away much of the noise) before being +compressed with the specified algorithm (either GZIP, Rice, or HCOMPRESS). +This technique produces much higher compression factors than +simply using the GZIP utility to externally compress the whole FITS file, but it also +means that the original floating value pixel values are not exactly +preserved. When done properly, this integer scaling technique will only +discard the insignificant noise while still preserving all the real +information in the image. The amount of precision that is retained in the +pixel values is controlled by the "quantization level" parameter, q. Larger +values of q will result in compressed images whose pixels more closely match +the floating point pixel values, but at the same time the amount of +compression that is achieved will be reduced. Users should experiment with +different values for this parameter to determine the optimal value that +preserves all the useful information in the image, without needlessly +preserving all the ``noise'' which will hurt the compression efficiency. + +The default value for the quantization scale factor is 16., which means that +scaled integer pixel values will be quantized such that the difference +between adjacent integer values will be 1/16th of the noise level in the +image background. CFITSIO uses an optimized algorithm to accurately estimate +the noise in the image. As an example, if the RMS noise in the background +pixels of an image = 32.0, then the spacing between adjacent scaled +integer pixel values will equal 2.0 by default. Note that the RMS noise is +independently calculated for each tile of the image, so the resulting +integer scaling factor may fluctuate slightly for each tile. In some cases +it may be desirable to specify the exact quantization level to be used, +instead of specifying it relative to the calculated noise value. This may +be done by specifying the negative of desired quantization level for the +value of q. In the previous example, one could specify q = -2.0 so that the +quantized integer levels differ by 2.0. Larger negative values for q means +that the levels are more coarsely spaced, and will produce higher +compression factors. + +There are 2 methods for specifying all the parameters needed to write a FITS +image in the tile compressed format. The parameters may either be specified +at run time as part of the file name of the output compressed FITS file, or +the writing program may call a set of helper CFITSIO subroutines that are provided +for specifying the parameter values, as described below: + +1) At run time, when specifying the name of the output FITS file to be +created, the user can indicate that images should be +written in tile-compressed format by enclosing the compression +parameters in square brackets following the root disk file name +in the following format: +- + [compress NAME T1,T2; q QLEVEL, s HSCALE] +- +where +- + NAME = algorithm name: GZIP, Rice, HCOMPRESS, HSCOMPRSS or PLIO + may be abbreviated to the first letter (or HS for HSCOMPRESS) + T1,T2 = tile dimension (e.g. 100,100 for square tiles 100 pixels wide) + QLEVEL = quantization level for floating point FITS images + HSCALE = HCOMPRESS scale factor; default = 0 which is lossless. +- + +Here are a few examples of this extended syntax: + +- + myfile.fit[compress] - use the default compression algorithm (Rice) + and the default tile size (row by row) + + myfile.fit[compress GZIP] - use the specified compression algorithm; + myfile.fit[compress Rice] only the first letter of the algorithm + myfile.fit[compress PLIO] name is required. + myfile.fit[compress HCOMP] + + myfile.fit[compress R 100,100] - use Rice and 100 x 100 pixel tiles + + myfile.fit[compress R; q 10.0] - quantization level = (RMS-noise) / 10. + myfile.fit[compress HS; s 2.0] - HSCOMPRESS (with smoothing) + and scale = 2.0 * RMS-noise +- + +2) Before calling the CFITSIO routine to write the image header +keywords (e.g., fits\_create\_image) the programmer can call the +routines described below to specify the compression algorithm and the +tiling pattern that is to be used. There are routines for specifying +the various compression parameters and similar routines to +return the current values of the parameters: +\label{ffsetcomp} \label{ffgetcomp} +- + int fits_set_compression_type(fitsfile *fptr, int comptype, int *status) + int fits_set_tile_dim(fitsfile *fptr, int ndim, long *tilesize, 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) + Set smooth = 1 to apply smoothing when uncompressing the image + + int fits_get_compression_type(fitsfile *fptr, int *comptype, int *status) + int fits_get_tile_dim(fitsfile *fptr, int ndim, long *tilesize, int *status) + int fits_get_quantize_level(fitsfile *fptr, float *level, int *status) + int fits_get_hcomp_scale(fitsfile *fptr, float *scale, int *status) + int fits_get_hcomp_smooth(fitsfile *fptr, int *smooth, int *status) +- +4 symbolic constants are defined for use as the value of the +`comptype' parameter: GZIP\_1, RICE\_1, HCOMPRESS\_1 or PLIO\_1. +Entering NULL for +comptype will turn off the tile-compression and cause normal FITS +images to be written. + + +No special action is required by software when read tile-compressed images because +all the CFITSIO routines that read normal uncompressed FITS images also +transparently read images in the tile-compressed format; CFITSIO essentially +treats the binary table that contains the compressed tiles as if +it were an IMAGE extension. + + +The following 2 routines are available for compressing or +or decompressing an image: +- + int fits_img_compress(fitsfile *infptr, fitsfile *outfptr, int *status); + int fits_img_decompress (fitsfile *infptr, fitsfile *outfptr, int *status); +- +Before calling the compression routine, the compression parameters must +first be defined in one of the 2 way described in the previous paragraphs. +There is also a routine to determine if the current HDU contains +a tile compressed image (it returns 1 or 0): +- + int fits_is_compressed_image(fitsfile *fptr, int *status); +- +A small example program called 'imcopy' is included with CFITSIO that +can be used to compress (or uncompress) any FITS image. This +program can be used to experiment with the various compression options +on existing FITS images as shown in these examples: +- +1) imcopy infile.fit 'outfile.fit[compress]' + + This will use the default compression algorithm (Rice) and the + default tile size (row by row) + +2) imcopy infile.fit 'outfile.fit[compress GZIP]' + + This will use the GZIP compression algorithm and the default + tile size (row by row). The allowed compression algorithms are + Rice, GZIP, and PLIO. Only the first letter of the algorithm + name needs to be specified. + +3) imcopy infile.fit 'outfile.fit[compress G 100,100]' + + This will use the GZIP compression algorithm and 100 X 100 pixel + tiles. + +4) imcopy infile.fit 'outfile.fit[compress R 100,100; q 10.0]' + + This will use the Rice compression algorithm, 100 X 100 pixel + tiles, and quantization level = RMSnoise / 10.0 (assuming the + input image has a floating point data type). + +5) imcopy infile.fit outfile.fit + + If the input file is in tile-compressed format, then it will be + uncompressed to the output file. Otherwise, it simply copies + the input image to the output image. + +6) imcopy 'infile.fit[1001:1500,2001:2500]' outfile.fit + + This extracts a 500 X 500 pixel section of the much larger + input image (which may be in tile-compressed format). The + output is a normal uncompressed FITS image. + +7) imcopy 'infile.fit[1001:1500,2001:2500]' outfile.fit.gz + + Same as above, except the output file is externally compressed + using the gzip algorithm. + +- +**G. ASCII and Binary Table Routines + +These routines perform read and write operations on columns of data in +FITS ASCII or Binary tables. Note that in the following discussions, +the first row and column in a table is at position 1 not 0. + +Users should also read the following chapter on the CFITSIO iterator +function which provides a more `object oriented' method of reading and +writing table columns. The iterator function is a little more +complicated to use, but the advantages are that it usually takes less +code to perform the same operation, and the resulting program often +runs faster because the FITS files are read and written using the most +efficient block size. + +***1. Create New Table + +>1 Create a new ASCII or bintable table extension. If + the FITS file is currently empty then a dummy primary array will be + created before appending the table extension to it. The tbltype + parameter defines the type of table and can have values of + ASCII\_TBL or BINARY\_TBL. The naxis2 parameter gives the initial + number of rows to be created in the table, and should normally be + set = 0. CFITSIO will automatically increase the size of the table + as additional rows are written. A non-zero number of rows may be + specified to reserve space for that many rows, even if a fewer + number of rows will be written. The tunit and extname parameters + are optional and a null pointer may be given if they are not + defined. The FITS Standard recommends that only letters, digits, + and the underscore character be used in column names (the ttype + parameter) with no embedded spaces. Trailing blank characters are +> not significant. \label{ffcrtb} +- + int fits_create_tbl / ffcrtb + (fitsfile *fptr, int tbltype, LONGLONG naxis2, int tfields, char *ttype[], + char *tform[], char *tunit[], char *extname, int *status) +- +***2. Column Information Routines + +>1 Get the number of rows or columns in the current FITS table. + The number of rows is given by the NAXIS2 keyword and the + number of columns is given by the TFIELDS keyword in the header +> of the table. \label{ffgnrw} +- + int fits_get_num_rows / ffgnrw + (fitsfile *fptr, > long *nrows, int *status); + + int fits_get_num_rowsll / ffgnrwll + (fitsfile *fptr, > LONGLONG *nrows, int *status); + + int fits_get_num_cols / ffgncl + (fitsfile *fptr, > int *ncols, int *status); +- + +>2 Get the table column number (and name) of the column whose name +matches an input template name. If casesen = CASESEN then the column +name match will be case-sensitive, whereas if casesen = CASEINSEN then +the case will be ignored. As a general rule, the column names should +be treated as case INsensitive. + +The input column name template may be either the exact name of the +column to be searched for, or it may contain wild card characters (*, +?, or \#), or it may contain the integer number of the desired column +(with the first column = 1). The `*' wild card character matches any +sequence of characters (including zero characters) and the `?' +character matches any single character. The \# wildcard will match any +consecutive string of decimal digits (0-9). If more than one column +name in the table matches the template string, then the first match is +returned and the status value will be set to COL\_NOT\_UNIQUE as a +warning that a unique match was not found. To find the other cases +that match the template, call the routine again leaving the input +status value equal to COL\_NOT\_UNIQUE and the next matching name will +then be returned. Repeat this process until a status = +COL\_NOT\_FOUND is returned. + +The FITS Standard recommends that only letters, digits, and the +underscore character be used in column names (with no embedded +spaces). Trailing blank characters are not significant. +> \label{ffgcno} \label{ffgcnn} +- + int fits_get_colnum / ffgcno + (fitsfile *fptr, int casesen, char *templt, > int *colnum, + int *status) + + int fits_get_colname / ffgcnn + (fitsfile *fptr, int casesen, char *templt, > char *colname, + int *colnum, int *status) +- +>3 Return the data type, vector repeat value, and the width in bytes + of a column in an ASCII or binary table. Allowed values for the + data type in ASCII tables are: TSTRING, TSHORT, TLONG, TFLOAT, and + TDOUBLE. Binary tables also support these types: TLOGICAL, TBIT, + TBYTE, TCOMPLEX and TDBLCOMPLEX. The negative of the data type code + value is returned if it is a variable length array column. Note + that in the case of a 'J' 32-bit integer binary table column, this + routine will return data type = TINT32BIT (which in fact is + equivalent to TLONG). With most current C compilers, a value in a + 'J' column has the same size as an 'int' variable, and may not be + equivalent to a 'long' variable, which is 64-bits long on an + increasing number of compilers. + + The 'repeat' parameter returns the vector repeat count on the binary + table TFORMn keyword value. (ASCII table columns always have repeat + = 1). The 'width' parameter returns the width in bytes of a single + column element (e.g., a '10D' binary table column will have width = + 8, an ASCII table 'F12.2' column will have width = 12, and a binary + table'60A' character string column will have width = 60); Note that + CFITSIO supports the local convention for specifying arrays of + fixed length strings within a binary table character column using + the syntax TFORM = 'rAw' where 'r' is the total number of characters + (= the width of the column) and 'w' is the width of a unit string + within the column. Thus if the column has TFORM = '60A12' then this + means that each row of the table contains 5 12-character substrings + within the 60-character field, and thus in this case this routine will + return typecode = TSTRING, repeat = 60, and width = 12. (The TDIMn + keyword may also be used to specify the unit string length; The pair + of keywords TFORMn = '60A' and TDIMn = '(12,5)' would have the + same effect as TFORMn = '60A12'). The number + of substrings in any binary table character string field can be + calculated by (repeat/width). A null pointer may be given for any of + the output parameters that are not needed. + + The second routine, fit\_get\_eqcoltype is similar except that in + the case of scaled integer columns it returns the 'equivalent' data + type that is needed to store the scaled values, and not necessarily + the physical data type of the unscaled values as stored in the FITS + table. For example if a '1I' column in a binary table has TSCALn = + 1 and TZEROn = 32768, then this column effectively contains unsigned + short integer values, and thus the returned value of typecode will + be TUSHORT, not TSHORT. Similarly, if a column has TTYPEn = '1I' + and TSCALn = 0.12, then the returned typecode +> will be TFLOAT. \label{ffgtcl} +- + int fits_get_coltype / ffgtcl + (fitsfile *fptr, int colnum, > int *typecode, long *repeat, + long *width, int *status) + + int fits_get_coltypell / ffgtclll + (fitsfile *fptr, int colnum, > int *typecode, LONGLONG *repeat, + LONGLONG *width, int *status) + + int fits_get_eqcoltype / ffeqty + (fitsfile *fptr, int colnum, > int *typecode, long *repeat, + long *width, int *status) + + int fits_get_eqcoltypell / ffeqtyll + (fitsfile *fptr, int colnum, > int *typecode, LONGLONG *repeat, + LONGLONG *width, int *status) +- +>4 Return the display width of a column. This is the length + of the string that will be returned by the fits\_read\_col routine + when reading the column as a formatted string. The display width is + determined by the TDISPn keyword, if present, otherwise by the data +> type of the column. \label{ffgcdw} +- + int fits_get_col_display_width / ffgcdw + (fitsfile *fptr, int colnum, > int *dispwidth, int *status) +- + +>5 Return the number of and size of the dimensions of a table column in + a binary table. Normally this information is given by the TDIMn keyword, + but if this keyword is not present then this routine returns naxis = 1 +> and naxes[0] equal to the repeat count in the TFORM keyword. \label{ffgtdm} +- + int fits_read_tdim / ffgtdm + (fitsfile *fptr, int colnum, int maxdim, > int *naxis, + long *naxes, int *status) + + int fits_read_tdimll / ffgtdmll + (fitsfile *fptr, int colnum, int maxdim, > int *naxis, + LONGLONG *naxes, int *status) +- +>6 Decode the input TDIMn keyword string (e.g. '(100,200)') and return the + number of and size of the dimensions of a binary table column. If the input + tdimstr character string is null, then this routine returns naxis = 1 + and naxes[0] equal to the repeat count in the TFORM keyword. This routine +> is called by fits\_read\_tdim. \label{ffdtdm} +- + int fits_decode_tdim / ffdtdm + (fitsfile *fptr, char *tdimstr, int colnum, int maxdim, > int *naxis, + long *naxes, int *status) + + int fits_decode_tdimll / ffdtdmll + (fitsfile *fptr, char *tdimstr, int colnum, int maxdim, > int *naxis, + LONGLONG *naxes, int *status) +- +>7 Write a TDIMn keyword whose value has the form '(l,m,n...)' + where l, m, n... are the dimensions of a multidimensional array +> column in a binary table. \label{ffptdm} +- + int fits_write_tdim / ffptdm + (fitsfile *fptr, int colnum, int naxis, long *naxes, > int *status) + + int fits_write_tdimll / ffptdmll + (fitsfile *fptr, int colnum, int naxis, LONGLONG *naxes, > int *status) +- + +***3. Routines to Edit Rows or Columns + +>1 Insert or delete rows in an ASCII or binary table. When inserting rows + all the rows following row FROW are shifted down by NROWS rows; if + FROW = 0 then the blank rows are inserted at the beginning of the + table. Note that it is *not* necessary to insert rows in a table before + writing data to those rows (indeed, it would be inefficient to do so). + Instead one may simply write data to any row of the table, whether that + row of data already exists or not. + + The first delete routine deletes NROWS consecutive rows + starting with row FIRSTROW. The second delete routine takes an + input string that lists the rows or row ranges (e.g., + '5-10,12,20-30'), whereas the third delete routine takes an input + integer array that specifies each individual row to be deleted. In + both latter cases, the input list of rows to delete must be sorted + in ascending order. These routines update the NAXIS2 keyword to + reflect the new number of rows in the +> table. \label{ffirow} \label{ffdrow} \label{ffdrws} \label{ffdrrg} +- + int fits_insert_rows / ffirow + (fitsfile *fptr, LONGLONG firstrow, LONGLONG nrows, > int *status) + + int fits_delete_rows / ffdrow + (fitsfile *fptr, LONGLONG firstrow, LONGLONG nrows, > int *status) + + int fits_delete_rowrange / ffdrrg + (fitsfile *fptr, char *rangelist, > int *status) + + int fits_delete_rowlist / ffdrws + (fitsfile *fptr, long *rowlist, long nrows, > int *status) + + int fits_delete_rowlistll / ffdrwsll + (fitsfile *fptr, LONGLONG *rowlist, LONGLONG nrows, > int *status) +- +>2 Insert or delete column(s) in an ASCII or binary + table. When inserting, COLNUM specifies the column number that the + (first) new column should occupy in the table. NCOLS specifies how + many columns are to be inserted. Any existing columns from this + position and higher are shifted over to allow room for the new + column(s). The index number on all the following keywords will be + incremented or decremented if necessary to reflect the new position + of the column(s) in the table: TBCOLn, TFORMn, TTYPEn, TUNITn, + TNULLn, TSCALn, TZEROn, TDISPn, TDIMn, TLMINn, TLMAXn, TDMINn, + TDMAXn, TCTYPn, TCRPXn, TCRVLn, TCDLTn, TCROTn, +> and TCUNIn. \label{fficol} \label{fficls} \label{ffdcol} +- + int fits_insert_col / fficol + (fitsfile *fptr, int colnum, char *ttype, char *tform, + > int *status) + + int fits_insert_cols / fficls + (fitsfile *fptr, int colnum, int ncols, char **ttype, + char **tform, > int *status) + + int fits_delete_col / ffdcol(fitsfile *fptr, int colnum, > int *status) +- +>3 Copy a column from one HDU to another (or to the same HDU). If + create\_col = TRUE, then a new column will be inserted in the output + table, at position `outcolumn', otherwise the existing output column will + be overwritten (in which case it must have a compatible data type). + If outcolnum is greater than the number of column in the table, then + the new column will be appended to the end of the table. + Note that the first column in a table is at colnum = 1. + The standard indexed keywords that related to the column (e.g., TDISPn, +> TUNITn, TCRPXn, TCDLTn, etc.) will also be copied. \label{ffcpcl} +- + int fits_copy_col / ffcpcl + (fitsfile *infptr, fitsfile *outfptr, int incolnum, int outcolnum, + int create_col, > int *status); +- +>4 Copy 'nrows' consecutive rows from one table to another, beginning + with row 'firstrow'. These rows will be appended to any existing + rows in the output table. +> Note that the first row in a table is at row = 1. \label{ffcprw} +- + int fits_copy_rows / ffcprw + (fitsfile *infptr, fitsfile *outfptr, LONGLONG firstrow, + LONGLONG nrows, > int *status); +- +>5 Modify the vector length of a binary table column (e.g., + change a column from TFORMn = '1E' to '20E'). The vector +> length may be increased or decreased from the current value. \label{ffmvec} +- + int fits_modify_vector_len / ffmvec + (fitsfile *fptr, int colnum, LONGLONG newveclen, > int *status) +- +***4. Read and Write Column Data Routines + +The following routines write or read data values in the current ASCII +or binary table extension. If a write operation extends beyond the +current size of the table, then the number of rows in the table will +automatically be increased and the NAXIS2 keyword value will be +updated. Attempts to read beyond the end of the table will result in +an error. + +Automatic data type conversion is performed for numerical data types +(only) if the data type of the column (defined by the TFORMn keyword) +differs from the data type of the array in the calling routine. ASCII and binary +tables support the following data type values: TSTRING, TBYTE, TSBYTE, TSHORT, +TUSHORT, TINT, TUINT, TLONG, TLONGLONG, TULONG, TFLOAT, or TDOUBLE. +Binary tables also support TLOGICAL (internally mapped to the `char' +data type), TCOMPLEX, and TDBLCOMPLEX. + +Note that it is *not* necessary to insert rows in a table before +writing data to those rows (indeed, it would be inefficient to do so). +Instead, one may simply write data to any row of the table, whether that +row of data already exists or not. + +Individual bits in a binary table 'X' or 'B' column may be read/written +to/from a *char array by specifying the TBIT datatype. The *char +array will be interpreted as an array of logical TRUE (1) or FALSE (0) +values that correspond to the value of each bit in the FITS 'X' or 'B' column. +Alternatively, the values in a binary table 'X' column may be read/written +8 bits at a time to/from an array of 8-bit integers by specifying the +TBYTE datatype. + +Note that within the context of these routines, the TSTRING data type +corresponds to a C 'char**' data type, i.e., a pointer to an array of +pointers to an array of characters. This is different from the keyword +reading and writing routines where TSTRING corresponds to a C 'char*' +data type, i.e., a single pointer to an array of characters. When +reading strings from a table, the char arrays obviously must have been +allocated long enough to hold the whole FITS table string. + +Numerical data values are automatically scaled by the TSCALn and TZEROn +keyword values (if they exist). + +In the case of binary tables with vector elements, the 'felem' +parameter defines the starting element (beginning with 1, not 0) within +the cell (a cell is defined as the intersection of a row and a column +and may contain a single value or a vector of values). The felem +parameter is ignored when dealing with ASCII tables. Similarly, in the +case of binary tables the 'nelements' parameter specifies the total +number of vector values to be read or written (continuing on subsequent +rows if required) and not the number of table cells. + +>>1 Write elements into an ASCII or binary table column. + The first routine simply writes the array of values to the FITS file + (doing data type conversion if necessary) whereas the second routine + will substitute the appropriate FITS null value for all elements + which are equal to the input value of nulval (note that this + parameter gives the address of nulval, not the null value + itself). For integer columns the FITS null value is defined by the + TNULLn keyword (an error is returned if the keyword doesn't exist). + For floating point columns the special IEEE NaN (Not-a-Number) + value will be written into the FITS file. If a null pointer is + entered for nulval, then the null value is ignored and this routine + behaves the same as the first routine. The third routine + simply writes undefined pixel values to the column. The fourth routine + fills every column in the table with null values, in the specified + rows (ignoring any columns that do not have a defined null value). + \label{ffpcl} \label{ffpcn} \label{ffpclu} +- + int fits_write_col / ffpcl + (fitsfile *fptr, int datatype, int colnum, LONGLONG firstrow, + LONGLONG firstelem, LONGLONG nelements, DTYPE *array, > int *status) + + int fits_write_colnull / ffpcn + (fitsfile *fptr, int datatype, int colnum, LONGLONG firstrow, + LONGLONG firstelem, LONGLONG nelements, DTYPE *array, DTYPE *nulval, + > int *status) + + int fits_write_col_null / ffpclu + (fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem, + LONGLONG nelements, > int *status) + + int fits_write_nullrows / ffprwu + (fitsfile *fptr, LONGLONG firstrow, LONGLONG nelements, > int *status) +- +>2 Read elements from an ASCII or binary table column. The data type + parameter specifies the data type of the `nulval' and `array' pointers; + Undefined array elements will be returned with a value = *nullval, + (note that this parameter gives the address of the null value, not the + null value itself) unless nulval = 0 or *nulval = 0, in which case + no checking for undefined pixels will be performed. The second + routine is similar except that any undefined pixels will have the + corresponding nullarray element set equal to TRUE (= 1). + + Any column, regardless of it's intrinsic data type, may be read as a + string. It should be noted however that reading a numeric column + as a string is 10 - 100 times slower than reading the same column + as a number due to the large overhead in constructing the formatted + strings. The display format of the returned strings will be + determined by the TDISPn keyword, if it exists, otherwise by the + data type of the column. The length of the returned strings (not + including the null terminating character) can be determined with + the fits\_get\_col\_display\_width routine. The following TDISPn + display formats are currently supported: +- + Iw.m Integer + Ow.m Octal integer + Zw.m Hexadecimal integer + Fw.d Fixed floating point + Ew.d Exponential floating point + Dw.d Exponential floating point + Gw.d General; uses Fw.d if significance not lost, else Ew.d +- + where w is the width in characters of the displayed values, m is the minimum + number of digits displayed, and d is the number of digits to the right of the + decimal. The .m field is optional. +> \label{ffgcv} \label{ffgcf} +- + int fits_read_col / ffgcv + (fitsfile *fptr, int datatype, int colnum, LONGLONG firstrow, LONGLONG firstelem, + LONGLONG nelements, DTYPE *nulval, DTYPE *array, int *anynul, int *status) + + int fits_read_colnull / ffgcf + (fitsfile *fptr, int datatype, int colnum, LONGLONG firstrow, LONGLONG firstelem, + LONGLONG nelements, DTYPE *array, char *nullarray, int *anynul, int *status) +- + +***5. Row Selection and Calculator Routines + +These routines all parse and evaluate an input string containing a user +defined arithmetic expression. The first 3 routines select rows in a +FITS table, based on whether the expression evaluates to true (not +equal to zero) or false (zero). The other routines evaluate the +expression and calculate a value for each row of the table. The +allowed expression syntax is described in the row filter section in the +`Extended File Name Syntax' chapter of this document. The expression +may also be written to a text file, and the name of the file, prepended +with a '@' character may be supplied for the 'expr' parameter (e.g. +'@filename.txt'). The expression in the file can be arbitrarily +complex and extend over multiple lines of the file. Lines that begin +with 2 slash characters ('//') will be ignored and may be used to add +comments to the file. + +>1 Evaluate a boolean expression over the indicated rows, returning an + array of flags indicating which rows evaluated to TRUE/FALSE. + Upon return, +> *n\_good\_rows contains the number of rows that evaluate to TRUE. \label{fffrow} +- + int fits_find_rows / fffrow + (fitsfile *fptr, char *expr, long firstrow, long nrows, + > long *n_good_rows, char *row_status, int *status) +- +>>2 Find the first row which satisfies the input boolean expression \label{ffffrw} +- + int fits_find_first_row / ffffrw + (fitsfile *fptr, char *expr, > long *rownum, int *status) +- +>3 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 output +table is not empty, then this routine will append the new +selected rows after the existing rows. If the +>files are the same, delete the FALSE rows (preserve the TRUE rows). \label{ffsrow} +- + int fits_select_rows / ffsrow + (fitsfile *infptr, fitsfile *outfptr, char *expr, > int *status ) +- +>4 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 fits\_test\_expr to obtain +>the dimensions of the results. \label{ffcrow} +- + int fits_calc_rows / ffcrow + (fitsfile *fptr, int datatype, char *expr, long firstrow, + long nelements, void *nulval, > void *array, int *anynul, int *status) +- +>5 Evaluate an expression and write the result either to a column (if +the expression is a function of other columns in the table) or to a +keyword (if the expression evaluates to a constant and is not a +function of other columns in the table). In the former case, the +parName parameter is the name of the column (which may or may not already +exist) into which to write the results, and parInfo contains an +optional TFORM keyword value if a new column is being created. If a +TFORM value is not specified then a default format will be used, +depending on the expression. If the expression evaluates to a constant, +then the result will be written to the keyword name given by the +parName parameter, and the parInfo parameter may be used to supply an +optional comment for the keyword. If the keyword does not already +exist, then the name of the keyword must be preceded with a '\#' character, +> otherwise the result will be written to a column with that name. \label{ffcalc} +- + int fits_calculator / ffcalc + (fitsfile *infptr, char *expr, fitsfile *outfptr, char *parName, + char *parInfo, > int *status) +- +>6 This calculator routine is similar to the previous routine, except +that the expression is only evaluated over the specified +row ranges. nranges specifies the number of row ranges, and firstrow +>and lastrow give the starting and ending row number of each range. \label{ffcalcrng} +- + int fits_calculator_rng / ffcalc_rng + (fitsfile *infptr, char *expr, fitsfile *outfptr, char *parName, + char *parInfo, int nranges, long *firstrow, long *lastrow + > int *status) +- +>7 Evaluate the given expression and return dimension and type information +on the result. The returned dimensions correspond to a single row entry +of the requested expression, and are equivalent to the result of fits\_read\_tdim(). +Note that strings are considered to be one element regardless of string length. +>If maxdim == 0, then naxes is optional. \label{fftexp} +- + int fits_test_expr / fftexp + (fitsfile *fptr, char *expr, int maxdim > int *datatype, long *nelem, int *naxis, + long *naxes, int *status) +- + +***6. Column Binning or Histogramming Routines + +The following routines may be useful when performing histogramming operations on +column(s) of a table to generate an image in a primary array or image extension. + +>1 Calculate the histogramming parameters (min, max, and bin size +for each axis of the histogram, based on a variety of possible input parameters. +If the input names of the columns to be binned are null, then the routine will first +look for the CPREF = "NAME1, NAME2, ..." keyword which lists the preferred +columns. If not present, then the routine will assume the column names X, Y, Z, and T +for up to 4 axes (as specified by the NAXIS parameter). + +MININ and MAXIN are input arrays that give the minimum and maximum value for +the histogram, along each axis. Alternatively, the name of keywords that give +the min, max, and binsize may be give with the MINNAME, MAXNAME, and BINNAME +array parameters. If the value = DOUBLENULLVALUE and no keyword names are +given, then the routine will use the TLMINn and TLMAXn keywords, if present, or the +actual min and/or max values in the column. + +BINSIZEIN is an array giving the binsize along each axis. +If the value = +DOUBLENULLVALUE, and a keyword name is not specified with BINNAME, +then this routine will first look for the TDBINn keyword, or else will +use a binsize = 1, or a binsize that produces 10 histogram bins, which ever +is smaller. +> \label{calcbinning} +- + int fits_calc_binning + Input parameters: + (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 */ + Output parameters: + int *colnum, /* O - column numbers, to be binned */ + long *naxes, /* 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) +- + +>2 Copy the relevant keywords from the header of the table that is being +binned, to the the header of the output histogram image. This will not +copy the table structure keywords (e.g., NAXIS, TFORMn, TTYPEn, etc.) nor +will it copy the keywords that apply to other columns of the table that are +not used to create the histogram. This routine will translate the names of +the World Coordinate System (WCS) keywords for the binned columns into the +form that is need for a FITS image (e.g., the TCTYPn table keyword will +be translated to the CTYPEn image keyword). +> \label{copypixlist2image} +- + int fits_copy_pixlist2image + (fitsfile *infptr, /* I - pointer to input HDU */ + fitsfile *outfptr, /* I - pointer to output HDU */ + int firstkey, /* I - first HDU keyword 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 */ +- + +>3 Write a set of default WCS keywords to the histogram header, IF the +WCS keywords do not already exist. This will create a linear WCS where +the coordinate types are equal to the original column names. +> \label{writekeyshisto} +- + 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 of the binned columns */ + int *status) +- + +>4 Update the WCS keywords in a histogram image header that give the location +of the reference pixel (CRPIXn), and the pixel size (CDELTn), in the binned +image. +> \label{rebinwcs} +- + 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) +- + +>5 Bin the values in the input table columns, and write the histogram +array to the output FITS image (histptr). +> \label{makehist} +- + int fits_make_hist + (fitsfile *fptr, /* I - 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 (FLOATNULLVALUE */ + /* for no weighting) */ + int wtcolnum, /* I - keyword or col for weight (or NULL) */ + int recip, /* I - use reciprocal of the weight? 0 or 1 */ + 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) +- + + +**H. Utility Routines + +***1. File Checksum Routines + +The following routines either compute or validate the checksums for the +CHDU. The DATASUM keyword is used to store the numerical value of the +32-bit, 1's complement checksum for the data unit alone. If there is +no data unit then the value is set to zero. The numerical value is +stored as an ASCII string of digits, enclosed in quotes, because the +value may be too large to represent as a 32-bit signed integer. The +CHECKSUM keyword is used to store the ASCII encoded COMPLEMENT of the +checksum for the entire HDU. Storing the complement, rather than the +actual checksum, forces the checksum for the whole HDU to equal zero. +If the file has been modified since the checksums were computed, then +the HDU checksum will usually not equal zero. These checksum keyword +conventions are based on a paper by Rob Seaman published in the +proceedings of the ADASS IV conference in Baltimore in November 1994 +and a later revision in June 1995. See Appendix B for the definition +of the parameters used in these routines. + +>1 Compute and write the DATASUM and CHECKSUM keyword values for the CHDU + into the current header. If the keywords already exist, their values + will be updated only if necessary (i.e., if the file + has been modified since the original keyword +> values were computed). \label{ffpcks} +- + int fits_write_chksum / ffpcks + (fitsfile *fptr, > int *status) +- +>2 Update the CHECKSUM keyword value in the CHDU, assuming that the + DATASUM keyword exists and already has the correct value. This routine + calculates the new checksum for the current header unit, adds it to the + data unit checksum, encodes the value into an ASCII string, and writes +> the string to the CHECKSUM keyword. \label{ffupck} +- + int fits_update_chksum / ffupck + (fitsfile *fptr, > int *status) +- +>3 Verify the CHDU by computing the checksums and comparing + them with the keywords. The data unit is verified correctly + if the computed checksum equals the value of the DATASUM + keyword. The checksum for the entire HDU (header plus data unit) is + correct if it equals zero. The output DATAOK and HDUOK parameters + in this routine are integers which will have a value = 1 + if the data or HDU is verified correctly, a value = 0 + if the DATASUM or CHECKSUM keyword is not present, or value = -1 +> if the computed checksum is not correct. \label{ffvcks} +- + int fits_verify_chksum / ffvcks + (fitsfile *fptr, > int *dataok, int *hduok, int *status) +- +>4 Compute and return the checksum values for the CHDU + without creating or modifying the + CHECKSUM and DATASUM keywords. This routine is used internally by +> ffvcks, but may be useful in other situations as well. \label{ffgcks} +- + int fits_get_chksum/ /ffgcks + (fitsfile *fptr, > unsigned long *datasum, unsigned long *hdusum, + int *status) +- +>5 Encode a checksum value + into a 16-character string. If complm is non-zero (true) then the 32-bit +> sum value will be complemented before encoding. \label{ffesum} +- + int fits_encode_chksum / ffesum + (unsigned long sum, int complm, > char *ascii); +- +>6 Decode a 16-character checksum string into a unsigned long value. + If is non-zero (true). then the 32-bit sum value will be complemented + after decoding. The checksum value is also returned as the +> value of the function. \label{ffdsum} +- + unsigned long fits_decode_chksum / ffdsum + (char *ascii, int complm, > unsigned long *sum); +- + +***2. Date and Time Utility Routines + +The following routines help to construct or parse the FITS date/time +strings. Starting in the year 2000, the FITS DATE keyword values (and +the values of other `DATE-' keywords) must have the form 'YYYY-MM-DD' +(date only) or 'YYYY-MM-DDThh:mm:ss.ddd...' (date and time) where the +number of decimal places in the seconds value is optional. These times +are in UTC. The older 'dd/mm/yy' date format may not be used for dates +after 01 January 2000. See Appendix B for the definition of the +parameters used in these routines. + +>1 Get the current system date. C already provides standard + library routines for getting the current date and time, + but this routine is provided for compatibility with + the Fortran FITSIO library. The returned year has 4 digits +> (1999, 2000, etc.) \label{ffgsdt} +- + int fits_get_system_date/ffgsdt + ( > int *day, int *month, int *year, int *status ) +- + +>2 Get the current system date and time string ('YYYY-MM-DDThh:mm:ss'). +The time will be in UTC/GMT if available, as indicated by a returned timeref +value = 0. If the returned value of timeref = 1 then this indicates that +it was not possible to convert the local time to UTC, and thus the local +>time was returned. +- + int fits_get_system_time/ffgstm + (> char *datestr, int *timeref, int *status) +- + +>3 Construct a date string from the input date values. If the year +is between 1900 and 1998, inclusive, then the returned date string will +have the old FITS format ('dd/mm/yy'), otherwise the date string will +have the new FITS format ('YYYY-MM-DD'). Use fits\_time2str instead +> to always return a date string using the new FITS format. \label{ffdt2s} +- + int fits_date2str/ffdt2s + (int year, int month, int day, > char *datestr, int *status) +- + +>4 Construct a new-format date + time string ('YYYY-MM-DDThh:mm:ss.ddd...'). + If the year, month, and day values all = 0 then only the time is encoded + with format 'hh:mm:ss.ddd...'. The decimals parameter specifies how many + decimal places of fractional seconds to include in the string. If `decimals' +> is negative, then only the date will be return ('YYYY-MM-DD'). +- + int fits_time2str/fftm2s + (int year, int month, int day, int hour, int minute, double second, + int decimals, > char *datestr, int *status) +- + +>5 Return the date as read from the input string, where the string may be +in either the old ('dd/mm/yy') or new ('YYYY-MM-DDThh:mm:ss' or +'YYYY-MM-DD') FITS format. Null pointers may be supplied for any +> unwanted output date parameters. +- + int fits_str2date/ffs2dt + (char *datestr, > int *year, int *month, int *day, int *status) +- + +>6 Return the date and time as read from the input string, where the +string may be in either the old or new FITS format. The returned hours, +minutes, and seconds values will be set to zero if the input string +does not include the time ('dd/mm/yy' or 'YYYY-MM-DD') . Similarly, +the returned year, month, and date values will be set to zero if the +date is not included in the input string ('hh:mm:ss.ddd...'). Null +pointers may be supplied for any unwanted output date and time +>parameters. +- + int fits_str2time/ffs2tm + (char *datestr, > int *year, int *month, int *day, int *hour, + int *minute, double *second, int *status) +- + +***3. General Utility Routines + +The following utility routines may be useful for certain applications. + +>1 Return the revision number of the CFITSIO library. + The revision number will be incremented with each new +> release of CFITSIO. \label{ffvers} +- + float fits_get_version / ffvers ( > float *version) +- +>2 Write an 80-character message to the CFITSIO error stack. Application + programs should not normally write to the stack, but there may be +> some situations where this is desirable. \label{ffpmsg} +- + void fits_write_errmsg / ffpmsg (char *err_msg) +- +>>3 Convert a character string to uppercase (operates in place). \label{ffupch} +- + void fits_uppercase / ffupch (char *string) +- +>4 Compare the input template string against the reference string + to see if they match. The template string may contain wildcard + characters: '*' will match any sequence of characters (including + zero characters) and '?' will match any single character in the + reference string. The '\#' character will match any consecutive string + of decimal digits (0 - 9). If casesen = CASESEN = TRUE then the match will + be case sensitive, otherwise the case of the letters will be ignored + if casesen = CASEINSEN = FALSE. The returned MATCH parameter will be + TRUE if the 2 strings match, and EXACT will be TRUE if the match is + exact (i.e., if no wildcard characters were used in the match). +> Both strings must be 68 characters or less in length. \label{ffcmps} +- + void fits_compare_str / ffcmps + (char *templt, char *string, int casesen, > int *match, int *exact) +- +>5 Split a string containing a list of names (typically file names or column + names) into individual name tokens by a sequence of calls to + fits\_split\_names. The names in the list 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 null terminator 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. \label{splitnames} +- + char *fits_split_names(char *namelist) +- + The following example shows 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 +- +>6 Test that the keyword name contains only legal characters (A-Z,0-9, + hyphen, and underscore) or that the keyword record contains only legal +> printable ASCII characters \label{fftkey} \label{fftrec} +- + int fits_test_keyword / fftkey (char *keyname, > int *status) + + int fits_test_record / fftrec (char *card, > int *status) +- +>7 Test whether the current header contains any NULL (ASCII 0) characters. + These characters are illegal in the header, but they will go undetected + by most of the CFITSIO keyword header routines, because the null is + interpreted as the normal end-of-string terminator. This routine returns + the position of the first null character in the header, or zero if there + are no nulls. For example a returned value of 110 would indicate that + the first NULL is located in the 30th character of the second keyword + in the header (recall that each header record is 80 characters long). + Note that this is one of the few CFITSIO routines in which the returned +> value is not necessarily equal to the status value). \label{ffnchk} +- + int fits_null_check / ffnchk (char *card, > int *status) +- +>8 Parse a header keyword record and return the name of the keyword, + and the length of the name. + The keyword name normally occupies the first 8 characters of the + record, except under the HIERARCH convention where the name can +> be up to 70 characters in length. \label{ffgknm} +- + int fits_get_keyname / ffgknm + (char *card, > char *keyname, int *keylength, int *status) +- +>9 Parse a header keyword record, returning the value (as + a literal character string) and comment strings. If the keyword has no + value (columns 9-10 not equal to '= '), then a null value string is + returned and the comment string is set equal to column 9 - 80 of the +> input string. \label{ffpsvc} +- + int fits_parse_value / ffpsvc + (char *card, > char *value, char *comment, int *status) +- +>10 Construct an array indexed keyword name (ROOT + nnn). + This routine appends the sequence number to the root string to create +> a keyword name (e.g., 'NAXIS' + 2 = 'NAXIS2') \label{ffkeyn} +- + int fits_make_keyn / ffkeyn + (char *keyroot, int value, > char *keyname, int *status) +- +>11 Construct a sequence keyword name (n + ROOT). + This routine concatenates the sequence number to the front of the +> root string to create a keyword name (e.g., 1 + 'CTYP' = '1CTYP') \label{ffnkey} +- + int fits_make_nkey / ffnkey + (int value, char *keyroot, > char *keyname, int *status) +- +>12 Determine the data type of a keyword value string. This routine + parses the keyword value string to determine its data type. + Returns 'C', 'L', 'I', 'F' or 'X', for character string, logical, +> integer, floating point, or complex, respectively. \label{ffdtyp} +- + int fits_get_keytype / ffdtyp + (char *value, > char *dtype, int *status) +- +>13 Determine the integer data type of an integer keyword value string. + The returned datatype value is the minimum integer datatype (starting + from top of the following list and working down) required +> to store the integer value: +- + Data Type Range + TSBYTE: -128 to 127 + TBYTE: 128 to 255 + TSHORT: -32768 to 32767 + TUSHORT: 32768 to 65535 + TINT -2147483648 to 2147483647 + TUINT 2147483648 to 4294967295 + TLONGLONG -9223372036854775808 to 9223372036854775807 +- +> The *neg parameter returns 1 if the input value is +> negative and returns 0 if it is non-negative.\label{ffinttyp} +- + int fits_get_inttype / ffinttyp + (char *value, > int *datatype, int *neg, int *status) +- +>14 Return the class of an input header record. The record is classified + into one of the following categories (the class values are + defined in fitsio.h). Note that this is one of the few CFITSIO +> routines that does not return a status value. \label{ffgkcl} +- + Class Value Keywords + TYP_STRUC_KEY 10 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 20 The experimental keywords used in the compressed + image format ZIMAGE, ZCMPTYPE, ZNAMEn, ZVALn, + ZTILEn, ZBITPIX, ZNAXISn, ZSCALE, ZZERO, ZBLANK + TYP_SCAL_KEY 30 BSCALE, BZERO, TSCALn, TZEROn + TYP_NULL_KEY 40 BLANK, TNULLn + TYP_DIM_KEY 50 TDIMn + TYP_RANG_KEY 60 TLMINn, TLMAXn, TDMINn, TDMAXn, DATAMIN, DATAMAX + TYP_UNIT_KEY 70 BUNIT, TUNITn + TYP_DISP_KEY 80 TDISPn + TYP_HDUID_KEY 90 EXTNAME, EXTVER, EXTLEVEL, HDUNAME, HDUVER, HDULEVEL + TYP_CKSUM_KEY 100 CHECKSUM, DATASUM + TYP_WCS_KEY 110 WCS keywords defined in the the WCS papers, including: + CTYPEn, CUNITn, CRVALn, CRPIXn, CROTAn, CDELTn + CDj_is, PVj_ms, LONPOLEs, LATPOLEs + TCTYPn, TCTYns, TCUNIn, TCUNns, TCRVLn, TCRVns, TCRPXn, + TCRPks, TCDn_k, TCn_ks, TPVn_m, TPn_ms, TCDLTn, TCROTn + jCTYPn, jCTYns, jCUNIn, jCUNns, jCRVLn, jCRVns, iCRPXn, + iCRPns, jiCDn, jiCDns, jPVn_m, jPn_ms, jCDLTn, jCROTn + (i,j,m,n are integers, s is any letter) + TYP_REFSYS_KEY 120 EQUINOXs, EPOCH, MJD-OBSs, RADECSYS, RADESYSs, DATE-OBS + TYP_COMM_KEY 130 COMMENT, HISTORY, (blank keyword) + TYP_CONT_KEY 140 CONTINUE + TYP_USER_KEY 150 all other keywords + + int fits_get_keyclass / ffgkcl (char *card) +- +>15 Parse the 'TFORM' binary table column format string. + This routine parses the input TFORM character string and returns the + integer data type code, the repeat count of the field, and, in the case + of character string fields, the length of the unit string. See Appendix + B for the allowed values for the returned typecode parameter. A +> null pointer may be given for any output parameters that are not needed. \label{ffbnfm} +- + int fits_binary_tform / ffbnfm + (char *tform, > int *typecode, long *repeat, long *width, + int *status) + + int fits_binary_tformll / ffbnfmll + (char *tform, > int *typecode, LONGLONG *repeat, long *width, + int *status) +- +>16 Parse the 'TFORM' keyword value that defines the column format in + an ASCII table. This routine parses the input TFORM character + string and returns the data type code, the width of the column, + and (if it is a floating point column) the number of decimal places + to the right of the decimal point. The returned data type codes are + the same as for the binary table, with the following + additional rules: integer columns that are between 1 and 4 characters + wide are defined to be short integers (code = TSHORT). Wider integer + columns are defined to be regular integers (code = TLONG). Similarly, + Fixed decimal point columns (with TFORM = 'Fw.d') are defined to + be single precision reals (code = TFLOAT) if w is between 1 and 7 characters + wide, inclusive. Wider 'F' columns will return a double precision + data code (= TDOUBLE). 'Ew.d' format columns will have datacode = TFLOAT, + and 'Dw.d' format columns will have datacode = TDOUBLE. A null +> pointer may be given for any output parameters that are not needed. \label{ffasfm} +- + int fits_ascii_tform / ffasfm + (char *tform, > int *typecode, long *width, int *decimals, + int *status) +- +>17 Calculate the starting column positions and total ASCII table width + based on the input array of ASCII table TFORM values. The SPACE input + parameter defines how many blank spaces to leave between each column + (it is recommended to have one space between columns for better human +> readability). \label{ffgabc} +- + int fits_get_tbcol / ffgabc + (int tfields, char **tform, int space, > long *rowlen, + long *tbcol, int *status) +- +>18 Parse a template header record and return a formatted 80-character string + suitable for appending to (or deleting from) a FITS header file. + This routine is useful for parsing lines from an ASCII template file + and reformatting them into legal FITS header records. The formatted + string may then be passed to the fits\_write\_record, ffmcrd, or + fits\_delete\_key routines +> to append or modify a FITS header record. \label{ffgthd} +- + int fits_parse_template / ffgthd + (char *templt, > char *card, int *keytype, int *status) +- + The input templt character string generally should contain 3 tokens: + (1) the KEYNAME, (2) the VALUE, and (3) the COMMENT string. The + TEMPLATE string must adhere to the following format: + +>- The KEYNAME token must begin in columns 1-8 and be a maximum of 8 + characters long. A legal FITS keyword name may only + contain the characters A-Z, 0-9, and '-' (minus sign) and + underscore. This routine will automatically convert any lowercase + characters to uppercase in the output string. If the first 8 characters + of the template line are + blank then the remainder of the line is considered to be a FITS comment +> (with a blank keyword name). + +>- The VALUE token must be separated from the KEYNAME token by one or more + spaces and/or an '=' character. The data type of the VALUE token + (numeric, logical, or character string) is automatically determined + and the output CARD string is formatted accordingly. The value + token may be forced to be interpreted as a string (e.g. if it is a + string of numeric digits) by enclosing it in single quotes. + If the value token is a character string that contains 1 or more + embedded blank space characters or slash ('/') characters then the +> entire character string must be enclosed in single quotes. + +>- The COMMENT token is optional, but if present must be separated from +> the VALUE token by a blank space or a '/' character. + +>- One exception to the above rules is that if the first non-blank + character in the first 8 characters of the template string is a + minus sign ('-') followed + by a single token, or a single token followed by an equal sign, + then it is interpreted as the name of a keyword which is to be +> deleted from the FITS header. + +>- The second exception is that if the template string starts with + a minus sign and is followed by 2 tokens (without an equals sign between + them) then the second token + is interpreted as the new name for the keyword specified by + first token. In this case the old keyword name (first token) + is returned in characters 1-8 of the returned CARD string, and + the new keyword name (the second token) is returned in characters + 41-48 of the returned CARD string. These old and new names + may then be passed to the ffmnam routine which will change +> the keyword name. + + The keytype output parameter indicates how the returned CARD string + should be interpreted: +- + keytype interpretation + ------- ------------------------------------------------- + -2 Rename the keyword with name = the first 8 characters of CARD + to the new name given in characters 41 - 48 of CARD. + + -1 delete the keyword with this name from the FITS header. + + 0 append the CARD string to the FITS header if the + keyword does not already exist, otherwise update + the keyword value and/or comment field if is already exists. + + 1 This is a HISTORY or COMMENT keyword; append it to the header + + 2 END record; do not explicitly write it to the FITS file. +- + EXAMPLES: The following lines illustrate valid input template strings: +- + INTVAL 7 / This is an integer keyword + RVAL 34.6 / This is a floating point keyword + EVAL=-12.45E-03 / This is a floating point keyword in exponential notation + lval F / This is a boolean keyword + This is a comment keyword with a blank keyword name + SVAL1 = 'Hello world' / this is a string keyword + SVAL2 '123.5' this is also a string keyword + sval3 123+ / this is also a string keyword with the value '123+ ' + # the following template line deletes the DATE keyword + - DATE + # the following template line modifies the NAME keyword to OBJECT + - NAME OBJECT +- +>19 Translate a keyword name into a new name, based on a set of patterns. +This routine is useful for translating keywords in cases such as +adding or deleting columns in +a table, or copying a column from one table to another, or extracting +an array from a cell in a binary table column into an image extension. In +these cases, it is necessary to translate the names of the keywords associated +with the original table column(s) into the appropriate keyword name in the final +file. For example, if column 2 is deleted from a table, +then the value of 'n' in all the +TFORMn and TTYPEn keywords for columns 3 and higher must be decremented +by 1. Even more complex translations are sometimes needed to convert the +WCS keywords when extracting an image out of a table column cell into +a separate image extension. + +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: + + "-" - 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.\label{translatekey} +- +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 */ +- +> Here is an example of some of the patterns used to convert the keywords associated +with an image in a cell of a table column into the keywords appropriate for +>an IMAGE extension: +- + 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"}}; +- +>20 Translate the keywords in the input HDU into the keywords that are +appropriate for the output HDU. This is a driver routine that calls +>the previously described routine. +- +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 */ +- + +>21 Parse the input string containing a list of rows or row ranges, and + return integer arrays containing the first and last row in each + range. For example, if rowlist = "3-5, 6, 8-9" then it will + return numranges = 3, rangemin = 3, 6, 8 and rangemax = 5, 6, 9. + At most, 'maxranges' number of ranges will be returned. 'maxrows' + is the maximum number of rows in the table; any rows or ranges + larger than this will be ignored. The rows must be specified in + increasing order, and the ranges must not overlap. A minus sign + may be use to specify all the rows to the upper or lower bound, so + "50-" means all the rows from 50 to the end of the table, and "-" + means all the rows in the table, from 1 - maxrows. +> \label{ffrwrg} +- + int fits_parse_range / ffrwrg(char *rowlist, LONGLONG maxrows, int maxranges, > + int *numranges, long *rangemin, long *rangemax, int *status) + + int fits_parse_rangell / ffrwrgll(char *rowlist, LONGLONG maxrows, int maxranges, > + int *numranges, LONGLONG *rangemin, LONGLONG *rangemax, int *status) +- +>22 Check that the Header fill bytes (if any) are all blank. These are the bytes + that may follow END keyword and before the beginning of data unit, + or the end of the HDU if there is no data unit. +> \label{ffchfl} +- + int ffchfl(fitsfile *fptr, > int *status) +- +>23 Check that the Data fill bytes (if any) are all zero (for IMAGE or + BINARY Table HDU) or all blanks (for ASCII table HDU). These file + bytes may be located after the last valid data byte in the HDU and + before the physical end of the HDU. +> \label{ffcdfl} +- + int ffcdfl(fitsfile *fptr, > int *status) +- +>24 Estimate the root-mean-squared (RMS) noise in an image. +These routines are mainly for use with the Hcompress image compression +algorithm. They return an estimate of the RMS noise in the background +pixels of the image. This robust algorithm (written by Richard +White, STScI) first attempts to estimate the RMS value +as 1.68 times the median of the absolute differences between successive +pixels in the image. If the median = 0, then the +algorithm falls back to computing the RMS of the difference between successive +pixels, after several N-sigma rejection cycles to remove +extreme values. The input parameters are: the array of image pixel values +(either float or short values), the number of values in the array, +the value that is used to represent null pixels (enter a very +>large number if there are no null pixels). \label{imageRMS} +- + int fits_rms_float (float fdata[], int npix, float in_null_value, + > double *rms, int *status) + int fits_rms_short (short fdata[], int npix, short in_null_value, + > double *rms, int *status) +- +>25 Was CFITSIO compiled with the -D\_REENTRANT directive +so that it may be safely used in multi-threaded environments? +The following function returns 1 if yes, 0 if no. Note, however, +that even if the -D\_REENTRANT directive was specified, this does +not guarantee that the CFITSIO routines are thread-safe, because +>some compilers may not support this feature.\label{reentrant} +- +int fits_is_reentrant(void) +- + +*VII. The CFITSIO Iterator Function + +The fits\_iterate\_data function in CFITSIO provides a unique method of +executing an arbitrary user-supplied `work' function that operates on +rows of data in FITS tables or on pixels in FITS images. Rather than +explicitly reading and writing the FITS images or columns of data, one +instead calls the CFITSIO iterator routine, passing to it the name of +the user's work function that is to be executed along with a list of +all the table columns or image arrays that are to be passed to the work +function. The CFITSIO iterator function then does all the work of +allocating memory for the arrays, reading the input data from the FITS +file, passing them to the work function, and then writing any output +data back to the FITS file after the work function exits. Because +it is often more efficient to process only a subset of the total table +rows at one time, the iterator function can determine the optimum +amount of data to pass in each iteration and repeatedly call the work +function until the entire table been processed. + +For many applications this single CFITSIO iterator function can +effectively replace all the other CFITSIO routines for reading or +writing data in FITS images or tables. Using the iterator has several +important advantages over the traditional method of reading and writing +FITS data files: + +\begin{itemize} +\item +It cleanly separates the data I/O from the routine that operates on +the data. This leads to a more modular and `object oriented' +programming style. + +\item +It simplifies the application program by eliminating the need to allocate +memory for the data arrays and eliminates most of the calls to the CFITSIO +routines that explicitly read and write the data. + +\item +It ensures that the data are processed as efficiently as possible. +This is especially important when processing tabular data since +the iterator function will calculate the most efficient number +of rows in the table to be passed at one time to the user's work +function on each iteration. + +\item +Makes it possible for larger projects to develop a library of work +functions that all have a uniform calling sequence and are all +independent of the details of the FITS file format. + +\end{itemize} + +There are basically 2 steps in using the CFITSIO iterator function. +The first step is to design the work function itself which must have a +prescribed set of input parameters. One of these parameters is a +structure containing pointers to the arrays of data; the work function +can perform any desired operations on these arrays and does not need to +worry about how the input data were read from the file or how the +output data get written back to the file. + +The second step is to design the driver routine that opens all the +necessary FITS files and initializes the input parameters to the +iterator function. The driver program calls the CFITSIO iterator +function which then reads the data and passes it to the user's work +function. + +The following 2 sections describe these steps in more detail. There +are also several example programs included with the CFITSIO +distribution which illustrate how to use the iterator function. + +**A The Iterator Work Function + +The user-supplied iterator work function must have the following set of +input parameters (the function can be given any desired name): + +- + int user_fn( long totaln, long offset, long firstn, long nvalues, + int narrays, iteratorCol *data, void *userPointer ) +- + +\begin{itemize} + +\item + totaln -- the total number of table rows or image pixels + that will be passed to the work function + during 1 or more iterations. + +\item + offset -- the offset applied to the first table row or image + pixel to be passed to the work function. In other + words, this is the number of rows or pixels that + are skipped over before starting the iterations. If + offset = 0, then all the table rows or image pixels + will be passed to the work function. + +\item + firstn -- the number of the first table row or image pixel + (starting with 1) that is being passed in this + particular call to the work function. + +\item + nvalues -- the number of table rows or image pixels that are + being passed in this particular call to the work + function. nvalues will always be less than or + equal to totaln and will have the same value on + each iteration, except possibly on the last + call which may have a smaller value. + +\item + narrays -- the number of arrays of data that are being passed + to the work function. There is one array for each + image or table column. + +\item + *data -- array of structures, one for each + column or image. Each structure contains a pointer + to the array of data as well as other descriptive + parameters about that array. + +\item + *userPointer -- a user supplied pointer that can be used + to pass ancillary information from the driver function + to the work function. + This pointer is passed to the CFITSIO iterator function + which then passes it on to the + work function without any modification. + It may point to a single number, to an array of values, + to a structure containing an arbitrary set of parameters + of different types, + or it may be a null pointer if it is not needed. + The work function must cast this pointer to the + appropriate data type before using it it. +\end{itemize} + +The totaln, offset, narrays, data, and userPointer parameters are +guaranteed to have the same value on each iteration. Only firstn, +nvalues, and the arrays of data pointed to by the data structures may +change on each iterative call to the work function. + +Note that the iterator treats an image as a long 1-D array of pixels +regardless of it's intrinsic dimensionality. The total number of +pixels is just the product of the size of each dimension, and the order +of the pixels is the same as the order that they are stored in the FITS +file. If the work function needs to know the number and size of the +image dimensions then these parameters can be passed via the +userPointer structure. + +The iteratorCol structure is currently defined as follows: +- +typedef struct /* structure for the iterator function column information */ +{ + /* structure elements required as input to fits_iterate_data: */ + + fitsfile *fptr; /* pointer to the HDU containing the column or image */ + int colnum; /* column number in the table; ignored for images */ + char colname[70]; /* name (TTYPEn) of the column; null for images */ + int datatype; /* output data type (converted if necessary) */ + int iotype; /* type: InputCol, InputOutputCol, or OutputCol */ + + /* output structure 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; set */ + /* equal to 1 for images */ + long tlmin; /* legal minimum data value, if any */ + long tlmax; /* legal maximum data value, if any */ + char unit[70]; /* physical unit string (BUNIT or TUNITn) */ + char tdisp[70]; /* suggested display format; null if none */ + +} iteratorCol; +- + +Instead of directly reading or writing the elements in this structure, +it is recommended that programmers use the access functions that are +provided for this purpose. + +The first five elements in this structure must be initially defined by +the driver routine before calling the iterator routine. The CFITSIO +iterator routine uses this information to determine what column or +array to pass to the work function, and whether the array is to be +input to the work function, output from the work function, or both. +The CFITSIO iterator function fills in the values of the remaining +structure elements before passing it to the work function. + +The array structure element is a pointer to the actual data array and +it must be cast to the correct data type before it is used. The +`repeat' structure element give the number of data values in each row +of the table, so that the total number of data values in the array is +given by repeat * nvalues. In the case of image arrays and ASCII +tables, repeat will always be equal to 1. When the data type is a +character string, the array pointer is actually a pointer to an array +of string pointers (i.e., char **array). The other output structure +elements are provided for convenience in case that information is +needed within the work function. Any other information may be passed +from the driver routine to the work function via the userPointer +parameter. + +Upon completion, the work routine must return an integer status value, +with 0 indicating success and any other value indicating an error which +will cause the iterator function to immediately exit at that point. Return status +values in the range 1 -- 1000 should be avoided since these are +reserved for use by CFITSIO. A return status value of -1 may be used to +force the CFITSIO iterator function to stop at that point and return +control to the driver routine after writing any output arrays to the +FITS file. CFITSIO does not considered this to be an error condition, +so any further processing by the application program will continue normally. + +**B The Iterator Driver Function + +The iterator driver function must open the necessary FITS files and +position them to the correct HDU. It must also initialize the following +parameters in the iteratorCol structure (defined above) for each +column or image before calling the CFITSIO iterator function. +Several `constructor' routines are provided in CFITSIO for this +purpose. + +\begin{itemize} +\item + *fptr -- The fitsfile pointer to the table or image. +\item +colnum -- the number of the column in the table. This value is ignored + in the case of images. If colnum equals 0, then the column name + will be used to identify the column to be passed to the + work function. + +\item +colname -- the name (TTYPEn keyword) of the column. This is + only required if colnum = 0 and is ignored for images. +\item +datatype -- The desired data type of the array to be passed to the + work function. For numerical data the data type does + not need to be the same as the actual data type in the + FITS file, in which case CFITSIO will do the conversion. + Allowed values are: TSTRING, TLOGICAL, TBYTE, TSBYTE, TSHORT, TUSHORT, + TINT, TLONG, TULONG, TFLOAT, TDOUBLE. If the input + value of data type equals 0, then the existing + data type of the column or image will be used without + any conversion. + +\item +iotype -- defines whether the data array is to be input to the + work function (i.e, read from the FITS file), or output + from the work function (i.e., written to the FITS file) or + both. Allowed values are InputCol, OutputCol, or InputOutputCol. + Variable-length array columns are supported as InputCol or + InputOutputCol types, but may not be used for an OutputCol type. +\end{itemize} + +After the driver routine has initialized all these parameters, it +can then call the CFITSIO iterator function: + +- + int fits_iterate_data(int narrays, iteratorCol *data, long offset, + long nPerLoop, int (*workFn)( ), void *userPointer, int *status); +- + +\begin{itemize} +\item + + narrays -- the number of columns or images that are to be passed + to the work function. +\item + *data -- pointer to array of structures containing information + about each column or image. + +\item + offset -- if positive, this number of rows at the + beginning of the table (or pixels in the image) + will be skipped and will not be passed to the work + function. + +\item + nPerLoop - specifies the number of table rows (or number of + image pixels) that are to be passed to the work + function on each iteration. If nPerLoop = 0 + then CFITSIO will calculate the optimum number + for greatest efficiency. + If nPerLoop is negative, then all the rows + or pixels will be passed at one time, and the work + function will only be called once. If any variable + length arrays are being processed, then the nPerLoop + value is ignored, and the iterator will always process + one row of the table at a time. + +\item + *workFn - the name (actually the address) of the work function + that is to be called by fits\_iterate\_data. + +\item + *userPointer - this is a user supplied pointer that can be used + to pass ancillary information from the driver routine + to the work function. It may point to a single number, + an array, or to a structure containing an arbitrary set + of parameters. + +\item + *status - The CFITSIO error status. Should = 0 on input; + a non-zero output value indicates an error. +\end{itemize} + +When fits\_iterate\_data is called it first allocates memory to hold +all the requested columns of data or image pixel arrays. It then reads +the input data from the FITS tables or images into the arrays then +passes the structure with pointers to these data arrays to the work +function. After the work function returns, the iterator function +writes any output columns of data or images back to the FITS files. It +then repeats this process for any remaining sets of rows or image +pixels until it has processed the entire table or image or until the +work function returns a non-zero status value. The iterator then frees +the memory that it initially allocated and returns control to the +driver routine that called it. + +**C. Guidelines for Using the Iterator Function + +The totaln, offset, firstn, and nvalues parameters that are passed to +the work function are useful for determining how much of the data has +been processed and how much remains left to do. On the very first call +to the work function firstn will be equal to offset + 1; the work +function may need to perform various initialization tasks before +starting to process the data. Similarly, firstn + nvalues - 1 will be +equal to totaln on the last iteration, at which point the work function +may need to perform some clean up operations before exiting for the +last time. The work function can also force an early termination of +the iterations by returning a status value = -1. + +The narrays and iteratorCol.datatype arguments allow the work function +to double check that the number of input arrays and their data types +have the expected values. The iteratorCol.fptr and iteratorCol.colnum +structure elements can be used if the work function needs to read or +write the values of other keywords in the FITS file associated with +the array. This should generally only be done during the +initialization step or during the clean up step after the last set of +data has been processed. Extra FITS file I/O during the main +processing loop of the work function can seriously degrade the speed of +the program. + +If variable-length array columns are being processed, then the iterator +will operate on one row of the table at a time. In this case the +the repeat element in the interatorCol structure will be set equal to +the number of elements in the current row that is being processed. + +One important feature of the iterator is that the first element in each +array that is passed to the work function gives the value that is used +to represent null or undefined values in the array. The real data then +begins with the second element of the array (i.e., array[1], not +array[0]). If the first array element is equal to zero, then this +indicates that all the array elements have defined values and there are +no undefined values. If array[0] is not equal to zero, then this +indicates that some of the data values are undefined and this value +(array[0]) is used to represent them. In the case of output arrays +(i.e., those arrays that will be written back to the FITS file by the +iterator function after the work function exits) the work function must +set the first array element to the desired null value if necessary, +otherwise the first element should be set to zero to indicate that +there are no null values in the output array. CFITSIO defines 2 +values, FLOATNULLVALUE and DOUBLENULLVALUE, that can be used as default +null values for float and double data types, respectively. In the case +of character string data types, a null string is always used to +represent undefined strings. + +In some applications it may be necessary to recursively call the iterator +function. An example of this is given by one of the example programs +that is distributed with CFITSIO: it first calls a work function that +writes out a 2D histogram image. That work function in turn calls +another work function that reads the `X' and `Y' columns in a table to +calculate the value of each 2D histogram image pixel. Graphically, the +program structure can be described as: +- + driver --> iterator --> work1_fn --> iterator --> work2_fn +- + +Finally, it should be noted that the table columns or image arrays that +are passed to the work function do not all have to come from the same +FITS file and instead may come from any combination of sources as long +as they have the same length. The length of the first table column or +image array is used by the iterator if they do not all have the same +length. + +**D. Complete List of Iterator Routines + +All of the iterator routines are listed below. Most of these routines +do not have a corresponding short function name. + +>1 Iterator `constructor' functions that set + the value of elements in the iteratorCol structure + that define the columns or arrays. These set the fitsfile + pointer, column name, column number, datatype, and iotype, + respectively. The last 2 routines allow all the parameters + to be set with one function call (one supplies the column +> name, the other the column number). \label{ffiterset} + +- + 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); + + 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); +- +>2 Iterator `accessor' functions that return the value of the + element in the iteratorCol structure +> that describes a particular data column or array \label{ffiterget} +- + 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); +- +>>3 The CFITSIO iterator function \label{ffiter} +- + int fits_iterate_data(int narrays, 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); +- + +*IX. World Coordinate System Routines + +The FITS community has adopted a set of keyword conventions that define +the transformations needed to convert between pixel locations in an +image and the corresponding celestial coordinates on the sky, or more +generally, that define world coordinates that are to be associated with +any pixel location in an n-dimensional FITS array. CFITSIO is distributed +with a a few self-contained World Coordinate System (WCS) routines, +however, these routines DO NOT support all the latest WCS conventions, +so it is STRONGLY RECOMMENDED that software developers use a more robust +external WCS library. Several recommended libraries are: + +- + WCSLIB - supported by Mark Calabretta + WCSTools - supported by Doug Mink + AST library - developed by the U.K. Starlink project +- + +More information about the WCS keyword conventions and links to all of +these WCS libraries can be found on the FITS Support Office web site at +http://fits.gsfc.nasa.gov under the WCS link. + +The functions provided in these external WCS libraries will need +access to the WCS keywords contained in the FITS file headers. +One convenient way to pass this information to the external library is +to use the fits\_hdr2str routine in CFITSIO (defined below) to copy the +header keywords into one long string, and then pass this string to an +interface routine in the external library that will extract +the necessary WCS information (e.g., the 'wcspih' routine in the WCSLIB +library and the 'astFitsChan' and 'astPutCards' functions in the AST +library). + +>1 Concatenate the header keywords in the CHDU into a single long + string of characters. Each 80-character fixed-length keyword + record is appended to the output character string, in order, with + no intervening separator or terminating characters. The last header + record is terminated with a NULL character. This routine allocates + memory for the returned character array, so the calling program must + free the memory when finished. + + There are 2 related routines: fits\_hdr2str simply concatenates all + the existing keywords in the header; fits\_convert\_hdr2str is similar, + except that if the CHDU is a tile compressed image (stored in a binary + table) then it will first convert that header back to that of a + normal FITS image before concatenating the keywords. + + Selected keywords may be excluded from the returned character string. + If the second parameter (nocomments) is TRUE (nonzero) then any + COMMENT, HISTORY, or blank keywords in the header will not be copied + to the output string. + + The 'exclist' parameter may be used to supply a list of keywords + that are to be excluded from the output character string. Wild card + characters (*, ?, and \#) may be used in the excluded keyword names. + If no additional keywords are to be excluded, then set nexc = 0 and +> specify NULL for the the **exclist parameter. \label{hdr2str} +- + int fits_hdr2str + (fitsfile *fptr, int nocomments, char **exclist, int nexc, + > char **header, int *nkeys, int *status) + + int fits_convert_hdr2str / ffcnvthdr2str + (fitsfile *fptr, int nocomments, char **exclist, int nexc, + > char **header, int *nkeys, int *status) +- + +>2 The following CFITSIO routine is specifically designed for use +in conjunction with the WCSLIB library. It is not expected that +applications programmers will call this routine directly, but it +is documented here for completeness. This routine extracts arrays +from a binary table that contain WCS information using the -TAB table +lookup convention. See the documentation provided with the WCSLIB +> library for more information. \label{wcstab} +- + int fits_read_wcstab + (fitsfile *fptr, int nwtb, wtbarr *wtb, int *status); +- +**A. Self-contained WCS Routines + +The following routines DO NOT support the more recent WCS conventions +that have been approved as part of the FITS standard. Consequently, +the following routines ARE NOW DEPRECATED. It is STRONGLY RECOMMENDED +that software developers not use these routines, and instead use an +external WCS library, as described in the previous section. + +These routines are included mainly for backward compatibility with +existing software. They support the following standard map +projections: -SIN, -TAN, -ARC, -NCP, -GLS, -MER, and -AIT (these are the +legal values for the coordtype parameter). These routines are based +on similar functions in Classic AIPS. All the angular quantities are +given in units of degrees. + +>1 Get the values of the basic set of standard FITS celestial coordinate + system keywords from the header of a FITS image (i.e., the primary + array or an IMAGE extension). These values may then be passed to + the fits\_pix\_to\_world and fits\_world\_to\_pix routines that + perform the coordinate transformations. If any or all of the WCS + keywords are not present, then default values will be returned. If + the first coordinate axis is the declination-like coordinate, then + this routine will swap them so that the longitudinal-like coordinate + is returned as the first axis. + + The first routine (ffgics) returns + the primary WCS, whereas the second routine returns the particular + version of the WCS specified by the 'version' parameter, which much + be a character ranging from 'A' to 'Z' (or a blank character, which is + equivalent to calling ffgics). + + If the file uses the newer 'CDj\_i' WCS transformation matrix + keywords instead of old style 'CDELTn' and 'CROTA2' keywords, then + this routine will calculate and return the values of the equivalent + old-style keywords. Note that the conversion from the new-style + keywords to the old-style values is sometimes only an + approximation, so if the approximation is larger than an internally + defined threshold level, then CFITSIO will still return the + approximate WCS keyword values, but will also return with status = + APPROX\_WCS\_KEY, to warn the calling program that approximations + have been made. It is then up to the calling program to decide + whether the approximations are sufficiently accurate for the + particular application, or whether more precise WCS transformations +> must be performed using new-style WCS keywords directly. \label{ffgics} +- + int fits_read_img_coord / ffgics + (fitsfile *fptr, > double *xrefval, double *yrefval, + double *xrefpix, double *yrefpix, double *xinc, double *yinc, + double *rot, char *coordtype, int *status) + + int fits_read_img_coord_version / ffgicsa + (fitsfile *fptr, char version, > double *xrefval, double *yrefval, + double *xrefpix, double *yrefpix, double *xinc, double *yinc, + double *rot, char *coordtype, int *status) +- +>2 Get the values of the standard FITS celestial coordinate system + keywords from the header of a FITS table where the X and Y (or RA + and DEC) coordinates are stored in 2 separate columns of the table + (as in the Event List table format that is often used by high energy + astrophysics missions). These values may then be passed to the + fits\_pix\_to\_world and fits\_world\_to\_pix routines that perform +> the coordinate transformations. \label{ffgtcs} +- + int fits_read_tbl_coord / ffgtcs + (fitsfile *fptr, int xcol, int ycol, > double *xrefval, + double *yrefval, double *xrefpix, double *yrefpix, double *xinc, + double *yinc, double *rot, char *coordtype, int *status) +- +>3 Calculate the celestial coordinate corresponding to the input +> X and Y pixel location in the image. \label{ffwldp} +- + int fits_pix_to_world / ffwldp + (double xpix, double ypix, double xrefval, double yrefval, + double xrefpix, double yrefpix, double xinc, double yinc, + double rot, char *coordtype, > double *xpos, double *ypos, + int *status) +- +>4 Calculate the X and Y pixel location corresponding to the input +> celestial coordinate in the image. \label{ffxypx} +- + int fits_world_to_pix / ffxypx + (double xpos, double ypos, double xrefval, double yrefval, + double xrefpix, double yrefpix, double xinc, double yinc, + double rot, char *coordtype, > double *xpix, double *ypix, + int *status) +- + + +*VIII Hierarchical Grouping Routines + +These functions allow for the creation and manipulation of FITS HDU +Groups, as defined in "A Hierarchical Grouping Convention for FITS" by +Jennings, Pence, Folk and Schlesinger: + +http://fits.gsfc.nasa.gov/group.html + +A group is a +collection of HDUs whose association is defined by a {\it grouping +table}. HDUs which are part of a group are referred to as {\it member +HDUs} or simply as {\it members}. Grouping table member HDUs may +themselves be grouping tables, thus allowing for the construction of +open-ended hierarchies of HDUs. + +Grouping tables contain one row for each member HDU. The grouping table +columns provide identification information that allows applications to +reference or "point to" the member HDUs. Member HDUs are expected, but +not required, to contain a set of GRPIDn/GRPLCn keywords in their +headers for each grouping table that they are referenced by. In this +sense, the GRPIDn/GRPLCn keywords "link" the member HDU back to its +Grouping table. Note that a member HDU need not reside in the same FITS +file as its grouping table, and that a given HDU may be referenced by +up to 999 grouping tables simultaneously. + +Grouping tables are implemented as FITS binary tables with up to six +pre-defined column TTYPEn values: 'MEMBER\_XTENSION', 'MEMBER\_NAME', +'MEMBER\_VERSION', 'MEMBER\_POSITION', 'MEMBER\_URI\_TYPE' and 'MEMBER\_LOCATION'. +The first three columns allow member HDUs to be identified by reference to +their XTENSION, EXTNAME and EXTVER keyword values. The fourth column allows +member HDUs to be identified by HDU position within their FITS file. +The last two columns identify the FITS file in which the member HDU resides, +if different from the grouping table FITS file. + +Additional user defined "auxiliary" columns may also be included with any +grouping table. When a grouping table is copied or modified the presence of +auxiliary columns is always taken into account by the grouping support +functions; however, the grouping support functions cannot directly +make use of this data. + +If a grouping table column is defined but the corresponding member HDU +information is unavailable then a null value of the appropriate data type +is inserted in the column field. Integer columns (MEMBER\_POSITION, +MEMBER\_VERSION) are defined with a TNULLn value of zero (0). Character field +columns (MEMBER\_XTENSION, MEMBER\_NAME, MEMBER\_URI\_TYPE, MEMBER\_LOCATION) +utilize an ASCII null character to denote a null field value. + +The grouping support functions belong to two basic categories: those that +work with grouping table HDUs (ffgt**) and those that work with member HDUs +(ffgm**). Two functions, fits\_copy\_group() and fits\_remove\_group(), have the +option to recursively copy/delete entire groups. Care should be taken when +employing these functions in recursive mode as poorly defined groups could +cause unpredictable results. The problem of a grouping table directly or +indirectly referencing itself (thus creating an infinite loop) is protected +against; in fact, neither function will attempt to copy or delete an HDU +twice. + +**A. Grouping Table Routines + +>1 Create (append) a grouping table at the end of the current FITS file + pointed to by fptr. The grpname parameter provides the grouping table + name (GRPNAME keyword value) and may be set to NULL if no group name + is to be specified. The grouptype parameter specifies the desired + structure of the grouping table and may take on the values: + GT\_ID\_ALL\_URI (all columns created), GT\_ID\_REF (ID by reference columns), + GT\_ID\_POS (ID by position columns), GT\_ID\_ALL (ID by reference and + position columns), GT\_ID\_REF\_URI (ID by reference and FITS file URI +> columns), and GT\_ID\_POS\_URI (ID by position and FITS file URI columns). \label{ffgtcr} +- + int fits_create_group / ffgtcr + (fitsfile *fptr, char *grpname, int grouptype, > int *status) +- +>2 Create (insert) a grouping table just after the CHDU of the current FITS + file pointed to by fptr. All HDUs below the the insertion point will be + shifted downwards to make room for the new HDU. The grpname parameter + provides the grouping table name (GRPNAME keyword value) and may be set to + NULL if no group name is to be specified. The grouptype parameter specifies + the desired structure of the grouping table and may take on the values: + GT\_ID\_ALL\_URI (all columns created), GT\_ID\_REF (ID by reference columns), + GT\_ID\_POS (ID by position columns), GT\_ID\_ALL (ID by reference and + position columns), GT\_ID\_REF\_URI (ID by reference and FITS file URI +> columns), and GT\_ID\_POS\_URI (ID by position and FITS file URI columns) \label{ffgtis}. +- + int fits_insert_group / ffgtis + (fitsfile *fptr, char *grpname, int grouptype, > int *status) +- +>3 Change the structure of an existing grouping table pointed to by + gfptr. The grouptype parameter (see fits\_create\_group() for valid + parameter values) specifies the new structure of the grouping table. This + function 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 null values appropriate to the +> column type. \label{ffgtch} +- + int fits_change_group / ffgtch + (fitsfile *gfptr, int grouptype, > int *status) +- +>4 Remove the group defined by the grouping table pointed to by gfptr, and + optionally all the group member HDUs. The rmopt parameter specifies the + action to be taken for + all members of the group defined by the grouping table. Valid values are: + OPT\_RM\_GPT (delete only the grouping table) and OPT\_RM\_ALL (recursively + delete all HDUs that belong to the group). Any groups containing the + grouping table gfptr as a member are updated, and if rmopt == OPT\_RM\_GPT + all members have their GRPIDn and GRPLCn keywords updated accordingly. + If rmopt == OPT\_RM\_ALL, then other groups that contain the deleted members +> of gfptr are updated to reflect the deletion accordingly. \label{ffgtrm} +- + int fits_remove_group / ffgtrm + (fitsfile *gfptr, int rmopt, > int *status) +- +>5 Copy (append) the group defined by the grouping table pointed to by infptr, + and optionally all group member HDUs, to the FITS file pointed to by + outfptr. The cpopt parameter specifies the action to be taken for all + members of the group infptr. Valid values are: OPT\_GCP\_GPT (copy only + the grouping table) and OPT\_GCP\_ALL (recursively copy ALL the HDUs that + belong to the group defined by infptr). If the cpopt == OPT\_GCP\_GPT then + the members of infptr have their GRPIDn and GRPLCn keywords updated to + reflect the existence of the new grouping table outfptr, since they now + belong to the new group. If cpopt == OPT\_GCP\_ALL then the new + grouping table outfptr only contains pointers to the copied member HDUs + and not the original member HDUs of infptr. Note that, when + cpopt == OPT\_GCP\_ALL, all members of the group defined by infptr will be + copied to a single FITS file pointed to by outfptr regardless of their +> file distribution in the original group. \label{ffgtcp} +- + int fits_copy_group / ffgtcp + (fitsfile *infptr, fitsfile *outfptr, int cpopt, > int *status) +- +>6 Merge the two groups defined by the grouping table HDUs infptr and outfptr + by combining their members into a single grouping table. All member HDUs + (rows) are copied from infptr to outfptr. If mgopt == OPT\_MRG\_COPY then + infptr continues to exist unaltered after the merge. If the mgopt == + OPT\_MRG\_MOV then infptr is deleted after the merge. In both cases, +> the GRPIDn and GRPLCn keywords of the member HDUs are updated accordingly. \label{ffgtmg} +- + int fits_merge_groups / ffgtmg + (fitsfile *infptr, fitsfile *outfptr, int mgopt, > int *status) +- +>7 "Compact" the group defined by grouping table pointed to by gfptr. The + compaction is achieved by merging (via fits\_merge\_groups()) all direct + member HDUs of gfptr that are themselves grouping tables. The cmopt + parameter defines whether the merged grouping table HDUs remain after + merging (cmopt == OPT\_CMT\_MBR) or if they are deleted after merging + (cmopt == OPT\_CMT\_MBR\_DEL). If the grouping table contains no direct + member HDUs that are themselves grouping tables then this function + does nothing. Note that this function is not recursive, i.e., only the +> direct member HDUs of gfptr are considered for merging. \label{ffgtcm} +- + int fits_compact_group / ffgtcm + (fitsfile *gfptr, int cmopt, > int *status) +- +>8 Verify the integrity of the grouping table pointed to by gfptr to make + sure that all group members are accessible and that all links to other + grouping tables are valid. The firstfailed parameter returns the member + ID (row number) of the first member HDU to fail verification (if positive + value) or the first group link to fail (if negative value). If gfptr is +> successfully verified then firstfailed contains a return value of 0. \label{ffgtvf} +- + int fits_verify_group / ffgtvf + (fitsfile *gfptr, > long *firstfailed, int *status) +- +>9 Open a grouping table that contains the member HDU pointed to by mfptr. + The grouping table to open is defined by the grpid parameter, which + contains the keyword index value of the GRPIDn/GRPLCn keyword(s) that + link the member HDU mfptr to the grouping table. If the grouping table + resides in a file other than the member HDUs file then an attempt is + first made to open the file readwrite, and failing that readonly. A + pointer to the opened grouping table HDU is returned in gfptr. + + Note that it is possible, although unlikely and undesirable, for the + GRPIDn/GRPLCn keywords in a member HDU 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. \label{ffgtop} +- + int fits_open_group / ffgtop + (fitsfile *mfptr, int group, > fitsfile **gfptr, int *status) +- +>10 Add a member HDU to an existing grouping table pointed to by gfptr. + The member HDU may either be pointed to mfptr (which must be positioned + to the member HDU) or, if mfptr == NULL, identified by the hdupos parameter + (the HDU position number, Primary array == 1) if both the grouping table + and the member HDU reside in the same FITS file. The new member HDU shall + have the appropriate GRPIDn and GRPLCn keywords created in its header. + Note that if the member HDU is already a member of the group then it will +> not be added a second time. \label{ffgtam} +- + int fits_add_group_member / ffgtam + (fitsfile *gfptr, fitsfile *mfptr, int hdupos, > int *status) +- + +**B. Group Member Routines + +>1 Return the number of member HDUs in a grouping table gfptr. The number + member HDUs is just the NAXIS2 value (number of rows) of the grouping +> table. \label{ffgtnm} +- + int fits_get_num_members / ffgtnm + (fitsfile *gfptr, > long *nmembers, int *status) +- +>2 Return the number of groups to which the HDU pointed to by mfptr is + linked, as defined by the number of GRPIDn/GRPLCn keyword records that + appear in its header. Note that each time this function is called, the + indices of the GRPIDn/GRPLCn keywords are checked to make sure they + are continuous (ie no gaps) and are re-enumerated to eliminate gaps if +> found. \label{ffgmng} +- + int fits_get_num_groups / ffgmng + (fitsfile *mfptr, > long *nmembers, int *status) +- +>3 Open a member of the grouping table pointed to by gfptr. The member to + open is identified by its row number within the grouping table as given + by the parameter 'member' (first member == 1) . A fitsfile pointer to + the opened member HDU is returned as mfptr. Note that if the member HDU + resides in a FITS file different from the grouping table HDU then the +> member file is first opened readwrite and, failing this, opened readonly. \label{ffgmop} +- + int fits_open_member / ffgmop + (fitsfile *gfptr, long member, > fitsfile **mfptr, int *status) +- +>4 Copy (append) a member HDU of the grouping table pointed to by gfptr. + The member HDU is identified by its row number within the grouping table + as given by the parameter 'member' (first member == 1). The copy of the + group member HDU will be appended to the FITS file pointed to by mfptr, + and upon return mfptr shall point to the copied member HDU. The cpopt + parameter may take on the following values: OPT\_MCP\_ADD which adds a new + entry in gfptr for the copied member HDU, OPT\_MCP\_NADD which does not add + an entry in gfptr for the copied member, and OPT\_MCP\_REPL which replaces +> the original member entry with the copied member entry. \label{ffgmcp} +- + int fits_copy_member / ffgmcp + (fitsfile *gfptr, fitsfile *mfptr, long member, int cpopt, > int *status) +- +>5 Transfer a group member HDU from the grouping table pointed to by + infptr to the grouping table pointed to by outfptr. The member HDU to + transfer is identified by its row number within infptr as specified by + the parameter 'member' (first member == 1). If tfopt == OPT\_MCP\_ADD then + the member HDU is made + a member of outfptr and remains a member of infptr. If tfopt == OPT\_MCP\_MOV +> then the member HDU is deleted from infptr after the transfer to outfptr. \label{ffgmtf} +- + int fits_transfer_member / ffgmtf + (fitsfile *infptr, fitsfile *outfptr, long member, int tfopt, + > int *status) +- +>6 Remove a member HDU from the grouping table pointed to by gfptr. The + member HDU to be deleted is identified by its row number in the grouping + table as specified by the parameter 'member' (first member == 1). The rmopt + parameter may take on the following values: OPT\_RM\_ENTRY which + removes the member HDU entry from the grouping table and updates the + member's GRPIDn/GRPLCn keywords, and OPT\_RM\_MBR which removes the member +> HDU entry from the grouping table and deletes the member HDU itself. \label{ffgmrm} +- + int fits_remove_member / ffgmrm + (fitsfile *fptr, long member, int rmopt, > int *status) +- + +*IX Specialized CFITSIO Interface Routines + +The basic interface routines described previously are recommended +for most uses, but the routines described in this chapter +are also available if necessary. Some of these routines perform more +specialized function that cannot easily be done with the basic +interface routines while others duplicate the functionality of the +basic routines but have a slightly different calling sequence. +See Appendix B for the definition of each function parameter. + +**A. FITS File Access Routines + +>1 Open an existing FITS file residing in core computer memory. This +routine is analogous to fits\_open\_file. The 'filename' is +currently ignored by this routine and may be any arbitrary string. In +general, the application must have preallocated an initial block of +memory to hold the FITS file prior to calling this routine: 'memptr' +points to the starting address and 'memsize' gives the initial size of +the block of memory. 'mem\_realloc' is a pointer to an optional +function that CFITSIO can call to allocate additional memory, if needed +(only if mode = READWRITE), and is modeled after the standard C +'realloc' function; a null pointer may be given if the initial +allocation of memory is all that will be required (e.g., if the file is +opened with mode = READONLY). The 'deltasize' parameter may be used to +suggest a minimum amount of additional memory that should be allocated +during each call to the memory reallocation function. By default, +CFITSIO will reallocate enough additional space to hold the entire +currently defined FITS file (as given by the NAXISn keywords) or 1 FITS +block (= 2880 bytes), which ever is larger. Values of deltasize less +than 2880 will be ignored. Since the memory reallocation operation can +be computationally expensive, allocating a larger initial block of +memory, and/or specifying a larger deltasize value may help to reduce +the number of reallocation calls and make the application program run +faster. Note that values of the memptr and memsize pointers will be updated +by CFITSIO if the location or size of the FITS file in memory +>should change as a result of allocating more memory. \label{ffomem} +- + int fits_open_memfile / ffomem + (fitsfile **fptr, const char *filename, int mode, void **memptr, + size_t *memsize, size_t deltasize, + void *(*mem_realloc)(void *p, size_t newsize), int *status) +- +>2 Create a new FITS file residing in core computer memory. This +routine is analogous to fits\_create\_file. In general, the +application must have preallocated an initial block of memory to hold +the FITS file prior to calling this routine: 'memptr' points to the +starting address and 'memsize' gives the initial size of the block of +memory. 'mem\_realloc' is a pointer to an optional function that +CFITSIO can call to allocate additional memory, if needed, and is +modeled after the standard C 'realloc' function; a null pointer may be +given if the initial allocation of memory is all that will be +required. The 'deltasize' parameter may be used to suggest a minimum +amount of additional memory that should be allocated during each call +to the memory reallocation function. By default, CFITSIO will +reallocate enough additional space to hold 1 FITS block (= 2880 bytes) +and values of deltasize less than 2880 will be ignored. Since the +memory reallocation operation can be computationally expensive, +allocating a larger initial block of memory, and/or specifying a larger +deltasize value may help to reduce the number of reallocation calls +and make the application program run +faster. Note that values of the memptr and memsize pointers will be updated +by CFITSIO if the location or size of the FITS file in memory +>should change as a result of allocating more memory. \label{ffimem} +- + int fits_create_memfile / ffimem + (fitsfile **fptr, void **memptr, + size_t *memsize, size_t deltasize, + void *(*mem_realloc)(void *p, size_t newsize), int *status) +- +>3 Reopen a FITS file that was previously opened with + fits\_open\_file or fits\_create\_file. The new fitsfile + pointer may then be treated as a separate file, and one may + simultaneously read or write to 2 (or more) different extensions in + the same file. The fits\_open\_file routine (above) automatically + detects cases where a previously opened file is being opened again, + and then internally call fits\_reopen\_file, so programs should rarely + need to explicitly call this routine. +>\label{ffreopen} +- + int fits_reopen_file / ffreopen + (fitsfile *openfptr, fitsfile **newfptr, > int *status) +- + +>4 Create a new FITS file, using a template file to define its + initial size and structure. The template may be another FITS HDU + or an ASCII template file. If the input template file name pointer + is null, then this routine behaves the same as fits\_create\_file. + The currently supported format of the ASCII template file is described + under the fits\_parse\_template routine (in the general Utilities + section) +>\label{fftplt} +- + int fits_create_template / fftplt + (fitsfile **fptr, char *filename, char *tpltfile > int *status) +- + +>5 Parse the input filename or URL into its component parts, namely: +\begin{itemize} +\item +the file type (file://, ftp://, http://, etc), +\item +the base input file name, +\item +the name of the output file that the input file is to be copied to prior +to opening, +\item +the HDU or extension specification, +\item +the filtering specifier, +\item +the binning specifier, +\item +the column specifier, +\item +and the +image pixel filtering specifier. +\end{itemize} +A null pointer (0) may be be specified for any of the output string arguments +that are not needed. Null strings will be returned for any components that are not +present in the input file name. The calling routine must allocate sufficient +memory to hold the returned character strings. Allocating the string lengths +equal to FLEN\_FILENAME is guaranteed to be safe. +These routines are mainly for internal use +>by other CFITSIO routines. \label{ffiurl} +- + int fits_parse_input_url / ffiurl + (char *filename, > char *filetype, char *infile, char *outfile, char + *extspec, char *filter, char *binspec, char *colspec, int *status) + + int fits_parse_input_filename / ffifile + (char *filename, > char *filetype, char *infile, char *outfile, char + *extspec, char *filter, char *binspec, char *colspec, char *pixspec, + int *status) +- +>6 Parse the input filename and return the HDU number that would be +moved to if the file were opened with fits\_open\_file. The returned +HDU number begins with 1 for the primary array, so for example, if the +input filename = `myfile.fits[2]' then hdunum = 3 will be returned. +CFITSIO does not open the file to check if the extension actually +exists if an extension number is specified. If an extension name is +included in the file name specification (e.g. `myfile.fits[EVENTS]' +then this routine will have to open the FITS file and look for the +position of the named extension, then close file again. This is not +possible if the file is being read from the stdin stream, and an error +will be returned in this case. If the filename does not specify an +explicit extension (e.g. 'myfile.fits') then hdunum = -99 will be +returned, which is functionally equivalent to hdunum = 1. This routine +is mainly used for backward compatibility in the ftools software +package and is not recommended for general use. It is generally better +and more efficient to first open the FITS file with fits\_open\_file, +then use fits\_get\_hdu\_num to determine which HDU in the file has +been opened, rather than calling fits\_parse\_input\_url followed by a +call to fits\_open\_file. +> \label{ffextn} +- + int fits_parse_extnum / ffextn + (char *filename, > int *hdunum, int *status) +- +>7 Parse the input file name and return the root file name. The root +name includes the file type if specified, (e.g. 'ftp://' or 'http://') +and the full path name, to the extent that it is specified in the input +filename. It does not include the HDU name or number, or any filtering +specifications. The calling routine must allocate sufficient +memory to hold the returned rootname character string. Allocating the length +equal to FLEN\_FILENAME is guaranteed to be safe. +> \label{ffrtnm} +- + int fits_parse_rootname / ffrtnm + (char *filename, > char *rootname, int *status); +- +>8 Test if the input file or a compressed version of the file (with +a .gz, .Z, .z, or .zip extension) exists on disk. The returned value of +the 'exists' parameter will have 1 of the 4 following values: +- + 2: the file does not exist, but a compressed version does exist + 1: the disk file does exist + 0: neither the file nor a compressed version of the file exist + -1: the input file name is not a disk file (could be a ftp, http, + smem, or mem file, or a file piped in on the STDIN stream) +- + +> \label{ffexist} +- + int fits_file_exists / ffexist + (char *filename, > int *exists, int *status); +- +>9 Flush any internal buffers of data to the output FITS file. These + routines rarely need to be called, but can be useful in cases where + other processes need to access the same FITS file in real time, + either on disk or in memory. These routines also help to ensure + that if the application program subsequently aborts then the FITS + file will have been closed properly. The first routine, + fits\_flush\_file is more rigorous and completely closes, then + reopens, the current HDU, before flushing the internal buffers, thus + ensuring that the output FITS file is identical to what would be + produced if the FITS was closed at that point (i.e., with a call to + fits\_close\_file). The second routine, fits\_flush\_buffer simply + flushes the internal CFITSIO buffers of data to the output FITS + file, without updating and closing the current HDU. This is much + faster, but there may be circumstances where the flushed file does + not completely reflect the final state of the file as it will exist + when the file is actually closed. + + A typical use of these routines would be to flush the state of a + FITS table to disk after each row of the table is written. It is + recommend that fits\_flush\_file be called after the first row is + written, then fits\_flush\_buffer may be called after each + subsequent row is written. Note that this latter routine will not + automatically update the NAXIS2 keyword which records the number of + rows of data in the table, so this keyword must be explicitly + updated by the application program after each row is written. +> \label{ffflus} +- + int fits_flush_file / ffflus + (fitsfile *fptr, > int *status) + + int fits_flush_buffer / ffflsh + (fitsfile *fptr, 0, > int *status) + + (Note: The second argument must be 0). +- + +**B. HDU Access Routines + +>1 Get the byte offsets in the FITS file to the start of the header + and the start and end of the data in the CHDU. The difference + between headstart and dataend equals the size of the CHDU. If the + CHDU is the last HDU in the file, then dataend is also equal to the + size of the entire FITS file. Null pointers may be input for any +> of the address parameters if their values are not needed. \label{ffghad} +- + int fits_get_hduaddr / ffghad (only supports files up to 2.1 GB in size) + (fitsfile *fptr, > long *headstart, long *datastart, long *dataend, + int *status) + + int fits_get_hduaddrll / ffghadll (supports large files) + (fitsfile *fptr, > LONGLONG *headstart, LONGLONG *datastart, + LONGLONG *dataend, int *status) +- +>2 Create (append) a new empty HDU at the end of the FITS file. + This is now the CHDU but it is completely empty and has + no header keywords. It is recommended that fits\_create\_img or +> fits\_create\_tbl be used instead of this routine. \label{ffcrhd} +- + int fits_create_hdu / ffcrhd + (fitsfile *fptr, > int *status) +- +>3 Insert a new IMAGE extension immediately following the CHDU, or + insert a new Primary Array at the beginning of the file. Any + following extensions in the file will be shifted down to make room + for the new extension. If the CHDU is the last HDU in the file + then the new image extension will simply be appended to the end of + the file. One can force a new primary array to be inserted at the + beginning of the FITS file by setting status = PREPEND\_PRIMARY prior + to calling the routine. In this case the old primary array will be + converted to an IMAGE extension. The new extension (or primary + array) will become the CHDU. Refer to Chapter 9 for a list of +> pre-defined bitpix values. \label{ffiimg} +- + int fits_insert_img / ffiimg + (fitsfile *fptr, int bitpix, int naxis, long *naxes, > int *status) + + int fits_insert_imgll / ffiimgll + (fitsfile *fptr, int bitpix, int naxis, LONGLONG *naxes, > int *status) +- +>4 Insert a new ASCII or binary table extension immediately following the CHDU. + Any following extensions will be shifted down to make room for the + new extension. If there are no other following extensions then the + new table extension will simply be appended to the end of the + file. If the FITS file is currently empty then this routine will + create a dummy primary array before appending the table to it. The + new extension will become the CHDU. The tunit and extname + parameters are optional and a null pointer may be given if they are + not defined. When inserting an ASCII table with + fits\_insert\_atbl, a null pointer may given for the *tbcol + parameter in which case each column of the table will be separated + by a single space character. Similarly, if the input value of + rowlen is 0, then CFITSIO will calculate the default rowlength + based on the tbcol and ttype values. Under normal circumstances, + the nrows + paramenter should have a value of 0; CFITSIO will automatically update + the number of rows as data is written to the table. When inserting a binary table + with fits\_insert\_btbl, if there are following extensions in the + file and if the table contains variable length array columns then + pcount must specify the expected final size of the data heap, +> otherwise pcount must = 0. \label{ffitab} \label{ffibin} +- + int fits_insert_atbl / ffitab + (fitsfile *fptr, LONGLONG rowlen, LONGLONG nrows, int tfields, char *ttype[], + long *tbcol, char *tform[], char *tunit[], char *extname, > int *status) + + int fits_insert_btbl / ffibin + (fitsfile *fptr, LONGLONG nrows, int tfields, char **ttype, + char **tform, char **tunit, char *extname, long pcount, > int *status) +- +>5 Modify the size, dimensions, and/or data type of the current + primary array or image extension. If the new image, as specified + by the input arguments, is larger than the current existing image + in the FITS file then zero fill data will be inserted at the end + of the current image and any following extensions will be moved + further back in the file. Similarly, if the new image is + smaller than the current image then any following extensions + will be shifted up towards the beginning of the FITS file + and the image data will be truncated to the new size. + This routine rewrites the BITPIX, NAXIS, and NAXISn keywords +> with the appropriate values for the new image. \label{ffrsim} +- + int fits_resize_img / ffrsim + (fitsfile *fptr, int bitpix, int naxis, long *naxes, > int *status) + + int fits_resize_imgll / ffrsimll + (fitsfile *fptr, int bitpix, int naxis, LONGLONG *naxes, > int *status) +- +>6 Copy the data (and not the header) from the CHDU associated with infptr + to the CHDU associated with outfptr. This will overwrite any data + previously in the output CHDU. This low level routine is used by + fits\_copy\_hdu, but it may also be useful in certain application programs + that want to copy the data from one FITS file to another but also + want to modify the header keywords. The required FITS header keywords + which define the structure of the HDU must be written to the +> output CHDU before calling this routine. \label{ffcpdt} +- + int fits_copy_data / ffcpdt + (fitsfile *infptr, fitsfile *outfptr, > int *status) +- +>7 Read or write a specified number of bytes starting at the specified byte + offset from the start of the extension data unit. These low + level routine are intended mainly for accessing the data in + non-standard, conforming extensions, and should not be used for standard +> IMAGE, TABLE, or BINTABLE extensions. \label{ffgextn} +- + int fits_read_ext / ffgextn + (fitsfile *fptr, LONGLONG offset, LONGLONG nbytes, void *buffer) + int fits_write_ext / ffpextn + (fitsfile *fptr, LONGLONG offset, LONGLONG nbytes, void *buffer) +- +>8 This routine forces CFITSIO to rescan the current header keywords that + define the structure of the HDU (such as the NAXIS and BITPIX + keywords) so that it reinitializes the internal buffers that + describe the HDU structure. This routine is useful for + reinitializing the structure of an HDU if any of the required + keywords (e.g., NAXISn) have been modified. In practice it should + rarely be necessary to call this routine because CFITSIO +> internally calls it in most situations. \label{ffrdef} +- + int fits_set_hdustruc / ffrdef + (fitsfile *fptr, > int *status) (DEPRECATED) +- +**C. Specialized Header Keyword Routines + +***1. Header Information Routines + +>1 Reserve space in the CHU for MOREKEYS more header keywords. + This routine may be called to allocate space for additional keywords + at the time the header is created (prior to writing any data). + CFITSIO can dynamically add more space to the header when needed, + however it is more efficient to preallocate the required space +> if the size is known in advance. \label{ffhdef} +- + int fits_set_hdrsize / ffhdef + (fitsfile *fptr, int morekeys, > int *status) +- +>2 Return the number of keywords in the header (not counting the END + keyword) and the current position + in the header. The position is the number of the keyword record that + will be read next (or one greater than the position of the last keyword + that was read). A value of 1 is returned if the pointer is +> positioned at the beginning of the header. \label{ffghps} +- + int fits_get_hdrpos / ffghps + (fitsfile *fptr, > int *keysexist, int *keynum, int *status) +- + +***2. Read and Write the Required Keywords + +>1 Write the required extension header keywords into the CHU. + These routines are not required, and instead the appropriate + header may be constructed by writing each individual keyword in the + proper sequence. + + The simpler fits\_write\_imghdr routine is equivalent to calling + fits\_write\_grphdr with the default values of simple = TRUE, pcount + = 0, gcount = 1, and extend = TRUE. The PCOUNT, GCOUNT and EXTEND + keywords are not required in the primary header and are only written + if pcount is not equal to zero, gcount is not equal to zero or one, + and if extend is TRUE, respectively. When writing to an IMAGE + extension, the SIMPLE and EXTEND parameters are ignored. It is + recommended that fits\_create\_image or fits\_create\_tbl be used + instead of these routines to write the + required header keywords. The general fits\_write\_exthdr routine + may be used to write the header of any conforming FITS +> extension. \label{ffphpr} \label{ffphps} +- + int fits_write_imghdr / ffphps + (fitsfile *fptr, int bitpix, int naxis, long *naxes, > int *status) + + int fits_write_imghdrll / ffphpsll + (fitsfile *fptr, int bitpix, int naxis, LONGLONG *naxes, > int *status) + + int fits_write_grphdr / ffphpr + (fitsfile *fptr, int simple, int bitpix, int naxis, long *naxes, + LONGLONG pcount, LONGLONG gcount, int extend, > int *status) + + int fits_write_grphdrll / ffphprll + (fitsfile *fptr, int simple, int bitpix, int naxis, LONGLONG *naxes, + LONGLONG pcount, LONGLONG gcount, int extend, > int *status) + + int fits_write_exthdr /ffphext + (fitsfile *fptr, char *xtension, int bitpix, int naxis, long *naxes, + LONGLONG pcount, LONGLONG gcount, > int *status) + +- +>2 Write the ASCII table header keywords into the CHU. The optional + TUNITn and EXTNAME keywords are written only if the input pointers + are not null. A null pointer may given for the + *tbcol parameter in which case a single space will be inserted + between each column of the table. Similarly, if rowlen is + given = 0, then CFITSIO will calculate the default rowlength based on +> the tbcol and ttype values. \label{ffphtb} +- + int fits_write_atblhdr / ffphtb + (fitsfile *fptr, LONGLONG rowlen, LONGLONG nrows, int tfields, char **ttype, + long *tbcol, char **tform, char **tunit, char *extname, > int *status) +- +>3 Write the binary table header keywords into the CHU. The optional + TUNITn and EXTNAME keywords are written only if the input pointers + are not null. The pcount parameter, which specifies the + size of the variable length array heap, should initially = 0; + CFITSIO will automatically update the PCOUNT keyword value if any + variable length array data is written to the heap. The TFORM keyword + value for variable length vector columns should have the form 'Pt(len)' + or '1Pt(len)' where `t' is the data type code letter (A,I,J,E,D, etc.) + and `len' is an integer specifying the maximum length of the vectors + in that column (len must be greater than or equal to the longest + vector in the column). If `len' is not specified when the table is + created (e.g., the input TFORMn value is just '1Pt') then CFITSIO will + scan the column when the table is first closed and will append the + maximum length to the TFORM keyword value. Note that if the table + is subsequently modified to increase the maximum length of the vectors + then the modifying program is responsible for also updating the TFORM +> keyword value. \label{ffphbn} +- + int fits_write_btblhdr / ffphbn + (fitsfile *fptr, LONGLONG nrows, int tfields, char **ttype, + char **tform, char **tunit, char *extname, LONGLONG pcount, > int *status) +- +>4 Read the required keywords from the CHDU (image or table). When + reading from an IMAGE extension the SIMPLE and EXTEND parameters are + ignored. A null pointer may be supplied for any of the returned +> parameters that are not needed. \label{ffghpr} \label{ffghtb} \label{ffghbn} +- + int fits_read_imghdr / ffghpr + (fitsfile *fptr, int maxdim, > int *simple, int *bitpix, int *naxis, + long *naxes, long *pcount, long *gcount, int *extend, int *status) + + int fits_read_imghdrll / ffghprll + (fitsfile *fptr, int maxdim, > int *simple, int *bitpix, int *naxis, + LONGLONG *naxes, long *pcount, long *gcount, int *extend, int *status) + + int fits_read_atblhdr / ffghtb + (fitsfile *fptr,int maxdim, > long *rowlen, long *nrows, + int *tfields, char **ttype, LONGLONG *tbcol, char **tform, char **tunit, + char *extname, int *status) + + int fits_read_atblhdrll / ffghtbll + (fitsfile *fptr,int maxdim, > LONGLONG *rowlen, LONGLONG *nrows, + int *tfields, char **ttype, long *tbcol, char **tform, char **tunit, + char *extname, int *status) + + int fits_read_btblhdr / ffghbn + (fitsfile *fptr, int maxdim, > long *nrows, int *tfields, + char **ttype, char **tform, char **tunit, char *extname, + long *pcount, int *status) + + int fits_read_btblhdrll / ffghbnll + (fitsfile *fptr, int maxdim, > LONGLONG *nrows, int *tfields, + char **ttype, char **tform, char **tunit, char *extname, + long *pcount, int *status) +- +***3. Write Keyword Routines + +These routines simply append a new keyword to the header and do not +check to see if a keyword with the same name already exists. In +general it is preferable to use the fits\_update\_key routine to ensure +that the same keyword is not written more than once to the header. See +Appendix B for the definition of the parameters used in these +routines. + + +>1 Write (append) a new keyword of the appropriate data type into the CHU. + A null pointer may be entered for the comment parameter, which + will cause the comment field of the keyword to be left blank. The + flt, dbl, cmp, and dblcmp versions of this routine have the added + feature that if the 'decimals' parameter is negative, then the 'G' + display format rather then the 'E' format will be used when + constructing the keyword value, taking the absolute value of + 'decimals' for the precision. This will suppress trailing zeros, + and will use a fixed format rather than an exponential format, +> depending on the magnitude of the value. \label{ffpkyx} +- + int fits_write_key_str / ffpkys + (fitsfile *fptr, char *keyname, char *value, char *comment, + > int *status) + + int fits_write_key_[log, lng] / ffpky[lj] + (fitsfile *fptr, char *keyname, DTYPE numval, char *comment, + > int *status) + + int fits_write_key_[flt, dbl, fixflg, fixdbl] / ffpky[edfg] + (fitsfile *fptr, char *keyname, DTYPE numval, int decimals, + char *comment, > int *status) + + int fits_write_key_[cmp, dblcmp, fixcmp, fixdblcmp] / ffpk[yc,ym,fc,fm] + (fitsfile *fptr, char *keyname, DTYPE *numval, int decimals, + char *comment, > int *status) +- +>2 Write (append) a string valued keyword into the CHU which may be longer + than 68 characters in length. This uses the Long String Keyword + convention that is described in the`Local FITS Conventions' section + in Chapter 4. Since this uses a non-standard FITS convention to + encode the long keyword string, programs which use this routine + should also call the fits\_write\_key\_longwarn routine to add some + COMMENT keywords to warn users of the FITS file that this + convention is being used. The fits\_write\_key\_longwarn routine + also writes a keyword called LONGSTRN to record the version of the + longstring convention that has been used, in case a new convention + is adopted at some point in the future. If the LONGSTRN keyword + is already present in the header, then fits\_write\_key\_longwarn + will +> simply return without doing anything. \label{ffpkls} \label{ffplsw} +- + int fits_write_key_longstr / ffpkls + (fitsfile *fptr, char *keyname, char *longstr, char *comment, + > int *status) + + int fits_write_key_longwarn / ffplsw + (fitsfile *fptr, > int *status) +- +>3 Write (append) a numbered sequence of keywords into the CHU. The + starting index number (nstart) must be greater than 0. One may + append the same comment to every keyword (and eliminate the need + to have an array of identical comment strings, one for each keyword) by + including the ampersand character as the last non-blank character in the + (first) COMMENTS string parameter. This same string + will then be used for the comment field in all the keywords. + One may also enter a null pointer for the comment parameter to +> leave the comment field of the keyword blank. \label{ffpknx} +- + int fits_write_keys_str / ffpkns + (fitsfile *fptr, char *keyroot, int nstart, int nkeys, + char **value, char **comment, > int *status) + + int fits_write_keys_[log, lng] / ffpkn[lj] + (fitsfile *fptr, char *keyroot, int nstart, int nkeys, + DTYPE *numval, char **comment, int *status) + + int fits_write_keys_[flt, dbl, fixflg, fixdbl] / ffpkne[edfg] + (fitsfile *fptr, char *keyroot, int nstart, int nkey, + DTYPE *numval, int decimals, char **comment, > int *status) +- +>4 Copy an indexed keyword from one HDU to another, modifying + the index number of the keyword name in the process. For example, + this routine could read the TLMIN3 keyword from the input HDU + (by giving keyroot = `TLMIN' and innum = 3) and write it to the + output HDU with the keyword name TLMIN4 (by setting outnum = 4). + If the input keyword does not exist, then this routine simply +> returns without indicating an error. \label{ffcpky} +- + int fits_copy_key / ffcpky + (fitsfile *infptr, fitsfile *outfptr, int innum, int outnum, + char *keyroot, > int *status) +- +>5 Write (append) a `triple precision' keyword into the CHU in F28.16 format. + The floating point keyword value is constructed by concatenating the + input integer value with the input double precision fraction value + (which must have a value between 0.0 and 1.0). The ffgkyt routine should + be used to read this keyword value, because the other keyword reading +> routines will not preserve the full precision of the value. \label{ffpkyt} +- + int fits_write_key_triple / ffpkyt + (fitsfile *fptr, char *keyname, long intval, double frac, + char *comment, > int *status) +- +>6 Write keywords to the CHDU that are defined in an ASCII template file. + The format of the template file is described under the fits\_parse\_template +> routine. \label{ffpktp} +- + int fits_write_key_template / ffpktp + (fitsfile *fptr, const char *filename, > int *status) +- +***4. Insert Keyword Routines + +These insert routines are somewhat less efficient than the `update' or +`write' keyword routines because the following keywords in the header +must be shifted down to make room for the inserted keyword. See +Appendix B for the definition of the parameters used in these +routines. + +>1 Insert a new keyword record into the CHU at the specified position + (i.e., immediately preceding the (keynum)th keyword in the header.) +> \label{ffirec} +- + int fits_insert_record / ffirec + (fitsfile *fptr, int keynum, char *card, > int *status) +- +>2 Insert a new keyword into the CHU. The new keyword is inserted + immediately following the last keyword that has been read from the + header. The `longstr' version has the same functionality as the + `str' version except that it also supports the local long string + keyword convention for strings longer than 68 characters. A null + pointer may be entered for the comment parameter which will cause + the comment field to be left blank. The flt, dbl, cmp, and dblcmp + versions of this routine have the added + feature that if the 'decimals' parameter is negative, then the 'G' + display format rather then the 'E' format will be used when + constructing the keyword value, taking the absolute value of + 'decimals' for the precision. This will suppress trailing zeros, + and will use a fixed format rather than an exponential format, +> depending on the magnitude of the value. \label{ffikyx} +- + int fits_insert_card / ffikey + (fitsfile *fptr, char *card, > int *status) + + int fits_insert_key_[str, longstr] / ffi[kys, kls] + (fitsfile *fptr, char *keyname, char *value, char *comment, + > int *status) + + int fits_insert_key_[log, lng] / ffiky[lj] + (fitsfile *fptr, char *keyname, DTYPE numval, char *comment, + > int *status) + + int fits_insert_key_[flt, fixflt, dbl, fixdbl] / ffiky[edfg] + (fitsfile *fptr, char *keyname, DTYPE numval, int decimals, + char *comment, > int *status) + + int fits_insert_key_[cmp, dblcmp, fixcmp, fixdblcmp] / ffik[yc,ym,fc,fm] + (fitsfile *fptr, char *keyname, DTYPE *numval, int decimals, + char *comment, > int *status) +- +>3 Insert a new keyword with an undefined, or null, value into the CHU. +> The value string of the keyword is left blank in this case. \label{ffikyu} +- + int fits_insert_key_null / ffikyu + (fitsfile *fptr, char *keyname, char *comment, > int *status) +- + +***5. Read Keyword Routines + +Wild card characters may be used when specifying the name of the +keyword to be read. + +>1 Read a keyword value (with the appropriate data type) and comment from + the CHU. If a NULL comment pointer is given on input, then the comment + string will not be returned. If the value of the keyword is not defined + (i.e., the value field is blank) then an error status = VALUE\_UNDEFINED + will be returned and the input value will not be changed (except that + ffgkys will reset the value to a null string). +> \label{ffgkyx} \label{ffgkls} +- + int fits_read_key_str / ffgkys + (fitsfile *fptr, char *keyname, > char *value, char *comment, + int *status); + + NOTE: after calling the following routine, programs must explicitly free + the memory allocated for 'longstr' after it is no longer needed or + call fits_free_memory. + + int fits_read_key_longstr / ffgkls + (fitsfile *fptr, char *keyname, > char **longstr, char *comment, + int *status) + + int fits_free_memory / fffree + (char *longstr, int *status); + + int fits_read_key_[log, lng, flt, dbl, cmp, dblcmp] / ffgky[ljedcm] + (fitsfile *fptr, char *keyname, > DTYPE *numval, char *comment, + int *status) + + int fits_read_key_lnglng / ffgkyjj + (fitsfile *fptr, char *keyname, > LONGLONG *numval, char *comment, + int *status) +- +>2 Read a sequence of indexed keyword values (e.g., NAXIS1, NAXIS2, ...). + The input starting index number (nstart) must be greater than 0. + If the value of any of the keywords is not defined (i.e., the value + field is blank) then an error status = VALUE\_UNDEFINED will be + returned and the input value for the undefined keyword(s) will not + be changed. These routines do not support wild card characters in + the root name. If there are no indexed keywords in the header with + the input root name then these routines do not return a non-zero +> status value and instead simply return nfound = 0. \label{ffgknx} +- + int fits_read_keys_str / ffgkns + (fitsfile *fptr, char *keyname, int nstart, int nkeys, + > char **value, int *nfound, int *status) + + int fits_read_keys_[log, lng, flt, dbl] / ffgkn[ljed] + (fitsfile *fptr, char *keyname, int nstart, int nkeys, + > DTYPE *numval, int *nfound, int *status) +- +>3 Read the value of a floating point keyword, returning the integer and + fractional parts of the value in separate routine arguments. + This routine may be used to read any keyword but is especially + useful for reading the 'triple precision' keywords written by ffpkyt. +> \label{ffgkyt} +- + int fits_read_key_triple / ffgkyt + (fitsfile *fptr, char *keyname, > long *intval, double *frac, + char *comment, int *status) +- +***6. Modify Keyword Routines + +These routines modify the value of an existing keyword. An error is +returned if the keyword does not exist. Wild card characters may be +used when specifying the name of the keyword to be modified. See +Appendix B for the definition of the parameters used in these +routines. + +>>1 Modify (overwrite) the nth 80-character header record in the CHU. \label{ffmrec} +- + int fits_modify_record / ffmrec + (fitsfile *fptr, int keynum, char *card, > int *status) +- +>2 Modify (overwrite) the 80-character header record for the named keyword + in the CHU. This can be used to overwrite the name of the keyword as +> well as its value and comment fields. \label{ffmcrd} +- + int fits_modify_card / ffmcrd + (fitsfile *fptr, char *keyname, char *card, > int *status) +- +>5 Modify the value and comment fields of an existing keyword in the CHU. + The `longstr' version has the same functionality as the `str' + version except that it also supports the local long string keyword + convention for strings longer than 68 characters. Optionally, one + may modify only the value field and leave the comment field + unchanged by setting the input COMMENT parameter equal to the + ampersand character (\&) or by entering a null pointer for the + comment parameter. The flt, dbl, cmp, and dblcmp versions of this + routine have the added feature that if the 'decimals' parameter is + negative, then the 'G' display format rather then the 'E' format + will be used when constructing the keyword value, taking the + absolute value of 'decimals' for the precision. This will suppress + trailing zeros, and will use a fixed format rather than an + exponential format, +> depending on the magnitude of the value. \label{ffmkyx} +- + int fits_modify_key_[str, longstr] / ffm[kys, kls] + (fitsfile *fptr, char *keyname, char *value, char *comment, + > int *status); + + int fits_modify_key_[log, lng] / ffmky[lj] + (fitsfile *fptr, char *keyname, DTYPE numval, char *comment, + > int *status) + + int fits_modify_key_[flt, dbl, fixflt, fixdbl] / ffmky[edfg] + (fitsfile *fptr, char *keyname, DTYPE numval, int decimals, + char *comment, > int *status) + + int fits_modify_key_[cmp, dblcmp, fixcmp, fixdblcmp] / ffmk[yc,ym,fc,fm] + (fitsfile *fptr, char *keyname, DTYPE *numval, int decimals, + char *comment, > int *status) +- +>6 Modify the value of an existing keyword to be undefined, or null. + The value string of the keyword is set to blank. + Optionally, one may leave the comment field unchanged by setting the + input COMMENT parameter equal to +> the ampersand character (\&) or by entering a null pointer. \label{ffmkyu} +- + int fits_modify_key_null / ffmkyu + (fitsfile *fptr, char *keyname, char *comment, > int *status) +- +***7. Update Keyword Routines + +>1 These update routines modify the value, and optionally the comment field, + of the keyword if it already exists, otherwise the new keyword is + appended to the header. A separate routine is provided for each + keyword data type. The `longstr' version has the same functionality + as the `str' version except that it also supports the local long + string keyword convention for strings longer than 68 characters. A + null pointer may be entered for the comment parameter which will + leave the comment field unchanged or blank. The flt, dbl, cmp, and + dblcmp versions of this routine have the added feature that if the + 'decimals' parameter is negative, then the 'G' display format + rather then the 'E' format will be used when constructing the + keyword value, taking the absolute value of 'decimals' for the + precision. This will suppress trailing zeros, and will use a fixed + format rather than an exponential format, +> depending on the magnitude of the value. \label{ffukyx} +- + int fits_update_key_[str, longstr] / ffu[kys, kls] + (fitsfile *fptr, char *keyname, char *value, char *comment, + > int *status) + + int fits_update_key_[log, lng] / ffuky[lj] + (fitsfile *fptr, char *keyname, DTYPE numval, char *comment, + > int *status) + + int fits_update_key_[flt, dbl, fixflt, fixdbl] / ffuky[edfg] + (fitsfile *fptr, char *keyname, DTYPE numval, int decimals, + char *comment, > int *status) + + int fits_update_key_[cmp, dblcmp, fixcmp, fixdblcmp] / ffuk[yc,ym,fc,fm] + (fitsfile *fptr, char *keyname, DTYPE *numval, int decimals, + char *comment, > int *status) +- + +**D. Define Data Scaling and Undefined Pixel Parameters + +These routines set or modify the internal parameters used by CFITSIO +to either scale the data or to represent undefined pixels. Generally +CFITSIO will scale the data according to the values of the BSCALE and +BZERO (or TSCALn and TZEROn) keywords, however these routines may be +used to override the keyword values. This may be useful when one wants +to read or write the raw unscaled values in the FITS file. Similarly, +CFITSIO generally uses the value of the BLANK or TNULLn keyword to +signify an undefined pixel, but these routines may be used to override +this value. These routines do not create or modify the corresponding +header keyword values. See Appendix B for the definition of the +parameters used in these routines. + +>1 Reset the scaling factors in the primary array or image extension; does + not change the BSCALE and BZERO keyword values and only affects the + automatic scaling performed when the data elements are written/read + to/from the FITS file. When reading from a FITS file the returned + data value = (the value given in the FITS array) * BSCALE + BZERO. + The inverse formula is used when writing data values to the FITS +> file. \label{ffpscl} +- + int fits_set_bscale / ffpscl + (fitsfile *fptr, double scale, double zero, > int *status) +- +>2 Reset the scaling parameters for a table column; does not change + the TSCALn or TZEROn keyword values and only affects the automatic + scaling performed when the data elements are written/read to/from + the FITS file. When reading from a FITS file the returned data + value = (the value given in the FITS array) * TSCAL + TZERO. The + inverse formula is used when writing data values to the FITS file. +> \label{fftscl} +- + int fits_set_tscale / fftscl + (fitsfile *fptr, int colnum, double scale, double zero, + > int *status) +- +>3 Define the integer value to be used to signify undefined pixels in the + primary array or image extension. This is only used if BITPIX = 8, 16, + or 32. This does not create or change the value of the BLANK keyword in +> the header. \label{ffpnul} +- + int fits_set_imgnull / ffpnul + (fitsfile *fptr, LONGLONG nulval, > int *status) +- +>4 Define the string to be used to signify undefined pixels in + a column in an ASCII table. This does not create or change the value +> of the TNULLn keyword. \label{ffsnul} +- + int fits_set_atblnull / ffsnul + (fitsfile *fptr, int colnum, char *nulstr, > int *status) +- +>5 Define the value to be used to signify undefined pixels in + an integer column in a binary table (where TFORMn = 'B', 'I', or 'J'). + This does not create or change the value of the TNULLn keyword. +> \label{fftnul} +- + int fits_set_btblnull / fftnul + (fitsfile *fptr, int colnum, LONGLONG nulval, > int *status) +- + +**E. Specialized FITS Primary Array or IMAGE Extension I/O Routines + +These routines read or write data values in the primary data array +(i.e., the first HDU in the FITS file) or an IMAGE extension. +Automatic data type conversion is performed for if the data type of the +FITS array (as defined by the BITPIX keyword) differs from the data +type of the array in the calling routine. The data values are +automatically scaled by the BSCALE and BZERO header values as they are +being written or read from the FITS array. Unlike the basic routines +described in the previous chapter, most of these routines specifically +support the FITS random groups format. See Appendix B for the +definition of the parameters used in these routines. + +The more primitive reading and writing routines (i. e., ffppr\_, +ffppn\_, ffppn, ffgpv\_, or ffgpf\_) simply treat the primary array as +a long 1-dimensional array of pixels, ignoring the intrinsic +dimensionality of the array. When dealing with a 2D image, for +example, the application program must calculate the pixel offset in the +1-D array that corresponds to any particular X, Y coordinate in the +image. C programmers should note that the ordering of arrays in FITS +files, and hence in all the CFITSIO calls, is more similar to the +dimensionality of arrays in Fortran rather than C. For instance if a +FITS image has NAXIS1 = 100 and NAXIS2 = 50, then a 2-D array just +large enough to hold the image should be declared as array[50][100] and +not as array[100][50]. + +For convenience, higher-level routines are also provided to specifically +deal with 2D images (ffp2d\_ and ffg2d\_) and 3D data cubes (ffp3d\_ +and ffg3d\_). The dimensionality of the FITS image is passed by the +naxis1, naxis2, and naxis3 parameters and the declared dimensions of +the program array are passed in the dim1 and dim2 parameters. Note +that the dimensions of the program array may be larger than the +dimensions of the FITS array. For example if a FITS image with NAXIS1 += NAXIS2 = 400 is read into a program array which is dimensioned as 512 +x 512 pixels, then the image will just fill the lower left corner of +the array with pixels in the range 1 - 400 in the X an Y directions. +This has the effect of taking a contiguous set of pixel value in the +FITS array and writing them to a non-contiguous array in program memory +(i.e., there are now some blank pixels around the edge of the image in +the program array). + +The most general set of routines (ffpss\_, ffgsv\_, and ffgsf\_) may be +used to transfer a rectangular subset of the pixels in a FITS +N-dimensional image to or from an array which has been declared in the +calling program. The fpixel and lpixel parameters are integer arrays +which specify the starting and ending pixel coordinate in each dimension +(starting with 1, not 0) of the FITS image that is to be read or +written. It is important to note that these are the starting and +ending pixels in the FITS image, not in the declared array in the +program. The array parameter in these routines is treated simply as a +large one-dimensional array of the appropriate data type containing the +pixel values; The pixel values in the FITS array are read/written +from/to this program array in strict sequence without any gaps; it is +up to the calling routine to correctly interpret the dimensionality of +this array. The two FITS reading routines (ffgsv\_ and ffgsf\_ ) also +have an `inc' parameter which defines the data sampling interval in +each dimension of the FITS array. For example, if inc[0]=2 and +inc[1]=3 when reading a 2-dimensional FITS image, then only every other +pixel in the first dimension and every 3rd pixel in the second +dimension will be returned to the 'array' parameter. + +Two types of routines are provided to read the data array which differ in +the way undefined pixels are handled. The first type of routines (e.g., +ffgpv\_) simply return an array of data elements in which undefined +pixels are set equal to a value specified by the user in the `nulval' +parameter. An additional feature of these routines is that if the user +sets nulval = 0, then no checks for undefined pixels will be performed, +thus reducing the amount of CPU processing. The second type of routines +(e.g., ffgpf\_) returns the data element array and, in addition, a char +array that indicates whether the value of the corresponding data pixel +is undefined (= 1) or defined (= 0). The latter type of routines may +be more convenient to use in some circumstances, however, it requires +an additional array of logical values which can be unwieldy when working +with large data arrays. + +>1 Write elements into the FITS data array. +> \label{ffppr} \label{ffpprx} \label{ffppn} \label{ffppnx} +- + int fits_write_img / ffppr + (fitsfile *fptr, int datatype, LONGLONG firstelem, LONGLONG nelements, + DTYPE *array, int *status); + + int fits_write_img_[byt, sht, usht, int, uint, lng, ulng, lnglng, flt, dbl] / + ffppr[b,i,ui,k,uk,j,uj,jj,e,d] + (fitsfile *fptr, long group, LONGLONG firstelem, LONGLONG nelements, + DTYPE *array, > int *status); + + int fits_write_imgnull / ffppn + (fitsfile *fptr, int datatype, LONGLONG firstelem, LONGLONG nelements, + DTYPE *array, DTYPE *nulval, > int *status); + + int fits_write_imgnull_[byt, sht, usht, int, uint, lng, ulng, lnglng, flt, dbl] / + ffppn[b,i,ui,k,uk,j,uj,jj,e,d] + (fitsfile *fptr, long group, LONGLONG firstelem, + LONGLONG nelements, DTYPE *array, DTYPE nulval, > int *status); +- +>>2 Set data array elements as undefined. \label{ffppru} +- + int fits_write_img_null / ffppru + (fitsfile *fptr, long group, LONGLONG firstelem, LONGLONG nelements, + > int *status) +- +>3 Write values into group parameters. This routine only applies + to the `Random Grouped' FITS format which has been used for + applications in radio interferometry, but is officially deprecated +> for future use. \label{ffpgpx} +- + int fits_write_grppar_[byt, sht, usht, int, uint, lng, ulng, lnglng, flt, dbl] / + ffpgp[b,i,ui,k,uk,j,uj,jj,e,d] + (fitsfile *fptr, long group, long firstelem, long nelements, + > DTYPE *array, int *status) +- +>>4 Write a 2-D or 3-D image into the data array. \label{ffp2dx} \label{ffp3dx} +- + int fits_write_2d_[byt, sht, usht, int, uint, lng, ulng, lnglng, flt, dbl] / + ffp2d[b,i,ui,k,uk,j,uj,jj,e,d] + (fitsfile *fptr, long group, LONGLONG dim1, LONGLONG naxis1, + LONGLONG naxis2, DTYPE *array, > int *status) + + int fits_write_3d_[byt, sht, usht, int, uint, lng, ulng, lnglng, flt, dbl] / + ffp3d[b,i,ui,k,uk,j,uj,jj,e,d] + (fitsfile *fptr, long group, LONGLONG dim1, LONGLONG dim2, LONGLONG naxis1, + LONGLONG naxis2, LONGLONG naxis3, DTYPE *array, > int *status) +- +>>5 Write an arbitrary data subsection into the data array. \label{ffpssx} +- + int fits_write_subset_[byt, sht, usht, int, uint, lng, ulng, lnglng, flt, dbl] / + ffpss[b,i,ui,k,uk,j,uj,jj,e,d] + (fitsfile *fptr, long group, long naxis, long *naxes, + long *fpixel, long *lpixel, DTYPE *array, > int *status) +- +>6 Read elements from the FITS data array. +> \label{ffgpv} \label{ffgpvx} \label{ffgpf} \label{ffgpfx} +- + int fits_read_img / ffgpv + (fitsfile *fptr, int datatype, long firstelem, long nelements, + DTYPE *nulval, > DTYPE *array, int *anynul, int *status) + + int fits_read_img_[byt, sht, usht, int, uint, lng, ulng, lnglng, flt, dbl] / + ffgpv[b,i,ui,k,uk,j,uj,jj,e,d] + (fitsfile *fptr, long group, long firstelem, long nelements, + DTYPE nulval, > DTYPE *array, int *anynul, int *status) + + int fits_read_imgnull / ffgpf + (fitsfile *fptr, int datatype, long firstelem, long nelements, + > DTYPE *array, char *nullarray, int *anynul, int *status) + + int fits_read_imgnull_[byt, sht, usht, int, uint, lng, ulng, flt, dbl] / + ffgpf[b,i,ui,k,uk,j,uj,jj,e,d] + (fitsfile *fptr, long group, long firstelem, long nelements, + > DTYPE *array, char *nullarray, int *anynul, int *status) +- +>7 Read values from group parameters. This routine only applies + to the `Random Grouped' FITS format which has been used for + applications in radio interferometry, but is officially deprecated +> for future use. \label{ffggpx} +- + int fits_read_grppar_[byt, sht, usht, int, uint, lng, ulng, lnglng, flt, dbl] / + ffggp[b,i,ui,k,uk,j,uj,jj,e,d] + (fitsfile *fptr, long group, long firstelem, long nelements, + > DTYPE *array, int *status) +- +>8 Read 2-D or 3-D image from the data array. Undefined + pixels in the array will be set equal to the value of 'nulval', + unless nulval=0 in which case no testing for undefined pixels will +> be performed. \label{ffg2dx} \label{ffg3dx} +- + int fits_read_2d_[byt, sht, usht, int, uint, lng, ulng, lnglng, flt, dbl] / + ffg2d[b,i,ui,k,uk,j,uj,jj,e,d] + (fitsfile *fptr, long group, DTYPE nulval, LONGLONG dim1, LONGLONG naxis1, + LONGLONG naxis2, > DTYPE *array, int *anynul, int *status) + + int fits_read_3d_[byt, sht, usht, int, uint, lng, ulng, lnglng, flt, dbl] / + ffg3d[b,i,ui,k,uk,j,uj,jj,e,d] + (fitsfile *fptr, long group, DTYPE nulval, LONGLONG dim1, + LONGLONG dim2, LONGLONG naxis1, LONGLONG naxis2, LONGLONG naxis3, + > DTYPE *array, int *anynul, int *status) +- +>9 Read an arbitrary data subsection from the data array. +> \label{ffgsvx} \label{ffgsfx} +- + int fits_read_subset_[byt, sht, usht, int, uint, lng, ulng, lnglng, flt, dbl] / + ffgsv[b,i,ui,k,uk,j,uj,jj,e,d] + (fitsfile *fptr, int group, int naxis, long *naxes, + long *fpixel, long *lpixel, long *inc, DTYPE nulval, + > DTYPE *array, int *anynul, int *status) + + int fits_read_subsetnull_[byt, sht, usht, int, uint, lng, ulng, lnglng, flt, dbl] / + ffgsf[b,i,ui,k,uk,j,uj,jj,e,d] + (fitsfile *fptr, int group, int naxis, long *naxes, + long *fpixel, long *lpixel, long *inc, > DTYPE *array, + char *nullarray, int *anynul, int *status) +- + +**F. Specialized FITS ASCII and Binary Table Routines + +***1. General Column Routines + +>1 Get information about an existing ASCII or binary table column. A null + pointer may be given for any of the output parameters that are not + needed. DATATYPE is a character string which returns the data type + of the column as defined by the TFORMn keyword (e.g., 'I', 'J','E', + 'D', etc.). In the case of an ASCII character column, typecode + will have a value of the form 'An' where 'n' is an integer + expressing the width of the field in characters. For example, if + TFORM = '160A8' then ffgbcl will return typechar='A8' and + repeat=20. All the returned parameters are scalar quantities. +> \label{ffgacl} \label{ffgbcl} +- + int fits_get_acolparms / ffgacl + (fitsfile *fptr, int colnum, > char *ttype, long *tbcol, + char *tunit, char *tform, double *scale, double *zero, + char *nulstr, char *tdisp, int *status) + + int fits_get_bcolparms / ffgbcl + (fitsfile *fptr, int colnum, > char *ttype, char *tunit, + char *typechar, long *repeat, double *scale, double *zero, + long *nulval, char *tdisp, int *status) + + int fits_get_bcolparmsll / ffgbclll + (fitsfile *fptr, int colnum, > char *ttype, char *tunit, + char *typechar, LONGLONG *repeat, double *scale, double *zero, + LONGLONG *nulval, char *tdisp, int *status) +- +>2 Return optimal number of rows to read or write at one time for + maximum I/O efficiency. Refer to the + ``Optimizing Code'' section in Chapter 5 for more discussion on how +> to use this routine. \label{ffgrsz} +- + int fits_get_rowsize / ffgrsz + (fitsfile *fptr, long *nrows, *status) +- +>3 Define the zero indexed byte offset of the 'heap' measured from + the start of the binary table data. By default the heap is assumed + to start immediately following the regular table data, i.e., at + location NAXIS1 x NAXIS2. This routine is only relevant for + binary tables which contain variable length array columns (with + TFORMn = 'Pt'). This routine also automatically writes + the value of theap to a keyword in the extension header. This + routine must be called after the required keywords have been + written (with ffphbn) +> but before any data is written to the table. \label{ffpthp} +- + int fits_write_theap / ffpthp + (fitsfile *fptr, long theap, > int *status) +- +>4 Test the contents of the binary table variable array heap, returning + the size of the heap, the number of unused bytes that are not currently + pointed to by any of the descriptors, and the number of bytes which are + pointed to by multiple descriptors. It also returns valid = FALSE if + any of the descriptors point to invalid addresses out of range of the +> heap. \label{fftheap} +- + int fits_test_heap / fftheap + (fitsfile *fptr, > LONGLONG *heapsize, LONGLONG *unused, LONGLONG *overlap, + int *validheap, int *status) +- +>5 Re-pack the vectors in the binary table variable array heap to recover + any unused space. Normally, when a vector in a variable length + array column is rewritten the previously written array remains in + the heap as wasted unused space. This routine will repack the + arrays that are still in use, thus eliminating any bytes in the + heap that are no longer in use. Note that if several vectors point + to the same bytes in the heap, then this routine will make + duplicate copies of the bytes for each vector, which will actually +> expand the size of the heap. \label{ffcmph} +- + int fits_compress_heap / ffcmph + (fitsfile *fptr, > int *status) +- + +***2. Low-Level Table Access Routines + +The following 2 routines provide low-level access to the data in ASCII +or binary tables and are mainly useful as an efficient way to copy all +or part of a table from one location to another. These routines simply +read or write the specified number of consecutive bytes in an ASCII or +binary table, without regard for column boundaries or the row length in +the table. These routines do not perform any machine dependent data +conversion or byte swapping. See Appendix B for the definition of the +parameters used in these routines. + +>1 Read or write a consecutive array of bytes from an ASCII or binary +> table \label{ffgtbb} \label{ffptbb} +- + int fits_read_tblbytes / ffgtbb + (fitsfile *fptr, LONGLONG firstrow, LONGLONG firstchar, LONGLONG nchars, + > unsigned char *values, int *status) + + int fits_write_tblbytes / ffptbb + (fitsfile *fptr, LONGLONG firstrow, LONGLONG firstchar, LONGLONG nchars, + unsigned char *values, > int *status) +- + +***3. Write Column Data Routines + +>1 Write elements into an ASCII or binary table column (in the CDU). + The data type of the array is implied by the suffix of the +> routine name. \label{ffpcls} +- + int fits_write_col_str / ffpcls + (fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem, + LONGLONG nelements, char **array, > int *status) + + int fits_write_col_[log,byt,sht,usht,int,uint,lng,ulng,lnglng,flt,dbl,cmp,dblcmp] / + ffpcl[l,b,i,ui,k,uk,j,uj,jj,e,d,c,m] + (fitsfile *fptr, int colnum, LONGLONG firstrow, + LONGLONG firstelem, LONGLONG nelements, DTYPE *array, > int *status) +- +>2 Write elements into an ASCII or binary table column + substituting the appropriate FITS null value for any elements that +> are equal to the nulval parameter. \label{ffpcnx} +- + int fits_write_colnull_[log, byt, sht, usht, int, uint, lng, ulng, lnglng, flt, dbl] / + ffpcn[l,b,i,ui,k,uk,j,uj,jj,e,d] + (fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem, + LONGLONG nelements, DTYPE *array, DTYPE nulval, > int *status) +- +>3 Write string elements into a binary table column (in the CDU) + substituting the FITS null value for any elements that +> are equal to the nulstr string. \label{ffpcns} +- + int fits_write_colnull_str / ffpcns + (fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem, + LONGLONG nelements, char **array, char *nulstr, > int *status) +- +>4 Write bit values into a binary byte ('B') or bit ('X') table column (in + the CDU). Larray is an array of characters corresponding to the + sequence of bits to be written. If an element of larray is true + (not equal to zero) then the corresponding bit in the FITS table is + set to 1, otherwise the bit is set to 0. The 'X' column in a FITS + table is always padded out to a multiple of 8 bits where the bit + array starts with the most significant bit of the byte and works + down towards the 1's bit. For example, a '4X' array, with the + first bit = 1 and the remaining 3 bits = 0 is equivalent to the 8-bit + unsigned byte decimal value of 128 ('1000 0000B'). In the case of + 'X' columns, CFITSIO can write to all 8 bits of each byte whether + they are formally valid or not. Thus if the column is defined as + '4X', and one calls ffpclx with firstbit=1 and nbits=8, then all + 8 bits will be written into the first byte (as opposed to writing + the first 4 bits into the first row and then the next 4 bits into + the next row), even though the last 4 bits of each byte are formally + not defined and should all be set = 0. It should also be noted that + it is more efficient to write 'X' columns an entire byte at a time, + instead of bit by bit. Any of the CFITSIO routines that write to + columns (e.g. fits\_write\_col\_byt) may be used for this purpose. + These routines will interpret 'X' columns as though they were 'B' + columns (e.g., '1X' through '8X' is equivalent +> to '1B', and '9X' through '16X' is equivalent to '2B'). \label{ffpclx} +- + int fits_write_col_bit / ffpclx + (fitsfile *fptr, int colnum, LONGLONG firstrow, long firstbit, + long nbits, char *larray, > int *status) +- +>5 Write the descriptor for a variable length column in a binary table. + This routine can be used in conjunction with ffgdes to enable + 2 or more arrays to point to the same storage location to save +> storage space if the arrays are identical. \label{ffpdes} +- + int fits_write_descript / ffpdes + (fitsfile *fptr, int colnum, LONGLONG rownum, LONGLONG repeat, + LONGLONG offset, > int *status) +- +***4. Read Column Data Routines + +Two types of routines are provided to get the column data which differ +in the way undefined pixels are handled. The first set of routines +(ffgcv) simply return an array of data elements in which undefined +pixels are set equal to a value specified by the user in the 'nullval' +parameter. If nullval = 0, then no checks for undefined pixels will be +performed, thus increasing the speed of the program. The second set of +routines (ffgcf) returns the data element array and in addition a +logical array of flags which defines whether the corresponding data +pixel is undefined. See Appendix B for the definition of the +parameters used in these routines. + + Any column, regardless of it's intrinsic data type, may be read as a + string. It should be noted however that reading a numeric column as + a string is 10 - 100 times slower than reading the same column as a number + due to the large overhead in constructing the formatted strings. + The display format of the returned strings will be + determined by the TDISPn keyword, if it exists, otherwise by the + data type of the column. The length of the returned strings (not + including the null terminating character) can be determined with + the fits\_get\_col\_display\_width routine. The following TDISPn + display formats are currently supported: +- + Iw.m Integer + Ow.m Octal integer + Zw.m Hexadecimal integer + Fw.d Fixed floating point + Ew.d Exponential floating point + Dw.d Exponential floating point + Gw.d General; uses Fw.d if significance not lost, else Ew.d +- + where w is the width in characters of the displayed values, m is + the minimum number of digits displayed, and d is the number of + digits to the right of the decimal. The .m field is optional. + +>1 Read elements from an ASCII or binary table column (in the CDU). These + routines return the values of the table column array elements. Undefined + array elements will be returned with a value = nulval, unless nulval = 0 + (or = ' ' for ffgcvs) in which case no checking for undefined values will + be performed. The ANYF parameter is set to true if any of the returned +> elements are undefined. \label{ffgcvx} +- + int fits_read_col_str / ffgcvs + (fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem, + LONGLONG nelements, char *nulstr, > char **array, int *anynul, + int *status) + + int fits_read_col_[log,byt,sht,usht,int,uint,lng,ulng, lnglng, flt, dbl, cmp, dblcmp] / + ffgcv[l,b,i,ui,k,uk,j,uj,jj,e,d,c,m] + (fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem, + LONGLONG nelements, DTYPE nulval, > DTYPE *array, int *anynul, + int *status) +- +>2 Read elements and null flags from an ASCII or binary table column (in the + CHDU). These routines return the values of the table column array elements. + Any undefined array elements will have the corresponding nullarray element + set equal to TRUE. The anynul parameter is set to true if any of the +> returned elements are undefined. \label{ffgcfx} +- + int fits_read_colnull_str / ffgcfs + (fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem, + LONGLONG nelements, > char **array, char *nullarray, int *anynul, + int *status) + + int fits_read_colnull_[log,byt,sht,usht,int,uint,lng,ulng,lnglng,flt,dbl,cmp,dblcmp] / + ffgcf[l,b,i,ui,k,uk,j,uj,jj,e,d,c,m] + (fitsfile *fptr, int colnum, LONGLONG firstrow, + LONGLONG firstelem, LONGLONG nelements, > DTYPE *array, + char *nullarray, int *anynul, int *status) +- +>3 Read an arbitrary data subsection from an N-dimensional array + in a binary table vector column. Undefined pixels + in the array will be set equal to the value of 'nulval', + unless nulval=0 in which case no testing for undefined pixels will + be performed. The first and last rows in the table to be read + are specified by fpixel(naxis+1) and lpixel(naxis+1), and hence + are treated as the next higher dimension of the FITS N-dimensional + array. The INC parameter specifies the sampling interval in +> each dimension between the data elements that will be returned. \label{ffgsvx2} +- + int fits_read_subset_[byt, sht, usht, int, uint, lng, ulng, lnglng, flt, dbl] / + ffgsv[b,i,ui,k,uk,j,uj,jj,e,d] + (fitsfile *fptr, int colnum, int naxis, long *naxes, long *fpixel, + long *lpixel, long *inc, DTYPE nulval, > DTYPE *array, int *anynul, + int *status) +- +>4 Read an arbitrary data subsection from an N-dimensional array + in a binary table vector column. Any Undefined + pixels in the array will have the corresponding 'nullarray' + element set equal to TRUE. The first and last rows in the table + to be read are specified by fpixel(naxis+1) and lpixel(naxis+1), + and hence are treated as the next higher dimension of the FITS + N-dimensional array. The INC parameter specifies the sampling + interval in each dimension between the data elements that will be +> returned. \label{ffgsfx2} +- + int fits_read_subsetnull_[byt, sht, usht, int, uint, lng, ulng, lnglng, flt, dbl] / + ffgsf[b,i,ui,k,uk,j,uj,jj,e,d] + (fitsfile *fptr, int colnum, int naxis, long *naxes, + long *fpixel, long *lpixel, long *inc, > DTYPE *array, + char *nullarray, int *anynul, int *status) +- +>5 Read bit values from a byte ('B') or bit (`X`) table column (in the + CDU). Larray is an array of logical values corresponding to the + sequence of bits to be read. If larray is true then the + corresponding bit was set to 1, otherwise the bit was set to 0. + The 'X' column in a FITS table is always padded out to a multiple + of 8 bits where the bit array starts with the most significant bit + of the byte and works down towards the 1's bit. For example, a + '4X' array, with the first bit = 1 and the remaining 3 bits = 0 is + equivalent to the 8-bit unsigned byte value of 128. + Note that in the case of 'X' columns, CFITSIO can read all 8 bits + of each byte whether they are formally valid or not. Thus if the + column is defined as '4X', and one calls ffgcx with firstbit=1 and + nbits=8, then all 8 bits will be read from the first byte (as + opposed to reading the first 4 bits from the first row and then the + first 4 bits from the next row), even though the last 4 bits of + each byte are formally not defined. It should also be noted that + it is more efficient to read 'X' columns an entire byte at a time, + instead of bit by bit. Any of the CFITSIO routines that read + columns (e.g. fits\_read\_col\_byt) may be used for this + purpose. These routines will interpret 'X' columns as though they + were 'B' columns (e.g., '8X' is equivalent to '1B', and '16X' is +> equivalent to '2B'). \label{ffgcx} +- + int fits_read_col_bit / ffgcx + (fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstbit, + LONGLONG nbits, > char *larray, int *status) +- +>6 Read any consecutive set of bits from an 'X' or 'B' column and + interpret them as an unsigned n-bit integer. nbits must be less + than 16 or 32 in ffgcxui and ffgcxuk, respectively. If nrows + is greater than 1, then the same set of bits will be read from + each row, starting with firstrow. The bits are numbered with + 1 = the most significant bit of the first element of the column. +> \label{ffgcxui} +- + int fits_read_col_bit_[usht, uint] / ffgcx[ui,uk] + (fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG, nrows, + long firstbit, long nbits, > DTYPE *array, int *status) +- +>7 Return the descriptor for a variable length column in a binary table. + The descriptor consists of 2 integer parameters: the number of elements + in the array and the starting offset relative to the start of the heap. + The first pair of routine returns a single descriptor whereas the second + pair of routine + returns the descriptors for a range of rows in the table. The only + difference between the 2 routines in each pair is that one returns + the parameters as 'long' integers, whereas the other returns the values + as 64-bit 'LONGLONG' integers. +> \label{ffgdes} +- + int fits_read_descript / ffgdes + (fitsfile *fptr, int colnum, LONGLONG rownum, > long *repeat, + long *offset, int *status) + + int fits_read_descriptll / ffgdesll + (fitsfile *fptr, int colnum, LONGLONG rownum, > LONGLONG *repeat, + LONGLONG *offset, int *status) + + int fits_read_descripts / ffgdess + (fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG nrows + > long *repeat, long *offset, int *status) + + int fits_read_descriptsll / ffgdessll + (fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG nrows + > LONGLONG *repeat, LONGLONG *offset, int *status) +- + +*X. Extended File Name Syntax + +**A. Overview + +CFITSIO supports an extended syntax when specifying the name of the +data file to be opened or created that includes the following +features: + +\begin{itemize} +\item +CFITSIO can read IRAF format images which have header file names that +end with the '.imh' extension, as well as reading and writing FITS +files, This feature is implemented in CFITSIO by first converting the +IRAF image into a temporary FITS format file in memory, then opening +the FITS file. Any of the usual CFITSIO routines then may be used to +read the image header or data. Similarly, raw binary data arrays can +be read by converting them on the fly into virtual FITS images. + +\item +FITS files on the Internet can be read (and sometimes written) using the FTP, +HTTP, or ROOT protocols. + +\item +FITS files can be piped between tasks on the stdin and stdout streams. + +\item +FITS files can be read and written in shared memory. This can +potentially achieve better data I/O performance compared to reading and +writing the same FITS files on magnetic disk. + +\item +Compressed FITS files in gzip or Unix COMPRESS format can be directly read. + +\item +Output FITS files can be written directly in compressed gzip format, +thus saving disk space. + +\item +FITS table columns can be created, modified, or deleted 'on-the-fly' as +the table is opened by CFITSIO. This creates a virtual FITS file containing +the modifications that is then opened by the application program. + +\item +Table rows may be selected, or filtered out, on the fly when the table +is opened by CFITSIO, based on an user-specified expression. +Only rows for which the expression evaluates to 'TRUE' are retained +in the copy of the table that is opened by the application program. + +\item +Histogram images may be created on the fly by binning the values in +table columns, resulting in a virtual N-dimensional FITS image. The +application program then only sees the FITS image (in the primary +array) instead of the original FITS table. +\end{itemize} + +The latter 3 table filtering features in particular add very powerful +data processing capabilities directly into CFITSIO, and hence into +every task that uses CFITSIO to read or write FITS files. For example, +these features transform a very simple program that just copies an +input FITS file to a new output file (like the `fitscopy' program that +is distributed with CFITSIO) into a multipurpose FITS file processing +tool. By appending fairly simple qualifiers onto the name of the input +FITS file, the user can perform quite complex table editing operations +(e.g., create new columns, or filter out rows in a table) or create +FITS images by binning or histogramming the values in table columns. +In addition, these functions have been coded using new state-of-the art +algorithms that are, in some cases, 10 - 100 times faster than previous +widely used implementations. + +Before describing the complete syntax for the extended FITS file names +in the next section, here are a few examples of FITS file names that +give a quick overview of the allowed syntax: + +\begin{itemize} +\item +{\tt myfile.fits}: the simplest case of a FITS file on disk in the current +directory. + +\item +{\tt myfile.imh}: opens an IRAF format image file and converts it on the +fly into a temporary FITS format image in memory which can then be read with +any other CFITSIO routine. + +\item +{\tt rawfile.dat[i512,512]}: opens a raw binary data array (a 512 x 512 +short integer array in this case) and converts it on the fly into a +temporary FITS format image in memory which can then be read with any +other CFITSIO routine. + +\item +{\tt myfile.fits.gz}: if this is the name of a new output file, the '.gz' +suffix will cause it to be compressed in gzip format when it is written to +disk. + +\item +{\tt myfile.fits.gz[events, 2]}: opens and uncompresses the gzipped file +myfile.fits then moves to the extension with the keywords EXTNAME += 'EVENTS' and EXTVER = 2. + +\item +{\tt -}: a dash (minus sign) signifies that the input file is to be read +from the stdin file stream, or that the output file is to be written to +the stdout stream. See also the stream:// driver which provides a +more efficient, but more restricted method of reading or writing to +the stdin or stdout streams. + +\item +{\tt ftp://legacy.gsfc.nasa.gov/test/vela.fits}: FITS files in any ftp +archive site on the Internet may be directly opened with read-only +access. + +\item +{\tt http://legacy.gsfc.nasa.gov/software/test.fits}: any valid URL to a +FITS file on the Web may be opened with read-only access. + +\item +{\tt root://legacy.gsfc.nasa.gov/test/vela.fits}: similar to ftp access +except that it provides write as well as read access to the files +across the network. This uses the root protocol developed at CERN. + +\item +{\tt shmem://h2[events]}: opens the FITS file in a shared memory segment and +moves to the EVENTS extension. + +\item +{\tt mem://}: creates a scratch output file in core computer memory. The +resulting 'file' will disappear when the program exits, so this +is mainly useful for testing purposes when one does not want a +permanent copy of the output file. + +\item +{\tt myfile.fits[3; Images(10)]}: opens a copy of the image contained in the +10th row of the 'Images' column in the binary table in the 3th extension +of the FITS file. The virtual file that is opened by the application just +contains this single image in the primary array. + +\item +{\tt myfile.fits[1:512:2, 1:512:2]}: opens a section of the input image +ranging from the 1st to the 512th pixel in X and Y, and selects every +second pixel in both dimensions, resulting in a 256 x 256 pixel input image +in this case. + +\item +{\tt myfile.fits[EVENTS][col Rad = sqrt(X**2 + Y**2)]}: creates and opens +a virtual file on the fly that is identical to +myfile.fits except that it will contain a new column in the EVENTS +extension called 'Rad' whose value is computed using the indicated +expression which is a function of the values in the X and Y columns. + +\item +{\tt myfile.fits[EVENTS][PHA > 5]}: creates and opens a virtual FITS +files that is identical to 'myfile.fits' except that the EVENTS table +will only contain the rows that have values of the PHA column greater +than 5. In general, any arbitrary boolean expression using a C or +Fortran-like syntax, which may combine AND and OR operators, +may be used to select rows from a table. + +\item +{\tt myfile.fits[EVENTS][bin (X,Y)=1,2048,4]}: creates a temporary FITS +primary array image which is computed on the fly by binning (i.e, +computing the 2-dimensional histogram) of the values in the X and Y +columns of the EVENTS extension. In this case the X and Y coordinates +range from 1 to 2048 and the image pixel size is 4 units in both +dimensions, so the resulting image is 512 x 512 pixels in size. + +\item +The final example combines many of these feature into one complex +expression (it is broken into several lines for clarity): +- + ftp://legacy.gsfc.nasa.gov/data/sample.fits.gz[EVENTS] + [col phacorr = pha * 1.1 - 0.3][phacorr >= 5.0 && phacorr <= 14.0] + [bin (X,Y)=32] +- +In this case, CFITSIO (1) copies and uncompresses the FITS file from +the ftp site on the legacy machine, (2) moves to the 'EVENTS' +extension, (3) calculates a new column called 'phacorr', (4) selects +the rows in the table that have phacorr in the range 5 to 14, and +finally (5) bins the remaining rows on the X and Y column coordinates, +using a pixel size = 32 to create a 2D image. All this processing is +completely transparent to the application program, which simply sees +the final 2-D image in the primary array of the opened file. +\end{itemize} + +The full extended CFITSIO FITS file name can contain several different +components depending on the context. These components are described in +the following sections: +- +When creating a new file: + filetype://BaseFilename(templateName)[compress] + +When opening an existing primary array or image HDU: + filetype://BaseFilename(outName)[HDUlocation][ImageSection][pixFilter] + +When opening an existing table HDU: + filetype://BaseFilename(outName)[HDUlocation][colFilter][rowFilter][binSpec] +- +The filetype, BaseFilename, outName, HDUlocation, ImageSection, and pixFilter +components, if present, must be given in that order, but the colFilter, +rowFilter, and binSpec specifiers may follow in any order. Regardless +of the order, however, the colFilter specifier, if present, will be +processed first by CFITSIO, followed by the rowFilter specifier, and +finally by the binSpec specifier. + +**A. Filetype + +The type of file determines the medium on which the file is located +(e.g., disk or network) and, hence, which internal device driver is used by +CFITSIO to read and/or write the file. Currently supported types are +- + file:// - file on local magnetic disk (default) + ftp:// - a readonly file accessed with the anonymous FTP protocol. + It also supports ftp://username:password@hostname/... + for accessing password-protected ftp sites. + http:// - a readonly file accessed with the HTTP protocol. It + supports username:password just like the ftp driver. + Proxy HTTP servers are supported using the http_proxy + environment variable (see following note). + stream:// - special driver to read an input FITS file from the stdin + stream, and/or write an output FITS file to the stdout + stream. This driver is fragile and has limited + functionality (see the following note). + gsiftp:// - access files on a computational grid using the gridftp + protocol in the Globus toolkit (see following note). + root:// - uses the CERN root protocol for writing as well as + reading files over the network (see following note). + shmem:// - opens or creates a file which persists in the computer's + shared memory (see following note). + mem:// - opens a temporary file in core memory. The file + disappears when the program exits so this is mainly + useful for test purposes when a permanent output file + is not desired. +- +If the filetype is not specified, then type file:// is assumed. +The double slashes '//' are optional and may be omitted in most cases. + +***1. Notes about HTTP proxy servers + +A proxy HTTP server may be used by defining the address (URL) and port +number of the proxy server with the http\_proxy environment variable. +For example +- + setenv http_proxy http://heasarc.gsfc.nasa.gov:3128 +- +will cause CFITSIO to use port 3128 on the heasarc proxy server whenever +reading a FITS file with HTTP. + +***2. Notes about the stream filetype driver + +The stream driver can be used to efficiently read a FITS file from the stdin +file stream or write a FITS to the stdout file stream. However, because these +input and output streams must be accessed sequentially, the FITS file reading or +writing application must also read and write the file sequentially, at least +within the tolerances described below. + +CFITSIO supports 2 different methods for accessing FITS files on the stdin and +stdout streams. The original method, which is invoked by specifying a dash +character, "-", as the name of the file when opening or creating it, works by +storing a complete copy of the entire FITS file in memory. In this case, when +reading from stdin, CFITSIO will copy the entire stream into memory before doing +any processing of the file. Similarly, when writing to stdout, CFITSIO will +create a copy of the entire FITS file in memory, before finally flushing it out +to the stdout stream when the FITS file is closed. Buffering the entire FITS +file in this way allows the application to randomly access any part of the FITS +file, in any order, but it also requires that the user have sufficient available +memory (or virtual memory) to store the entire file, which may not be possible +in the case of very large files. + +The newer stream filetype provides a more memory-efficient method of accessing +FITS files on the stdin or stdout streams. Instead of storing a copy of the +entire FITS file in memory, CFITSIO only uses a set of internal buffer which by +default can store 40 FITS blocks, or about 100K bytes of the FITS file. The +application program must process the FITS file sequentially from beginning to +end, within this 100K buffer. Generally speaking the application program must +conform to the following restrictions: + +\begin{itemize} +\item +The program must finish reading or writing the header keywords +before reading or writing any data in the HDU. +\item +The HDU can contain at most about 1400 header keywords. This is the +maximum that can fit in the nominal 40 FITS block buffer. In principle, +this limit could be increased by recompiling CFITSIO with a larger +buffer limit, which is set by the NIOBUF parameter in fitsio2.h. +\item +The program must read or write the data in a sequential manner from the +beginning to the end of the HDU. Note that CFITSIO's internal +100K buffer allows a little latitude in meeting this requirement. +\item +The program cannot move back to a previous HDU in the FITS file. +\item +Reading or writing of variable length array columns in binary tables is not +supported on streams, because this requires moving back and forth between the +fixed-length portion of the binary table and the following heap area where the +arrays are actually stored. +\item +Reading or writing of tile-compressed images is not supported on streams, +because the images are internally stored using variable length arrays. +\end{itemize} + +***3. Notes about the gsiftp filetype + +DEPENDENCIES: Globus toolkit (2.4.3 or higher) (GT) should be installed. +There are two different ways to install GT: + +1) goto the globus toolkit web page www.globus.org and follow the + download and compilation instructions; + +2) goto the Virtual Data Toolkit web page http://vdt.cs.wisc.edu/ + and follow the instructions (STRONGLY SUGGESTED); + +Once a globus client has been installed in your system with a specific flavour +it is possible to compile and install the CFITSIO libraries. +Specific configuration flags must be used: + +1) --with-gsiftp[[=PATH]] Enable Globus Toolkit gsiftp protocol support + PATH=GLOBUS\_LOCATION i.e. the location of your globus installation + +2) --with-gsiftp-flavour[[=PATH] defines the specific Globus flavour + ex. gcc32 + +Both the flags must be used and it is mandatory to set both the PATH and the +flavour. + +USAGE: To access files on a gridftp server it is necessary to use a gsiftp prefix: + +example: gsiftp://remote\_server\_fqhn/directory/filename + +The gridftp driver uses a local buffer on a temporary file the file is located +in the /tmp directory. If you have special permissions on /tmp or you do not have a /tmp +directory, it is possible to force another location setting the GSIFTP\_TMPFILE environment +variable (ex. export GSIFTP\_TMPFILE=/your/location/yourtmpfile). + +Grid FTP supports multi channel transfer. By default a single channel transmission is +available. However, it is possible to modify this behavior setting the GSIFTP\_STREAMS +environment variable (ex. export GSIFTP\_STREAMS=8). + + +***4. Notes about the root filetype + +The original rootd server can be obtained from: +\verb-ftp://root.cern.ch/root/rootd.tar.gz- +but, for it to work correctly with CFITSIO one has to use a modified +version which supports a command to return the length of the file. +This modified version is available in rootd subdirectory +in the CFITSIO ftp area at +- + ftp://legacy.gsfc.nasa.gov/software/fitsio/c/root/rootd.tar.gz. +- + +This small server is started either by inetd when a client requests a +connection to a rootd server or by hand (i.e. from the command line). +The rootd server works with the ROOT TNetFile class. It allows remote +access to ROOT database files in either read or write mode. By default +TNetFile assumes port 432 (which requires rootd to be started as root). +To run rootd via inetd add the following line to /etc/services: +- + rootd 432/tcp +- +and to /etc/inetd.conf, add the following line: +- + rootd stream tcp nowait root /user/rdm/root/bin/rootd rootd -i +- +Force inetd to reread its conf file with \verb+kill -HUP +. +You can also start rootd by hand running directly under your private +account (no root system privileges needed). For example to start +rootd listening on port 5151 just type: \verb+rootd -p 5151+ +Notice that no \& is needed. Rootd will go into background by itself. +- + Rootd arguments: + -i says we were started by inetd + -p port# specifies a different port to listen on + -d level level of debug info written to syslog + 0 = no debug (default) + 1 = minimum + 2 = medium + 3 = maximum +- +Rootd can also be configured for anonymous usage (like anonymous ftp). +To setup rootd to accept anonymous logins do the following (while being +logged in as root): +- + - Add the following line to /etc/passwd: + + rootd:*:71:72:Anonymous rootd:/var/spool/rootd:/bin/false + + where you may modify the uid, gid (71, 72) and the home directory + to suite your system. + + - Add the following line to /etc/group: + + rootd:*:72:rootd + + where the gid must match the gid in /etc/passwd. + + - Create the directories: + + mkdir /var/spool/rootd + mkdir /var/spool/rootd/tmp + chmod 777 /var/spool/rootd/tmp + + Where /var/spool/rootd must match the rootd home directory as + specified in the rootd /etc/passwd entry. + + - To make writeable directories for anonymous do, for example: + + mkdir /var/spool/rootd/pub + chown rootd:rootd /var/spool/rootd/pub +- +That's all. Several additional remarks: you can login to an anonymous +server either with the names "anonymous" or "rootd". The password should +be of type user@host.do.main. Only the @ is enforced for the time +being. In anonymous mode the top of the file tree is set to the rootd +home directory, therefore only files below the home directory can be +accessed. Anonymous mode only works when the server is started via +inetd. + +***5. Notes about the shmem filetype: + +Shared memory files are currently supported on most Unix platforms, +where the shared memory segments are managed by the operating system +kernel and `live' independently of processes. They are not deleted (by +default) when the process which created them terminates, although they +will disappear if the system is rebooted. Applications can create +shared memory files in CFITSIO by calling: +- + fit_create_file(&fitsfileptr, "shmem://h2", &status); +- +where the root `file' names are currently restricted to be 'h0', 'h1', +'h2', 'h3', etc., up to a maximum number defined by the the value of +SHARED\_MAXSEG (equal to 16 by default). This is a prototype +implementation of the shared memory interface and a more robust +interface, which will have fewer restrictions on the number of files +and on their names, may be developed in the future. + +When opening an already existing FITS file in shared memory one calls +the usual CFITSIO routine: +- + fits_open_file(&fitsfileptr, "shmem://h7", mode, &status) +- +The file mode can be READWRITE or READONLY just as with disk files. +More than one process can operate on READONLY mode files at the same +time. CFITSIO supports proper file locking (both in READONLY and +READWRITE modes), so calls to fits\_open\_file may be locked out until +another other process closes the file. + +When an application is finished accessing a FITS file in a shared +memory segment, it may close it (and the file will remain in the +system) with fits\_close\_file, or delete it with fits\_delete\_file. +Physical deletion is postponed until the last process calls +ffclos/ffdelt. fits\_delete\_file tries to obtain a READWRITE lock on +the file to be deleted, thus it can be blocked if the object was not +opened in READWRITE mode. + +A shared memory management utility program called `smem', is included +with the CFITSIO distribution. It can be built by typing `make smem'; +then type `smem -h' to get a list of valid options. Executing smem +without any options causes it to list all the shared memory segments +currently residing in the system and managed by the shared memory +driver. To get a list of all the shared memory objects, run the system +utility program `ipcs [-a]'. + +**B. Base Filename + +The base filename is the name of the file optionally including the +director/subdirectory path, and in the case of `ftp', `http', and `root' +filetypes, the machine identifier. Examples: +- + myfile.fits + !data.fits + /data/myfile.fits + fits.gsfc.nasa.gov/ftp/sampledata/myfile.fits.gz +- + +When creating a new output file on magnetic disk (of type file://) if +the base filename begins with an exclamation point (!) then any +existing file with that same basename will be deleted prior to creating +the new FITS file. Otherwise if the file to be created already exists, +then CFITSIO will return an error and will not overwrite the existing +file. Note that the exclamation point, '!', is a special UNIX +character, so if it is used on the command line rather than entered at +a task prompt, it must be preceded by a backslash to force the UNIX +shell to pass it verbatim to the application program. + +If the output disk file name ends with the suffix '.gz', then CFITSIO +will compress the file using the gzip compression algorithm before +writing it to disk. This can reduce the amount of disk space used by +the file. Note that this feature requires that the uncompressed file +be constructed in memory before it is compressed and written to disk, +so it can fail if there is insufficient available memory. + +An input FITS file may be compressed with the gzip or Unix compress +algorithms, in which case CFITSIO will uncompress the file on the fly +into a temporary file (in memory or on disk). Compressed files may +only be opened with read-only permission. When specifying the name of +a compressed FITS file it is not necessary to append the file suffix +(e.g., `.gz' or `.Z'). If CFITSIO cannot find the input file name +without the suffix, then it will automatically search for a compressed +file with the same root name. In the case of reading ftp and http type +files, CFITSIO generally looks for a compressed version of the file +first, before trying to open the uncompressed file. By default, +CFITSIO copies (and uncompressed if necessary) the ftp or http FITS +file into memory on the local machine before opening it. This will +fail if the local machine does not have enough memory to hold the whole +FITS file, so in this case, the output filename specifier (see the next +section) can be used to further control how CFITSIO reads ftp and http +files. + +If the input file is an IRAF image file (*.imh file) then CFITSIO will +automatically convert it on the fly into a virtual FITS image before it +is opened by the application program. IRAF images can only be opened +with READONLY file access. + +Similarly, if the input file is a raw binary data array, then CFITSIO +will convert it on the fly into a virtual FITS image with the basic set +of required header keywords before it is opened by the application +program (with READONLY access). In this case the data type and +dimensions of the image must be specified in square brackets following +the filename (e.g. rawfile.dat[ib512,512]). The first character (case +insensitive) defines the data type of the array: +- + b 8-bit unsigned byte + i 16-bit signed integer + u 16-bit unsigned integer + j 32-bit signed integer + r or f 32-bit floating point + d 64-bit floating point +- +An optional second character specifies the byte order of the array +values: b or B indicates big endian (as in FITS files and the native +format of SUN UNIX workstations and Mac PCs) and l or L indicates +little endian (native format of DEC OSF workstations and IBM PCs). If +this character is omitted then the array is assumed to have the native +byte order of the local machine. These data type characters are then +followed by a series of one or more integer values separated by commas +which define the size of each dimension of the raw array. Arrays with +up to 5 dimensions are currently supported. Finally, a byte offset to +the position of the first pixel in the data file may be specified by +separating it with a ':' from the last dimension value. If omitted, it +is assumed that the offset = 0. This parameter may be used to skip +over any header information in the file that precedes the binary data. +Further examples: +- + raw.dat[b10000] 1-dimensional 10000 pixel byte array + raw.dat[rb400,400,12] 3-dimensional floating point big-endian array + img.fits[ib512,512:2880] reads the 512 x 512 short integer array in + a FITS file, skipping over the 2880 byte header +- + +One special case of input file is where the filename = `-' (a dash or +minus sign) or 'stdin' or 'stdout', which signifies that the input file +is to be read from the stdin stream, or written to the stdout stream if +a new output file is being created. In the case of reading from stdin, +CFITSIO first copies the whole stream into a temporary FITS file (in +memory or on disk), and subsequent reading of the FITS file occurs in +this copy. When writing to stdout, CFITSIO first constructs the whole +file in memory (since random access is required), then flushes it out +to the stdout stream when the file is closed. In addition, if the +output filename = '-.gz' or 'stdout.gz' then it will be gzip compressed +before being written to stdout. + +This ability to read and write on the stdin and stdout steams allows +FITS files to be piped between tasks in memory rather than having to +create temporary intermediate FITS files on disk. For example if task1 +creates an output FITS file, and task2 reads an input FITS file, the +FITS file may be piped between the 2 tasks by specifying +- + task1 - | task2 - +- +where the vertical bar is the Unix piping symbol. This assumes that the 2 +tasks read the name of the FITS file off of the command line. + +**C. Output File Name when Opening an Existing File + +An optional output filename may be specified in parentheses immediately +following the base file name to be opened. This is mainly useful in +those cases where CFITSIO creates a temporary copy of the input FITS +file before it is opened and passed to the application program. This +happens by default when opening a network FTP or HTTP-type file, when +reading a compressed FITS file on a local disk, when reading from the +stdin stream, or when a column filter, row filter, or binning specifier +is included as part of the input file specification. By default this +temporary file is created in memory. If there is not enough memory to +create the file copy, then CFITSIO will exit with an error. In these +cases one can force a permanent file to be created on disk, instead of +a temporary file in memory, by supplying the name in parentheses +immediately following the base file name. The output filename can +include the '!' clobber flag. + +Thus, if the input filename to CFITSIO is: +\verb+file1.fits.gz(file2.fits)+ +then CFITSIO will uncompress `file1.fits.gz' into the local disk file +`file2.fits' before opening it. CFITSIO does not automatically delete +the output file, so it will still exist after the application program +exits. + +The output filename "mem://" is also allowed, which will write the +output file into memory, and also allow write access to the file. This +'file' will disappear when it is closed, but this may be useful for +some applications which only need to modify a temporary copy of the file. + +In some cases, several different temporary FITS files will be created +in sequence, for instance, if one opens a remote file using FTP, then +filters rows in a binary table extension, then create an image by +binning a pair of columns. In this case, the remote file will be +copied to a temporary local file, then a second temporary file will be +created containing the filtered rows of the table, and finally a third +temporary file containing the binned image will be created. In cases +like this where multiple files are created, the outfile specifier will +be interpreted the name of the final file as described below, in descending +priority: + +\begin{itemize} +\item +as the name of the final image file if an image within a single binary +table cell is opened or if an image is created by binning a table column. +\item +as the name of the file containing the filtered table if a column filter +and/or a row filter are specified. +\item +as the name of the local copy of the remote FTP or HTTP file. +\item +as the name of the uncompressed version of the FITS file, if a +compressed FITS file on local disk has been opened. +\item +otherwise, the output filename is ignored. +\end{itemize} + +The output file specifier is useful when reading FTP or HTTP-type +FITS files since it can be used to create a local disk copy of the file +that can be reused in the future. If the output file name = `*' then a +local file with the same name as the network file will be created. +Note that CFITSIO will behave differently depending on whether the +remote file is compressed or not as shown by the following examples: +\begin{itemize} +\item +\verb+ftp://remote.machine/tmp/myfile.fits.gz(*)+ - the remote compressed +file is copied to the local compressed file `myfile.fits.gz', which +is then uncompressed in local memory before being opened and passed +to the application program. + +\item +\verb+ftp://remote.machine/tmp/myfile.fits.gz(myfile.fits)+ - the +remote compressed file is copied and uncompressed into the local file +`myfile.fits'. This example requires less local memory than the +previous example since the file is uncompressed on disk instead of in +memory. + +\item +\verb+ftp://remote.machine/tmp/myfile.fits(myfile.fits.gz)+ - this will +usually produce an error since CFITSIO itself cannot compress files. +\end{itemize} + +The exact behavior of CFITSIO in the latter case depends on the type of +ftp server running on the remote machine and how it is configured. In +some cases, if the file `myfile.fits.gz' exists on the remote machine, +then the server will copy it to the local machine. In other cases the +ftp server will automatically create and transmit a compressed version +of the file if only the uncompressed version exists. This can get +rather confusing, so users should use a certain amount of caution when +using the output file specifier with FTP or HTTP file types, to make +sure they get the behavior that they expect. + +**D. Template File Name when Creating a New File + +When a new FITS file is created with a call to fits\_create\_file, the +name of a template file may be supplied in parentheses immediately +following the name of the new file to be created. This template is +used to define the structure of one or more HDUs in the new file. The +template file may be another FITS file, in which case the newly created +file will have exactly the same keywords in each HDU as in the template +FITS file, but all the data units will be filled with zeros. The +template file may also be an ASCII text file, where each line (in +general) describes one FITS keyword record. The format of the ASCII +template file is described in the following Template Files chapter. + +**E. Image Tile-Compression Specification + +When specifying the name of the output FITS file to be created, the +user can indicate that images should be written in tile-compressed +format (see section 5.5, ``Primary Array or IMAGE Extension I/O +Routines'') by enclosing the compression parameters in square brackets +following the root disk file name. Here are some examples of the +syntax for specifying tile-compressed output images: +- + myfile.fit[compress] - use Rice algorithm and default tile size + + myfile.fit[compress GZIP] - use the specified compression algorithm; + myfile.fit[compress Rice] only the first letter of the algorithm + myfile.fit[compress PLIO] name is required. + + myfile.fit[compress Rice 100,100] - use 100 x 100 pixel tile size + myfile.fit[compress Rice 100,100;2] - as above, and use noisebits = 2 +- + +**F. HDU Location Specification + +The optional HDU location specifier defines which HDU (Header-Data +Unit, also known as an `extension') within the FITS file to initially +open. It must immediately follow the base file name (or the output +file name if present). If it is not specified then the first HDU (the +primary array) is opened. The HDU location specifier is required if +the colFilter, rowFilter, or binSpec specifiers are present, because +the primary array is not a valid HDU for these operations. The HDU may +be specified either by absolute position number, starting with 0 for +the primary array, or by reference to the HDU name, and optionally, the +version number and the HDU type of the desired extension. The location +of an image within a single cell of a binary table may also be +specified, as described below. + +The absolute position of the extension is specified either by enclosed +the number in square brackets (e.g., `[1]' = the first extension +following the primary array) or by preceded the number with a plus sign +(`+1'). To specify the HDU by name, give the name of the desired HDU +(the value of the EXTNAME or HDUNAME keyword) and optionally the +extension version number (value of the EXTVER keyword) and the +extension type (value of the XTENSION keyword: IMAGE, ASCII or TABLE, +or BINTABLE), separated by commas and all enclosed in square brackets. +If the value of EXTVER and XTENSION are not specified, then the first +extension with the correct value of EXTNAME is opened. The extension +name and type are not case sensitive, and the extension type may be +abbreviated to a single letter (e.g., I = IMAGE extension or primary +array, A or T = ASCII table extension, and B = binary table BINTABLE +extension). If the HDU location specifier is equal to `[PRIMARY]' or +`[P]', then the primary array (the first HDU) will be opened. + +An optional pound sign character ("\#") may be appended to the extension +name or number to signify that any other extensions in the file should +be ignored during any subsequent file filtering operations. For example, +when doing row filtering operations on a table extension, CFITSIO normally +creates a copy of the filtered table in memory, along with a verbatim +copy of all the other extensions in the input FITS file. If the pound +sign is appended to the table extension name, then only that extension, +and none of the other extensions in the file, will by copied to memory, +as in the following example: +- + myfile.fit[events#][TIME > 10000] +- + +FITS images are most commonly stored in the primary array or an image +extension, but images can also be stored as a vector in a single cell +of a binary table (i.e. each row of the vector column contains a +different image). Such an image can be opened with CFITSIO by +specifying the desired column name and the row number after the binary +table HDU specifier as shown in the following examples. The column name +is separated from the HDU specifier by a semicolon and the row number +is enclosed in parentheses. In this case CFITSIO copies the image from +the table cell into a temporary primary array before it is opened. The +application program then just sees the image in the primary array, +without any extensions. The particular row to be opened may be +specified either by giving an absolute integer row number (starting +with 1 for the first row), or by specifying a boolean expression that +evaluates to TRUE for the desired row. The first row that satisfies +the expression will be used. The row selection expression has the same +syntax as described in the Row Filter Specifier section, below. + + Examples: +- + myfile.fits[3] - open the 3rd HDU following the primary array + myfile.fits+3 - same as above, but using the FTOOLS-style notation + myfile.fits[EVENTS] - open the extension that has EXTNAME = 'EVENTS' + myfile.fits[EVENTS, 2] - same as above, but also requires EXTVER = 2 + myfile.fits[events,2,b] - same, but also requires XTENSION = 'BINTABLE' + myfile.fits[3; images(17)] - opens the image in row 17 of the 'images' + column in the 3rd extension of the file. + myfile.fits[3; images(exposure > 100)] - as above, but opens the image + in the first row that has an 'exposure' column value + greater than 100. +- + +**G. Image Section + +A virtual file containing a rectangular subsection of an image can be +extracted and opened by specifying the range of pixels (start:end) +along each axis to be extracted from the original image. One can also +specify an optional pixel increment (start:end:step) for each axis of +the input image. A pixel step = 1 will be assumed if it is not +specified. If the start pixel is larger then the end pixel, then the +image will be flipped (producing a mirror image) along that dimension. +An asterisk, '*', may be used to specify the entire range of an axis, +and '-*' will flip the entire axis. The input image can be in the +primary array, in an image extension, or contained in a vector cell of +a binary table. In the later 2 cases the extension name or number must +be specified before the image section specifier. + + Examples: +- + myfile.fits[1:512:2, 2:512:2] - open a 256x256 pixel image + consisting of the odd numbered columns (1st axis) and + the even numbered rows (2nd axis) of the image in the + primary array of the file. + + myfile.fits[*, 512:256] - open an image consisting of all the columns + in the input image, but only rows 256 through 512. + The image will be flipped along the 2nd axis since + the starting pixel is greater than the ending pixel. + + myfile.fits[*:2, 512:256:2] - same as above but keeping only + every other row and column in the input image. + + myfile.fits[-*, *] - copy the entire image, flipping it along + the first axis. + + myfile.fits[3][1:256,1:256] - opens a subsection of the image that + is in the 3rd extension of the file. + + myfile.fits[4; images(12)][1:10,1:10] - open an image consisting + of the first 10 pixels in both dimensions. The original + image resides in the 12th row of the 'images' vector + column in the table in the 4th extension of the file. +- + +When CFITSIO opens an image section it first creates a temporary file +containing the image section plus a copy of any other HDUs in the +file. (If a `\#' character is appended to the name or number of the +image HDU, as in "myfile.fits[1\#][1:200,1:200]", then the other +HDUs in the input file will not be copied into memory). +This temporary file is then opened by the application program, +so it is not possible to write to or modify the input file when +specifying an image section. Note that CFITSIO automatically updates +the world coordinate system keywords in the header of the image +section, if they exist, so that the coordinate associated with each +pixel in the image section will be computed correctly. + +**H. Image Transform Filters + +CFITSIO can apply a user-specified mathematical function to the value +of every pixel in a FITS image, thus creating a new virtual image +in computer memory that is then opened and read by the application +program. The original FITS image is not modified by this process. + +The image transformation specifier is appended to the input +FITS file name and is enclosed in square brackets. It begins with the +letters 'PIX' to distinguish it from other types of FITS file filters +that are recognized by CFITSIO. The image transforming function may +use any of the mathematical operators listed in the following +'Row Filtering Specification' section of this document. +Some examples of image transform filters are: +- + [pix X * 2.0] - multiply each pixel by 2.0 + [pix sqrt(X)] - take the square root of each pixel + [pix X + #ZEROPT - add the value of the ZEROPT keyword + [pix X>0 ? log10(X) : -99.] - if the pixel value is greater + than 0, compute the base 10 log, + else set the pixel = -99. +- +Use the letter 'X' in the expression to represent the current pixel value +in the image. The expression is evaluated +independently for each pixel in the image and may be a function of 1) the +original pixel value, 2) the value of other pixels in the image at +a given relative offset from the position of the pixel that is being +evaluated, and 3) the value of +any header keywords. Header keyword values are represented +by the name of the keyword preceded by the '\#' sign. + + +To access the the value of adjacent pixels in the image, +specify the (1-D) offset from the current pixel in curly brackets. +For example +- + [pix (x{-1} + x + x{+1}) / 3] +- +will replace each pixel value with the running mean of the values of that +pixel and it's 2 neighboring pixels. Note that in this notation the image +is treated as a 1-D array, where each row of the image (or higher dimensional +cube) is appended one after another in one long array of pixels. +It is possible to refer to pixels +in the rows above or below the current pixel by using the value of the +NAXIS1 header keyword. For example +- + [pix (x{-#NAXIS1} + x + x{#NAXIS1}) / 3] +- +will compute the mean of each image pixel and the pixels immediately +above and below it in the adjacent rows of the image. +The following more complex example +creates a smoothed virtual image where each pixel +is a 3 x 3 boxcar average of the input image pixels: +- + [pix (X + X{-1} + X{+1} + + X{-#NAXIS1} + X{-#NAXIS1 - 1} + X{-#NAXIS1 + 1} + + X{#NAXIS1} + X{#NAXIS1 - 1} + X{#NAXIS1 + 1}) / 9.] +- +If the pixel offset +extends beyond the first or last pixel in the image, the function will +evaluate to undefined, or NULL. + +For complex or commonly used image filtering operations, +one can write the expression into an external text file and +then import it into the +filter using the syntax '[pix @filename.txt]'. The mathematical +expression can +extend over multiple lines of text in the file. +Any lines in the external text file +that begin with 2 slash characters ('//') will be ignored and may be +used to add comments into the file. + +By default, the datatype of the resulting image will be the same as +the original image, but one may force a different datatype by appended +a code letter to the 'pix' keyword: +- + pixb - 8-bit byte image with BITPIX = 8 + pixi - 16-bit integer image with BITPIX = 16 + pixj - 32-bit integer image with BITPIX = 32 + pixr - 32-bit float image with BITPIX = -32 + pixd - 64-bit float image with BITPIX = -64 +- +Also by default, any other HDUs in the input file will be copied without +change to the +output virtual FITS file, but one may discard the other HDUs by adding +the number '1' to the 'pix' keyword (and following any optional datatype code +letter). For example: +- + myfile.fits[3][pixr1 sqrt(X)] +- +will create a virtual FITS file containing only a primary array image +with 32-bit floating point pixels that have a value equal to the square +root of the pixels in the image that is in the 3rd extension +of the 'myfile.fits' file. + + +**I. Column and Keyword Filtering Specification + +The optional column/keyword filtering specifier is used to modify the +column structure and/or the header keywords in the HDU that was +selected with the previous HDU location specifier. This filtering +specifier must be enclosed in square brackets and can be distinguished +from a general row filter specifier (described below) by the fact that +it begins with the string 'col ' and is not immediately followed by an +equals sign. The original file is not changed by this filtering +operation, and instead the modifications are made on a copy of the +input FITS file (usually in memory), which also contains a copy of all +the other HDUs in the file. (If a `\#' character is appended to the name +or number of the +table HDU then only the primary array, and none of the other +HDUs in the input file will be copied into memory). +This temporary file is passed to the +application program and will persist only until the file is closed or +until the program exits, unless the outfile specifier (see above) is +also supplied. + +The column/keyword filter can be used to perform the following +operations. More than one operation may be specified by separating +them with commas or semi-colons. + +\begin{itemize} + +\item +Copy only a specified list of columns columns to the filtered input file. +The list of column name should be separated by semi-colons. Wild card +characters may be used in the column names to match multiple columns. +If the expression contains both a list of columns to be included and +columns to be deleted, then all the columns in the original table +except the explicitly deleted columns will appear in the filtered +table (i.e., there is no need to explicitly list the columns to +be included if any columns are being deleted). + +\item +Delete a column or keyword by listing the name preceded by a minus sign +or an exclamation mark (!), e.g., '-TIME' will delete the TIME column +if it exists, otherwise the TIME keyword. An error is returned if +neither a column nor keyword with this name exists. Note that the +exclamation point, '!', is a special UNIX character, so if it is used +on the command line rather than entered at a task prompt, it must be +preceded by a backslash to force the UNIX shell to ignore it. + +\item +Rename an existing column or keyword with the syntax 'NewName == +OldName'. An error is returned if neither a column nor keyword with +this name exists. + +\item +Append a new column or keyword to the table. To create a column, +give the new name, optionally followed by the data type in parentheses, +followed by a single equals sign and an expression to be used to +compute the value (e.g., 'newcol(1J) = 0' will create a new 32-bit +integer column called 'newcol' filled with zeros). The data type is +specified using the same syntax that is allowed for the value of the +FITS TFORMn keyword (e.g., 'I', 'J', 'E', 'D', etc. for binary tables, +and 'I8', F12.3', 'E20.12', etc. for ASCII tables). If the data type is +not specified then an appropriate data type will be chosen depending on +the form of the expression (may be a character string, logical, bit, long +integer, or double column). An appropriate vector count (in the case +of binary tables) will also be added if not explicitly specified. + +When creating a new keyword, the keyword name must be preceded by a +pound sign '\#', and the expression must evaluate to a scalar +(i.e., cannot have a column name in the expression). The comment +string for the keyword may be specified in parentheses immediately +following the keyword name (instead of supplying a data type as in +the case of creating a new column). If the keyword name ends with a +pound sign '\#', then cfitsio will substitute the number of the +most recently referenced column for the \# character . +This is especially useful when writing +a column-related keyword like TUNITn for a newly created column, +as shown in the following examples. + +\item +Recompute (overwrite) the values in an existing column or keyword by +giving the name followed by an equals sign and an arithmetic +expression. +\end{itemize} + +The expression that is used when appending or recomputing columns or +keywords can be arbitrarily complex and may be a function of other +header keyword values and other columns (in the same row). The full +syntax and available functions for the expression are described below +in the row filter specification section. + +If the expression contains both a list of columns to be included and +columns to be deleted, then all the columns in the original table +except the explicitly deleted columns will appear in the filtered +table. If no columns to be deleted are specified, then only the +columns that are explicitly listed will be included in the filtered +output table. To include all the columns, add the '*' wildcard +specifier at the end of the list, as shown in the examples. + +For complex or commonly used operations, one can place the +operations into an external text file and import it into the column +filter using the syntax '[col @filename.txt]'. The operations can +extend over multiple lines of the file, but multiple operations must +still be separated by semicolons. Any lines in the external text file +that begin with 2 slash characters ('//') will be ignored and may be +used to add comments into the file. + +Examples: +- + [col Time; rate] - only the Time and rate columns will + appear in the filtered input file. + + [col Time, *raw] - include the Time column and any other + columns whose name ends with 'raw'. + + [col -TIME; Good == STATUS] - deletes the TIME column and + renames the status column to 'Good' + + [col PI=PHA * 1.1 + 0.2; #TUNIT#(column units) = 'counts';*] + - creates new PI column from PHA values + and also writes the TUNITn keyword + for the new column. The final '*' + expression means preserve all the + columns in the input table in the + virtual output table; without the '*' + the output table would only contain + the single 'PI' column. + + [col rate = rate/exposure; TUNIT#(&) = 'counts/s';*] + - recomputes the rate column by dividing + it by the EXPOSURE keyword value. This + also modifies the value of the TUNITn + keyword for this column. The use of the + '&' character for the keyword comment + string means preserve the existing + comment string for that keyword. The + final '*' preserves all the columns + in the input table in the virtual + output table. +- + +**J. Row Filtering Specification + + When entering the name of a FITS table that is to be opened by a + program, an optional row filter may be specified to select a subset + of the rows in the table. A temporary new FITS file is created on + the fly which contains only those rows for which the row filter + expression evaluates to true. The primary array and any other + extensions in the input file are also copied to the temporary + file. +(If a `\#' character is appended to the name +or number of the +table HDU then only the primary array, and none of the other +HDUs in the input file will be copied into the temporary file). + The original FITS file is closed and the new virtual file + is opened by the application program. The row filter expression is + enclosed in square brackets following the file name and extension + name (e.g., 'file.fits[events][GRADE==50]' selects only those rows + where the GRADE column value equals 50). When dealing with tables + where each row has an associated time and/or 2D spatial position, + the row filter expression can also be used to select rows based on + the times in a Good Time Intervals (GTI) extension, or on spatial + position as given in a SAO-style region file. + +***1. General Syntax + + The row filtering expression can be an arbitrarily complex series + of operations performed on constants, keyword values, and column + data taken from the specified FITS TABLE extension. The expression + must evaluate to a boolean value for each row of the table, where + a value of FALSE means that the row will be excluded. + + For complex or commonly used filters, one can place the expression + into a text file and import it into the row filter using the syntax + '[@filename.txt]'. The expression can be arbitrarily complex and + extend over multiple lines of the file. Any lines in the external + text file that begin with 2 slash characters ('//') will be ignored + and may be used to add comments into the file. + + Keyword and column data are referenced by name. Any string of + characters not surrounded by quotes (ie, a constant string) or + followed by an open parentheses (ie, a function name) will be + initially interpreted as a column name and its contents for the + current row inserted into the expression. If no such column exists, + a keyword of that name will be searched for and its value used, if + found. To force the name to be interpreted as a keyword (in case + there is both a column and keyword with the same name), precede the + keyword name with a single pound sign, '\#', as in '\#NAXIS2'. Due to + the generalities of FITS column and keyword names, if the column or + keyword name contains a space or a character which might appear as + an arithmetic term then enclose the name in '\$' characters as in + \$MAX PHA\$ or \#\$MAX-PHA\$. Names are case insensitive. + + To access a table entry in a row other than the current one, follow + the column's name with a row offset within curly braces. For + example, 'PHA\{-3\}' will evaluate to the value of column PHA, 3 rows + above the row currently being processed. One cannot specify an + absolute row number, only a relative offset. Rows that fall outside + the table will be treated as undefined, or NULLs. + + Boolean operators can be used in the expression in either their + Fortran or C forms. The following boolean operators are available: +- + "equal" .eq. .EQ. == "not equal" .ne. .NE. != + "less than" .lt. .LT. < "less than/equal" .le. .LE. <= =< + "greater than" .gt. .GT. > "greater than/equal" .ge. .GE. >= => + "or" .or. .OR. || "and" .and. .AND. && + "negation" .not. .NOT. ! "approx. equal(1e-7)" ~ +- + +Note that the exclamation +point, '!', is a special UNIX character, so if it is used on the +command line rather than entered at a task prompt, it must be preceded +by a backslash to force the UNIX shell to ignore it. + + The expression may also include arithmetic operators and functions. + Trigonometric functions use radians, not degrees. The following + arithmetic operators and functions can be used in the expression + (function names are case insensitive). A null value will be returned + in case of illegal operations such as divide by zero, sqrt(negative) + log(negative), log10(negative), arccos(.gt. 1), arcsin(.gt. 1). + +- + "addition" + "subtraction" - + "multiplication" * "division" / + "negation" - "exponentiation" ** ^ + "absolute value" abs(x) "cosine" cos(x) + "sine" sin(x) "tangent" tan(x) + "arc cosine" arccos(x) "arc sine" arcsin(x) + "arc tangent" arctan(x) "arc tangent" arctan2(y,x) + "hyperbolic cos" cosh(x) "hyperbolic sin" sinh(x) + "hyperbolic tan" tanh(x) "round to nearest int" round(x) + "round down to int" floor(x) "round up to int" ceil(x) + "exponential" exp(x) "square root" sqrt(x) + "natural log" log(x) "common log" log10(x) + "modulus" x % y "random # [0.0,1.0)" random() + "random Gaussian" randomn() "random Poisson" randomp(x) + "minimum" min(x,y) "maximum" max(x,y) + "cumulative sum" accum(x) "sequential difference" seqdiff(x) + "if-then-else" b?x:y + "angular separation" angsep(ra1,dec1,ra2,de2) (all in degrees) + "substring" strmid(s,p,n) "string search" strstr(s,r) +- +Three different random number functions are provided: random(), with +no arguments, produces a uniform random deviate between 0 and 1; +randomn(), also with no arguments, produces a normal (Gaussian) random +deviate with zero mean and unit standard deviation; randomp(x) +produces a Poisson random deviate whose expected number of counts is +X. X may be any positive real number of expected counts, including +fractional values, but the return value is an integer. + +When the random functions are used in a vector expression, by default +the same random value will be used when evaluating each element of the vector. +If different random numbers are desired, then the name of a vector +column should be supplied as the single argument to the random +function (e.g., "flux + 0.1 * random(flux)", where "flux' is the +name of a vector column). This will create a vector of +random numbers that will be used in sequence when evaluating each +element of the vector expression. + +An alternate syntax for the min and max functions has only a single +argument which should be a vector value (see below). The result +will be the minimum/maximum element contained within the vector. + +The accum(x) function forms the cumulative sum of x, element by element. +Vector columns are supported simply by performing the summation process +through all the values. Null values are treated as 0. The seqdiff(x) +function forms the sequential difference of x, element by element. +The first value of seqdiff is the first value of x. A single null +value in x causes a pair of nulls in the output. The seqdiff and +accum functions are functional inverses, i.e., seqdiff(accum(x)) == x +as long as no null values are present. + +In the if-then-else expression, "b?x:y", b is an explicit boolean +value or expression. There is no automatic type conversion from +numeric to boolean values, so one needs to use "iVal!=0" instead of +merely "iVal" as the boolean argument. x and y can be any scalar data +type (including string). + +The angsep function computes the angular separation in degrees +between 2 celestial positions, where the first 2 parameters +give the RA-like and Dec-like coordinates (in decimal degrees) +of the first position, and the 3rd and 4th parameters give the +coordinates of the second position. + +The substring function strmid(S,P,N) extracts a substring from S, +starting at string position P, with a substring length N. The first +character position in S is labeled as 1. If P is 0, or refers to a +position beyond the end of S, then the extracted substring will be +NULL. S, P, and N may be functions of other columns. + +The string search function strstr(S,R) searches for the first occurrence +of the substring R in S. The result is an integer, indicating the +character position of the first match (where 1 is the first character +position of S). If no match is found, then strstr() returns a NULL +value. + +The following type casting operators are available, where the +inclosing parentheses are required and taken from the C language +usage. Also, the integer to real casts values to double precision: +- + "real to integer" (int) x (INT) x + "integer to real" (float) i (FLOAT) i +- + + In addition, several constants are built in for use in numerical + expressions: + +- + #pi 3.1415... #e 2.7182... + #deg #pi/180 #row current row number + #null undefined value #snull undefined string +- + + A string constant must be enclosed in quotes as in 'Crab'. The + "null" constants are useful for conditionally setting table values + to a NULL, or undefined, value (eg., "col1==-99 ? \#NULL : col1"). + + There is also a function for testing if two values are close to + each other, i.e., if they are "near" each other to within a user + specified tolerance. The arguments, value\_1 and value\_2 can be + integer or real and represent the two values who's proximity is + being tested to be within the specified tolerance, also an integer + or real: +- + near(value_1, value_2, tolerance) +- + When a NULL, or undefined, value is encountered in the FITS table, + the expression will evaluate to NULL unless the undefined value is + not actually required for evaluation, e.g. "TRUE .or. NULL" + evaluates to TRUE. The following two functions allow some NULL + detection and handling: +- + "a null value?" ISNULL(x) + "define a value for null" DEFNULL(x,y) +- + The former + returns a boolean value of TRUE if the argument x is NULL. The + later "defines" a value to be substituted for NULL values; it + returns the value of x if x is not NULL, otherwise it returns the + value of y. + + + +***2. Bit Masks + + Bit masks can be used to select out rows from bit columns (TFORMn = + \#X) in FITS files. To represent the mask, binary, octal, and hex + formats are allowed: + +- + binary: b0110xx1010000101xxxx0001 + octal: o720x1 -> (b111010000xxx001) + hex: h0FxD -> (b00001111xxxx1101) +- + + In all the representations, an x or X is allowed in the mask as a + wild card. Note that the x represents a different number of wild + card bits in each representation. All representations are case + insensitive. + + To construct the boolean expression using the mask as the boolean + equal operator described above on a bit table column. For example, + if you had a 7 bit column named flags in a FITS table and wanted + all rows having the bit pattern 0010011, the selection expression + would be: + +- + flags == b0010011 + or + flags .eq. b10011 +- + + It is also possible to test if a range of bits is less than, less + than equal, greater than and greater than equal to a particular + boolean value: + +- + flags <= bxxx010xx + flags .gt. bxxx100xx + flags .le. b1xxxxxxx +- + + Notice the use of the x bit value to limit the range of bits being + compared. + + It is not necessary to specify the leading (most significant) zero + (0) bits in the mask, as shown in the second expression above. + + Bit wise AND, OR and NOT operations are also possible on two or + more bit fields using the '\&'(AND), '$|$'(OR), and the '!'(NOT) + operators. All of these operators result in a bit field which can + then be used with the equal operator. For example: + +- + (!flags) == b1101100 + (flags & b1000001) == bx000001 +- + + Bit fields can be appended as well using the '+' operator. Strings + can be concatenated this way, too. + +***3. Vector Columns + + Vector columns can also be used in building the expression. No + special syntax is required if one wants to operate on all elements + of the vector. Simply use the column name as for a scalar column. + Vector columns can be freely intermixed with scalar columns or + constants in virtually all expressions. The result will be of the + same dimension as the vector. Two vectors in an expression, though, + need to have the same number of elements and have the same + dimensions. + + Arithmetic and logical operations are all performed on an element by + element basis. Comparing two vector columns, eg "COL1 == COL2", + thus results in another vector of boolean values indicating which + elements of the two vectors are equal. + + Eight functions are available that operate on a vector and return a + scalar result: +- + "minimum" MIN(V) "maximum" MAX(V) + "average" AVERAGE(V) "median" MEDIAN(V) + "summation" SUM(V) "standard deviation" STDDEV(V) + "# of values" NELEM(V) "# of non-null values" NVALID(V) +- + where V represents the name of a vector column or a manually + constructed vector using curly brackets as described below. The + first 6 of these functions ignore any null values in the vector when + computing the result. The STDDEV() function computes the sample + standard deviation, i.e. it is proportional to 1/SQRT(N-1) instead + of 1/SQRT(N), where N is NVALID(V). + + The SUM function literally sums all the elements in x, returning a + scalar value. If V is a boolean vector, SUM returns the number + of TRUE elements. The NELEM function returns the number of elements + in vector V whereas NVALID return the number of non-null elements in + the vector. (NELEM also operates on bit and string columns, + returning their column widths.) As an example, to test whether all + elements of two vectors satisfy a given logical comparison, one can + use the expression +- + SUM( COL1 > COL2 ) == NELEM( COL1 ) +- + + which will return TRUE if all elements of COL1 are greater than + their corresponding elements in COL2. + + To specify a single element of a vector, give the column name + followed by a comma-separated list of coordinates enclosed in + square brackets. For example, if a vector column named PHAS exists + in the table as a one dimensional, 256 component list of numbers + from which you wanted to select the 57th component for use in the + expression, then PHAS[57] would do the trick. Higher dimensional + arrays of data may appear in a column. But in order to interpret + them, the TDIMn keyword must appear in the header. Assuming that a + (4,4,4,4) array is packed into each row of a column named ARRAY4D, + the (1,2,3,4) component element of each row is accessed by + ARRAY4D[1,2,3,4]. Arrays up to dimension 5 are currently + supported. Each vector index can itself be an expression, although + it must evaluate to an integer value within the bounds of the + vector. Vector columns which contain spaces or arithmetic operators + must have their names enclosed in "\$" characters as with + \$ARRAY-4D\$[1,2,3,4]. + + A more C-like syntax for specifying vector indices is also + available. The element used in the preceding example alternatively + could be specified with the syntax ARRAY4D[4][3][2][1]. Note the + reverse order of indices (as in C), as well as the fact that the + values are still ones-based (as in Fortran -- adopted to avoid + ambiguity for 1D vectors). With this syntax, one does not need to + specify all of the indices. To extract a 3D slice of this 4D + array, use ARRAY4D[4]. + + Variable-length vector columns are not supported. + + Vectors can be manually constructed within the expression using a + comma-separated list of elements surrounded by curly braces ('\{\}'). + For example, '\{1,3,6,1\}' is a 4-element vector containing the values + 1, 3, 6, and 1. The vector can contain only boolean, integer, and + real values (or expressions). The elements will be promoted to the + highest data type present. Any elements which are themselves + vectors, will be expanded out with each of its elements becoming an + element in the constructed vector. + +***4. Good Time Interval Filtering + + A common filtering method involves selecting rows which have a time + value which lies within what is called a Good Time Interval or GTI. + The time intervals are defined in a separate FITS table extension + which contains 2 columns giving the start and stop time of each + good interval. The filtering operation accepts only those rows of + the input table which have an associated time which falls within + one of the time intervals defined in the GTI extension. A high + level function, gtifilter(a,b,c,d), is available which evaluates + each row of the input table and returns TRUE or FALSE depending + whether the row is inside or outside the good time interval. The + syntax is +- + gtifilter( [ "gtifile" [, expr [, "STARTCOL", "STOPCOL" ] ] ] ) + or + gtifilter( [ 'gtifile' [, expr [, 'STARTCOL', 'STOPCOL' ] ] ] ) +- + where each "[]" demarks optional parameters. Note that the quotes + around the gtifile and START/STOP column are required. Either single + or double quotes may be used. In cases where this expression is + entered on the Unix command line, enclose the entire expression in + double quotes, and then use single quotes within the expression to + enclose the 'gtifile' and other terms. It is also usually possible + to do the reverse, and enclose the whole expression in single quotes + and then use double quotes within the expression. The gtifile, + if specified, can be blank ("") which will mean to use the first + extension with the name "*GTI*" in the current file, a plain + extension specifier (eg, "+2", "[2]", or "[STDGTI]") which will be + used to select an extension in the current file, or a regular + filename with or without an extension specifier which in the latter + case will mean to use the first extension with an extension name + "*GTI*". Expr can be any arithmetic expression, including simply + the time column name. A vector time expression will produce a + vector boolean result. STARTCOL and STOPCOL are the names of the + START/STOP columns in the GTI extension. If one of them is + specified, they both must be. + + In its simplest form, no parameters need to be provided -- default + values will be used. The expression "gtifilter()" is equivalent to +- + gtifilter( "", TIME, "*START*", "*STOP*" ) +- + This will search the current file for a GTI extension, filter the + TIME column in the current table, using START/STOP times taken from + columns in the GTI extension with names containing the strings + "START" and "STOP". The wildcards ('*') allow slight variations in + naming conventions such as "TSTART" or "STARTTIME". The same + default values apply for unspecified parameters when the first one + or two parameters are specified. The function automatically + searches for TIMEZERO/I/F keywords in the current and GTI + extensions, applying a relative time offset, if necessary. + +***5. Spatial Region Filtering + + Another common filtering method selects rows based on whether the + spatial position associated with each row is located within a given + 2-dimensional region. The syntax for this high-level filter is +- + regfilter( "regfilename" [ , Xexpr, Yexpr [ , "wcs cols" ] ] ) +- + where each "[]" demarks optional parameters. The region file name + is required and must be enclosed in quotes. The remaining + parameters are optional. There are 2 supported formats for the + region file: ASCII file or FITS binary table. The region file + contains a list of one or more geometric shapes (circle, + ellipse, box, etc.) which defines a region on the celestial sphere + or an area within a particular 2D image. The region file is + typically generated using an image display program such as fv/POW + (distribute by the HEASARC), or ds9 (distributed by the Smithsonian + Astrophysical Observatory). Users should refer to the documentation + provided with these programs for more details on the syntax used in + the region files. The FITS region file format is defined in a document + available from the FITS Support Office at + http://fits.gsfc.nasa.gov/ registry/ region.html + + In its simplest form, (e.g., regfilter("region.reg") ) the + coordinates in the default 'X' and 'Y' columns will be used to + determine if each row is inside or outside the area specified in + the region file. Alternate position column names, or expressions, + may be entered if needed, as in +- + regfilter("region.reg", XPOS, YPOS) +- + Region filtering can be applied most unambiguously if the positions + in the region file and in the table to be filtered are both give in + terms of absolute celestial coordinate units. In this case the + locations and sizes of the geometric shapes in the region file are + specified in angular units on the sky (e.g., positions given in + R.A. and Dec. and sizes in arcseconds or arcminutes). Similarly, + each row of the filtered table will have a celestial coordinate + associated with it. This association is usually implemented using + a set of so-called 'World Coordinate System' (or WCS) FITS keywords + that define the coordinate transformation that must be applied to + the values in the 'X' and 'Y' columns to calculate the coordinate. + + Alternatively, one can perform spatial filtering using unitless + 'pixel' coordinates for the regions and row positions. In this + case the user must be careful to ensure that the positions in the 2 + files are self-consistent. A typical problem is that the region + file may be generated using a binned image, but the unbinned + coordinates are given in the event table. The ROSAT events files, + for example, have X and Y pixel coordinates that range from 1 - + 15360. These coordinates are typically binned by a factor of 32 to + produce a 480x480 pixel image. If one then uses a region file + generated from this image (in image pixel units) to filter the + ROSAT events file, then the X and Y column values must be converted + to corresponding pixel units as in: +- + regfilter("rosat.reg", X/32.+.5, Y/32.+.5) +- + Note that this binning conversion is not necessary if the region + file is specified using celestial coordinate units instead of pixel + units because CFITSIO is then able to directly compare the + celestial coordinate of each row in the table with the celestial + coordinates in the region file without having to know anything + about how the image may have been binned. + + The last "wcs cols" parameter should rarely be needed. If supplied, + this string contains the names of the 2 columns (space or comma + separated) which have the associated WCS keywords. If not supplied, + the filter will scan the X and Y expressions for column names. + If only one is found in each expression, those columns will be + used, otherwise an error will be returned. + + These region shapes are supported (names are case insensitive): +- + Point ( X1, Y1 ) <- One pixel square region + Line ( X1, Y1, X2, Y2 ) <- One pixel wide region + Polygon ( X1, Y1, X2, Y2, ... ) <- Rest are interiors with + Rectangle ( X1, Y1, X2, Y2, A ) | boundaries considered + Box ( Xc, Yc, Wdth, Hght, A ) V within the region + Diamond ( Xc, Yc, Wdth, Hght, A ) + Circle ( Xc, Yc, R ) + Annulus ( Xc, Yc, Rin, Rout ) + Ellipse ( Xc, Yc, Rx, Ry, A ) + Elliptannulus ( Xc, Yc, Rinx, Riny, Routx, Routy, Ain, Aout ) + Sector ( Xc, Yc, Amin, Amax ) +- + where (Xc,Yc) is the coordinate of the shape's center; (X\#,Y\#) are + the coordinates of the shape's edges; Rxxx are the shapes' various + Radii or semimajor/minor axes; and Axxx are the angles of rotation + (or bounding angles for Sector) in degrees. For rotated shapes, the + rotation angle can be left off, indicating no rotation. Common + alternate names for the regions can also be used: rotbox = box; + rotrectangle = rectangle; (rot)rhombus = (rot)diamond; and pie + = sector. When a shape's name is preceded by a minus sign, '-', + the defined region is instead the area *outside* its boundary (ie, + the region is inverted). All the shapes within a single region + file are OR'd together to create the region, and the order is + significant. The overall way of looking at region files is that if + the first region is an excluded region then a dummy included region + of the whole detector is inserted in the front. Then each region + specification as it is processed overrides any selections inside of + that region specified by previous regions. Another way of thinking + about this is that if a previous excluded region is completely + inside of a subsequent included region the excluded region is + ignored. + + The positional coordinates may be given either in pixel units, + decimal degrees or hh:mm:ss.s, dd:mm:ss.s units. The shape sizes + may be given in pixels, degrees, arcminutes, or arcseconds. Look + at examples of region file produced by fv/POW or ds9 for further + details of the region file format. + + There are three low-level functions that are primarily for use with + regfilter function, but they can be called directly. They + return a boolean true or false depending on whether a two + dimensional point is in the region or not. The positional coordinates + must be given in pixel units: +- + "point in a circular region" + circle(xcntr,ycntr,radius,Xcolumn,Ycolumn) + + "point in an elliptical region" + ellipse(xcntr,ycntr,xhlf_wdth,yhlf_wdth,rotation,Xcolumn,Ycolumn) + + "point in a rectangular region" + box(xcntr,ycntr,xfll_wdth,yfll_wdth,rotation,Xcolumn,Ycolumn) + + where + (xcntr,ycntr) are the (x,y) position of the center of the region + (xhlf_wdth,yhlf_wdth) are the (x,y) half widths of the region + (xfll_wdth,yfll_wdth) are the (x,y) full widths of the region + (radius) is half the diameter of the circle + (rotation) is the angle(degrees) that the region is rotated with + respect to (xcntr,ycntr) + (Xcoord,Ycoord) are the (x,y) coordinates to test, usually column + names + NOTE: each parameter can itself be an expression, not merely a + column name or constant. +- + +***5. Example Row Filters +- + [ binary && mag <= 5.0] - Extract all binary stars brighter + than fifth magnitude (note that + the initial space is necessary to + prevent it from being treated as a + binning specification) + + [#row >= 125 && #row <= 175] - Extract row numbers 125 through 175 + + [IMAGE[4,5] .gt. 100] - Extract all rows that have the + (4,5) component of the IMAGE column + greater than 100 + + [abs(sin(theta * #deg)) < 0.5] - Extract all rows having the + absolute value of the sine of theta + less than a half where the angles + are tabulated in degrees + + [SUM( SPEC > 3*BACKGRND )>=1] - Extract all rows containing a + spectrum, held in vector column + SPEC, with at least one value 3 + times greater than the background + level held in a keyword, BACKGRND + + [VCOL=={1,4,2}] - Extract all rows whose vector column + VCOL contains the 3-elements 1, 4, and + 2. + + [@rowFilter.txt] - Extract rows using the expression + contained within the text file + rowFilter.txt + + [gtifilter()] - Search the current file for a GTI + extension, filter the TIME + column in the current table, using + START/STOP times taken from + columns in the GTI extension + + [regfilter("pow.reg")] - Extract rows which have a coordinate + (as given in the X and Y columns) + within the spatial region specified + in the pow.reg region file. + + [regfilter("pow.reg", Xs, Ys)] - Same as above, except that the + Xs and Ys columns will be used to + determine the coordinate of each + row in the table. +- + +**J. Binning or Histogramming Specification + +The optional binning specifier is enclosed in square brackets and can +be distinguished from a general row filter specification by the fact +that it begins with the keyword 'bin' not immediately followed by an +equals sign. When binning is specified, a temporary N-dimensional FITS +primary array is created by computing the histogram of the values in +the specified columns of a FITS table extension. After the histogram +is computed the input FITS file containing the table is then closed and +the temporary FITS primary array is opened and passed to the +application program. Thus, the application program never sees the +original FITS table and only sees the image in the new temporary file +(which has no additional extensions). Obviously, the application +program must be expecting to open a FITS image and not a FITS table in +this case. + +The data type of the FITS histogram image may be specified by appending +'b' (for 8-bit byte), 'i' (for 16-bit integers), 'j' (for 32-bit +integer), 'r' (for 32-bit floating points), or 'd' (for 64-bit double +precision floating point) to the 'bin' keyword (e.g. '[binr X]' +creates a real floating point image). If the data type is not +explicitly specified then a 32-bit integer image will be created by +default, unless the weighting option is also specified in which case +the image will have a 32-bit floating point data type by default. + +The histogram image may have from 1 to 4 dimensions (axes), depending +on the number of columns that are specified. The general form of the +binning specification is: +- + [bin{bijrd} Xcol=min:max:binsize, Ycol= ..., Zcol=..., Tcol=...; weight] +- +in which up to 4 columns, each corresponding to an axis of the image, +are listed. The column names are case insensitive, and the column +number may be given instead of the name, preceded by a pound sign +(e.g., [bin \#4=1:512]). If the column name is not specified, then +CFITSIO will first try to use the 'preferred column' as specified by +the CPREF keyword if it exists (e.g., 'CPREF = 'DETX,DETY'), otherwise +column names 'X', 'Y', 'Z', and 'T' will be assumed for each of the 4 +axes, respectively. In cases where the column name could be confused +with an arithmetic expression, enclose the column name in parentheses to +force the name to be interpreted literally. + +Each column name may be followed by an equals sign and then the lower +and upper range of the histogram, and the size of the histogram bins, +separated by colons. Spaces are allowed before and after the equals +sign but not within the 'min:max:binsize' string. The min, max and +binsize values may be integer or floating point numbers, or they may be +the names of keywords in the header of the table. If the latter, then +the value of that keyword is substituted into the expression. + +Default values for the min, max and binsize quantities will be +used if not explicitly given in the binning expression as shown +in these examples: +- + [bin x = :512:2] - use default minimum value + [bin x = 1::2] - use default maximum value + [bin x = 1:512] - use default bin size + [bin x = 1:] - use default maximum value and bin size + [bin x = :512] - use default minimum value and bin size + [bin x = 2] - use default minimum and maximum values + [bin x] - use default minimum, maximum and bin size + [bin 4] - default 2-D image, bin size = 4 in both axes + [bin] - default 2-D image +- +CFITSIO will use the value of the TLMINn, TLMAXn, and TDBINn keywords, +if they exist, for the default min, max, and binsize, respectively. If +they do not exist then CFITSIO will use the actual minimum and maximum +values in the column for the histogram min and max values. The default +binsize will be set to 1, or (max - min) / 10., whichever is smaller, +so that the histogram will have at least 10 bins along each axis. + +A shortcut notation is allowed if all the columns/axes have the same +binning specification. In this case all the column names may be listed +within parentheses, followed by the (single) binning specification, as +in: +- + [bin (X,Y)=1:512:2] + [bin (X,Y) = 5] +- + +The optional weighting factor is the last item in the binning specifier +and, if present, is separated from the list of columns by a +semi-colon. As the histogram is accumulated, this weight is used to +incremented the value of the appropriated bin in the histogram. If the +weighting factor is not specified, then the default weight = 1 is +assumed. The weighting factor may be a constant integer or floating +point number, or the name of a keyword containing the weighting value. +Or the weighting factor may be the name of a table column in which case +the value in that column, on a row by row basis, will be used. + +In some cases, the column or keyword may give the reciprocal of the +actual weight value that is needed. In this case, precede the weight +keyword or column name by a slash '/' to tell CFITSIO to use the +reciprocal of the value when constructing the histogram. + +For complex or commonly used histograms, one can also place its +description into a text file and import it into the binning +specification using the syntax [bin @filename.txt]. The file's +contents can extend over multiple lines, although it must still +conform to the no-spaces rule for the min:max:binsize syntax and each +axis specification must still be comma-separated. Any lines in the +external text file that begin with 2 slash characters ('//') will be +ignored and may be used to add comments into the file. + + Examples: + +- + [bini detx, dety] - 2-D, 16-bit integer histogram + of DETX and DETY columns, using + default values for the histogram + range and binsize + + [bin (detx, dety)=16; /exposure] - 2-D, 32-bit real histogram of DETX + and DETY columns with a bin size = 16 + in both axes. The histogram values + are divided by the EXPOSURE keyword + value. + + [bin time=TSTART:TSTOP:0.1] - 1-D lightcurve, range determined by + the TSTART and TSTOP keywords, + with 0.1 unit size bins. + + [bin pha, time=8000.:8100.:0.1] - 2-D image using default binning + of the PHA column for the X axis, + and 1000 bins in the range + 8000. to 8100. for the Y axis. + + [bin @binFilter.txt] - Use the contents of the text file + binFilter.txt for the binning + specifications. + +- +*X. Template Files + +When a new FITS file is created with a call to fits\_create\_file, the +name of a template file may be supplied in parentheses immediately +following the name of the new file to be created. This template is +used to define the structure of one or more HDUs in the new file. The +template file may be another FITS file, in which case the newly created +file will have exactly the same keywords in each HDU as in the template +FITS file, but all the data units will be filled with zeros. The +template file may also be an ASCII text file, where each line (in +general) describes one FITS keyword record. The format of the ASCII +template file is described in the following sections. + +**A Detailed Template Line Format + +The format of each ASCII template line closely follows the format of a +FITS keyword record: +- + KEYWORD = KEYVALUE / COMMENT +- +except that free format may be used (e.g., the equals sign may appear +at any position in the line) and TAB characters are allowed and are +treated the same as space characters. The KEYVALUE and COMMENT fields +are optional. The equals sign character is also optional, but it is +recommended that it be included for clarity. Any template line that +begins with the pound '\#' character is ignored by the template parser +and may be use to insert comments into the template file itself. + +The KEYWORD name field is limited to 8 characters in length and only +the letters A-Z, digits 0-9, and the hyphen and underscore characters +may be used, without any embedded spaces. Lowercase letters in the +template keyword name will be converted to uppercase. Leading spaces +in the template line preceding the keyword name are generally ignored, +except if the first 8 characters of a template line are all blank, then +the entire line is treated as a FITS comment keyword (with a blank +keyword name) and is copied verbatim into the FITS header. + +The KEYVALUE field may have any allowed FITS data type: character +string, logical, integer, real, complex integer, or complex real. The +character string values need not be enclosed in single quote characters +unless they are necessary to distinguish the string from a different +data type (e.g. 2.0 is a real but '2.0' is a string). The keyword has +an undefined (null) value if the template record only contains blanks +following the "=" or between the "=" and the "/" comment field +delimiter. + +String keyword values longer than 68 characters (the maximum length +that will fit in a single FITS keyword record) are permitted using the +CFITSIO long string convention. They can either be specified as a +single long line in the template, or by using multiple lines where the +continuing lines contain the 'CONTINUE' keyword, as in this example: +- + LONGKEY = 'This is a long string value that is contin&' + CONTINUE 'ued over 2 records' / comment field goes here +- +The format of template lines with CONTINUE keyword is very strict: 3 +spaces must follow CONTINUE and the rest of the line is copied verbatim +to the FITS file. + +The start of the optional COMMENT field must be preceded by "/", which +is used to separate it from the keyword value field. Exceptions are if +the KEYWORD name field contains COMMENT, HISTORY, CONTINUE, or if the +first 8 characters of the template line are blanks. + +More than one Header-Data Unit (HDU) may be defined in the template +file. The start of an HDU definition is denoted with a SIMPLE or +XTENSION template line: + +1) SIMPLE begins a Primary HDU definition. SIMPLE may only appear as +the first keyword in the template file. If the template file begins +with XTENSION instead of SIMPLE, then a default empty Primary HDU is +created, and the template is then assumed to define the keywords +starting with the first extension following the Primary HDU. + +2) XTENSION marks the beginning of a new extension HDU definition. The +previous HDU will be closed at this point and processing of the next +extension begins. + +**B Auto-indexing of Keywords + +If a template keyword name ends with a "\#" character, it is said to be +'auto-indexed'. Each "\#" character will be replaced by the current +integer index value, which gets reset = 1 at the start of each new HDU +in the file (or 7 in the special case of a GROUP definition). The +FIRST indexed keyword in each template HDU definition is used as the +'incrementor'; each subsequent occurrence of this SAME keyword will +cause the index value to be incremented. This behavior can be rather +subtle, as illustrated in the following examples in which the TTYPE +keyword is the incrementor in both cases: +- + TTYPE# = TIME + TFORM# = 1D + TTYPE# = RATE + TFORM# = 1E +- +will create TTYPE1, TFORM1, TTYPE2, and TFORM2 keywords. But if the +template looks like, +- + TTYPE# = TIME + TTYPE# = RATE + TFORM# = 1D + TFORM# = 1E +- +this results in a FITS files with TTYPE1, TTYPE2, TFORM2, and TFORM2, +which is probably not what was intended! + +**C Template Parser Directives + +In addition to the template lines which define individual keywords, the +template parser recognizes 3 special directives which are each preceded +by the backslash character: \verb+ \include, \group+, and \verb+ \end+. + +The 'include' directive must be followed by a filename. It forces the +parser to temporarily stop reading the current template file and begin +reading the include file. Once the parser reaches the end of the +include file it continues parsing the current template file. Include +files can be nested, and HDU definitions can span multiple template +files. + +The start of a GROUP definition is denoted with the 'group' directive, +and the end of a GROUP definition is denoted with the 'end' directive. +Each GROUP contains 0 or more member blocks (HDUs or GROUPs). Member +blocks of type GROUP can contain their own member blocks. The GROUP +definition itself occupies one FITS file HDU of special type (GROUP +HDU), so if a template specifies 1 group with 1 member HDU like: +- +\group +grpdescr = 'demo' +xtension bintable +# this bintable has 0 cols, 0 rows +\end +- +then the parser creates a FITS file with 3 HDUs : +- +1) dummy PHDU +2) GROUP HDU (has 1 member, which is bintable in HDU number 3) +3) bintable (member of GROUP in HDU number 2) +- +Technically speaking, the GROUP HDU is a BINTABLE with 6 columns. Applications +can define additional columns in a GROUP HDU using TFORMn and TTYPEn +(where n is 7, 8, ....) keywords or their auto-indexing equivalents. + +For a more complicated example of a template file using the group directives, +look at the sample.tpl file that is included in the CFITSIO distribution. + +**D Formal Template Syntax + +The template syntax can formally be defined as follows: +- + TEMPLATE = BLOCK [ BLOCK ... ] + + BLOCK = { HDU | GROUP } + + GROUP = \GROUP [ BLOCK ... ] \END + + HDU = XTENSION [ LINE ... ] { XTENSION | \GROUP | \END | EOF } + + LINE = [ KEYWORD [ = ] ] [ VALUE ] [ / COMMENT ] + + X ... - X can be present 1 or more times + { X | Y } - X or Y + [ X ] - X is optional +- + +At the topmost level, the template defines 1 or more template blocks. Blocks +can be either HDU (Header Data Unit) or a GROUP. For each block the parser +creates 1 (or more for GROUPs) FITS file HDUs. + + +**E Errors + +In general the fits\_execute\_template() function tries to be as atomic +as possible, so either everything is done or nothing is done. If an +error occurs during parsing of the template, fits\_execute\_template() +will (try to) delete the top level BLOCK (with all its children if any) +in which the error occurred, then it will stop reading the template file +and it will return with an error. + +**F Examples + +1. This template file will create a 200 x 300 pixel image, with 4-byte +integer pixel values, in the primary HDU: +- + SIMPLE = T + BITPIX = 32 + NAXIS = 2 / number of dimensions + NAXIS1 = 100 / length of first axis + NAXIS2 = 200 / length of second axis + OBJECT = NGC 253 / name of observed object +- +The allowed values of BITPIX are 8, 16, 32, -32, or -64, +representing, respectively, 8-bit integer, 16-bit integer, 32-bit +integer, 32-bit floating point, or 64 bit floating point pixels. + +2. To create a FITS table, the template first needs to include +XTENSION = TABLE or BINTABLE to define whether it is an ASCII or binary +table, and NAXIS2 to define the number of rows in the table. Two +template lines are then needed to define the name (TTYPEn) and FITS data +format (TFORMn) of the columns, as in this example: +- + xtension = bintable + naxis2 = 40 + ttype# = Name + tform# = 10a + ttype# = Npoints + tform# = j + ttype# = Rate + tunit# = counts/s + tform# = e +- +The above example defines a null primary array followed by a 40-row +binary table extension with 3 columns called 'Name', 'Npoints', and +'Rate', with data formats of '10A' (ASCII character string), '1J' +(integer) and '1E' (floating point), respectively. Note that the other +required FITS keywords (BITPIX, NAXIS, NAXIS1, PCOUNT, GCOUNT, TFIELDS, +and END) do not need to be explicitly defined in the template because +their values can be inferred from the other keywords in the template. +This example also illustrates that the templates are generally +case-insensitive (the keyword names and TFORMn values are converted to +upper-case in the FITS file) and that string keyword values generally +do not need to be enclosed in quotes. + +*XI. Local FITS Conventions + +CFITSIO supports several local FITS conventions which are not +defined in the official NOST FITS standard and which are not +necessarily recognized or supported by other FITS software packages. +Programmers should be cautious about using these features, especially +if the FITS files that are produced are expected to be processed by +other software systems which do not use the CFITSIO interface. + +**A. 64-Bit Long Integers + +CFITSIO supports reading and writing FITS images or table columns containing +64-bit integer data values. Support for 64-bit integers was added to the +official FITS Standard in December 2005. + FITS 64-bit images have BITPIX = +64, and the 64-bit binary table columns have TFORMn = 'K'. CFITSIO also +supports the 'Q' variable-length array table column format which is +analogous to the 'P' column format except that the array descriptor +is stored as a pair of 64-bit integers. + +For the convenience of C programmers, the fitsio.h include file +defines (with a typedef statement) the 'LONGLONG' datatype to be +equivalent to an appropriate 64-bit integer datatype on each platform. +Since there is currently no universal standard +for the name of the 64-bit integer datatype (it might be defined as +'long long', 'long', or '\_\_int64' depending on the platform) +C programmers may prefer to use the 'LONGLONG' datatype when +declaring or allocating 64-bit integer quantities when writing +code which needs to run on multiple platforms. +Note that CFITSIO will implicitly convert the datatype when reading +or writing FITS 64-bit integer images and columns with data arrays of +a different integer or floating point datatype, but there is an +increased risk of loss of numerical precision or +numerical overflow in this case. + +**B. Long String Keyword Values. + +The length of a standard FITS string keyword is limited to 68 +characters because it must fit entirely within a single FITS header +keyword record. In some instances it is necessary to encode strings +longer than this limit, so CFITSIO supports a local convention in which +the string value is continued over multiple keywords. This +continuation convention uses an ampersand character at the end of each +substring to indicate that it is continued on the next keyword, and the +continuation keywords all have the name CONTINUE without an equal sign +in column 9. The string value may be continued in this way over as many +additional CONTINUE keywords as is required. The following lines +illustrate this continuation convention which is used in the value of +the STRKEY keyword: +- +LONGSTRN= 'OGIP 1.0' / The OGIP Long String Convention may be used. +STRKEY = 'This is a very long string keyword&' / Optional Comment +CONTINUE ' value that is continued over 3 keywords in the & ' +CONTINUE 'FITS header.' / This is another optional comment. +- +It is recommended that the LONGSTRN keyword, as shown here, always be +included in any HDU that uses this longstring convention as a warning +to any software that must read the keywords. A routine called fits\_write\_key\_longwarn +has been provided in CFITSIO to write this keyword if it does not +already exist. + +This long string convention is supported by the following CFITSIO +routines: +- + fits_write_key_longstr - write a long string keyword value + fits_insert_key_longstr - insert a long string keyword value + fits_modify_key_longstr - modify a long string keyword value + fits_update_key_longstr - modify a long string keyword value + fits_read_key_longstr - read a long string keyword value + fits_delete_key - delete a keyword +- +The fits\_read\_key\_longstr routine is unique among all the CFITSIO +routines in that it internally allocates memory for the long string +value; all the other CFITSIO routines that deal with arrays require +that the calling program pre-allocate adequate space to hold the array +of data. Consequently, programs which use the fits\_read\_key\_longstr +routine must be careful to free the allocated memory for the string +when it is no longer needed. + +The following 2 routines also have limited support for this long string +convention, +- + fits_modify_key_str - modify an existing string keyword value + fits_update_key_str - update a string keyword value +- +in that they will correctly overwrite an existing long string value, +but the new string value is limited to a maximum of 68 characters in +length. + +The more commonly used CFITSIO routines to write string valued keywords +(fits\_update\_key and fits\_write\_key) do not support this long +string convention and only support strings up to 68 characters in +length. This has been done deliberately to prevent programs from +inadvertently writing keywords using this non-standard convention +without the explicit intent of the programmer or user. The +fits\_write\_key\_longstr routine must be called instead to write long +strings. This routine can also be used to write ordinary string values +less than 68 characters in length. + +**C. Arrays of Fixed-Length Strings in Binary Tables + +CFITSIO supports 2 ways to specify that a character column in a binary +table contains an array of fixed-length strings. The first way, which +is officially supported by the FITS Standard document, uses the TDIMn keyword. +For example, if TFORMn = '60A' and TDIMn = '(12,5)' then that +column will be interpreted as containing an array of 5 strings, each 12 +characters long. + +CFITSIO also supports a +local convention for the format of the TFORMn keyword value of the form +'rAw' where 'r' is an integer specifying the total width in characters +of the column, and 'w' is an integer specifying the (fixed) length of +an individual unit string within the vector. For example, TFORM1 = +'120A10' would indicate that the binary table column is 120 characters +wide and consists of 12 10-character length strings. This convention +is recognized by the CFITSIO routines that read or write strings in +binary tables. The Binary Table definition document specifies that +other optional characters may follow the data type code in the TFORM +keyword, so this local convention is in compliance with the +FITS standard although other FITS readers may not +recognize this convention. + +The Binary Table definition document that was approved by the IAU in +1994 contains an appendix describing an alternate convention for +specifying arrays of fixed or variable length strings in a binary table +character column (with the form 'rA:SSTRw/nnn)'. This appendix was not +officially voted on by the IAU and hence is still provisional. CFITSIO +does not currently support this proposal. + +**D. Keyword Units Strings + +One limitation of the current FITS Standard is that it does not define +a specific convention for recording the physical units of a keyword +value. The TUNITn keyword can be used to specify the physical units of +the values in a table column, but there is no analogous convention for +keyword values. The comment field of the keyword is often used for +this purpose, but the units are usually not specified in a well defined +format that FITS readers can easily recognize and extract. + +To solve this problem, CFITSIO uses a local convention in which the +keyword units are enclosed in square brackets as the first token in the +keyword comment field; more specifically, the opening square bracket +immediately follows the slash '/' comment field delimiter and a single +space character. The following examples illustrate keywords that use +this convention: + +- +EXPOSURE= 1800.0 / [s] elapsed exposure time +V_HELIO = 16.23 / [km s**(-1)] heliocentric velocity +LAMBDA = 5400. / [angstrom] central wavelength +FLUX = 4.9033487787637465E-30 / [J/cm**2/s] average flux +- + +In general, the units named in the IAU(1988) Style Guide are +recommended, with the main exception that the preferred unit for angle +is 'deg' for degrees. + +The fits\_read\_key\_unit and fits\_write\_key\_unit routines in +CFITSIO read and write, respectively, the keyword unit strings in an +existing keyword. + +**E. HIERARCH Convention for Extended Keyword Names + +CFITSIO supports the HIERARCH keyword convention which allows keyword +names that are longer then 8 characters and may contain the full range +of printable ASCII text characters. This convention +was developed at the European Southern Observatory (ESO) to support +hierarchical FITS keyword such as: +- +HIERARCH ESO INS FOCU POS = -0.00002500 / Focus position +- +Basically, this convention uses the FITS keyword 'HIERARCH' to indicate +that this convention is being used, then the actual keyword name +({\tt'ESO INS FOCU POS'} in this example) begins in column 10 and can +contain any printable ASCII text characters, including spaces. The +equals sign marks the end of the keyword name and is followed by the +usual value and comment fields just as in standard FITS keywords. +Further details of this convention are described at +http://arcdev.hq.eso.org/dicb/dicd/dic-1-1.4.html (search for +HIERARCH). + +This convention allows a much broader range of keyword names +than is allowed by the FITS Standard. Here are more examples +of such keywords: +- +HIERARCH LongKeyword = 47.5 / Keyword has > 8 characters, and mixed case +HIERARCH XTE$TEMP = 98.6 / Keyword contains the '$' character +HIERARCH Earth is a star = F / Keyword contains embedded spaces +- +CFITSIO will transparently read and write these keywords, so application +programs do not in general need to know anything about the specific +implementation details of the HIERARCH convention. In particular, +application programs do not need to specify the `HIERARCH' part of the +keyword name when reading or writing keywords (although it +may be included if desired). When writing a keyword, CFITSIO first +checks to see if the keyword name is legal as a standard FITS keyword +(no more than 8 characters long and containing only letters, digits, or +a minus sign or underscore). If so it writes it as a standard FITS +keyword, otherwise it uses the hierarch convention to write the +keyword. The maximum keyword name length is 67 characters, which +leaves only 1 space for the value field. A more practical limit is +about 40 characters, which leaves enough room for most keyword values. +CFITSIO returns an error if there is not enough room for both the +keyword name and the keyword value on the 80-character card, except for +string-valued keywords which are simply truncated so that the closing +quote character falls in column 80. In the current implementation, +CFITSIO preserves the case of the letters when writing the keyword +name, but it is case-insensitive when reading or searching for a +keyword. The current implementation allows any ASCII text character +(ASCII 32 to ASCII 126) in the keyword name except for the '=' +character. A space is also required on either side of the equal sign. + +**F. Tile-Compressed Image Format + +CFITSIO supports a convention for compressing n-dimensional images and +storing the resulting byte stream in a variable-length column in a FITS +binary table. The general principle used in this convention is to +first divide the n-dimensional image into a rectangular grid of +subimages or `tiles'. Each tile is then compressed as a continuous +block of data, and the resulting compressed byte stream is stored in a +row of a variable length column in a FITS binary table. By dividing the +image into tiles it is generally possible to extract and uncompress +subsections of the image without having to uncompress the whole image. +The default tiling pattern treats each row of a 2-dimensional image (or +higher dimensional cube) as a tile, such that each tile contains NAXIS1 +pixels (except the default with the HCOMPRESS algorithm is to +compress the whole 2D image as a single tile). Any other rectangular +tiling pattern may also be defined. In +the case of relatively small images it may be sufficient to compress +the entire image as a single tile, resulting in an output binary table +with 1 row. In the case of 3-dimensional data cubes, it may be +advantageous to treat each plane of the cube as a separate tile if +application software typically needs to access the cube on a plane by +plane basis. + +See section 5.6 ``Image Compression'' +for more information on using this tile-compressed image format. + +*XII. Optimizing Programs + +CFITSIO has been carefully designed to obtain the highest possible +speed when reading and writing FITS files. In order to achieve the +best performance, however, application programmers must be careful to +call the CFITSIO routines appropriately and in an efficient sequence; +inappropriate usage of CFITSIO routines can greatly slow down the +execution speed of a program. + +The maximum possible I/O speed of CFITSIO depends of course on the type +of computer system that it is running on. As a rough guide, the +current generation of workstations can achieve speeds of 2 -- 10 MB/s +when reading or writing FITS images and similar, or slightly slower +speeds with FITS binary tables. Reading of FITS files can occur at +even higher rates (30MB/s or more) if the FITS file is still cached in +system memory following a previous read or write operation on the same +file. To more accurately predict the best performance that is possible +on any particular system, a diagnostic program called ``speed.c'' is +included with the CFITSIO distribution which can be run to +approximately measure the maximum possible speed of writing and reading +a test FITS file. + +The following 2 sections provide some background on how CFITSIO +internally manages the data I/O and describes some strategies that may +be used to optimize the processing speed of software that uses +CFITSIO. + +**A. How CFITSIO Manages Data I/O + +Many CFITSIO operations involve transferring only a small number of +bytes to or from the FITS file (e.g, reading a keyword, or writing a +row in a table); it would be very inefficient to physically read or +write such small blocks of data directly in the FITS file on disk, +therefore CFITSIO maintains a set of internal Input--Output (IO) +buffers in RAM memory that each contain one FITS block (2880 bytes) of +data. Whenever CFITSIO needs to access data in the FITS file, it first +transfers the FITS block containing those bytes into one of the IO +buffers in memory. The next time CFITSIO needs to access bytes in the +same block it can then go to the fast IO buffer rather than using a +much slower system disk access routine. The number of available IO +buffers is determined by the NIOBUF parameter (in fitsio2.h) and is +currently set to 40 by default. + +Whenever CFITSIO reads or writes data it first checks to see if that +block of the FITS file is already loaded into one of the IO buffers. +If not, and if there is an empty IO buffer available, then it will load +that block into the IO buffer (when reading a FITS file) or will +initialize a new block (when writing to a FITS file). If all the IO +buffers are already full, it must decide which one to reuse (generally +the one that has been accessed least recently), and flush the contents +back to disk if it has been modified before loading the new block. + +The one major exception to the above process occurs whenever a large +contiguous set of bytes are accessed, as might occur when reading or +writing a FITS image. In this case CFITSIO bypasses the internal IO +buffers and simply reads or writes the desired bytes directly in the +disk file with a single call to a low-level file read or write +routine. The minimum threshold for the number of bytes to read or +write this way is set by the MINDIRECT parameter and is currently set +to 3 FITS blocks = 8640 bytes. This is the most efficient way to read +or write large chunks of data and can achieve IO transfer rates of +5 -- 10MB/s or greater. Note that this fast direct IO process is not +applicable when accessing columns of data in a FITS table because the +bytes are generally not contiguous since they are interleaved by the +other columns of data in the table. This explains why the speed for +accessing FITS tables is generally slower than accessing +FITS images. + +Given this background information, the general strategy for efficiently +accessing FITS files should be apparent: when dealing with FITS +images, read or write large chunks of data at a time so that the direct +IO mechanism will be invoked; when accessing FITS headers or FITS +tables, on the other hand, once a particular FITS block has been +loading into one of the IO buffers, try to access all the needed +information in that block before it gets flushed out of the IO buffer. +It is important to avoid the situation where the same FITS block is +being read then flushed from a IO buffer multiple times. + +The following section gives more specific suggestions for optimizing +the use of CFITSIO. + +**B. Optimization Strategies + +1. Because the data in FITS files is always stored in "big-endian" byte order, +where the first byte of numeric values contains the most significant bits and the +last byte contains the least significant bits, CFITSIO must swap the order of the bytes +when reading or writing FITS files when running on little-endian machines (e.g., +Linux and Microsoft Windows operating systems running on PCs with x86 CPUs). + +On fairly new CPUs that support "SSSE3" machine instructions +(e.g., starting with Intel Core 2 CPUs in 2007, and in AMD CPUs +beginning in 2011) significantly faster 4-byte and 8-byte swapping +algorithms are available. These faster byte swapping functions are +not used by default in CFITSIO (because of the potential code +portablility issues), but users can enable them on supported +platforms by adding the appropriate compiler flags (-mssse3 with gcc +or icc on linux) when compiling the swapproc.c source file, which will +allow the compiler to generate code using the SSSE3 instruction set. +A convenient way to do this is to configure the CFITSIO library +with the following command: +- + > ./configure --enable-ssse3 +- +Note, however, that a binary executable file that is +created using these faster functions will only run on +machines that support the SSSE3 machine instructions. It will +crash on machines that do not support them. + +For faster 2-byte swaps on virtually all x86-64 CPUs (even those that +do not support SSSE3), a variant using only SSE2 instructions exists. +SSE2 is enabled by default on x86\_64 CPUs with 64-bit operating systems +(and is also automatically enabled by the --enable-ssse3 flag). +When running on x86\_64 CPUs with 32-bit operating systems, these faster +2-byte swapping algorithms are not used by default in CFITSIO, but can be +enabled explicitly with: +- +./configure --enable-sse2 +- +Preliminary testing indicates that these SSSE3 and SSE2 based +byte-swapping algorithms can boost the CFITSIO performance when +reading or writing FITS images by 20\% - 30\% or more. +It is important to note, however, that compiler optimization must be +turned on (e.g., by using the -O1 or -O2 flags in gcc) when building +programs that use these fast byte-swapping algorithms in order +to reap the full benefit of the SSSE3 and SSE2 instructions; without +optimization, the code may actually run slower than when using +more traditional byte-swapping techniques. + +2. When dealing with a FITS primary array or IMAGE extension, it is +more efficient to read or write large chunks of the image at a time +(at least 3 FITS blocks = 8640 bytes) so that the direct IO mechanism +will be used as described in the previous section. Smaller chunks of +data are read or written via the IO buffers, which is somewhat less +efficient because of the extra copy operation and additional +bookkeeping steps that are required. In principle it is more efficient +to read or write as big an array of image pixels at one time as +possible, however, if the array becomes so large that the operating +system cannot store it all in RAM, then the performance may be degraded +because of the increased swapping of virtual memory to disk. + +3. When dealing with FITS tables, the most important efficiency factor +in the software design is to read or write the data in the FITS file in +a single pass through the file. An example of poor program design +would be to read a large, 3-column table by sequentially reading the +entire first column, then going back to read the 2nd column, and +finally the 3rd column; this obviously requires 3 passes through the +file which could triple the execution time of an IO limited program. +For small tables this is not important, but when reading multi-megabyte +sized tables these inefficiencies can become significant. The more +efficient procedure in this case is to read or write only as many rows +of the table as will fit into the available internal IO buffers, then +access all the necessary columns of data within that range of rows. +Then after the program is completely finished with the data in those +rows it can move on to the next range of rows that will fit in the +buffers, continuing in this way until the entire file has been +processed. By using this procedure of accessing all the columns of a +table in parallel rather than sequentially, each block of the FITS file +will only be read or written once. + +The optimal number of rows to read or write at one time in a given +table depends on the width of the table row and on the number of IO +buffers that have been allocated in CFITSIO. The CFITSIO Iterator routine +will automatically use the optimal-sized buffer, but there is also a +CFITSIO routine that will return the optimal number of rows for a given +table: fits\_get\_rowsize. It is not critical to use exactly the +value of nrows returned by this routine, as long as one does not exceed +it. Using a very small value however can also lead to poor performance +because of the overhead from the larger number of subroutine calls. + +The optimal number of rows returned by fits\_get\_rowsize is valid only +as long as the application program is only reading or writing data in +the specified table. Any other calls to access data in the table +header would cause additional blocks of data +to be loaded into the IO buffers displacing data from the original +table, and should be avoided during the critical period while the table +is being read or written. + +4. Use the CFITSIO Iterator routine. This routine provides a +more `object oriented' way of reading and writing FITS files +which automatically uses the most appropriate data buffer size +to achieve the maximum I/O throughput. + +5. Use binary table extensions rather than ASCII table +extensions for better efficiency when dealing with tabular data. The +I/O to ASCII tables is slower because of the overhead in formatting or +parsing the ASCII data fields and because ASCII tables are about twice +as large as binary tables that have the same information content. + +6. Design software so that it reads the FITS header keywords in the +same order in which they occur in the file. When reading keywords, +CFITSIO searches forward starting from the position of the last keyword +that was read. If it reaches the end of the header without finding the +keyword, it then goes back to the start of the header and continues the +search down to the position where it started. In practice, as long as +the entire FITS header can fit at one time in the available internal IO +buffers, then the header keyword access will be relatively fast and it makes +little difference which order they are accessed. + +7. Avoid the use of scaling (by using the BSCALE and BZERO or TSCAL and +TZERO keywords) in FITS files since the scaling operations add to the +processing time needed to read or write the data. In some cases it may +be more efficient to temporarily turn off the scaling (using fits\_set\_bscale or +fits\_set\_tscale) and then read or write the raw unscaled values in the FITS +file. + +8. Avoid using the `implicit data type conversion' capability in +CFITSIO. For instance, when reading a FITS image with BITPIX = -32 +(32-bit floating point pixels), read the data into a single precision +floating point data array in the program. Forcing CFITSIO to convert +the data to a different data type can slow the program. + +9. Where feasible, design FITS binary tables using vector column +elements so that the data are written as a contiguous set of bytes, +rather than as single elements in multiple rows. For example, it is +faster to access the data in a table that contains a single row +and 2 columns with TFORM keywords equal to '10000E' and '10000J', than +it is to access the same amount of data in a table with 10000 rows +which has columns with the TFORM keywords equal to '1E' and '1J'. In +the former case the 10000 floating point values in the first column are +all written in a contiguous block of the file which can be read or +written quickly, whereas in the second case each floating point value +in the first column is interleaved with the integer value in the second +column of the same row so CFITSIO has to explicitly move to the +position of each element to be read or written. + +10. Avoid the use of variable length vector columns in binary tables, +since any reading or writing of these data requires that CFITSIO first +look up or compute the starting address of each row of data in the +heap. In practice, this is probably not a significant efficiency issue. + +11. When copying data from one FITS table to another, it is faster to +transfer the raw bytes instead of reading then writing each column of +the table. The CFITSIO routines fits\_read\_tblbytes and +fits\_write\_tblbytes will perform low-level reads or writes of any +contiguous range of bytes in a table extension. These routines can be +used to read or write a whole row (or multiple rows for even greater +efficiency) of a table with a single function call. These routines +are fast because they bypass all the usual data scaling, error checking +and machine dependent data conversion that is normally done by CFITSIO, +and they allow the program to write the data to the output file in +exactly the same byte order. For these same reasons, these routines +can corrupt the FITS data file if used incorrectly because no +validation or machine dependent conversion is performed by these +routines. These routines are only recommended for optimizing critical +pieces of code and should only be used by programmers who thoroughly +understand the internal format of the FITS tables they are reading or +writing. + +12. Another strategy for improving the speed of writing a FITS table, +similar to the previous one, is to directly construct the entire byte +stream for a whole table row (or multiple rows) within the application +program and then write it to the FITS file with +fits\_write\_tblbytes. This avoids all the overhead normally present +in the column-oriented CFITSIO write routines. This technique should +only be used for critical applications because it makes the code more +difficult to understand and maintain, and it makes the code more system +dependent (e.g., do the bytes need to be swapped before writing to the +FITS file?). + +13. Finally, external factors such as the speed of the data storage device, +the size of the data cache, the amount of disk fragmentation, and the amount of +RAM available on the system can all have a significant impact on +overall I/O efficiency. For critical applications, the entire hardware +and software system should be reviewed to identify any +potential I/O bottlenecks. + + +\appendix +*1 Index of Routines +\begin{tabular}{lr} +fits\_add\_group\_member & \pageref{ffgtam} \\ +fits\_ascii\_tform & \pageref{ffasfm} \\ +fits\_binary\_tform & \pageref{ffbnfm} \\ +fits\_calculator & \pageref{ffcalc} \\ +fits\_calculator\_rng & \pageref{ffcalcrng} \\ +fits\_calc\_binning & \pageref{calcbinning} \\ +fits\_calc\_rows & \pageref{ffcrow} \\ +fits\_change\_group & \pageref{ffgtch} \\ +fits\_clear\_errmark & \pageref{ffpmrk} \\ +fits\_clear\_errmsg & \pageref{ffcmsg} \\ +fits\_close\_file & \pageref{ffclos} \\ +fits\_compact\_group & \pageref{ffgtcm} \\ +fits\_compare\_str & \pageref{ffcmps} \\ +fits\_compress\_heap & \pageref{ffcmph} \\ +fits\_convert\_hdr2str & \pageref{ffhdr2str}, \pageref{hdr2str} \\ +fits\_copy\_cell2image & \pageref{copycell} \\ +fits\_copy\_col & \pageref{ffcpcl} \\ +fits\_copy\_data & \pageref{ffcpdt} \\ +fits\_copy\_file & \pageref{ffcpfl} \\ +fits\_copy\_group & \pageref{ffgtcp} \\ +fits\_copy\_hdu & \pageref{ffcopy} \\ +fits\_copy\_header & \pageref{ffcphd} \\ +fits\_copy\_image2cell & \pageref{copycell} \\ +fits\_copy\_image\_section & \pageref{ffcpimg} \\ +fits\_copy\_key & \pageref{ffcpky} \\ +fits\_copy\_member & \pageref{ffgmcp} \\ +fits\_copy\_pixlist2image & \pageref{copypixlist2image} \\ +fits\_copy\_rows & \pageref{ffcprw} \\ +fits\_create\_diskfile & \pageref{ffinit} \\ +fits\_create\_file & \pageref{ffinit} \\ +fits\_create\_group & \pageref{ffgtcr} \\ +fits\_create\_hdu & \pageref{ffcrhd} \\ + +\end{tabular} +\begin{tabular}{lr} +fits\_create\_img & \pageref{ffcrim} \\ +fits\_create\_memfile & \pageref{ffimem} \\ +fits\_create\_tbl & \pageref{ffcrtb} \\ +fits\_create\_template & \pageref{fftplt} \\ +fits\_date2str & \pageref{ffdt2s} \\ +fits\_decode\_chksum & \pageref{ffdsum} \\ +fits\_decode\_tdim & \pageref{ffdtdm} \\ +fits\_delete\_col & \pageref{ffdcol} \\ +fits\_delete\_file & \pageref{ffdelt} \\ +fits\_delete\_hdu & \pageref{ffdhdu} \\ +fits\_delete\_key & \pageref{ffdkey} \\ +fits\_delete\_record & \pageref{ffdrec} \\ +fits\_delete\_rowlist & \pageref{ffdrws} \\ +fits\_delete\_rowrange & \pageref{ffdrrg} \\ +fits\_delete\_rows & \pageref{ffdrow} \\ +fits\_delete\_str & \pageref{ffdkey} \\ +fits\_encode\_chksum & \pageref{ffesum} \\ +fits\_file\_exists & \pageref{ffexist} \\ +fits\_file\_mode & \pageref{ffflmd} \\ +fits\_file\_name & \pageref{ffflnm} \\ +fits\_find\_first\_row & \pageref{ffffrw} \\ +fits\_find\_nextkey & \pageref{ffgnxk} \\ +fits\_find\_rows & \pageref{fffrow} \\ +fits\_flush\_buffer & \pageref{ffflus} \\ +fits\_flush\_file & \pageref{ffflus} \\ +fits\_free\_memory & \pageref{ffgkls} \\ +fits\_get\_acolparms & \pageref{ffgacl} \\ +fits\_get\_bcolparms & \pageref{ffgbcl} \\ +fits\_get\_chksum & \pageref{ffgcks} \\ +fits\_get\_col\_display\_width & \pageref{ffgcdw} \\ +fits\_get\_colname & \pageref{ffgcnn} \\ +fits\_get\_colnum & \pageref{ffgcno} \\ +\end{tabular} +\begin{tabular}{lr} +fits\_get\_coltype & \pageref{ffgtcl} \\ +fits\_get\_compression\_type & \pageref{ffgetcomp} \\ +fits\_get\_eqcoltype & \pageref{ffgtcl} \\ +fits\_get\_errstatus & \pageref{ffgerr} \\ +fits\_get\_hdrpos & \pageref{ffghps} \\ +fits\_get\_hdrspace & \pageref{ffghsp} \\ +fits\_get\_hdu\_num & \pageref{ffghdn} \\ +fits\_get\_hdu\_type & \pageref{ffghdt} \\ +fits\_get\_hduaddr & \pageref{ffghad} \\ +fits\_get\_hduaddrll & \pageref{ffghad} \\ +fits\_get\_img\_dim & \pageref{ffgidm} \\ +fits\_get\_img\_equivtype & \pageref{ffgidt} \\ +fits\_get\_img\_param & \pageref{ffgipr} \\ +fits\_get\_img\_size & \pageref{ffgisz} \\ +fits\_get\_img\_type & \pageref{ffgidt} \\ +fits\_get\_inttype & \pageref{ffinttyp} \\ +fits\_get\_keyclass & \pageref{ffgkcl} \\ +fits\_get\_keyname & \pageref{ffgknm} \\ +fits\_get\_keytype & \pageref{ffdtyp} \\ +fits\_get\_noise\_bits & \pageref{ffgetcomp} \\ +fits\_get\_num\_cols & \pageref{ffgnrw} \\ +fits\_get\_num\_groups & \pageref{ffgmng} \\ +fits\_get\_num\_hdus & \pageref{ffthdu} \\ +fits\_get\_num\_members & \pageref{ffgtnm} \\ +fits\_get\_num\_rows & \pageref{ffgnrw} \\ +fits\_get\_rowsize & \pageref{ffgrsz} \\ +fits\_get\_system\_time & \pageref{ffdt2s} \\ +fits\_get\_tile\_dim & \pageref{ffgetcomp} \\ +fits\_get\_tbcol & \pageref{ffgabc} \\ +fits\_get\_version & \pageref{ffvers} \\ +fits\_hdr2str & \pageref{ffhdr2str}, \pageref{hdr2str} \\ +fits\_insert\_atbl & \pageref{ffitab} \\ +\end{tabular} +\newpage +\begin{tabular}{lr} +fits\_insert\_btbl & \pageref{ffibin} \\ +fits\_insert\_col & \pageref{fficol} \\ +fits\_insert\_cols & \pageref{fficls} \\ +fits\_insert\_group & \pageref{ffgtis} \\ +fits\_insert\_img & \pageref{ffiimg} \\ +fits\_insert\_key\_null & \pageref{ffikyu} \\ +fits\_insert\_key\_TYP & \pageref{ffikyx} \\ +fits\_insert\_record & \pageref{ffirec} \\ +fits\_insert\_rows & \pageref{ffirow} \\ +fits\_is\_reentrant & \pageref{reentrant} \\ +fits\_iterate\_data & \pageref{ffiter} \\ +fits\_make\_hist & \pageref{makehist} \\ +fits\_make\_keyn & \pageref{ffkeyn} \\ +fits\_make\_nkey & \pageref{ffnkey} \\ +fits\_merge\_groups & \pageref{ffgtmg} \\ +fits\_modify\_card & \pageref{ffmcrd} \\ +fits\_modify\_comment & \pageref{ffmcom} \\ +fits\_modify\_key\_null & \pageref{ffmkyu} \\ +fits\_modify\_key\_TYP & \pageref{ffmkyx} \\ +fits\_modify\_name & \pageref{ffmnam} \\ +fits\_modify\_record & \pageref{ffmrec} \\ +fits\_modify\_vector\_len & \pageref{ffmvec} \\ +fits\_movabs\_hdu & \pageref{ffmahd} \\ +fits\_movnam\_hdu & \pageref{ffmnhd} \\ +fits\_movrel\_hdu & \pageref{ffmrhd} \\ +fits\_null\_check & \pageref{ffnchk} \\ +fits\_open\_data & \pageref{ffopen} \\ +fits\_open\_diskfile & \pageref{ffopen} \\ +fits\_open\_file & \pageref{ffopen} \\ +fits\_open\_image & \pageref{ffopen} \\ +fits\_open\_table & \pageref{ffopen} \\ +fits\_open\_group & \pageref{ffgtop} \\ +fits\_open\_member & \pageref{ffgmop} \\ +fits\_open\_memfile & \pageref{ffomem} \\ +fits\_parse\_extnum & \pageref{ffextn} \\ +fits\_parse\_input\_filename & \pageref{ffiurl} \\ +fits\_parse\_input\_url & \pageref{ffiurl} \\ +fits\_parse\_range & \pageref{ffrwrg} \\ +fits\_parse\_rootname & \pageref{ffrtnm} \\ +fits\_parse\_template & \pageref{ffgthd} \\ +fits\_parse\_value & \pageref{ffpsvc} \\ +fits\_pix\_to\_world & \pageref{ffwldp} \\ +fits\_read\_2d\_TYP & \pageref{ffg2dx} \\ +fits\_read\_3d\_TYP & \pageref{ffg3dx} \\ +fits\_read\_atblhdr & \pageref{ffghtb} \\ +fits\_read\_btblhdr & \pageref{ffghbn} \\ +fits\_read\_card & \pageref{ffgcrd} \\ +fits\_read\_col & \pageref{ffgcv} \\ +\end{tabular} +\begin{tabular}{lr} +fits\_read\_col\_bit\_ & \pageref{ffgcx} \\ +fits\_read\_col\_TYP & \pageref{ffgcvx} \\ +fits\_read\_colnull & \pageref{ffgcf} \\ +fits\_read\_colnull\_TYP & \pageref{ffgcfx} \\ +fits\_read\_descript & \pageref{ffgdes} \\ +fits\_read\_descripts & \pageref{ffgdes} \\ +fits\_read\_errmsg & \pageref{ffgmsg} \\ +fits\_read\_ext & \pageref{ffgextn} \\ +fits\_read\_grppar\_TYP & \pageref{ffggpx} \\ +fits\_read\_img & \pageref{ffgpv} \\ +fits\_read\_img\_coord & \pageref{ffgics} \\ +fits\_read\_img\_TYP & \pageref{ffgpvx} \\ +fits\_read\_imghdr & \pageref{ffghpr} \\ +fits\_read\_imgnull & \pageref{ffgpf} \\ +fits\_read\_imgnull\_TYP & \pageref{ffgpfx} \\ +fits\_read\_key & \pageref{ffgky} \\ +fits\_read\_key\_longstr & \pageref{ffgkls} \\ +fits\_read\_key\_triple & \pageref{ffgkyt} \\ +fits\_read\_key\_unit & \pageref{ffgunt} \\ +fits\_read\_key\_TYP & \pageref{ffgkyx} \\ +fits\_read\_keyn & \pageref{ffgkyn} \\ +fits\_read\_keys\_TYP & \pageref{ffgknx} \\ +fits\_read\_keyword & \pageref{ffgkey} \\ +fits\_read\_pix & \pageref{ffgpxv} \\ +fits\_read\_pixnull & \pageref{ffgpxf} \\ +fits\_read\_record & \pageref{ffgrec} \\ +fits\_read\_str & \pageref{ffgcrd} \\ +fits\_read\_subset\_TYP & \pageref{ffgsvx} \pageref{ffgsvx2}\\ +fits\_read\_subsetnull\_TYP & \pageref{ffgsfx} \pageref{ffgsfx2} \\ +fits\_read\_tbl\_coord & \pageref{ffgtcs} \\ +fits\_read\_tblbytes & \pageref{ffgtbb} \\ +fits\_read\_tdim & \pageref{ffgtdm} \\ +fits\_read\_wcstab & \pageref{wcstab} \\ +fits\_rebin\_wcs & \pageref{rebinwcs} \\ +fits\_remove\_group & \pageref{ffgtrm} \\ +fits\_remove\_member & \pageref{ffgmrm} \\ +fits\_reopen\_file & \pageref{ffreopen} \\ +fits\_report\_error & \pageref{ffrprt} \\ +fits\_resize\_img & \pageref{ffrsim} \\ +fits\_rms\_float & \pageref{imageRMS} \\ +fits\_rms\_short & \pageref{imageRMS} \\ +fits\_select\_rows & \pageref{ffsrow} \\ +fits\_set\_atblnull & \pageref{ffsnul} \\ +fits\_set\_bscale & \pageref{ffpscl} \\ +fits\_set\_btblnull & \pageref{fftnul} \\ +fits\_set\_compression\_type & \pageref{ffsetcomp} \\ +fits\_set\_hdrsize & \pageref{ffhdef} \\ +fits\_set\_hdustruc & \pageref{ffrdef} \\ +\end{tabular} +\begin{tabular}{lr} +fits\_set\_imgnull & \pageref{ffpnul} \\ +fits\_set\_noise\_bits & \pageref{ffsetcomp} \\ +fits\_set\_tile\_dim & \pageref{ffsetcomp} \\ +fits\_set\_tscale & \pageref{fftscl} \\ +fits\_split\_names & \pageref{splitnames} \\ +fits\_str2date & \pageref{ffdt2s} \\ +fits\_str2time & \pageref{ffdt2s} \\ +fits\_test\_expr & \pageref{fftexp} \\ +fits\_test\_heap & \pageref{fftheap} \\ +fits\_test\_keyword & \pageref{fftkey} \\ +fits\_test\_record & \pageref{fftrec} \\ +fits\_time2str & \pageref{ffdt2s} \\ +fits\_transfer\_member & \pageref{ffgmtf} \\ +fits\_translate\_keyword & \pageref{translatekey} \\ +fits\_update\_card & \pageref{ffucrd} \\ +fits\_update\_chksum & \pageref{ffupck} \\ +fits\_update\_key & \pageref{ffuky} \\ +fits\_update\_key\_longstr & \pageref{ffukyx} \\ +fits\_update\_key\_null & \pageref{ffukyu} \\ +fits\_update\_key\_TYP & \pageref{ffukyx} \\ +fits\_uppercase & \pageref{ffupch} \\ +fits\_url\_type & \pageref{ffurlt} \\ +fits\_verify\_chksum & \pageref{ffvcks} \\ +fits\_verify\_group & \pageref{ffgtvf} \\ +fits\_world\_to\_pix & \pageref{ffxypx} \\ +fits\_write\_2d\_TYP & \pageref{ffp2dx} \\ +fits\_write\_3d\_TYP & \pageref{ffp3dx} \\ +fits\_write\_atblhdr & \pageref{ffphtb} \\ +fits\_write\_btblhdr & \pageref{ffphbn} \\ +fits\_write\_chksum & \pageref{ffpcks} \\ +fits\_write\_col & \pageref{ffpcl} \\ +fits\_write\_col\_bit & \pageref{ffpclx} \\ +fits\_write\_col\_TYP & \pageref{ffpcls} \\ +fits\_write\_col\_null & \pageref{ffpclu} \\ +fits\_write\_colnull & \pageref{ffpcn} \\ +fits\_write\_colnull\_TYP & \pageref{ffpcnx} \\ +fits\_write\_comment & \pageref{ffpcom} \\ +fits\_write\_date & \pageref{ffpdat} \\ +fits\_write\_descript & \pageref{ffpdes} \\ +fits\_write\_errmark & \pageref{ffpmrk} \\ +fits\_write\_errmsg & \pageref{ffpmsg} \\ +fits\_write\_ext & \pageref{ffgextn} \\ +fits\_write\_exthdr & \pageref{ffphps} \\ +fits\_write\_grphdr & \pageref{ffphpr} \\ +fits\_write\_grppar\_TYP & \pageref{ffpgpx} \\ +fits\_write\_hdu & \pageref{ffwrhdu} \\ +fits\_write\_history & \pageref{ffphis} \\ +fits\_write\_img & \pageref{ffppr} \\ +\end{tabular} +\newpage +\begin{tabular}{lr} +fits\_write\_img\_null & \pageref{ffppru} \\ +fits\_write\_img\_TYP & \pageref{ffpprx} \\ +fits\_write\_imghdr & \pageref{ffphps} \\ +fits\_write\_imgnull & \pageref{ffppn} \\ +fits\_write\_imgnull\_TYP & \pageref{ffppnx} \\ +fits\_write\_key & \pageref{ffpky} \\ +fits\_write\_key\_longstr & \pageref{ffpkls} \\ +fits\_write\_key\_longwarn & \pageref{ffplsw} \\ +fits\_write\_key\_null & \pageref{ffpkyu} \\ +fits\_write\_key\_template & \pageref{ffpktp} \\ +fits\_write\_key\_triple & \pageref{ffpkyt} \\ +fits\_write\_key\_unit & \pageref{ffpunt} \\ +fits\_write\_key\_TYP & \pageref{ffpkyx} \\ +fits\_write\_keys\_TYP & \pageref{ffpknx} \\ +fits\_write\_keys\_histo & \pageref{writekeyshisto} \\ +fits\_write\_null\_img & \pageref{ffpprn} \\ +fits\_write\_nullrows & \pageref{ffpclu} \\ +fits\_write\_pix & \pageref{ffppx} \\ +fits\_write\_pixnull & \pageref{ffppxn} \\ +fits\_write\_record & \pageref{ffprec} \\ +fits\_write\_subset & \pageref{ffpss} \\ +fits\_write\_subset\_TYP & \pageref{ffpssx} \\ +fits\_write\_tblbytes & \pageref{ffptbb} \\ +fits\_write\_tdim & \pageref{ffptdm} \\ +fits\_write\_theap & \pageref{ffpthp} \\ +\end{tabular} +\newpage +\begin{tabular}{lr} +ffasfm & \pageref{ffasfm} \\ +ffbnfm & \pageref{ffbnfm} \\ +ffcalc & \pageref{ffcalc} \\ +ffcalc\_rng & \pageref{ffcalcrng} \\ +ffclos & \pageref{ffclos} \\ +ffcmph & \pageref{ffcmph} \\ +ffcmps & \pageref{ffcmps} \\ +ffcmrk & \pageref{ffpmrk} \\ +ffcmsg & \pageref{ffcmsg} \\ +ffcopy & \pageref{ffcopy} \\ +ffcpcl & \pageref{ffcpcl} \\ +ffcpdt & \pageref{ffcpdt} \\ +ffcpfl & \pageref{ffcpfl} \\ +ffcphd & \pageref{ffcphd} \\ +ffcpimg & \pageref{ffcpimg} \\ +ffcpky & \pageref{ffcpky} \\ +ffcprw & \pageref{ffcprw} \\ +ffcrhd & \pageref{ffcrhd} \\ +ffcrim & \pageref{ffcrim} \\ +ffcrow & \pageref{ffcrow} \\ +ffcrtb & \pageref{ffcrtb} \\ +ffdcol & \pageref{ffdcol} \\ +ffdelt & \pageref{ffdelt} \\ +ffdhdu & \pageref{ffdhdu} \\ +ffdkey & \pageref{ffdkey} \\ +ffdkinit & \pageref{ffinit} \\ +ffdkopen & \pageref{ffopen} \\ +ffdopn & \pageref{ffopen} \\ +ffdrec & \pageref{ffdrec} \\ +ffdrow & \pageref{ffdrow} \\ +ffdrrg & \pageref{ffdrrg} \\ +ffdrws & \pageref{ffdrws} \\ +ffdstr & \pageref{ffdkey} \\ +ffdsum & \pageref{ffdsum} \\ +ffdt2s & \pageref{ffdt2s} \\ +ffdtdm & \pageref{ffdtdm} \\ +ffdtyp & \pageref{ffdtyp} \\ +ffeqty & \pageref{ffgtcl} \\ +ffesum & \pageref{ffesum} \\ +ffexest & \pageref{ffexist} \\ +ffextn & \pageref{ffextn} \\ +ffffrw & \pageref{ffffrw} \\ +ffflmd & \pageref{ffflmd} \\ +ffflnm & \pageref{ffflnm} \\ +ffflsh & \pageref{ffflus} \\ +ffflus & \pageref{ffflus} \\ +fffree & \pageref{ffgkls} \\ +fffrow & \pageref{fffrow} \\ +\end{tabular} +\begin{tabular}{lr} +ffg2d\_ & \pageref{ffg2dx} \\ +ffg3d\_ & \pageref{ffg3dx} \\ +ffgabc & \pageref{ffgabc} \\ +ffgacl & \pageref{ffgacl} \\ +ffgbcl & \pageref{ffgbcl} \\ +ffgcdw & \pageref{ffgcdw} \\ +ffgcf & \pageref{ffgcf} \\ +ffgcf\_ & \pageref{ffgcfx} \\ +ffgcks & \pageref{ffgcks} \\ +ffgcnn & \pageref{ffgcnn} \\ +ffgcno & \pageref{ffgcno} \\ +ffgcrd & \pageref{ffgcrd} \\ +ffgcv & \pageref{ffgcv} \\ +ffgcv\_ & \pageref{ffgcvx} \\ +ffgcx & \pageref{ffgcx} \\ +ffgdes & \pageref{ffgdes} \\ +ffgdess & \pageref{ffgdes} \\ +ffgerr & \pageref{ffgerr} \\ +ffgextn & \pageref{ffgextn} \\ +ffggp\_ & \pageref{ffggpx} \\ +ffghad & \pageref{ffghad} \\ +ffghbn & \pageref{ffghbn} \\ +ffghdn & \pageref{ffghdn} \\ +ffghdt & \pageref{ffghdt} \\ +ffghpr & \pageref{ffghpr} \\ +ffghps & \pageref{ffghps} \\ +ffghsp & \pageref{ffghsp} \\ +ffghtb & \pageref{ffghtb} \\ +ffgics & \pageref{ffgics} \\ +ffgidm & \pageref{ffgidm} \\ +ffgidt & \pageref{ffgidt} \\ +ffgiet & \pageref{ffgidt} \\ +ffgipr & \pageref{ffgipr} \\ +ffgisz & \pageref{ffgisz} \\ +ffgkcl & \pageref{ffgkcl} \\ +ffgkey & \pageref{ffgkey} \\ +ffgkls & \pageref{ffgkls} \\ +ffgkn\_ & \pageref{ffgknx} \\ +ffgknm & \pageref{ffgknm} \\ +ffgky & \pageref{ffgky} \\ +ffgkyn & \pageref{ffgkyn} \\ +ffgkyt & \pageref{ffgkyt} \\ +ffgky\_ & \pageref{ffgkyx} \\ +ffgmcp & \pageref{ffgmcp} \\ +ffgmng & \pageref{ffgmng} \\ +ffgmop & \pageref{ffgmop} \\ +ffgmrm & \pageref{ffgmrm} \\ +ffgmsg & \pageref{ffgmsg} \\ + +\end{tabular} +\begin{tabular}{lr} +ffgmtf & \pageref{ffgmtf} \\ +ffgncl & \pageref{ffgnrw} \\ +ffgnrw & \pageref{ffgnrw} \\ +ffgnxk & \pageref{ffgnxk} \\ +ffgpf & \pageref{ffgpf} \\ +ffgpf\_ & \pageref{ffgpfx} \\ +ffgpv & \pageref{ffgpv} \\ +ffgpv\_ & \pageref{ffgpvx} \\ +ffgpxv & \pageref{ffgpxv} \\ +ffgpxf & \pageref{ffgpxf} \\ +ffgrec & \pageref{ffgrec} \\ +ffgrsz & \pageref{ffgrsz} \\ +ffgsdt & \pageref{ffdt2s} \\ +ffgsf\_ & \pageref{ffgsfx} \pageref{ffgsfx2} \\ +ffgstm & \pageref{ffdt2s} \\ +ffgstr & \pageref{ffgcrd} \\ +ffgsv\_ & \pageref{ffgsvx} \pageref{ffgsvx2}\\ +ffgtam & \pageref{ffgtam} \\ +ffgtbb & \pageref{ffgtbb} \\ +ffgtch & \pageref{ffgtch} \\ +ffgtcl & \pageref{ffgtcl} \\ +ffgtcm & \pageref{ffgtcm} \\ +ffgtcp & \pageref{ffgtcp} \\ +ffgtcr & \pageref{ffgtcr} \\ +ffgtcs & \pageref{ffgtcs} \\ +ffgtdm & \pageref{ffgtdm} \\ +ffgthd & \pageref{ffgthd} \\ +ffgtis & \pageref{ffgtis} \\ +ffgtmg & \pageref{ffgtmg} \\ +ffgtnm & \pageref{ffgtnm} \\ +ffgtop & \pageref{ffgtop} \\ +ffgtrm & \pageref{ffgtrm} \\ +ffgtvf & \pageref{ffgtvf} \\ +ffgunt & \pageref{ffgunt} \\ +ffhdef & \pageref{ffhdef} \\ +ffibin & \pageref{ffibin} \\ +fficls & \pageref{fficls} \\ +fficol & \pageref{fficol} \\ +ffifile & \pageref{ffiurl} \\ +ffiimg & \pageref{ffiimg} \\ +ffikls & \pageref{ffikyx} \\ +ffikyu & \pageref{ffikyu} \\ +ffiky\_ & \pageref{ffikyx} \\ +ffimem & \pageref{ffimem} \\ +ffinit & \pageref{ffinit} \\ +ffinttyp & \pageref{ffinttyp} \\ +ffiopn & \pageref{ffopen} \\ +ffirec & \pageref{ffirec} \\ + +\end{tabular} +\begin{tabular}{lr} + +ffirow & \pageref{ffirow} \\ +ffitab & \pageref{ffitab} \\ +ffiter & \pageref{ffiter} \\ +ffiurl & \pageref{ffiurl} \\ +ffkeyn & \pageref{ffkeyn} \\ +ffmahd & \pageref{ffmahd} \\ +ffmcom & \pageref{ffmcom} \\ +ffmcrd & \pageref{ffmcrd} \\ +ffmkls & \pageref{ffmkyx} \\ +ffmkyu & \pageref{ffmkyu} \\ +ffmky\_ & \pageref{ffmkyx} \\ +ffmnam & \pageref{ffmnam} \\ +ffmnhd & \pageref{ffmnhd} \\ +ffmrec & \pageref{ffmrec} \\ +ffmrhd & \pageref{ffmrhd} \\ +ffmvec & \pageref{ffmvec} \\ +ffnchk & \pageref{ffnchk} \\ +ffnkey & \pageref{ffnkey} \\ +ffomem & \pageref{ffomem} \\ +ffopen & \pageref{ffopen} \\ +ffp2d\_ & \pageref{ffp2dx} \\ +ffp3d\_ & \pageref{ffp3dx} \\ +ffpcks & \pageref{ffpcks} \\ +ffpcl & \pageref{ffpcl} \\ +ffpcls & \pageref{ffpcls} \\ +ffpcl\_ & \pageref{ffpclx} \\ +ffpclu & \pageref{ffpclu} \\ +ffpcn & \pageref{ffpcn} \\ +ffpcn\_ & \pageref{ffpcnx} \\ +ffpcom & \pageref{ffpcom} \\ +ffpdat & \pageref{ffpdat} \\ +ffpdes & \pageref{ffpdes} \\ +ffpextn & \pageref{ffgextn} \\ +ffpgp\_ & \pageref{ffpgpx} \\ +ffphbn & \pageref{ffphbn} \\ +ffphext & \pageref{ffphpr} \\ +ffphis & \pageref{ffphis} \\ +ffphpr & \pageref{ffphpr} \\ +ffphps & \pageref{ffphps} \\ +ffphtb & \pageref{ffphtb} \\ +ffpkls & \pageref{ffpkls} \\ +ffpkn\_ & \pageref{ffpknx} \\ +ffpktp & \pageref{ffpktp} \\ +ffpky & \pageref{ffpky} \\ +ffpkyt & \pageref{ffpkyt} \\ +ffpkyu & \pageref{ffpkyu} \\ +ffpky\_ & \pageref{ffpkyx} \\ +ffplsw & \pageref{ffplsw} \\ + +\end{tabular} +\begin{tabular}{lr} + +ffpmrk & \pageref{ffpmrk} \\ +ffpmsg & \pageref{ffpmsg} \\ +ffpnul & \pageref{ffpnul} \\ +ffppn & \pageref{ffppn} \\ +ffppn\_ & \pageref{ffppnx} \\ +ffppr & \pageref{ffppr} \\ +ffpprn & \pageref{ffpprn} \\ +ffppru & \pageref{ffppru} \\ +ffppr\_ & \pageref{ffpprx} \\ +ffppx & \pageref{ffppx} \\ +ffppxn & \pageref{ffppxn} \\ +ffprec & \pageref{ffprec} \\ +ffprwu & \pageref{ffpclu} \\ +ffpscl & \pageref{ffpscl} \\ +ffpss & \pageref{ffpss} \\ +ffpss\_ & \pageref{ffpssx} \\ +ffpsvc & \pageref{ffpsvc} \\ +ffptbb & \pageref{ffptbb} \\ +ffptdm & \pageref{ffptdm} \\ +ffpthp & \pageref{ffpthp} \\ +ffpunt & \pageref{ffpunt} \\ +ffrdef & \pageref{ffrdef} \\ +ffreopen & \pageref{ffreopen} \\ +ffrprt & \pageref{ffrprt} \\ +ffrsim & \pageref{ffrsim} \\ +ffrtnm & \pageref{ffrtnm} \\ +ffrwrg & \pageref{ffrwrg} \\ +ffs2dt & \pageref{ffdt2s} \\ +ffs2tm & \pageref{ffdt2s} \\ +ffsnul & \pageref{ffsnul} \\ +ffsrow & \pageref{ffsrow} \\ +fftexp & \pageref{fftexp} \\ +ffthdu & \pageref{ffthdu} \\ +fftheap & \pageref{fftheap} \\ +fftkey & \pageref{fftkey} \\ +fftm2s & \pageref{ffdt2s} \\ +fftnul & \pageref{fftnul} \\ +fftopn & \pageref{ffopen} \\ +fftplt & \pageref{fftplt} \\ +fftrec & \pageref{fftrec} \\ +fftscl & \pageref{fftscl} \\ +ffucrd & \pageref{ffucrd} \\ +ffukls & \pageref{ffukyx} \\ +ffuky & \pageref{ffuky} \\ +ffukyu & \pageref{ffukyu} \\ +ffuky\_ & \pageref{ffukyx} \\ +ffupch & \pageref{ffupch} \\ +ffupck & \pageref{ffupck} \\ + +\end{tabular} +\newpage +\begin{tabular}{lr} + +ffurlt & \pageref{ffurlt} \\ +ffvcks & \pageref{ffvcks} \\ +ffvers & \pageref{ffvers} \\ +ffwldp & \pageref{ffwldp} \\ +ffwrhdu & \pageref{ffwrhdu} \\ +ffxypx & \pageref{ffxypx} \\ + +\end{tabular} + + +*2 Parameter Definitions +- +anynul - set to TRUE (=1) if any returned values are undefined, else FALSE +array - array of numerical data values to read or write +ascii - encoded checksum string +binspec - the input table binning specifier +bitpix - bits per pixel. The following symbolic mnemonics are predefined: + BYTE_IMG = 8 (unsigned char) + SHORT_IMG = 16 (signed short integer) + LONG_IMG = 32 (signed long integer) + LONGLONG_IMG = 64 (signed long 64-bit integer) + FLOAT_IMG = -32 (float) + DOUBLE_IMG = -64 (double). + The LONGLONG_IMG type is experimental and is not officially + recognized in the FITS Standard document. + Two additional values, USHORT_IMG and ULONG_IMG are also available + for creating unsigned integer images. These are equivalent to + creating a signed integer image with BZERO offset keyword values + of 32768 or 2147483648, respectively, which is the convention that + FITS uses to store unsigned integers. +card - header record to be read or written (80 char max, null-terminated) +casesen - CASESEN (=1) for case-sensitive string matching, else CASEINSEN (=0) +cmopt - grouping table "compact" option parameter. Allowed values are: + OPT_CMT_MBR and OPT_CMT_MBR_DEL. +colname - name of the column (null-terminated) +colnum - column number (first column = 1) +colspec - the input file column specification; used to delete, create, or rename + table columns +comment - the keyword comment field (72 char max, null-terminated) +complm - should the checksum be complemented? +comptype - compression algorithm to use: GZIP_1, RICE_1, HCOMPRESS_1, or PLIO_1 +coordtype- type of coordinate projection (-SIN, -TAN, -ARC, -NCP, + -GLS, -MER, or -AIT) +cpopt - grouping table copy option parameter. Allowed values are: + OPT_GCP_GPT, OPT_GCP_MBR, OPT_GCP_ALL, OPT_MCP_ADD, OPT_MCP_NADD, + OPT_MCP_REPL, amd OPT_MCP_MOV. +create_col- If TRUE, then insert a new column in the table, otherwise + overwrite the existing column. +current - if TRUE, then the current HDU will be copied +dataok - was the data unit verification successful (=1) or + not (= -1). Equals zero if the DATASUM keyword is not present. +datasum - 32-bit 1's complement checksum for the data unit +dataend - address (in bytes) of the end of the HDU +datastart- address (in bytes) of the start of the data unit +datatype - specifies the data type of the value. Allowed value are: TSTRING, + TLOGICAL, TBYTE, TSBYTE, TSHORT, TUSHORT, TINT, TUINT, TLONG, TULONG, + TFLOAT, TDOUBLE, TCOMPLEX, and TDBLCOMPLEX +datestr - FITS date/time string: 'YYYY-MM-DDThh:mm:ss.ddd', 'YYYY-MM-dd', + or 'dd/mm/yy' +day - calendar day (UTC) (1-31) +decimals - number of decimal places to be displayed +deltasize - increment for allocating more memory +dim1 - declared size of the first dimension of the image or cube array +dim2 - declared size of the second dimension of the data cube array +dispwidth - display width of a column = length of string that will be read +dtype - data type of the keyword ('C', 'L', 'I', 'F' or 'X') + C = character string + L = logical + I = integer + F = floating point number + X = complex, e.g., "(1.23, -4.56)" +err_msg - error message on the internal stack (80 chars max) +err_text - error message string corresponding to error number (30 chars max) +exact - TRUE (=1) if the strings match exactly; + FALSE (=0) if wildcards are used +exclist - array of pointers to keyword names to be excluded from search +exists - flag indicating whether the file or compressed file exists on disk +expr - boolean or arithmetic expression +extend - TRUE (=1) if FITS file may have extensions, else FALSE (=0) +extname - value of the EXTNAME keyword (null-terminated) +extspec - the extension or HDU specifier; a number or name, version, and type +extver - value of the EXTVER keyword = integer version number +filename - full name of the FITS file, including optional HDU and filtering specs +filetype - type of file (file://, ftp://, http://, etc.) +filter - the input file filtering specifier +firstchar- starting byte in the row (first byte of row = 1) +firstfailed - member HDU ID (if positive) or grouping table GRPIDn index + value (if negative) that failed grouping table verification. +firstelem- first element in a vector (ignored for ASCII tables) +firstrow - starting row number (first row of table = 1) +following- if TRUE, any HDUs following the current HDU will be copied +fpixel - coordinate of the first pixel to be read or written in the + FITS array. The array must be of length NAXIS and have values such + that fpixel[0] is in the range 1 to NAXIS1, fpixel[1] is in the + range 1 to NAXIS2, etc. +fptr - pointer to a 'fitsfile' structure describing the FITS file. +frac - factional part of the keyword value +gcount - number of groups in the primary array (usually = 1) +gfptr - fitsfile* pointer to a grouping table HDU. +group - GRPIDn/GRPLCn index value identifying a grouping table HDU, or + data group number (=0 for non-grouped data) +grouptype - Grouping table parameter that specifies the columns to be + created in a grouping table HDU. Allowed values are: GT_ID_ALL_URI, + GT_ID_REF, GT_ID_POS, GT_ID_ALL, GT_ID_REF_URI, and GT_ID_POS_URI. +grpname - value to use for the GRPNAME keyword value. +hdunum - sequence number of the HDU (Primary array = 1) +hduok - was the HDU verification successful (=1) or + not (= -1). Equals zero if the CHECKSUM keyword is not present. +hdusum - 32 bit 1's complement checksum for the entire CHDU +hdutype - HDU type: IMAGE_HDU (0), ASCII_TBL (1), BINARY_TBL (2), ANY_HDU (-1) +header - returned character string containing all the keyword records +headstart- starting address (in bytes) of the CHDU +heapsize - size of the binary table heap, in bytes +history - the HISTORY keyword comment string (70 char max, null-terminated) +hour - hour within day (UTC) (0 - 23) +inc - sampling interval for pixels in each FITS dimension +inclist - array of pointers to matching keyword names +incolnum - input column number; range = 1 to TFIELDS +infile - the input filename, including path if specified +infptr - pointer to a 'fitsfile' structure describing the input FITS file. +intval - integer part of the keyword value +iomode - file access mode: either READONLY (=0) or READWRITE (=1) +keyname - name of a keyword (8 char max, null-terminated) +keynum - position of keyword in header (1st keyword = 1) +keyroot - root string for the keyword name (5 char max, null-terminated) +keysexist- number of existing keyword records in the CHU +keytype - header record type: -1=delete; 0=append or replace; + 1=append; 2=this is the END keyword +longstr - arbitrarily long string keyword value (null-terminated) +lpixel - coordinate of the last pixel to be read or written in the + FITS array. The array must be of length NAXIS and have values such + that lpixel[0] is in the range 1 to NAXIS1, lpixel[1] is in the + range 1 to NAXIS2, etc. +match - TRUE (=1) if the 2 strings match, else FALSE (=0) +maxdim - maximum number of values to return +member - row number of a grouping table member HDU. +memptr - pointer to the a FITS file in memory +mem_realloc - pointer to a function for reallocating more memory +memsize - size of the memory block allocated for the FITS file +mfptr - fitsfile* pointer to a grouping table member HDU. +mgopt - grouping table merge option parameter. Allowed values are: + OPT_MRG_COPY, and OPT_MRG_MOV. +minute - minute within hour (UTC) (0 - 59) +month - calendar month (UTC) (1 - 12) +morekeys - space in the header for this many more keywords +n_good_rows - number of rows evaluating to TRUE +namelist - string containing a comma or space delimited list of names +naxes - size of each dimension in the FITS array +naxis - number of dimensions in the FITS array +naxis1 - length of the X/first axis of the FITS array +naxis2 - length of the Y/second axis of the FITS array +naxis3 - length of the Z/third axis of the FITS array +nbytes - number of bytes or characters to read or write +nchars - number of characters to read or write +nelements- number of data elements to read or write +newfptr - returned pointer to the reopened file +newveclen- new value for the column vector repeat parameter +nexc - number of names in the exclusion list (may = 0) +nfound - number of keywords found (highest keyword number) +nkeys - number of keywords in the sequence +ninc - number of names in the inclusion list +nmembers - Number of grouping table members (NAXIS2 value). +nmove - number of HDUs to move (+ or -), relative to current position +nocomments - if equal to TRUE, then no commentary keywords will be copied +noisebits- number of bits to ignore when compressing floating point images +nrows - number of rows in the table +nstart - first integer value +nullarray- set to TRUE (=1) if corresponding data element is undefined +nulval - numerical value to represent undefined pixels +nulstr - character string used to represent undefined values in ASCII table +numval - numerical data value, of the appropriate data type +offset - byte offset in the heap or data unit to the first element of the vector +openfptr - pointer to a currently open FITS file +overlap - number of bytes in the binary table heap pointed to by more than 1 + descriptor +outcolnum- output column number; range = 1 to TFIELDS + 1 +outfile - and optional output filename; the input file will be copied to this prior + to opening the file +outfptr - pointer to a 'fitsfile' structure describing the output FITS file. +pcount - value of the PCOUNT keyword = size of binary table heap +previous - if TRUE, any previous HDUs in the input file will be copied. +repeat - length of column vector (e.g. 12J); == 1 for ASCII table +rmopt - grouping table remove option parameter. Allowed values are: + OPT_RM_GPT, OPT_RM_ENTRY, OPT_RM_MBR, and OPT_RM_ALL. +rootname - root filename, minus any extension or filtering specifications +rot - celestial coordinate rotation angle (degrees) +rowlen - length of a table row, in characters or bytes +rowlist - sorted list of row numbers to be deleted from the table +rownum - number of the row (first row = 1) +rowrange - list of rows or row ranges: '3,6-8,12,56-80' or '500-' +row_status - array of True/False results for each row that was evaluated +scale - linear scaling factor; true value = (FITS value) * scale + zero +second - second within minute (0 - 60.9999999999) (leap second!) +section - section of image to be copied (e.g. 21:80,101:200) +simple - TRUE (=1) if FITS file conforms to the Standard, else FALSE (=0) +space - number of blank spaces to leave between ASCII table columns +status - returned error status code (0 = OK) +sum - 32 bit unsigned checksum value +tbcol - byte position in row to start of column (1st col has tbcol = 1) +tdisp - Fortran style display format for the table column +tdimstr - the value of the TDIMn keyword +templt - template string used in comparison (null-terminated) +tfields - number of fields (columns) in the table +tfopt - grouping table member transfer option parameter. Allowed values are: + OPT_MCP_ADD, and OPT_MCP_MOV. +tform - format of the column (null-terminated); allowed values are: + ASCII tables: Iw, Aw, Fww.dd, Eww.dd, or Dww.dd + Binary tables: rL, rX, rB, rI, rJ, rA, rAw, rE, rD, rC, rM + where 'w'=width of the field, 'd'=no. of decimals, 'r'=repeat count. + Variable length array columns are denoted by a '1P' before the data type + character (e.g., '1PJ'). When creating a binary table, 2 addition tform + data type codes are recognized by CFITSIO: 'rU' and 'rV' for unsigned + 16-bit and unsigned 32-bit integer, respectively. + +theap - zero indexed byte offset of starting address of the heap + relative to the beginning of the binary table data +tilesize - array of length NAXIS that specifies the dimensions of + the image compression tiles +ttype - label or name for table column (null-terminated) +tunit - physical unit for table column (null-terminated) +typechar - symbolic code of the table column data type +typecode - data type code of the table column. The negative of + the value indicates a variable length array column. + Datatype typecode Mnemonic + bit, X 1 TBIT + byte, B 11 TBYTE + logical, L 14 TLOGICAL + ASCII character, A 16 TSTRING + short integer, I 21 TSHORT + integer, J 41 TINT32BIT (same as TLONG) + long long integer, K 81 TLONGLONG + real, E 42 TFLOAT + double precision, D 82 TDOUBLE + complex, C 83 TCOMPLEX + double complex, M 163 TDBLCOMPLEX +unit - the physical unit string (e.g., 'km/s') for a keyword +unused - number of unused bytes in the binary table heap +urltype - the file type of the FITS file (file://, ftp://, mem://, etc.) +validheap- returned value = FALSE if any of the variable length array + address are outside the valid range of addresses in the heap +value - the keyword value string (70 char max, null-terminated) +version - current version number of the CFITSIO library +width - width of the character string field +xcol - number of the column containing the X coordinate values +xinc - X axis coordinate increment at reference pixel (deg) +xpix - X axis pixel location +xpos - X axis celestial coordinate (usually RA) (deg) +xrefpix - X axis reference pixel array location +xrefval - X axis coordinate value at the reference pixel (deg) +ycol - number of the column containing the X coordinate values +year - calendar year (e.g. 1999, 2000, etc) +yinc - Y axis coordinate increment at reference pixel (deg) +ypix - y axis pixel location +ypos - y axis celestial coordinate (usually DEC) (deg) +yrefpix - Y axis reference pixel array location +yrefval - Y axis coordinate value at the reference pixel (deg) +zero - scaling offset; true value = (FITS value) * scale + zero +- + +*3 CFITSIO Error Status Codes + +The following table lists all the error status codes used by CFITSIO. +Programmers are encouraged to use the symbolic mnemonics (defined in +the file fitsio.h) rather than the actual integer status values to +improve the readability of their code. +- + Symbolic Const Value Meaning + -------------- ----- ----------------------------------------- + 0 OK, no error + SAME_FILE 101 input and output files are the same + TOO_MANY_FILES 103 tried to open too many FITS files at once + FILE_NOT_OPENED 104 could not open the named file + FILE_NOT_CREATED 105 could not create the named file + WRITE_ERROR 106 error writing to FITS file + END_OF_FILE 107 tried to move past end of file + READ_ERROR 108 error reading from FITS file + FILE_NOT_CLOSED 110 could not close the file + ARRAY_TOO_BIG 111 array dimensions exceed internal limit + READONLY_FILE 112 Cannot write to readonly file + MEMORY_ALLOCATION 113 Could not allocate memory + BAD_FILEPTR 114 invalid fitsfile pointer + NULL_INPUT_PTR 115 NULL input pointer to routine + SEEK_ERROR 116 error seeking position in file + + BAD_URL_PREFIX 121 invalid URL prefix on file name + TOO_MANY_DRIVERS 122 tried to register too many IO drivers + DRIVER_INIT_FAILED 123 driver initialization failed + NO_MATCHING_DRIVER 124 matching driver is not registered + URL_PARSE_ERROR 125 failed to parse input file URL + RANGE_PARSE_ERROR 126 parse error in range list + + SHARED_BADARG 151 bad argument in shared memory driver + SHARED_NULPTR 152 null pointer passed as an argument + SHARED_TABFULL 153 no more free shared memory handles + SHARED_NOTINIT 154 shared memory driver is not initialized + SHARED_IPCERR 155 IPC error returned by a system call + SHARED_NOMEM 156 no memory in shared memory driver + SHARED_AGAIN 157 resource deadlock would occur + SHARED_NOFILE 158 attempt to open/create lock file failed + SHARED_NORESIZE 159 shared memory block cannot be resized at the moment + + HEADER_NOT_EMPTY 201 header already contains keywords + KEY_NO_EXIST 202 keyword not found in header + KEY_OUT_BOUNDS 203 keyword record number is out of bounds + VALUE_UNDEFINED 204 keyword value field is blank + NO_QUOTE 205 string is missing the closing quote + BAD_INDEX_KEY 206 illegal indexed keyword name (e.g. 'TFORM1000') + BAD_KEYCHAR 207 illegal character in keyword name or card + BAD_ORDER 208 required keywords out of order + NOT_POS_INT 209 keyword value is not a positive integer + NO_END 210 couldn't find END keyword + BAD_BITPIX 211 illegal BITPIX keyword value + BAD_NAXIS 212 illegal NAXIS keyword value + BAD_NAXES 213 illegal NAXISn keyword value + BAD_PCOUNT 214 illegal PCOUNT keyword value + BAD_GCOUNT 215 illegal GCOUNT keyword value + BAD_TFIELDS 216 illegal TFIELDS keyword value + NEG_WIDTH 217 negative table row size + NEG_ROWS 218 negative number of rows in table + COL_NOT_FOUND 219 column with this name not found in table + BAD_SIMPLE 220 illegal value of SIMPLE keyword + NO_SIMPLE 221 Primary array doesn't start with SIMPLE + NO_BITPIX 222 Second keyword not BITPIX + NO_NAXIS 223 Third keyword not NAXIS + NO_NAXES 224 Couldn't find all the NAXISn keywords + NO_XTENSION 225 HDU doesn't start with XTENSION keyword + NOT_ATABLE 226 the CHDU is not an ASCII table extension + NOT_BTABLE 227 the CHDU is not a binary table extension + NO_PCOUNT 228 couldn't find PCOUNT keyword + NO_GCOUNT 229 couldn't find GCOUNT keyword + NO_TFIELDS 230 couldn't find TFIELDS keyword + NO_TBCOL 231 couldn't find TBCOLn keyword + NO_TFORM 232 couldn't find TFORMn keyword + NOT_IMAGE 233 the CHDU is not an IMAGE extension + BAD_TBCOL 234 TBCOLn keyword value < 0 or > rowlength + NOT_TABLE 235 the CHDU is not a table + COL_TOO_WIDE 236 column is too wide to fit in table + COL_NOT_UNIQUE 237 more than 1 column name matches template + BAD_ROW_WIDTH 241 sum of column widths not = NAXIS1 + UNKNOWN_EXT 251 unrecognizable FITS extension type + UNKNOWN_REC 252 unknown record; 1st keyword not SIMPLE or XTENSION + END_JUNK 253 END keyword is not blank + BAD_HEADER_FILL 254 Header fill area contains non-blank chars + BAD_DATA_FILL 255 Illegal data fill bytes (not zero or blank) + BAD_TFORM 261 illegal TFORM format code + BAD_TFORM_DTYPE 262 unrecognizable TFORM data type code + BAD_TDIM 263 illegal TDIMn keyword value + BAD_HEAP_PTR 264 invalid BINTABLE heap pointer is out of range + + BAD_HDU_NUM 301 HDU number < 1 + BAD_COL_NUM 302 column number < 1 or > tfields + NEG_FILE_POS 304 tried to move to negative byte location in file + NEG_BYTES 306 tried to read or write negative number of bytes + BAD_ROW_NUM 307 illegal starting row number in table + BAD_ELEM_NUM 308 illegal starting element number in vector + NOT_ASCII_COL 309 this is not an ASCII string column + NOT_LOGICAL_COL 310 this is not a logical data type column + BAD_ATABLE_FORMAT 311 ASCII table column has wrong format + BAD_BTABLE_FORMAT 312 Binary table column has wrong format + NO_NULL 314 null value has not been defined + NOT_VARI_LEN 317 this is not a variable length column + BAD_DIMEN 320 illegal number of dimensions in array + BAD_PIX_NUM 321 first pixel number greater than last pixel + ZERO_SCALE 322 illegal BSCALE or TSCALn keyword = 0 + NEG_AXIS 323 illegal axis length < 1 + + NOT_GROUP_TABLE 340 Grouping function error + HDU_ALREADY_MEMBER 341 + MEMBER_NOT_FOUND 342 + GROUP_NOT_FOUND 343 + BAD_GROUP_ID 344 + TOO_MANY_HDUS_TRACKED 345 + HDU_ALREADY_TRACKED 346 + BAD_OPTION 347 + IDENTICAL_POINTERS 348 + BAD_GROUP_ATTACH 349 + BAD_GROUP_DETACH 350 + + NGP_NO_MEMORY 360 malloc failed + NGP_READ_ERR 361 read error from file + NGP_NUL_PTR 362 null pointer passed as an argument. + Passing null pointer as a name of + template file raises this error + NGP_EMPTY_CURLINE 363 line read seems to be empty (used + internally) + NGP_UNREAD_QUEUE_FULL 364 cannot unread more then 1 line (or single + line twice) + NGP_INC_NESTING 365 too deep include file nesting (infinite + loop, template includes itself ?) + NGP_ERR_FOPEN 366 fopen() failed, cannot open template file + NGP_EOF 367 end of file encountered and not expected + NGP_BAD_ARG 368 bad arguments passed. Usually means + internal parser error. Should not happen + NGP_TOKEN_NOT_EXPECT 369 token not expected here + + BAD_I2C 401 bad int to formatted string conversion + BAD_F2C 402 bad float to formatted string conversion + BAD_INTKEY 403 can't interpret keyword value as integer + BAD_LOGICALKEY 404 can't interpret keyword value as logical + BAD_FLOATKEY 405 can't interpret keyword value as float + BAD_DOUBLEKEY 406 can't interpret keyword value as double + BAD_C2I 407 bad formatted string to int conversion + BAD_C2F 408 bad formatted string to float conversion + BAD_C2D 409 bad formatted string to double conversion + BAD_DATATYPE 410 illegal datatype code value + BAD_DECIM 411 bad number of decimal places specified + NUM_OVERFLOW 412 overflow during data type conversion + DATA_COMPRESSION_ERR 413 error compressing image + DATA_DECOMPRESSION_ERR 414 error uncompressing image + + BAD_DATE 420 error in date or time conversion + + PARSE_SYNTAX_ERR 431 syntax error in parser expression + PARSE_BAD_TYPE 432 expression did not evaluate to desired type + PARSE_LRG_VECTOR 433 vector result too large to return in array + PARSE_NO_OUTPUT 434 data parser failed not sent an out column + PARSE_BAD_COL 435 bad data encounter while parsing column + PARSE_BAD_OUTPUT 436 Output file not of proper type + + ANGLE_TOO_BIG 501 celestial angle too large for projection + BAD_WCS_VAL 502 bad celestial coordinate or pixel value + WCS_ERROR 503 error in celestial coordinate calculation + BAD_WCS_PROJ 504 unsupported type of celestial projection + NO_WCS_KEY 505 celestial coordinate keywords not found + APPROX_WCS_KEY 506 approximate wcs keyword values were returned +- +\end{document} + diff --git a/external/cfitsio/cfitsio.pc.in b/external/cfitsio/cfitsio.pc.in new file mode 100644 index 0000000..5dab97a --- /dev/null +++ b/external/cfitsio/cfitsio.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: cfitsio +Description: FITS File Subroutine Library +Version: 3.31 +Libs: -L${libdir} -lcfitsio @LIBS@ +Libs.private: -lm +Cflags: -I${includedir} diff --git a/external/cfitsio/cfitsio.tex b/external/cfitsio/cfitsio.tex new file mode 100644 index 0000000..f3985f3 --- /dev/null +++ b/external/cfitsio/cfitsio.tex @@ -0,0 +1,10644 @@ +\documentclass[11pt]{book} +\input{html.sty} +\htmladdtonavigation + {\begin{rawhtml} + FITSIO Home + \end{rawhtml}} +\oddsidemargin=0.00in +\evensidemargin=0.00in +\textwidth=6.5in +%\topmargin=0.0in +\textheight=8.75in +\parindent=0cm +\parskip=0.2cm +\begin{document} +\pagenumbering{roman} + +\begin{titlepage} +\normalsize +\vspace*{4.0cm} +\begin{center} +{\Huge \bf CFITSIO User's Reference Guide}\\ +\end{center} +\medskip +\medskip +\begin{center} +{\LARGE \bf An Interface to FITS Format Files}\\ +\end{center} +\begin{center} +{\LARGE \bf for C Programmers}\\ +\end{center} +\medskip +\medskip +\begin{center} +{\Large Version 3.3 \\} +\end{center} +\bigskip +\vskip 2.5cm +\begin{center} +{HEASARC\\ +Code 662\\ +Goddard Space Flight Center\\ +Greenbelt, MD 20771\\ +USA} +\end{center} + +\vfill +\bigskip +\begin{center} +{\Large April 2012\\} +\end{center} +\vfill +\end{titlepage} + +\clearpage + +\tableofcontents +\chapter{Introduction } +\pagenumbering{arabic} + + +\section{ A Brief Overview} + +CFITSIO is a machine-independent library of routines for reading and +writing data files in the FITS (Flexible Image Transport System) data +format. It can also read IRAF format image files and raw binary data +arrays by converting them on the fly into a virtual FITS format file. +This library is written in ANSI C and provides a powerful yet simple +interface for accessing FITS files which will run on most commonly used +computers and workstations. CFITSIO supports all the features +described in the official NOST definition of the FITS format and can +read and write all the currently defined types of extensions, including +ASCII tables (TABLE), Binary tables (BINTABLE) and IMAGE extensions. +The CFITSIO routines insulate the programmer from having to deal with +the complicated formatting details in the FITS file, however, it is +assumed that users have a general knowledge about the structure and +usage of FITS files. + +CFITSIO also contains a set of Fortran callable wrapper routines which +allow Fortran programs to call the CFITSIO routines. See the companion +``FITSIO User's Guide'' for the definition of the Fortran subroutine +calling sequences. These wrappers replace the older Fortran FITSIO +library which is no longer supported. + +The CFITSIO package was initially developed by the HEASARC (High Energy +Astrophysics Science Archive Research Center) at the NASA Goddard Space +Flight Center to convert various existing and newly acquired +astronomical data sets into FITS format and to further analyze data +already in FITS format. New features continue to be added to CFITSIO +in large part due to contributions of ideas or actual code from +users of the package. The Integral Science Data Center in Switzerland, +and the XMM/ESTEC project in The Netherlands made especially significant +contributions that resulted in many of the new features that appeared +in v2.0 of CFITSIO. + + +\section{Sources of FITS Software and Information} + +The latest version of the CFITSIO source code, +documentation, and example programs are available on the World-Wide +Web or via anonymous ftp from: + +\begin{verbatim} + http://heasarc.gsfc.nasa.gov/fitsio + ftp://legacy.gsfc.nasa.gov/software/fitsio/c +\end{verbatim} + +Any questions, bug reports, or suggested enhancements related to the CFITSIO +package should be sent to the primary author: + +\begin{verbatim} + Dr. William Pence Telephone: (301) 286-4599 + HEASARC, Code 662 E-mail: William.D.Pence@nasa.gov + NASA/Goddard Space Flight Center + Greenbelt, MD 20771, USA +\end{verbatim} +This User's Guide assumes that readers already have a general +understanding of the definition and structure of FITS format files. +Further information about FITS formats is available from the FITS Support +Office at {\tt http://fits.gsfc.nasa.gov}. In particular, the +'NOST FITS Standard' gives the authoritative definition of the FITS data +format, and the `FITS User's Guide' provides additional historical background +and practical advice on using FITS files. + +The HEASARC also provides a very sophisticated FITS file analysis +program called `Fv' which can be used to display and edit the contents +of any FITS file as well as construct new FITS files from scratch. The +display functions in Fv allow users to interactively adjust the +brightness and contrast of images, pan, zoom, and blink images, and +measure the positions and brightnesses of objects within images. FITS +tables can be displayed like a spread sheet, and then modified using +powerful calculator and sorting functions. Fv is freely available for +most Unix platforms, Mac PCs, and Windows PCs. +CFITSIO users may also be interested in the FTOOLS package of programs +that can be used to manipulate and analyze FITS format files. +Fv and FTOOLS are available from their respective Web sites at: + +\begin{verbatim} + http://fv.gsfc.nasa.gov + http://heasarc.gsfc.nasa.gov/ftools +\end{verbatim} + + +\section{Acknowledgments} + +The development of the many powerful features in CFITSIO was made +possible through collaborations with many people or organizations from +around the world. The following in particular have made especially +significant contributions: + +Programmers from the Integral Science Data Center, Switzerland (namely, +Jurek Borkowski, Bruce O'Neel, and Don Jennings), designed the concept +for the plug-in I/O drivers that was introduced with CFITSIO 2.0. The +use of `drivers' greatly simplified the low-level I/O, which in turn +made other new features in CFITSIO (e.g., support for compressed FITS +files and support for IRAF format image files) much easier to +implement. Jurek Borkowski wrote the Shared Memory driver, and Bruce +O'Neel wrote the drivers for accessing FITS files over the network +using the FTP, HTTP, and ROOT protocols. Also, in 2009, Bruce O'Neel +was the key developer of the thread-safe version of CFITSIO. + +The ISDC also provided the template parsing routines (written by Jurek +Borkowski) and the hierarchical grouping routines (written by Don +Jennings). The ISDC DAL (Data Access Layer) routines are layered on +top of CFITSIO and make extensive use of these features. + +Giuliano Taffoni and Andrea Barisani, at INAF, University of Trieste, +Italy, implemented the I/O driver routines for accessing FITS files +on the computational grids using the gridftp protocol. + +Uwe Lammers (XMM/ESA/ESTEC, The Netherlands) designed the +high-performance lexical parsing algorithm that is used to do +on-the-fly filtering of FITS tables. This algorithm essentially +pre-compiles the user-supplied selection expression into a form that +can be rapidly evaluated for each row. Peter Wilson (RSTX, NASA/GSFC) +then wrote the parsing routines used by CFITSIO based on Lammers' +design, combined with other techniques such as the CFITSIO iterator +routine to further enhance the data processing throughput. This effort +also benefited from a much earlier lexical parsing routine that was +developed by Kent Blackburn (NASA/GSFC). More recently, Craig Markwardt +(NASA/GSFC) implemented additional functions (median, average, stddev) +and other enhancements to the lexical parser. + +The CFITSIO iterator function is loosely based on similar ideas +developed for the XMM Data Access Layer. + +Peter Wilson (RSTX, NASA/GSFC) wrote the complete set of +Fortran-callable wrappers for all the CFITSIO routines, which in turn +rely on the CFORTRAN macro developed by Burkhard Burow. + +The syntax used by CFITSIO for filtering or binning input FITS files is +based on ideas developed for the AXAF Science Center Data Model by +Jonathan McDowell, Antonella Fruscione, Aneta Siemiginowska and Bill +Joye. See http://heasarc.gsfc.nasa.gov/docs/journal/axaf7.html for +further description of the AXAF Data Model. + +The file decompression code were taken directly from the gzip (GNU zip) +program developed by Jean-loup Gailly and others. + +The new compressed image data format (where the image is tiled and +the compressed byte stream from each tile is stored in a binary table) +was implemented in collaboration with Richard White (STScI), Perry +Greenfield (STScI) and Doug Tody (NOAO). + +Doug Mink (SAO) provided the routines for converting IRAF format +images into FITS format. + +Martin Reinecke (Max Planck Institute, Garching)) provided the modifications to +cfortran.h that are necessary to support 64-bit integer values when calling +C routines from fortran programs. The cfortran.h macros were originally developed +by Burkhard Burow (CERN). + +Julian Taylor (ESO, Garching) provided the fast byte-swapping algorithms +that use the SSE2 and SSSE3 machine instructions available on x86\_64 CPUs. + +In addition, many other people have made valuable contributions to the +development of CFITSIO. These include (with apologies to others that may +have inadvertently been omitted): + +Steve Allen, Carl Akerlof, Keith Arnaud, Morten Krabbe Barfoed, Kent +Blackburn, G Bodammer, Romke Bontekoe, Lucio Chiappetti, Keith Costorf, +Robin Corbet, John Davis, Richard Fink, Ning Gan, Emily Greene, Gretchen +Green, Joe Harrington, Cheng Ho, Phil Hodge, Jim Ingham, Yoshitaka +Ishisaki, Diab Jerius, Mark Levine, Todd Karakaskian, Edward King, +Scott Koch, Claire Larkin, Rob Managan, Eric Mandel, Richard Mathar, +John Mattox, Carsten Meyer, Emi Miyata, Stefan Mochnacki, Mike Noble, +Oliver Oberdorf, Clive Page, Arvind Parmar, Jeff Pedelty, Tim Pearson, +Philippe Prugniel, Maren Purves, Scott Randall, Chris Rogers, Arnold Rots, +Rob Seaman, Barry Schlesinger, Robin Stebbins, Andrew Szymkowiak, Allyn Tennant, +Peter Teuben, James Theiler, Doug Tody, Shiro Ueno, Steve Walton, Archie +Warnock, Alan Watson, Dan Whipple, Wim Wimmers, Peter Young, Jianjun Xu, +and Nelson Zarate. + + +\section{Legal Stuff} + +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." + +\chapter{ Creating the CFITSIO Library } + + +\section{Building the Library} + +The CFITSIO code is contained in about 40 C source files (*.c) and header +files (*.h). On VAX/VMS systems 2 assembly-code files (vmsieeed.mar and +vmsieeer.mar) are also needed. + +CFITSIO has currently been tested on the following platforms (not up-to-date): + +\begin{verbatim} + OPERATING SYSTEM COMPILER + Sun OS gcc and cc (3.0.1) + Sun Solaris gcc and cc + Silicon Graphics IRIX gcc and cc + Silicon Graphics IRIX64 MIPS + Dec Alpha OSF/1 gcc and cc + DECstation Ultrix gcc + Dec Alpha OpenVMS cc + DEC VAX/VMS gcc and cc + HP-UX gcc + IBM AIX gcc + Linux gcc + MkLinux DR3 + Windows 95/98/NT Borland C++ V4.5 + Windows 95/98/NT/ME/XP Microsoft/Compaq Visual C++ v5.0, v6.0 + Windows 95/98/NT Cygwin gcc + MacOS 7.1 or greater Metrowerks 10.+ + MacOS-X 10.1 or greater cc (gcc) +\end{verbatim} +CFITSIO will probably run on most other Unix platforms. Cray +supercomputers are currently not supported. + + +\subsection{Unix Systems} + +The CFITSIO library is built on Unix systems by typing: + +\begin{verbatim} + > ./configure [--prefix=/target/installation/path] [--enable-reentrant] + [--enable-sse2] [--enable-ssse3] + > make (or 'make shared') + > make install (this step is optional) +\end{verbatim} +at the operating system prompt. The configure command customizes the +Makefile for the particular system, then the `make' command compiles the +source files and builds the library. Type `./configure' and not simply +`configure' to ensure that the configure script in the current directory +is run and not some other system-wide configure script. The optional +'prefix' argument to configure gives the path to the directory where +the CFITSIO library and include files should be installed via the later +'make install' command. For example, + +\begin{verbatim} + > ./configure --prefix=/usr1/local +\end{verbatim} +will cause the 'make install' command to copy the CFITSIO libcfitsio file +to /usr1/local/lib and the necessary include files to /usr1/local/include +(assuming of course that the process has permission to write to these +directories). + +The optional --enable-reentrant flag will attempt to configure CFITSIO +so that it can be used in multi-threaded programs. See the "Using CFITSIO in Multi-threaded Environments" section, below, for more + +The optional --enable-sse2 and --enable-ssse3 flags will cause configure to +attempt to build CFITSIO using faster byte-swapping algorithms. +See the "Optimizing Programs" chapter of this manual for +more information about these options. + +The 'make shared' option builds a shared or dynamic version of the +CFITSIO library. When using the shared library the executable code is +not copied into your program at link time and instead the program +locates the necessary library code at run time, normally through +LD\_LIBRARY\_PATH or some other method. The advantages of using a shared +library are: + +\begin{verbatim} + 1. Less disk space if you build more than 1 program + 2. Less memory if more than one copy of a program using the shared + library is running at the same time since the system is smart + enough to share copies of the shared library at run time. + 3. Possibly easier maintenance since a new version of the shared + library can be installed without relinking all the software + that uses it (as long as the subroutine names and calling + sequences remain unchanged). + 4. No run-time penalty. +\end{verbatim} +The disadvantages are: + +\begin{verbatim} + 1. More hassle at runtime. You have to either build the programs + specially or have LD_LIBRARY_PATH set right. + 2. There may be a slight start up penalty, depending on where you are + reading the shared library and the program from and if your CPU is + either really slow or really heavily loaded. +\end{verbatim} + +On Mac OS X platforms the 'make shared' command works like on other +UNIX platforms, but a .dylib file will be created instead of .so. If +installed in a nonstandard location, add its location to the +DYLD\_LIBRARY\_PATH environment variable so that the library can be found +at run time. + +On HP/UX systems, the environment variable CFLAGS should be set +to -Ae before running configure to enable "extended ANSI" features. + +By default, a set of Fortran-callable wrapper routines are +also built and included in the CFITSIO library. If these wrapper +routines are not needed (i.e., the CFITSIO library will not +be linked to any Fortran applications which call FITSIO subroutines) +then they may be omitted from the build by typing 'make all-nofitsio' +instead of simply typing 'make'. This will reduce the size +of the CFITSIO library slightly. + +It may not be possible to statically link programs that use CFITSIO on +some platforms (namely, on Solaris 2.6) due to the network drivers +(which provide FTP and HTTP access to FITS files). It is possible to +make both a dynamic and a static version of the CFITSIO library, but +network file access will not be possible using the static version. + + +\subsection{VMS} + +On VAX/VMS and ALPHA/VMS systems the make\_gfloat.com command file may +be executed to build the cfitsio.olb object library using the default +G-floating point option for double variables. The make\_dfloat.com and +make\_ieee.com files may be used instead to build the library with the +other floating point options. Note that the getcwd function that is +used in the group.c module may require that programs using CFITSIO be +linked with the ALPHA\$LIBRARY:VAXCRTL.OLB library. See the example +link line in the next section of this document. + + +\subsection{Windows PCs} + +A precompiled DLL version of CFITSIO is available for IBM-PC users of +the Borland or Microsoft Visual C++ compilers in the files +cfitsiodll\_3xxx\_borland.zip and cfitsiodll\_3xxx\_vcc.zip, where +'3xxx' represents the current release number. These zip archives also +contains other files and instructions on how to use the CFITSIO DLL +library. + +The CFITSIO library may also be built from the source code using the +makefile.bc or makefile.vcc files. Finally, the makepc.bat file gives +an example of building CFITSIO with the Borland C++ v4.5 or v5.5 compiler +using older DOS commands. + + +\subsection{Macintosh PCs} + +When building on Mac OS-X, users should follow the Unix instructions, +above. See the README.MacOS file for instructions on building a Universal +Binary that supports both Intel and PowerPC CPUs. + + +\section{Testing the Library} + +The CFITSIO library should be tested by building and running +the testprog.c program that is included with the release. +On Unix systems, type: + +\begin{verbatim} + % make testprog + % testprog > testprog.lis + % diff testprog.lis testprog.out + % cmp testprog.fit testprog.std +\end{verbatim} + On VMS systems, +(assuming cc is the name of the C compiler command), type: + +\begin{verbatim} + $ cc testprog.c + $ link testprog, cfitsio/lib, alpha$library:vaxcrtl/lib + $ run testprog +\end{verbatim} +The test program should produce a FITS file called `testprog.fit' +that is identical to the `testprog.std' FITS file included with this +release. The diagnostic messages (which were piped to the file +testprog.lis in the Unix example) should be identical to the listing +contained in the file testprog.out. The 'diff' and 'cmp' commands +shown above should not report any differences in the files. (There +may be some minor format differences, such as the presence or +absence of leading zeros, or 3 digit exponents in numbers, +which can be ignored). + +The Fortran wrappers in CFITSIO may be tested with the testf77 +program on Unix systems with: + +\begin{verbatim} + % f77 -o testf77 testf77.f -L. -lcfitsio -lnsl -lsocket + or + % f77 -f -o testf77 testf77.f -L. -lcfitsio (under SUN O/S) + or + % f77 -o testf77 testf77.f -Wl,-L. -lcfitsio -lm -lnsl -lsocket (HP/UX) + + % testf77 > testf77.lis + % diff testf77.lis testf77.out + % cmp testf77.fit testf77.std +\end{verbatim} +On machines running SUN O/S, Fortran programs must be compiled with the +'-f' option to force double precision variables to be aligned on 8-byte +boundarys to make the fortran-declared variables compatible with C. A +similar compiler option may be required on other platforms. Failing to +use this option may cause the program to crash on FITSIO routines that +read or write double precision variables. + +Also note that on some systems, the output listing of the testf77 +program may differ slightly from the testf77.std template, if leading +zeros are not printed by default before the decimal point when using F +format. + +A few other utility programs are included with CFITSIO; the first four +of this programs can be compiled an linked by typing `make +program\_name' where `program\_name' is the actual name of the program: + +\begin{verbatim} + speed - measures the maximum throughput (in MB per second) + for writing and reading FITS files with CFITSIO. + + listhead - lists all the header keywords in any FITS file + + fitscopy - copies any FITS file (especially useful in conjunction + with the CFITSIO's extended input filename syntax). + + cookbook - a sample program that performs common read and + write operations on a FITS file. + + iter_a, iter_b, iter_c - examples of the CFITSIO iterator routine +\end{verbatim} + + +\section{Linking Programs with CFITSIO} + +When linking applications software with the CFITSIO library, several +system libraries usually need to be specified on the link command +line. On Unix systems, the most reliable way to determine what +libraries are required is to type 'make testprog' and see what +libraries the configure script has added. The typical libraries that +need to be added are -lm (the math library) and -lnsl and -lsocket +(needed only for FTP and HTTP file access). These latter 2 libraries +are not needed on VMS and Windows platforms, because FTP file access is +not currently supported on those platforms. + +Note that when upgrading to a newer version of CFITSIO it is usually +necessary to recompile, as well as relink, the programs that use CFITSIO, +because the definitions in fitsio.h often change. + + +\section{Using CFITSIO in Multi-threaded Environments} + +CFITSIO can be used either with the +POSIX pthreads interface or the OpenMP interface for multi-threaded +parallel programs. When used in a multi-threaded environment, +the CFITSIO library *must* be built using +the -D\_REENTRANT compiler directive. This can be done using the following +build commands: + +\begin{verbatim} + >./configure --enable-reentrant + > make +\end{verbatim} +A function called fits\_is\_reentrant is available to test +whether or not CFITSIO was compiled with the -D\_REENTRANT +directive. When this feature is enabled, multiple threads can +call any of the CFITSIO routines +to simultaneously read or write separate +FITS files. Multiple threads can also read data from +the same FITS file simultaneously, as long as the file +was opened independently by each thread. This relies on +the operating system to correctly deal with reading the +same file by multiple processes. Different threads should +not share the same 'fitsfile' pointer to read an opened +FITS file, unless locks are placed around the calls to +the CFITSIO reading routines. +Different threads should never try to write to the same +FITS file. + + +\section{Getting Started with CFITSIO} + +In order to effectively use the CFITSIO library it is recommended that +new users begin by reading the ``CFITSIO Quick Start Guide''. It +contains all the basic information needed to write programs that +perform most types of operations on FITS files. The set of example +FITS utility programs that are available from the CFITSIO web site are +also very useful for learning how to use CFITSIO. To learn even more +about the capabilities of the CFITSIO library the following steps are +recommended: + +1. Read the following short `FITS Primer' chapter for an overview of +the structure of FITS files. + +2. Review the Programming Guidelines in Chapter 4 to become familiar +with the conventions used by the CFITSIO interface. + +3. Refer to the cookbook.c, listhead.c, and fitscopy.c programs that +are included with this release for examples of routines that perform +various common FITS file operations. Type 'make program\_name' to +compile and link these programs on Unix systems. + +4. Write a simple program to read or write a FITS file using the Basic +Interface routines described in Chapter 5. + +5. Scan through the more specialized routines that are described in +the following chapters to become familiar with the functionality that +they provide. + + +\section{Example Program} + +The following listing shows an example of how to use the CFITSIO +routines in a C program. Refer to the cookbook.c program that is +included with the CFITSIO distribution for other example routines. + +This program creates a new FITS file, containing a FITS image. An +`EXPOSURE' keyword is written to the header, then the image data are +written to the FITS file before closing the FITS file. + +\begin{verbatim} +#include "fitsio.h" /* required by every program that uses CFITSIO */ +main() +{ + fitsfile *fptr; /* pointer to the FITS file; defined in fitsio.h */ + int status, ii, jj; + long fpixel = 1, naxis = 2, nelements, exposure; + long naxes[2] = { 300, 200 }; /* image is 300 pixels wide by 200 rows */ + short array[200][300]; + + status = 0; /* initialize status before calling fitsio routines */ + fits_create_file(&fptr, "testfile.fits", &status); /* create new file */ + + /* Create the primary array image (16-bit short integer pixels */ + fits_create_img(fptr, SHORT_IMG, naxis, naxes, &status); + + /* Write a keyword; must pass the ADDRESS of the value */ + exposure = 1500.; + fits_update_key(fptr, TLONG, "EXPOSURE", &exposure, + "Total Exposure Time", &status); + + /* Initialize the values in the image with a linear ramp function */ + for (jj = 0; jj < naxes[1]; jj++) + for (ii = 0; ii < naxes[0]; ii++) + array[jj][ii] = ii + jj; + + nelements = naxes[0] * naxes[1]; /* number of pixels to write */ + + /* Write the array of integers to the image */ + fits_write_img(fptr, TSHORT, fpixel, nelements, array[0], &status); + + fits_close_file(fptr, &status); /* close the file */ + + fits_report_error(stderr, status); /* print out any error messages */ + return( status ); +} +\end{verbatim} + +\chapter{ A FITS Primer } + +This section gives a brief overview of the structure of FITS files. +Users should refer to the documentation available from the NOST, as +described in the introduction, for more detailed information on FITS +formats. + +FITS was first developed in the late 1970's as a standard data +interchange format between various astronomical observatories. Since +then FITS has become the standard data format supported by most +astronomical data analysis software packages. + +A FITS file consists of one or more Header + Data Units (HDUs), where +the first HDU is called the `Primary HDU', or `Primary Array'. The +primary array contains an N-dimensional array of pixels, such as a 1-D +spectrum, a 2-D image, or a 3-D data cube. Six different primary +data types are supported: Unsigned 8-bit bytes, 16-bit, 32-bit, and 64-bit signed +integers, and 32 and 64-bit floating point reals. FITS also has a +convention for storing 16 and 32-bit unsigned integers (see the later +section entitled `Unsigned Integers' for more details). The primary HDU +may also consist of only a header with a null array containing no +data pixels. + +Any number of additional HDUs may follow the primary array; these +additional HDUs are called FITS `extensions'. There are currently 3 +types of extensions defined by the FITS standard: + +\begin{itemize} +\item + Image Extension - a N-dimensional array of pixels, like in a primary array +\item + ASCII Table Extension - rows and columns of data in ASCII character format +\item + Binary Table Extension - rows and columns of data in binary representation +\end{itemize} + +In each case the HDU consists of an ASCII Header Unit followed by an optional +Data Unit. For historical reasons, each Header or Data unit must be an +exact multiple of 2880 8-bit bytes long. Any unused space is padded +with fill characters (ASCII blanks or zeros). + +Each Header Unit consists of any number of 80-character keyword records +or `card images' which have the +general form: + +\begin{verbatim} + KEYNAME = value / comment string + NULLKEY = / comment: This keyword has no value +\end{verbatim} +The keyword names may be up to 8 characters long and can only contain +uppercase letters, the digits 0-9, the hyphen, and the underscore +character. The keyword name is (usually) followed by an equals sign and +a space character (= ) in columns 9 - 10 of the record, followed by the +value of the keyword which may be either an integer, a floating point +number, a character string (enclosed in single quotes), or a boolean +value (the letter T or F). A keyword may also have a null or undefined +value if there is no specified value string, as in the second example, above + +The last keyword in the header is always the `END' keyword which has no +value or comment fields. There are many rules governing the exact +format of a keyword record (see the NOST FITS Standard) so it is better +to rely on standard interface software like CFITSIO to correctly +construct or to parse the keyword records rather than try to deal +directly with the raw FITS formats. + +Each Header Unit begins with a series of required keywords which depend +on the type of HDU. These required keywords specify the size and +format of the following Data Unit. The header may contain other +optional keywords to describe other aspects of the data, such as the +units or scaling values. Other COMMENT or HISTORY keywords are also +frequently added to further document the data file. + +The optional Data Unit immediately follows the last 2880-byte block in +the Header Unit. Some HDUs do not have a Data Unit and only consist of +the Header Unit. + +If there is more than one HDU in the FITS file, then the Header Unit of +the next HDU immediately follows the last 2880-byte block of the +previous Data Unit (or Header Unit if there is no Data Unit). + +The main required keywords in FITS primary arrays or image extensions are: +\begin{itemize} +\item +BITPIX -- defines the data type of the array: 8, 16, 32, 64, -32, -64 for +unsigned 8--bit byte, 16--bit signed integer, 32--bit signed integer, +32--bit IEEE floating point, and 64--bit IEEE double precision floating +point, respectively. +\item +NAXIS -- the number of dimensions in the array, usually 0, 1, 2, 3, or 4. +\item +NAXISn -- (n ranges from 1 to NAXIS) defines the size of each dimension. +\end{itemize} + +FITS tables start with the keyword XTENSION = `TABLE' (for ASCII +tables) or XTENSION = `BINTABLE' (for binary tables) and have the +following main keywords: +\begin{itemize} +\item +TFIELDS -- number of fields or columns in the table +\item +NAXIS2 -- number of rows in the table +\item +TTYPEn -- for each column (n ranges from 1 to TFIELDS) gives the +name of the column +\item +TFORMn -- the data type of the column +\item +TUNITn -- the physical units of the column (optional) +\end{itemize} + +Users should refer to the FITS Support Office at {\tt http://fits.gsfc.nasa.gov} +for further information about the FITS format and related software +packages. + + +\chapter{ Programming Guidelines } + + +\section{CFITSIO Definitions} + +Any program that uses the CFITSIO interface must include the fitsio.h +header file with the statement + +\begin{verbatim} + #include "fitsio.h" +\end{verbatim} +This header file contains the prototypes for all the CFITSIO user +interface routines as well as the definitions of various constants used +in the interface. It also defines a C structure of type `fitsfile' +that is used by CFITSIO to store the relevant parameters that define +the format of a particular FITS file. Application programs must define +a pointer to this structure for each FITS file that is to be opened. +This structure is initialized (i.e., memory is allocated for the +structure) when the FITS file is first opened or created with the +fits\_open\_file or fits\_create\_file routines. This fitsfile pointer +is then passed as the first argument to every other CFITSIO routine +that operates on the FITS file. Application programs must not directly +read or write elements in this fitsfile structure because the +definition of the structure may change in future versions of CFITSIO. + +A number of symbolic constants are also defined in fitsio.h for the +convenience of application programmers. Use of these symbolic +constants rather than the actual numeric value will help to make the +source code more readable and easier for others to understand. + +\begin{verbatim} +String Lengths, for use when allocating character arrays: + + #define FLEN_FILENAME 1025 /* max length of a filename */ + #define FLEN_KEYWORD 72 /* max length of a keyword */ + #define FLEN_CARD 81 /* max 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 CFITSIO error message */ + #define FLEN_STATUS 31 /* max length of a CFITSIO status text string */ + + Note that FLEN_KEYWORD is longer than the nominal 8-character keyword + name length because the HIERARCH convention supports longer keyword names. + +Access modes when opening a FITS file: + + #define READONLY 0 + #define READWRITE 1 + +BITPIX data type code values for FITS images: + + #define BYTE_IMG 8 /* 8-bit unsigned integers */ + #define SHORT_IMG 16 /* 16-bit signed integers */ + #define LONG_IMG 32 /* 32-bit signed integers */ + #define LONGLONG_IMG 64 /* 64-bit signed integers */ + #define FLOAT_IMG -32 /* 32-bit single precision floating point */ + #define DOUBLE_IMG -64 /* 64-bit double precision floating point */ + + The following 4 data type codes are also supported by CFITSIO: + #define SBYTE_IMG 10 /* 8-bit signed integers, equivalent to */ + /* BITPIX = 8, BSCALE = 1, BZERO = -128 */ + #define USHORT_IMG 20 /* 16-bit unsigned integers, equivalent to */ + /* BITPIX = 16, BSCALE = 1, BZERO = 32768 */ + #define ULONG_IMG 40 /* 32-bit unsigned integers, equivalent to */ + /* BITPIX = 32, BSCALE = 1, BZERO = 2147483648 */ + +Codes for the data type of binary table columns and/or for the +data type of variables when reading or writing keywords or data: + + DATATYPE TFORM CODE + #define TBIT 1 /* 'X' */ + #define TBYTE 11 /* 8-bit unsigned byte, 'B' */ + #define TLOGICAL 14 /* logicals (int for keywords */ + /* and char for table cols 'L' */ + #define TSTRING 16 /* ASCII string, 'A' */ + #define TSHORT 21 /* signed short, 'I' */ + #define TLONG 41 /* signed long, */ + #define TLONGLONG 81 /* 64-bit long signed integer 'K' */ + #define TFLOAT 42 /* single precision float, 'E' */ + #define TDOUBLE 82 /* double precision float, 'D' */ + #define TCOMPLEX 83 /* complex (pair of floats) 'C' */ + #define TDBLCOMPLEX 163 /* double complex (2 doubles) 'M' */ + + The following data type codes are also supported by CFITSIO: + #define TINT 31 /* int */ + #define TSBYTE 12 /* 8-bit signed byte, 'S' */ + #define TUINT 30 /* unsigned int 'V' */ + #define TUSHORT 20 /* unsigned short 'U' */ + #define TULONG 40 /* unsigned long */ + + The following data type code is only for use with fits\_get\_coltype + #define TINT32BIT 41 /* signed 32-bit int, 'J' */ + + +HDU type code values (value returned when moving to new HDU): + + #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 type of HDU */ + +Column name and string matching case-sensitivity: + + #define CASESEN 1 /* do case-sensitive string match */ + #define CASEINSEN 0 /* do case-insensitive string match */ + +Logical states (if TRUE and FALSE are not already defined): + + #define TRUE 1 + #define FALSE 0 + +Values to represent undefined floating point numbers: + + #define FLOATNULLVALUE -9.11912E-36F + #define DOUBLENULLVALUE -9.1191291391491E-36 + +Image compression algorithm definitions + + #define RICE_1 11 + #define GZIP_1 21 + #define PLIO_1 31 + #define HCOMPRESS_1 41 +\end{verbatim} + + +\section{Current Header Data Unit (CHDU)} + +The concept of the Current Header and Data Unit, or CHDU, is +fundamental to the use of the CFITSIO library. A simple FITS image may +only contain a single Header and Data unit (HDU), but in general FITS +files can contain multiple Header Data Units (also known as +`extensions'), concatenated one after the other in the file. The user +can specify which HDU should be initially opened at run time by giving +the HDU name or number after the root file name. For example, +'myfile.fits[4]' opens the 5th HDU in the file (note that the numbering +starts with 0), and 'myfile.fits[EVENTS] opens the HDU with the name +'EVENTS' (as defined by the EXTNAME or HDUNAME keywords). If no HDU is +specified then CFITSIO opens the first HDU (the primary array) by +default. The CFITSIO routines which read and write data only operate +within the opened HDU, Other CFITSIO routines are provided to move to +and open any other existing HDU within the FITS file or to append or +insert new HDUs in the FITS file. + + +\section{Function Names and Variable Datatypes} + +Most of the CFITSIO routines have both a short name as well as a +longer descriptive name. The short name is only 5 or 6 characters long +and is similar to the subroutine name in the Fortran-77 version of +FITSIO. The longer name is more descriptive and it is recommended that +it be used instead of the short name to more clearly document the +source code. + +Many of the CFITSIO routines come in families which differ only in the +data type of the associated parameter(s). The data type of these +routines is indicated by the suffix of the routine name. The short +routine names have a 1 or 2 character suffix (e.g., 'j' in 'ffpkyj') +while the long routine names have a 4 character or longer suffix +as shown in the following table: + +\begin{verbatim} + Long Short Data + Names Names Type + ----- ----- ---- + _bit x bit + _byt b unsigned byte + _sbyt sb signed byte + _sht i short integer + _lng j long integer + _lnglng jj 8-byte LONGLONG integer (see note below) + _usht ui unsigned short integer + _ulng uj unsigned long integer + _uint uk unsigned int integer + _int k int integer + _flt e real exponential floating point (float) + _fixflt f real fixed-decimal format floating point (float) + _dbl d double precision real floating-point (double) + _fixdbl g double precision fixed-format floating point (double) + _cmp c complex reals (pairs of float values) + _fixcmp fc complex reals, fixed-format floating point + _dblcmp m double precision complex (pairs of double values) + _fixdblcmp fm double precision complex, fixed-format floating point + _log l logical (int) + _str s character string +\end{verbatim} + +The logical data type corresponds to `int' for logical keyword values, +and `byte' for logical binary table columns. In other words, the value +when writing a logical keyword must be stored in an `int' variable, and +must be stored in a `char' array when reading or writing to `L' columns +in a binary table. Implicit data type conversion is not supported for +logical table columns, but is for keywords, so a logical keyword may be +read and cast to any numerical data type; a returned value = 0 +indicates false, and any other value = true. + +The `int' data type may be 2 bytes long on some old PC compilers, +but otherwise it is nearly always 4 bytes long. Some 64-bit +machines, like the Alpha/OSF, define the `short', `int', +and `long' integer data types to be 2, 4, and 8 bytes long, +respectively. + +Because there is no universal C compiler standard for the name of the +8-byte integer datatype, the fitsio.h include file typedef's +'LONGLONG' to be equivalent to an +appropriate 8-byte integer data type on each supported platform. +For maximum software portability it is recommended that +this LONGLONG datatype be used to define 8-byte integer variables +rather than using the native data type name on a particular +platform. On most +32-bit Unix and Mac OS-X operating systems LONGLONG is equivalent to the +intrinsic 'long long' 8-byte integer datatype. On 64-bit systems (which currently +includes Alpha OSF/1, 64-bit Sun Solaris, 64-bit SGI MIPS, and 64-bit +Itanium and Opteron PC systems), LONGLONG is simply typedef'ed to be +equivalent to 'long'. Microsoft Visual C++ Version 6.0 does not define +a 'long long' data type, so LONGLONG is typedef'ed to be equivalent to +the '\_\_int64' data type on 32-bit windows systems when using Visual C++. + +A related issue that affects the portability of software is how to print +out the value of a 'LONGLONG' variable with printf. Developers may +find it convenient to use the following preprocessing statements +in their C programs to handle this in a machine-portable manner: + + +\begin{verbatim} +#if defined(_MSC_VER) /* Microsoft Visual C++ */ + printf("%I64d", longlongvalue); + +#elif (USE_LL_SUFFIX == 1) + printf("%lld", longlongvalue); + +#else + printf("%ld", longlongvalue); +#endif +\end{verbatim} + +Similarly, the name of the C utility routine that converts a character +string of digits into a 8-byte integer value is platform dependent: + + +\begin{verbatim} +#if defined(_MSC_VER) /* Microsoft Visual C++ */ + /* VC++ 6.0 does not seem to have an 8-byte conversion routine */ + +#elif (USE_LL_SUFFIX == 1) + longlongvalue = atoll(*string); + +#else + longlongvalue = atol(*string); +#endif +\end{verbatim} + +When dealing with the FITS byte data type it is important to remember +that the raw values (before any scaling by the BSCALE and BZERO, or +TSCALn and TZEROn keyword values) in byte arrays (BITPIX = 8) or byte +columns (TFORMn = 'B') are interpreted as unsigned bytes with values +ranging from 0 to 255. Some C compilers define a 'char' variable as +signed, so it is important to explicitly declare a numeric char +variable as 'unsigned char' to avoid any ambiguity + +One feature of the CFITSIO routines is that they can operate on a `X' +(bit) column in a binary table as though it were a `B' (byte) column. +For example a `11X' data type column can be interpreted the same as a +`2B' column (i.e., 2 unsigned 8-bit bytes). In some instances, it can +be more efficient to read and write whole bytes at a time, rather than +reading or writing each individual bit. + +The complex and double precision complex data types are not directly +supported in ANSI C so these data types should be interpreted as pairs +of float or double values, respectively, where the first value in each +pair is the real part, and the second is the imaginary part. + + +\section{Support for Unsigned Integers and Signed Bytes} + +Although FITS does not directly support unsigned integers as one of its +fundamental data types, FITS can still be used to efficiently store +unsigned integer data values in images and binary tables. The +convention used in FITS files is to store the unsigned integers as +signed integers with an associated offset (specified by the BZERO or +TZEROn keyword). For example, to store unsigned 16-bit integer values +in a FITS image the image would be defined as a signed 16-bit integer +(with BITPIX keyword = SHORT\_IMG = 16) with the keywords BSCALE = 1.0 +and BZERO = 32768. Thus the unsigned values of 0, 32768, and 65535, +for example, are physically stored in the FITS image as -32768, 0, and +32767, respectively; CFITSIO automatically adds the BZERO offset to +these values when they are read. Similarly, in the case of unsigned +32-bit integers the BITPIX keyword would be equal to LONG\_IMG = 32 and +BZERO would be equal to 2147483648 (i.e. 2 raised to the 31st power). + +The CFITSIO interface routines will efficiently and transparently apply +the appropriate offset in these cases so in general application +programs do not need to be concerned with how the unsigned values are +actually stored in the FITS file. As a convenience for users, CFITSIO +has several predefined constants for the value of BITPIX (USHORT\_IMG, +ULONG\_IMG) and for the TFORMn value in the case of binary tables (`U' +and `V') which programmers can use when creating FITS files containing +unsigned integer values. The following code fragment illustrates how +to write a FITS 1-D primary array of unsigned 16-bit integers: + +\begin{verbatim} + unsigned short uarray[100]; + int naxis, status; + long naxes[10], group, firstelem, nelements; + ... + status = 0; + naxis = 1; + naxes[0] = 100; + fits_create_img(fptr, USHORT_IMG, naxis, naxes, &status); + + firstelem = 1; + nelements = 100; + fits_write_img(fptr, TUSHORT, firstelem, nelements, + uarray, &status); + ... +\end{verbatim} +In the above example, the 2nd parameter in fits\_create\_img tells +CFITSIO to write the header keywords appropriate for an array of 16-bit +unsigned integers (i.e., BITPIX = 16 and BZERO = 32768). Then the +fits\_write\_img routine writes the array of unsigned short integers +(uarray) into the primary array of the FITS file. Similarly, a 32-bit +unsigned integer image may be created by setting the second parameter +in fits\_create\_img equal to `ULONG\_IMG' and by calling the +fits\_write\_img routine with the second parameter = TULONG to write +the array of unsigned long image pixel values. + +An analogous set of routines are available for reading or writing unsigned +integer values and signed byte values in a FITS binary table extension. +When specifying the TFORMn keyword value which defines the format of a +column, CFITSIO recognized 3 additional data type codes besides those +already defined in the FITS standard: `U' meaning a 16-bit unsigned +integer column, `V' for a 32-bit unsigned integer column, and 'S' +for a signed byte column. These non-standard data type codes are not +actually written into the FITS file but instead are just used internally +within CFITSIO. The following code fragment illustrates how to use +these features: + +\begin{verbatim} + unsigned short uarray[100]; + unsigned int varray[100]; + + int colnum, tfields, status; + long nrows, firstrow, firstelem, nelements, pcount; + + char extname[] = "Test_table"; /* extension name */ + + /* define the name, data type, and physical units for the 2 columns */ + char *ttype[] = { "Col_1", "Col_2", "Col_3" }; + char *tform[] = { "1U", "1V", "1S"}; /* special CFITSIO codes */ + char *tunit[] = { " ", " ", " " }; + ... + + /* write the header keywords */ + status = 0; + nrows = 1; + tfields = 3 + pcount = 0; + fits_create_tbl(fptr, BINARY_TBL, nrows, tfields, ttype, tform, + tunit, extname, &status); + + /* write the unsigned shorts to the 1st column */ + colnum = 1; + firstrow = 1; + firstelem = 1; + nelements = 100; + fits_write_col(fptr, TUSHORT, colnum, firstrow, firstelem, + nelements, uarray, &status); + + /* now write the unsigned longs to the 2nd column */ + colnum = 2; + fits_write_col(fptr, TUINT, colnum, firstrow, firstelem, + nelements, varray, &status); + ... +\end{verbatim} +Note that the non-standard TFORM values for the 3 columns, `U' and `V', +tell CFITSIO to write the keywords appropriate for unsigned 16-bit and +unsigned 32-bit integers, respectively (i.e., TFORMn = '1I' and TZEROn += 32678 for unsigned 16-bit integers, and TFORMn = '1J' and TZEROn = +2147483648 for unsigned 32-bit integers). The 'S' TFORMn value tells +CFITSIO to write the keywords appropriate for a signed 8-bit byte column +with TFORMn = '1B' and TZEROn = -128. The calls to fits\_write\_col +then write the arrays of unsigned integer values to the columns. + + +\section{Dealing with Character Strings} + +The character string values in a FITS header or in an ASCII column in a +FITS table extension are generally padded out with non-significant +space characters (ASCII 32) to fill up the header record or the column +width. When reading a FITS string value, the CFITSIO routines will +strip off these non-significant trailing spaces and will return a +null-terminated string value containing only the significant +characters. Leading spaces in a FITS string are considered +significant. If the string contains all blanks, then CFITSIO will +return a single blank character, i.e, the first blank is considered to +be significant, since it distinguishes the string from a null or +undefined string, but the remaining trailing spaces are not +significant. + +Similarly, when writing string values to a FITS file the +CFITSIO routines expect to get a null-terminated string as input; +CFITSIO will pad the string with blanks if necessary when writing it +to the FITS file. + +When calling CFITSIO routines that return a character string it is +vital that the size of the char array be large enough to hold the +entire string of characters, otherwise CFITSIO will overwrite whatever +memory locations follow the char array, possibly causing the program to +execute incorrectly. This type of error can be difficult to debug, so +programmers should always ensure that the char arrays are allocated +enough space to hold the longest possible string, {\bf including} the +terminating NULL character. The fitsio.h file contains the following +defined constants which programmers are strongly encouraged to use +whenever they are allocating space for char arrays: + +\begin{verbatim} +#define FLEN_FILENAME 1025 /* max length of a filename */ +#define FLEN_KEYWORD 72 /* max length of a keyword */ +#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 CFITSIO error message */ +#define FLEN_STATUS 31 /* max length of a CFITSIO status text string */ +\end{verbatim} +For example, when declaring a char array to hold the value string +of FITS keyword, use the following statement: + +\begin{verbatim} + char value[FLEN_VALUE]; +\end{verbatim} +Note that FLEN\_KEYWORD is longer than needed for the nominal 8-character +keyword name because the HIERARCH convention supports longer keyword names. + + +\section{Implicit Data Type Conversion} + +The CFITSIO routines that read and write numerical data can perform +implicit data type conversion. This means that the data type of the +variable or array in the program does not need to be the same as the +data type of the value in the FITS file. Data type conversion is +supported for numerical and string data types (if the string contains a +valid number enclosed in quotes) when reading a FITS header keyword +value and for numeric values when reading or writing values in the +primary array or a table column. CFITSIO returns status = +NUM\_OVERFLOW if the converted data value exceeds the range of the +output data type. Implicit data type conversion is not supported +within binary tables for string, logical, complex, or double complex +data types. + +In addition, any table column may be read as if it contained string values. +In the case of numeric columns the returned string will be formatted +using the TDISPn display format if it exists. + + +\section{Data Scaling} + +When reading numerical data values in the primary array or a +table column, the values will be scaled automatically by the BSCALE and +BZERO (or TSCALn and TZEROn) header values if they are +present in the header. The scaled data that is returned to the reading +program will have + +\begin{verbatim} + output value = (FITS value) * BSCALE + BZERO +\end{verbatim} +(a corresponding formula using TSCALn and TZEROn is used when reading +from table columns). In the case of integer output values the floating +point scaled value is truncated to an integer (not rounded to the +nearest integer). The fits\_set\_bscale and fits\_set\_tscale routines +(described in the `Advanced' chapter) may be used to override the +scaling parameters defined in the header (e.g., to turn off the scaling +so that the program can read the raw unscaled values from the FITS +file). + +When writing numerical data to the primary array or to a table column +the data values will generally be automatically inversely scaled by the +value of the BSCALE and BZERO (or TSCALn and TZEROn) keyword values if +they they exist in the header. These keywords must have been written +to the header before any data is written for them to have any immediate +effect. One may also use the fits\_set\_bscale and fits\_set\_tscale +routines to define or override the scaling keywords in the header +(e.g., to turn off the scaling so that the program can write the raw +unscaled values into the FITS file). If scaling is performed, the +inverse scaled output value that is written into the FITS file will +have + +\begin{verbatim} + FITS value = ((input value) - BZERO) / BSCALE +\end{verbatim} +(a corresponding formula using TSCALn and TZEROn is used when +writing to table columns). Rounding to the nearest integer, rather +than truncation, is performed when writing integer data types to the +FITS file. + + +\section{Support for IEEE Special Values} + +The ANSI/IEEE-754 floating-point number standard defines certain +special values that are used to represent such quantities as +Not-a-Number (NaN), denormalized, underflow, overflow, and infinity. +(See the Appendix in the NOST FITS standard or the NOST FITS User's +Guide for a list of these values). The CFITSIO routines that read +floating point data in FITS files recognize these IEEE special values +and by default interpret the overflow and infinity values as being +equivalent to a NaN, and convert the underflow and denormalized values +into zeros. In some cases programmers may want access to the raw IEEE +values, without any modification by CFITSIO. This can be done by +calling the fits\_read\_img or fits\_read\_col routines while +specifying 0.0 as the value of the NULLVAL parameter. This will force +CFITSIO to simply pass the IEEE values through to the application +program without any modification. This is not fully supported on +VAX/VMS machines, however, where there is no easy way to bypass the +default interpretation of the IEEE special values. This is also not +supported when reading floating-point images that have been compressed +with the FITS tiled image compression convention that is discussed in +section 5.6; the pixels values in tile compressed images are +represented by scaled integers, and a reserved integer value +(not a NaN) is used to represent undefined pixels. + + +\section{Error Status Values and the Error Message Stack} + +Nearly all the CFITSIO routines return an error status value +in 2 ways: as the value of the last parameter in the function call, +and as the returned value of the function itself. This provides +some flexibility in the way programmers can test if an error +occurred, as illustrated in the following 2 code fragments: + +\begin{verbatim} + if ( fits_write_record(fptr, card, &status) ) + printf(" Error occurred while writing keyword."); + +or, + + fits_write_record(fptr, card, &status); + if ( status ) + printf(" Error occurred while writing keyword."); +\end{verbatim} +A listing of all the CFITSIO status code values is given at the end of +this document. Programmers are encouraged to use the symbolic +mnemonics (defined in fitsio.h) rather than the actual integer status +values to improve the readability of their code. + +The CFITSIO library uses an `inherited status' convention for the +status parameter which means that if a routine is called with a +positive input value of the status parameter as input, then the routine +will exit immediately without changing the value of the status +parameter. Thus, if one passes the status value returned from each +CFITSIO routine as input to the next CFITSIO routine, then whenever an +error is detected all further CFITSIO processing will cease. This +convention can simplify the error checking in application programs +because it is not necessary to check the value of the status parameter +after every single CFITSIO routine call. If a program contains a +sequence of several CFITSIO calls, one can just check the status value +after the last call. Since the returned status values are generally +distinctive, it should be possible to determine which routine +originally returned the error status. + +CFITSIO also maintains an internal stack of error messages +(80-character maximum length) which in many cases provide a more +detailed explanation of the cause of the error than is provided by the +error status number alone. It is recommended that the error message +stack be printed out whenever a program detects a CFITSIO error. The +function fits\_report\_error will print out the entire error message +stack, or alternatively one may call fits\_read\_errmsg to get the +error messages one at a time. + + +\section{Variable-Length Arrays in Binary Tables} + +CFITSIO provides easy-to-use support for reading and writing data in +variable length fields of a binary table. The variable length columns +have TFORMn keyword values of the form `1Pt(len)' where `t' is the +data type code (e.g., I, J, E, D, etc.) and `len' is an integer +specifying the maximum length of the vector in the table. (CFITSIO also +supports the experimental 'Q' datatype, which is identical to the 'P' type +except that is supports is a 64-bit address space and hence much larger +data structures). If the value +of `len' is not specified when the table is created (e.g., if the TFORM +keyword value is simply specified as '1PE' instead of '1PE(400) ), then +CFITSIO will automatically scan the table when it is closed to +determine the maximum length of the vector and will append this value +to the TFORMn value. + +The same routines that read and write data in an ordinary fixed length +binary table extension are also used for variable length fields, +however, the routine parameters take on a slightly different +interpretation as described below. + +All the data in a variable length field is written into an area called +the `heap' which follows the main fixed-length FITS binary table. The +size of the heap, in bytes, is specified by the PCOUNT keyword in the +FITS header. When creating a new binary table, the initial value of +PCOUNT should usually be set to zero. CFITSIO will recompute the size +of the heap as the data is written and will automatically update the +PCOUNT keyword value when the table is closed. When writing variable +length data to a table, CFITSIO will automatically extend the size +of the heap area if necessary, so that any following HDUs do not +get overwritten. + +By default the heap data area starts immediately after the last row of +the fixed-length table. This default starting location may be +overridden by the THEAP keyword, but this is not recommended. +If additional rows of data are added to the table, CFITSIO will +automatically shift the the heap down to make room for the new +rows, but it is obviously be more efficient to initially +create the table with the necessary number of blank rows, so that +the heap does not needed to be constantly moved. + +When writing row of data to a variable length field the entire array of values for +a given row of the table must be written with a single call to +fits\_write\_col. +The total length of the array is given by nelements ++ firstelem - 1. Additional elements cannot be appended to an existing +vector at a later time since any attempt to do so will simply overwrite +all the previously written data and the new data will be +written to a new area of the heap. The fits\_compress\_heap routine +is provided to compress the heap and recover any unused space. +To avoid having to deal with this issue, it is recommended +that rows in a variable length field should only be written once. +An exception to +this general rule occurs when setting elements of an array as +undefined. It is allowed to first write a dummy value into the array with +fits\_write\_col, and then call fits\_write\_col\_nul to flag the +desired elements as undefined. Note that the rows of a table, +whether fixed or variable length, do not have to be written +consecutively and may be written in any order. + +When writing to a variable length ASCII character field (e.g., TFORM = +'1PA') only a single character string can be written. The `firstelem' +and `nelements' parameter values in the fits\_write\_col routine are +ignored and the number of characters to write is simply determined by +the length of the input null-terminated character string. + +The fits\_write\_descript routine is useful in situations where +multiple rows of a variable length column have the identical array of +values. One can simply write the array once for the first row, and +then use fits\_write\_descript to write the same descriptor values into +the other rows; all the rows will then point to the same storage +location thus saving disk space. + +When reading from a variable length array field one can only read as +many elements as actually exist in that row of the table; reading does +not automatically continue with the next row of the table as occurs +when reading an ordinary fixed length table field. Attempts to read +more than this will cause an error status to be returned. One can +determine the number of elements in each row of a variable column with +the fits\_read\_descript routine. + + +\section{Multiple Access to the Same FITS File} + +CFITSIO supports simultaneous read and write access to multiple HDUs in +the same FITS file. Thus, one can open the same FITS file twice within +a single program and move to 2 different HDUs in the file, and then +read and write data or keywords to the 2 extensions just as if one were +accessing 2 completely separate FITS files. Since in general it is +not possible to physically open the same file twice and then expect to +be able to simultaneously (or in alternating succession) write to 2 +different locations in the file, CFITSIO recognizes when the file to be +opened (in the call to fits\_open\_file) has already been opened and +instead of actually opening the file again, just logically links the +new file to the old file. (This of course does not prevent the same +file from being simultaneously opened by more than one program). Then +before CFITSIO reads or writes to either (logical) file, it makes sure +that any modifications made to the other file have been completely +flushed from the internal buffers to the file. Thus, in principle, one +could open a file twice, in one case pointing to the first extension +and in the other pointing to the 2nd extension and then write data to +both extensions, in any order, without danger of corrupting the file. +There may be some efficiency penalties in doing this however, since +CFITSIO has to flush all the internal buffers related to one file +before switching to the other, so it would still be prudent to +minimize the number of times one switches back and forth between doing +I/O to different HDUs in the same file. + +Some restriction apply: a FITS file cannot be opened the first time +with READONLY access, and then opened a second time with READWRITE access, +because this may be phyically impossible (e.g., if the file resides +on read-only media such as a CDROM). Also, in multi-threaded environoments, +one should never open the same file with write access in different threads. + + +\section{When the Final Size of the FITS HDU is Unknown} + +It is not required to know the total size of a FITS data array or table +before beginning to write the data to the FITS file. In the case of +the primary array or an image extension, one should initially create +the array with the size of the highest dimension (largest NAXISn +keyword) set to a dummy value, such as 1. Then after all the data have +been written and the true dimensions are known, then the NAXISn value +should be updated using the fits\_update\_key routine before moving to +another extension or closing the FITS file. + +When writing to FITS tables, CFITSIO automatically keeps track of the +highest row number that is written to, and will increase the size of +the table if necessary. CFITSIO will also automatically insert space +in the FITS file if necessary, to ensure that the data 'heap', if it +exists, and/or any additional HDUs that follow the table do not get +overwritten as new rows are written to the table. + +As a general rule it is best to specify the initial number of rows = 0 +when the table is created, then let CFITSIO keep track of the number of +rows that are actually written. The application program should not +manually update the number of rows in the table (as given by the NAXIS2 +keyword) since CFITSIO does this automatically. If a table is +initially created with more than zero rows, then this will usually be +considered as the minimum size of the table, even if fewer rows are +actually written to the table. Thus, if a table is initially created +with NAXIS2 = 20, and CFITSIO only writes 10 rows of data before +closing the table, then NAXIS2 will remain equal to 20. If however, 30 +rows of data are written to this table, then NAXIS2 will be increased +from 20 to 30. The one exception to this automatic updating of the +NAXIS2 keyword is if the application program directly modifies the +value of NAXIS2 (up or down) itself just before closing the table. In this +case, CFITSIO does not update NAXIS2 again, since it assumes that the +application program must have had a good reason for changing the value +directly. This is not recommended, however, and is only provided for +backward compatibility with software that initially creates a table +with a large number of rows, than decreases the NAXIS2 value to the +actual smaller value just before closing the table. + + +\section{CFITSIO Size Limitations} + +CFITSIO places very few restrictions on the size of FITS files that it +reads or writes. There are a few limits, however, that may affect +some extreme cases: + +1. The maximum number of FITS files that may be simultaneously opened +by CFITSIO is set by NMAXFILES as defined in fitsio2.h. It is currently +set = 300 by default. CFITSIO will allocate about 80 * NMAXFILES bytes +of memory for internal use. Note that the underlying C compiler or +operating system, may have a smaller limit on the number of opened files. +The C symbolic constant FOPEN\_MAX is intended to define the maximum +number of files that may open at once (including any other text or +binary files that may be open, not just FITS files). On some systems it +has been found that gcc supports a maximum of 255 opened files. + +2. It used to be common for computer systems to only support disk files up +to 2**31 bytes = 2.1 GB in size, but most systems now support larger files. +CFITSIO can optionally read and write these so-called 'large files' that +are greater than 2.1 GB on +platforms where they are supported, but this +usually requires that special compiler option flags be specified to turn +on this option. On linux and solaris systems the compiler flags are +'-D\_LARGEFILE\_SOURCE' and `-D\_FILE\_OFFSET\_BITS=64'. These flags +may also work on other platforms but this has not been tested. Starting +with version 3.0 of CFITSIO, the default Makefile that is distributed +with CFITSIO will include these 2 compiler flags when building on Solaris +and Linux PC systems. Users on other platforms will need to add these +compiler flags manually if they want to support large files. In most +cases it appears that it is not necessary to include these compiler +flags when compiling application code that call the CFITSIO library +routines. + +When CFITSIO is built with large file support (e.g., on Solaris and +Linux PC system by default) then it can read and write FITS data files +on disk that have any of these conditions: + +\begin{itemize} +\item +FITS files larger than 2.1 GB in size +\item +FITS images containing greater than 2.1 G pixels +\item +FITS images that have one dimension with more than 2.1 G pixels +(as given by one of the NAXISn keyword) +\item +FITS tables containing more than 2.1E09 rows (given by the NAXIS2 keyword), +or with rows that are more than 2.1 GB wide (given by the NAXIS1 keyword) +\item +FITS binary tables with a variable-length array heap that is larger +than 2.1 GB (given by the PCOUNT keyword) +\end{itemize} + +The current maximum FITS file size supported by CFITSIO +is about 6 terabytes (containing +2**31 FITS blocks, each 2880 bytes in size). Currently, support for large +files in CFITSIO has been tested on the Linux, Solaris, and IBM AIX +operating systems. + +Note that when writing application programs that are intended to support +large files it is important to use 64-bit integer variables +to store quantities such as the dimensions of images, or the number of +rows in a table. These programs must also call the special versions +of some of the CFITSIO routines that have been adapted to +support 64-bit integers. The names of these routines end in +'ll' ('el' 'el') to distinguish them from the 32-bit integer +version (e.g., fits\_get\_num\_rowsll). + + +\chapter{Basic CFITSIO Interface Routines } + +This chapter describes the basic routines in the CFITSIO user interface +that provide all the functions normally needed to read and write most +FITS files. It is recommended that these routines be used for most +applications and that the more advanced routines described in the +next chapter only be used in special circumstances when necessary. + +The following conventions are used in this chapter in the description +of each function: + +1. Most functions have 2 names: a long descriptive name and a short +concise name. Both names are listed on the first line of the following +descriptions, separated by a slash (/) character. Programmers may use +either name in their programs but the long names are recommended to +help document the code and make it easier to read. + +2. A right arrow symbol ($>$) is used in the function descriptions to +separate the input parameters from the output parameters in the +definition of each routine. This symbol is not actually part of the C +calling sequence. + +3. The function parameters are defined in more detail in the +alphabetical listing in Appendix B. + +4. The first argument in almost all the functions is a pointer to a +structure of type `fitsfile'. Memory for this structure is allocated +by CFITSIO when the FITS file is first opened or created and is freed +when the FITS file is closed. + +5. The last argument in almost all the functions is the error status +parameter. It must be equal to 0 on input, otherwise the function will +immediately exit without doing anything. A non-zero output value +indicates that an error occurred in the function. In most cases the +status value is also returned as the value of the function itself. + + +\section{CFITSIO Error Status Routines} + + +\begin{description} +\item[1 ] Return a descriptive text string (30 char max.) corresponding to + a CFITSIO error status code.\label{ffgerr} +\end{description} + +\begin{verbatim} + void fits_get_errstatus / ffgerr (int status, > char *err_text) +\end{verbatim} + +\begin{description} +\item[2 ] Return the top (oldest) 80-character error message from the + internal CFITSIO stack of error messages and shift any remaining + messages on the stack up one level. Call this routine + repeatedly to get each message in sequence. The function returns + a value = 0 and a null error message when the error stack is empty. +\label{ffgmsg} +\end{description} + +\begin{verbatim} + int fits_read_errmsg / ffgmsg (char *err_msg) +\end{verbatim} + +\begin{description} +\item[3 ] Print out the error message corresponding to the input status + value and all the error messages on the CFITSIO stack to the specified + file stream (normally to stdout or stderr). If the input + status value = 0 then this routine does nothing. +\label{ffrprt} +\end{description} + +\begin{verbatim} + void fits_report_error / ffrprt (FILE *stream, status) +\end{verbatim} + +\begin{description} +\item[4 ]The fits\_write\_errmark routine puts an invisible marker on the + CFITSIO error stack. The fits\_clear\_errmark routine can then be + used to delete any more recent error messages on the stack, back to + the position of the marker. This preserves any older error messages + on the stack. The fits\_clear\_errmsg routine simply clears all the + messages (and marks) from the stack. These routines are called + without any arguments. +\label{ffpmrk} \label{ffcmsg} +\end{description} + +\begin{verbatim} + void fits_write_errmark / ffpmrk (void) + void fits_clear_errmark / ffcmrk (void) + void fits_clear_errmsg / ffcmsg (void) +\end{verbatim} + + +\section{FITS File Access Routines} + + +\begin{description} +\item[1 ] Open an existing data file. \label{ffopen} + + +\begin{verbatim} +int fits_open_file / ffopen + (fitsfile **fptr, char *filename, int iomode, > int *status) + +int fits_open_diskfile / ffdkopen + (fitsfile **fptr, char *filename, int iomode, > int *status) + +int fits_open_data / ffdopn + (fitsfile **fptr, char *filename, int iomode, > int *status) + +int fits_open_table / fftopn + (fitsfile **fptr, char *filename, int iomode, > int *status) + +int fits_open_image / ffiopn + (fitsfile **fptr, char *filename, int iomode, > int *status) +\end{verbatim} + +The iomode parameter determines the read/write access allowed in the +file and can have values of READONLY (0) or READWRITE (1). The filename +parameter gives the name of the file to be opened, followed by an +optional argument giving the name or index number of the extension +within the FITS file that should be moved to and opened (e.g., +\verb-myfile.fits+3- or \verb-myfile.fits[3]- moves to the 3rd extension within +the file, and \verb-myfile.fits[events]- moves to the extension with the +keyword EXTNAME = 'EVENTS'). + +The fits\_open\_diskfile routine is similar to the fits\_open\_file routine +except that it does not support the extended filename syntax in the input +file name. This routine simply tries to open the specified input file +on magnetic disk. This routine is mainly for use in cases where the +filename (or directory path) contains square or curly bracket characters +that would confuse the extended filename parser. + +The fits\_open\_data routine is similar to the fits\_open\_file routine +except that it will move to the first HDU containing significant data, +if a HDU name or number to open was not explicitly specified as +part of the filename. In this case, it will look for the first +IMAGE HDU with NAXIS greater than 0, or the first table that does not contain the +strings `GTI' (Good Time Interval extension) or `OBSTABLE' in the +EXTNAME keyword value. + +The fits\_open\_table and fits\_open\_image routines are similar to +fits\_open\_data except they will move to the first significant table +HDU or image HDU in the file, respectively, if a HDU name or +number is not specified as part of the filename. + +IRAF images (.imh format files) and raw binary data arrays may also be +opened with READONLY access. CFITSIO will automatically test if the +input file is an IRAF image, and if, so will convert it on the fly into +a virtual FITS image before it is opened by the application program. +If the input file is a raw binary data array of numbers, then the data type +and dimensions of the array must be specified in square brackets +following the name of the file (e.g. 'rawfile.dat[i512,512]' opens a +512 x 512 short integer image). See the `Extended File Name Syntax' +chapter for more details on how to specify the raw file name. The raw +file is converted on the fly into a virtual FITS image in memory that +is then opened by the application program with READONLY access. + +Programs can read the input file from the 'stdin' file stream if a dash +character ('-') is given as the filename. Files can also be opened over +the network using FTP or HTTP protocols by supplying the appropriate URL +as the filename. + +The input file can be modified in various ways to create a virtual file +(usually stored in memory) that is then opened by the application +program by supplying a filtering or binning specifier in square brackets +following the filename. Some of the more common filtering methods are +illustrated in the following paragraphs, but users should refer to the +'Extended File Name Syntax' chapter for a complete description of +the full file filtering syntax. + +When opening an image, a rectangular subset of the physical image may be +opened by listing the first and last pixel in each dimension (and +optional pixel skipping factor): + +\begin{verbatim} +myimage.fits[101:200,301:400] +\end{verbatim} +will create and open a 100x100 pixel virtual image of that section of +the physical image, and \verb+myimage.fits[*,-*]+ opens a virtual image +that is the same size as the physical image but has been flipped in +the vertical direction. + +When opening a table, the filtering syntax can be used to add or delete +columns or keywords in the virtual table: +\verb-myfile.fits[events][col !time; PI = PHA*1.2]- opens a virtual table in which the TIME column +has been deleted and a new PI column has been added with a value 1.2 +times that of the PHA column. Similarly, one can filter a table to keep +only those rows that satisfy a selection criterion: +\verb-myfile.fits[events][pha > 50]- creates and opens a virtual table +containing only those rows with a PHA value greater than 50. A large +number of boolean and mathematical operators can be used in the +selection expression. One can also filter table rows using 'Good Time +Interval' extensions, and spatial region filters as in +\verb-myfile.fits[events][gtifilter()]- and +\verb-myfile.fits[events][regfilter( "stars.rng")]-. + +Finally, table columns may be binned or histogrammed to generate a +virtual image. For example, \verb-myfile.fits[events][bin (X,Y)=4]- will +result in a 2-dimensional image calculated by binning the X and Y +columns in the event table with a bin size of 4 in each dimension. The +TLMINn and TLMAXn keywords will be used by default to determine the +range of the image. + +A single program can open the same FITS file more than once and then +treat the resulting fitsfile pointers as though they were completely +independent FITS files. Using this facility, a program can open a FITS +file twice, move to 2 different extensions within the file, and then + read and write data in those extensions in any order. +\end{description} + + +\begin{description} +\item[2 ] Create and open a new empty output FITS file. \label{ffinit} + + +\begin{verbatim} +int fits_create_file / ffinit + (fitsfile **fptr, char *filename, > int *status) + +int fits_create_diskfile / ffdkinit + (fitsfile **fptr, char *filename, > int *status) +\end{verbatim} + +An error will be returned if the specified file already exists, unless +the filename is prefixed with an exclamation point (!). In that case +CFITSIO will overwrite (delete) any existing file with the same name. +Note that the exclamation point is a special UNIX character so if +it is used on the command line it must be preceded by a backslash to +force the UNIX shell to accept the character as part of the filename. + +The output file will be written to the 'stdout' file stream if a dash +character ('-') or the string 'stdout' is given as the filename. Similarly, +'-.gz' or 'stdout.gz' will cause the file to be gzip compressed before +it is written out to the stdout stream. + +Optionally, the name of a template file that is used to define the +structure of the new file may be specified in parentheses following the +output file name. The template file may be another FITS file, in which +case the new file, at the time it is opened, will be an exact copy of +the template file except that the data structures (images and tables) +will be filled with zeros. Alternatively, the template file may be an +ASCII format text file containing directives that define the keywords to be +created in each HDU of the file. See the 'Extended File Name Syntax' + section for a complete description of the template file syntax. + +The fits\_create\_diskfile routine is similar to the fits\_create\_file routine +except that it does not support the extended filename syntax in the input +file name. This routine simply tries to create the specified file +on magnetic disk. This routine is mainly for use in cases where the +filename (or directory path) contains square or curly bracket characters + that would confuse the extended filename parser. +\end{description} + + + +\begin{description} +\item[3 ] Close a previously opened FITS file. The first routine simply +closes the file, whereas the second one also DELETES THE FILE, which +can be useful in cases where a FITS file has been partially created, +but then an error occurs which prevents it from being completed. + \label{ffclos} \label{ffdelt} +\end{description} + +\begin{verbatim} + int fits_close_file / ffclos (fitsfile *fptr, > int *status) + + int fits_delete_file / ffdelt (fitsfile *fptr, > int *status) +\end{verbatim} + +\begin{description} +\item[4 ]Return the name, I/O mode (READONLY or READWRITE), and/or the file +type (e.g. 'file://', 'ftp://') of the opened FITS file. \label{ffflnm} + \label{ffflmd} \label{ffurlt} +\end{description} + +\begin{verbatim} + int fits_file_name / ffflnm (fitsfile *fptr, > char *filename, int *status) + + int fits_file_mode / ffflmd (fitsfile *fptr, > int *iomode, int *status) + + int fits_url_type / ffurlt (fitsfile *fptr, > char *urltype, int *status) +\end{verbatim} + +\section{HDU Access Routines} + +The following functions perform operations on Header-Data Units (HDUs) +as a whole. + + +\begin{description} +\item[1 ] Move to a different HDU in the file. The first routine moves to a + specified absolute HDU number (starting with 1 for the primary + array) in the FITS file, and the second routine moves a relative + number HDUs forward or backward from the current HDU. A null + pointer may be given for the hdutype parameter if it's value is not + needed. The third routine moves to the (first) HDU which has the + specified extension type and EXTNAME and EXTVER keyword values (or + HDUNAME and HDUVER keywords). The hdutype parameter may have a + value of IMAGE\_HDU, ASCII\_TBL, BINARY\_TBL, or ANY\_HDU where + ANY\_HDU means that only the extname and extver values will be used + to locate the correct extension. If the input value of extver is 0 + then the EXTVER keyword is ignored and the first HDU with a + matching EXTNAME (or HDUNAME) keyword will be found. If no + matching HDU is found in the file then the current HDU will remain + unchanged and a status = BAD\_HDU\_NUM will be returned. + \label{ffmahd} \label{ffmrhd} \label{ffmnhd} +\end{description} + +\begin{verbatim} + int fits_movabs_hdu / ffmahd + (fitsfile *fptr, int hdunum, > int *hdutype, int *status) + + int fits_movrel_hdu / ffmrhd + (fitsfile *fptr, int nmove, > int *hdutype, int *status) + + int fits_movnam_hdu / ffmnhd + (fitsfile *fptr, int hdutype, char *extname, int extver, > int *status) +\end{verbatim} + +\begin{description} +\item[2 ] Return the total number of HDUs in the FITS file. This returns the +number of completely defined HDUs in the file. If a new HDU has just been added to +the FITS file, then that last HDU will only be counted if it has been closed, +or if data has been written to the HDU. + The current HDU remains unchanged by this routine. \label{ffthdu} +\end{description} + +\begin{verbatim} + int fits_get_num_hdus / ffthdu + (fitsfile *fptr, > int *hdunum, int *status) +\end{verbatim} + +\begin{description} +\item[3 ] Return the number of the current HDU (CHDU) in the FITS file (where + the primary array = 1). This function returns the HDU number + rather than a status value. \label{ffghdn} +\end{description} + +\begin{verbatim} + int fits_get_hdu_num / ffghdn + (fitsfile *fptr, > int *hdunum) +\end{verbatim} + +\begin{description} +\item[4 ] Return the type of the current HDU in the FITS file. The possible + values for hdutype are: IMAGE\_HDU, ASCII\_TBL, or BINARY\_TBL. \label{ffghdt} +\end{description} + +\begin{verbatim} + int fits_get_hdu_type / ffghdt + (fitsfile *fptr, > int *hdutype, int *status) +\end{verbatim} + +\begin{description} +\item[5 ] Copy all or part of the HDUs in the FITS file associated with infptr + and append them to the end of the FITS file associated with + outfptr. If 'previous' is true (not 0), then any HDUs preceding + the current HDU in the input file will be copied to the output + file. Similarly, 'current' and 'following' determine whether the + current HDU, and/or any following HDUs in the input file will be + copied to the output file. Thus, if all 3 parameters are true, then the + entire input file will be copied. On exit, the current HDU in + the input file will be unchanged, and the last HDU in the output + file will be the current HDU. \label{ffcpfl} +\end{description} + +\begin{verbatim} + int fits_copy_file / ffcpfl + (fitsfile *infptr, fitsfile *outfptr, int previous, int current, + int following, > int *status) +\end{verbatim} + +\begin{description} +\item[6 ] Copy the current HDU from the FITS file associated with infptr and append it + to the end of the FITS file associated with outfptr. Space may be + reserved for MOREKEYS additional keywords in the output header. \label{ffcopy} +\end{description} + +\begin{verbatim} + int fits_copy_hdu / ffcopy + (fitsfile *infptr, fitsfile *outfptr, int morekeys, > int *status) +\end{verbatim} + +\begin{description} +\item[7 ] Write the current HDU in the input FITS file to the + output FILE stream (e.g., to stdout). \label{ffwrhdu} +\end{description} + +\begin{verbatim} + int fits_write_hdu / ffwrhdu + (fitsfile *infptr, FILE *stream, > int *status) +\end{verbatim} + +\begin{description} +\item[8 ] Copy the header (and not the data) from the CHDU associated with infptr + to the CHDU associated with outfptr. If the current output HDU + is not completely empty, then the CHDU will be closed and a new + HDU will be appended to the output file. An empty output data unit + will be created with all values initially = 0). \label{ffcphd} +\end{description} + +\begin{verbatim} + int fits_copy_header / ffcphd + (fitsfile *infptr, fitsfile *outfptr, > int *status) +\end{verbatim} + +\begin{description} +\item[9 ] Delete the CHDU in the FITS file. Any following HDUs will be shifted + forward in the file, to fill in the gap created by the deleted + HDU. In the case of deleting the primary array (the first HDU in + the file) then the current primary array will be replace by a null + primary array containing the minimum set of required keywords and + no data. If there are more extensions in the file following the + one that is deleted, then the the CHDU will be redefined to point + to the following extension. If there are no following extensions + then the CHDU will be redefined to point to the previous HDU. The + output hdutype parameter returns the type of the new CHDU. A null + pointer may be given for + hdutype if the returned value is not needed. \label{ffdhdu} +\end{description} + +\begin{verbatim} + int fits_delete_hdu / ffdhdu + (fitsfile *fptr, > int *hdutype, int *status) +\end{verbatim} + +\section{Header Keyword Read/Write Routines} + +These routines read or write keywords in the Current Header Unit +(CHU). Wild card characters (*, ?, or \#) may be used when specifying +the name of the keyword to be read: a '?' will match any single +character at that position in the keyword name and a '*' will match any +length (including zero) string of characters. The '\#' character will +match any consecutive string of decimal digits (0 - 9). When a wild +card is used the routine will only search for a match from the current +header position to the end of the header and will not resume the search +from the top of the header back to the original header position as is +done when no wildcards are included in the keyword name. The +fits\_read\_record routine may be used to set the starting position +when doing wild card searches. A status value of KEY\_NO\_EXIST is +returned if the specified keyword to be read is not found in the +header. + + +\subsection{Keyword Reading Routines} + + +\begin{description} +\item[1 ] Return the number of existing keywords (not counting the + END keyword) and the amount of space currently available for more + keywords. It returns morekeys = -1 if the header has not yet been + closed. Note that CFITSIO will dynamically add space if required + when writing new keywords to a header so in practice there is no + limit to the number of keywords that can be added to a header. A + null pointer may be entered for the morekeys parameter if it's + value is not needed. \label{ffghsp} +\end{description} + +\begin{verbatim} + int fits_get_hdrspace / ffghsp + (fitsfile *fptr, > int *keysexist, int *morekeys, int *status) +\end{verbatim} + +\begin{description} +\item[2 ] Return the specified keyword. In the first routine, + the datatype parameter specifies the desired returned data type of the + keyword value and can have one of the following symbolic constant + values: TSTRING, TLOGICAL (== int), TBYTE, TSHORT, TUSHORT, TINT, + TUINT, TLONG, TULONG, TLONGLONG, TFLOAT, TDOUBLE, TCOMPLEX, and TDBLCOMPLEX. + Within the context of this routine, TSTRING corresponds to a + 'char*' data type, i.e., a pointer to a character array. Data type + conversion will be performed for numeric values if the keyword + value does not have the same data type. If the value of the keyword + is undefined (i.e., the value field is blank) then an error status + = VALUE\_UNDEFINED will be returned. + + The second routine returns the keyword value as a character string + (a literal copy of what is in the value field) regardless of the + intrinsic data type of the keyword. The third routine returns + the entire 80-character header record of the keyword, with any + trailing blank characters stripped off. The fourth routine returns + the (next) header record that contains the literal string of characters + specified by the 'string' argument. + + If a NULL comment pointer is supplied then the comment string + will not be returned. \label{ffgky} \label{ffgkey} \label{ffgcrd} +\end{description} + +\begin{verbatim} + int fits_read_key / ffgky + (fitsfile *fptr, int datatype, char *keyname, > DTYPE *value, + char *comment, int *status) + + int fits_read_keyword / ffgkey + (fitsfile *fptr, char *keyname, > char *value, char *comment, + int *status) + + int fits_read_card / ffgcrd + (fitsfile *fptr, char *keyname, > char *card, int *status) + + int fits_read_str / ffgstr + (fitsfile *fptr, char *string, > char *card, int *status) +\end{verbatim} + +\begin{description} +\item[3 ] Return the nth header record in the CHU. The first keyword + in the header is at keynum = 1; if keynum = 0 then these routines + simply reset the internal CFITSIO pointer to the beginning of the header + so that subsequent keyword operations will start at the top of the + header (e.g., prior to searching for keywords using wild cards in + the keyword name). The first routine returns the entire + 80-character header record (with trailing blanks truncated), + while the second routine parses the record and returns the name, + value, and comment fields as separate (blank truncated) + character strings. If a NULL comment pointer is given on input, + then the comment string will not be + returned. \label{ffgrec} \label{ffgkyn} +\end{description} + +\begin{verbatim} + int fits_read_record / ffgrec + (fitsfile *fptr, int keynum, > char *card, int *status) + + int fits_read_keyn / ffgkyn + (fitsfile *fptr, int keynum, > char *keyname, char *value, + char *comment, int *status) +\end{verbatim} + +\begin{description} +\item[4 ] Return the next keyword whose name matches one of the strings in + 'inclist' but does not match any of the strings in 'exclist'. + The strings in inclist and exclist may contain wild card characters + (*, ?, and \#) as described at the beginning of this section. + This routine searches from the current header position to the + end of the header, only, and does not continue the search from + the top of the header back to the original position. The current + header position may be reset with the ffgrec routine. Note + that nexc may be set = 0 if there are no keywords to be excluded. + This routine returns status = KEY\_NO\_EXIST if a matching + keyword is not found. \label{ffgnxk} +\end{description} + +\begin{verbatim} + int fits_find_nextkey / ffgnxk + (fitsfile *fptr, char **inclist, int ninc, char **exclist, + int nexc, > char *card, int *status) +\end{verbatim} + +\begin{description} +\item[5 ] Return the physical units string from an existing keyword. This + routine uses a local convention, shown in the following example, + in which the keyword units are enclosed in square brackets in the + beginning of the keyword comment field. A null string is returned + if no units are defined for the keyword. \label{ffgunt} +\end{description} + +\begin{verbatim} + VELOCITY= 12.3 / [km/s] orbital speed + + int fits_read_key_unit / ffgunt + (fitsfile *fptr, char *keyname, > char *unit, int *status) +\end{verbatim} + +\begin{description} +\item[6 ] Concatenate the header keywords in the CHDU into a single long + string of characters. This provides a convenient way of passing + all or part of the header information in a FITS HDU to other subroutines. + Each 80-character fixed-length keyword record is appended to the + output character string, in order, with no intervening separator or + terminating characters. The last header record is terminated with + a NULL character. These routine allocates memory for the returned + character array, so the calling program must free the memory when + finished. The cleanest way to do this is to + call the fits\_free\_memory routine. + + There are 2 related routines: fits\_hdr2str simply concatenates all + the existing keywords in the header; fits\_convert\_hdr2str is similar, + except that if the CHDU is a tile compressed image (stored in a binary + table) then it will first convert that header back to that of the corresponding + normal FITS image before concatenating the keywords. + + Selected keywords may be excluded from the returned character string. + If the second parameter (nocomments) is TRUE (nonzero) then any + COMMENT, HISTORY, or blank keywords in the header will not be copied + to the output string. + + The 'exclist' parameter may be used to supply a list of keywords + that are to be excluded from the output character string. Wild card + characters (*, ?, and \#) may be used in the excluded keyword names. + If no additional keywords are to be excluded, then set nexc = 0 and + specify NULL for the the **exclist parameter. \label{ffhdr2str} +\end{description} + +\begin{verbatim} + int fits_hdr2str / ffhdr2str + (fitsfile *fptr, int nocomments, char **exclist, int nexc, + > char **header, int *nkeys, int *status) + + int fits_convert_hdr2str / ffcnvthdr2str + (fitsfile *fptr, int nocomments, char **exclist, int nexc, + > char **header, int *nkeys, int *status) +\end{verbatim} + + +\subsection{Keyword Writing Routines} + + +\begin{description} +\item[1 ] Write a keyword of the appropriate data type into the + CHU. The first routine simply appends a new keyword whereas the + second routine will update the value and comment fields of the + keyword if it already exists, otherwise it appends a new + keyword. Note that the address to the value, and not the value + itself, must be entered. The datatype parameter specifies the + data type of the keyword value with one of the following values: + TSTRING, TLOGICAL (== int), TBYTE, TSHORT, TUSHORT, TINT, TUINT, + TLONG, TLONGLONG, TULONG, TFLOAT, TDOUBLE. Within the context of this + routine, TSTRING corresponds to a 'char*' data type, i.e., a pointer + to a character array. A null pointer may be entered for the + comment parameter in which case the keyword comment + field will be unmodified or left blank. \label{ffpky} \label{ffuky} +\end{description} + +\begin{verbatim} + int fits_write_key / ffpky + (fitsfile *fptr, int datatype, char *keyname, DTYPE *value, + char *comment, > int *status) + + int fits_update_key / ffuky + (fitsfile *fptr, int datatype, char *keyname, DTYPE *value, + char *comment, > int *status) +\end{verbatim} + +\begin{description} +\item[2 ] Write a keyword with a null or undefined value (i.e., the + value field in the keyword is left blank). The first routine + simply appends a new keyword whereas the second routine will update + the value and comment fields of the keyword if it already exists, + otherwise it appends a new keyword. A null pointer may be + entered for the comment parameter in which case the keyword + comment + field will be unmodified or left blank. \label{ffpkyu} \label{ffukyu} +\end{description} + +\begin{verbatim} + int fits_write_key_null / ffpkyu + (fitsfile *fptr, char *keyname, char *comment, > int *status) + + int fits_update_key_null / ffukyu + (fitsfile *fptr, char *keyname, char *comment, > int *status) +\end{verbatim} + +\begin{description} +\item[3 ] Write (append) a COMMENT or HISTORY keyword to the CHU. The comment or + history string will be continued over multiple keywords if it is longer + than 70 characters. \label{ffpcom} \label{ffphis} +\end{description} + +\begin{verbatim} + int fits_write_comment / ffpcom + (fitsfile *fptr, char *comment, > int *status) + + int fits_write_history / ffphis + (fitsfile *fptr, char *history, > int *status) +\end{verbatim} + +\begin{description} +\item[4 ] Write the DATE keyword to the CHU. The keyword value will contain + the current system date as a character string in 'yyyy-mm-ddThh:mm:ss' + format. If a DATE keyword already exists in the header, then this + routine will simply update the keyword value with the current date. + \label{ffpdat} +\end{description} + +\begin{verbatim} + int fits_write_date / ffpdat + (fitsfile *fptr, > int *status) +\end{verbatim} + +\begin{description} +\item[5 ]Write a user specified keyword record into the CHU. This is + a low--level routine which can be used to write any arbitrary + record into the header. The record must conform to the all + the FITS format requirements. \label{ffprec} +\end{description} + +\begin{verbatim} + int fits_write_record / ffprec + (fitsfile *fptr, char *card, > int *status) +\end{verbatim} + +\begin{description} +\item[6 ]Update an 80-character record in the CHU. If a keyword with the input + name already exists, then it is overwritten by the value of card. This + could modify the keyword name as well as the value and comment fields. + If the keyword doesn't already exist then a new keyword card is appended + to the header. \label{ffucrd} +\end{description} + +\begin{verbatim} + int fits_update_card / ffucrd + (fitsfile *fptr, char *keyname, char *card, > int *status) +\end{verbatim} + + +\begin{description} +\item[7 ] Modify (overwrite) the comment field of an existing keyword. \label{ffmcom} +\end{description} + +\begin{verbatim} + int fits_modify_comment / ffmcom + (fitsfile *fptr, char *keyname, char *comment, > int *status) +\end{verbatim} + + +\begin{description} +\item[8 ] Write the physical units string into an existing keyword. This + routine uses a local convention, shown in the following example, + in which the keyword units are enclosed in square brackets in the + beginning of the keyword comment field. \label{ffpunt} +\end{description} + +\begin{verbatim} + VELOCITY= 12.3 / [km/s] orbital speed + + int fits_write_key_unit / ffpunt + (fitsfile *fptr, char *keyname, char *unit, > int *status) +\end{verbatim} + +\begin{description} +\item[9 ] Rename an existing keyword, preserving the current value + and comment fields. \label{ffmnam} +\end{description} + +\begin{verbatim} + int fits_modify_name / ffmnam + (fitsfile *fptr, char *oldname, char *newname, > int *status) +\end{verbatim} + +\begin{description} +\item[10] Delete a keyword record. The space occupied by + the keyword is reclaimed by moving all the following header records up + one row in the header. The first routine deletes a keyword at a + specified position in the header (the first keyword is at position 1), + whereas the second routine deletes a specifically named keyword. + Wild card characters may be used when specifying the name of the keyword + to be deleted. The third routine deletes the (next) keyword that contains + the literal character string specified by the 'string' + argument.\label{ffdrec} \label{ffdkey} +\end{description} + +\begin{verbatim} + int fits_delete_record / ffdrec + (fitsfile *fptr, int keynum, > int *status) + + int fits_delete_key / ffdkey + (fitsfile *fptr, char *keyname, > int *status) + + int fits_delete_str / ffdstr + (fitsfile *fptr, char *string, > int *status) +\end{verbatim} + +\section{Primary Array or IMAGE Extension I/O Routines} + +These routines read or write data values in the primary data array (i.e., +the first HDU in a FITS file) or an IMAGE extension. There are also +routines to get information about the data type and size of the image. +Users should also read the following chapter on the CFITSIO iterator +function which provides a more `object oriented' method of reading and +writing images. The iterator function is a little more complicated to +use, but the advantages are that it usually takes less code to perform +the same operation, and the resulting program often runs faster because +the FITS files are read and written using the most efficient block size. + +C programmers should note that the ordering of arrays in FITS files, and +hence in all the CFITSIO calls, is more similar to the dimensionality +of arrays in Fortran rather than C. For instance if a FITS image has +NAXIS1 = 100 and NAXIS2 = 50, then a 2-D array just large enough to hold +the image should be declared as array[50][100] and not as array[100][50]. + +The `datatype' parameter specifies the data type of the `nulval' and +`array' pointers and can have one of the following values: TBYTE, +TSBYTE, TSHORT, TUSHORT, TINT, TUINT, TLONG, TLONGLONG, TULONG, TFLOAT, +TDOUBLE. Automatic data type conversion is performed if the data type +of the FITS array (as defined by the BITPIX keyword) differs from that +specified by 'datatype'. The data values are also automatically scaled +by the BSCALE and BZERO keyword values as they are being read or written +in the FITS array. + + +\begin{description} +\item[1 ] Get the data type or equivalent data type of the image. The + first routine returns the physical data type of the FITS image, as + given by the BITPIX keyword, with allowed values of BYTE\_IMG (8), + SHORT\_IMG (16), LONG\_IMG (32), LONGLONG\_IMG (64), + FLOAT\_IMG (-32), and DOUBLE\_IMG + (-64). + The second routine is similar, except that if the image pixel + values are scaled, with non-default values for the BZERO and BSCALE + keywords, then the routine will return the 'equivalent' data type + that is needed to store the scaled values. For example, if BITPIX + = 16 and BSCALE = 0.1 then the equivalent data type is FLOAT\_IMG. + Similarly if BITPIX = 16, BSCALE = 1, and BZERO = 32768, then the + the pixel values span the range of an unsigned short integer and + the returned data type will be USHORT\_IMG. \label{ffgidt} +\end{description} + +\begin{verbatim} + int fits_get_img_type / ffgidt + (fitsfile *fptr, > int *bitpix, int *status) + + int fits_get_img_equivtype / ffgiet + (fitsfile *fptr, > int *bitpix, int *status) +\end{verbatim} + +\begin{description} +\item[2 ] Get the number of dimensions, and/or the size of + each dimension in the image . The number of axes in the image is + given by naxis, and the size of each dimension is given by the + naxes array (a maximum of maxdim dimensions will be returned). + \label{ffgidm} \label{ffgisz} \label{ffgipr} +\end{description} + +\begin{verbatim} + int fits_get_img_dim / ffgidm + (fitsfile *fptr, > int *naxis, int *status) + + int fits_get_img_size / ffgisz + (fitsfile *fptr, int maxdim, > long *naxes, int *status) + + int fits_get_img_sizell / ffgiszll + (fitsfile *fptr, int maxdim, > LONGLONG *naxes, int *status) + + int fits_get_img_param / ffgipr + (fitsfile *fptr, int maxdim, > int *bitpix, int *naxis, long *naxes, + int *status) + + int fits_get_img_paramll / ffgiprll + (fitsfile *fptr, int maxdim, > int *bitpix, int *naxis, LONGLONG *naxes, + int *status) +\end{verbatim} + +\begin{description} +\item[3 ]Create a new primary array or IMAGE extension with a specified + data type and size. If the FITS file is currently empty then a + primary array is created, otherwise a new IMAGE extension is + appended to the file. \label{ffcrim} +\end{description} + +\begin{verbatim} + int fits_create_img / ffcrim + ( fitsfile *fptr, int bitpix, int naxis, long *naxes, > int *status) + + int fits_create_imgll / ffcrimll + ( fitsfile *fptr, int bitpix, int naxis, LONGLONG *naxes, > int *status) +\end{verbatim} + +\begin{description} +\item[4 ] Copy an n-dimensional image in a particular row and column of a + binary table (in a vector column) + to or from a primary array or image extension. + + The 'cell2image' routine + will append a new image extension (or primary array) to the output file. + Any WCS keywords associated with the input column image will be translated + into the appropriate form for an image extension. Any other keywords + in the table header that are not specifically related to defining the + binary table structure or to other columns in the table + will also be copied to the header of the output image. + + The 'image2cell' routine will copy the input image into the specified row + and column of the current binary table in the output file. The binary table + HDU must exist before calling this routine, but it + may be empty, with no rows or columns of data. The specified column + (and row) will be created if it does not already exist. The 'copykeyflag' + parameter controls which keywords are copied from the input + image to the header of the output table: 0 = no keywords will be copied, + 1 = all keywords will be copied (except those keywords that would be invalid in + the table header), and 2 = copy only the WCS keywords. \label{copycell} +\end{description} + +\begin{verbatim} + int fits_copy_cell2image + (fitsfile *infptr, fitsfile *outfptr, char *colname, long rownum, + > int *status) + + int fits_copy_image2cell + (fitsfile *infptr, fitsfile *outfptr, char *colname, long rownum, + int copykeyflag > int *status) +\end{verbatim} + + +\begin{description} +\item[5 ] Write a rectangular subimage (or the whole image) to the FITS data + array. The fpixel and lpixel arrays give the coordinates of the + first (lower left corner) and last (upper right corner) pixels in + FITS image to be written to. \label{ffpss} +\end{description} + +\begin{verbatim} + int fits_write_subset / ffpss + (fitsfile *fptr, int datatype, long *fpixel, long *lpixel, + DTYPE *array, > int *status) +\end{verbatim} + +\begin{description} +\item[6 ] Write pixels into the FITS data array. 'fpixel' is an array of + length NAXIS which gives the coordinate of the starting pixel to be + written to, such that fpixel[0] is in the range 1 to NAXIS1, + fpixel[1] is in the range 1 to NAXIS2, etc. The first pair of routines + simply writes the array of pixels to the FITS file (doing data type + conversion if necessary) whereas the second routines will substitute + the appropriate FITS null value for any elements which are equal to + the input value of nulval (note that this parameter gives the + address of the null value, not the null value itself). For integer + FITS arrays, the FITS null value is defined by the BLANK keyword (an + error is returned if the BLANK keyword doesn't exist). For floating + point FITS arrays the special IEEE NaN (Not-a-Number) value will be + written into the FITS file. If a null pointer is entered for + nulval, then the null value is ignored and this routine behaves + the same as fits\_write\_pix. \label{ffppx} \label{ffppxn} +\end{description} + +\begin{verbatim} + int fits_write_pix / ffppx + (fitsfile *fptr, int datatype, long *fpixel, LONGLONG nelements, + DTYPE *array, int *status); + + int fits_write_pixll / ffppxll + (fitsfile *fptr, int datatype, LONGLONG *fpixel, LONGLONG nelements, + DTYPE *array, int *status); + + int fits_write_pixnull / ffppxn + (fitsfile *fptr, int datatype, long *fpixel, LONGLONG nelements, + DTYPE *array, DTYPE *nulval, > int *status); + + int fits_write_pixnullll / ffppxnll + (fitsfile *fptr, int datatype, LONGLONG *fpixel, LONGLONG nelements, + DTYPE *array, DTYPE *nulval, > int *status); +\end{verbatim} + +\begin{description} +\item[7 ] Set FITS data array elements equal to the appropriate null pixel + value. For integer FITS arrays, the FITS null value is defined by + the BLANK keyword (an error is returned if the BLANK keyword + doesn't exist). For floating point FITS arrays the special IEEE NaN + (Not-a-Number) value will be written into the FITS file. Note that + 'firstelem' is a scalar giving the offset to the first pixel to be + written in the equivalent 1-dimensional array of image pixels. \label{ffpprn} +\end{description} + +\begin{verbatim} + int fits_write_null_img / ffpprn + (fitsfile *fptr, LONGLONG firstelem, LONGLONG nelements, > int *status) +\end{verbatim} + +\begin{description} +\item[8 ] Read a rectangular subimage (or the whole image) from the FITS + data array. The fpixel and lpixel arrays give the coordinates of + the first (lower left corner) and last (upper right corner) pixels + to be read from the FITS image. Undefined FITS array elements will + be returned with a value = *nullval, (note that this parameter + gives the address of the null value, not the null value itself) + unless nulval = 0 or *nulval = 0, in which case no checks for + undefined pixels will be performed. \label{ffgsv} +\end{description} + +\begin{verbatim} + int fits_read_subset / ffgsv + (fitsfile *fptr, int datatype, long *fpixel, long *lpixel, long *inc, + DTYPE *nulval, > DTYPE *array, int *anynul, int *status) +\end{verbatim} + +\begin{description} +\item[9 ] Read pixels from the FITS data array. 'fpixel' is the starting + pixel location and is an array of length NAXIS such that fpixel[0] + is in the range 1 to NAXIS1, fpixel[1] is in the range 1 to NAXIS2, + etc. The nelements parameter specifies the number of pixels to + read. If fpixel is set to the first pixel, and nelements is set + equal to the NAXIS1 value, then this routine would read the first + row of the image. Alternatively, if nelements is set equal to + NAXIS1 * NAXIS2 then it would read an entire 2D image, or the first + plane of a 3-D datacube. + + The first 2 routines will return any undefined pixels in the FITS array + equal to the value of *nullval (note that this parameter gives the + address of the null value, not the null value itself) unless nulval + = 0 or *nulval = 0, in which case no checks for undefined pixels + will be performed. The second 2 routines are similar except that any + undefined pixels will have the corresponding nullarray element set + equal to TRUE (= 1). \label{ffgpxv} \label{ffgpxf} +\end{description} + +\begin{verbatim} + int fits_read_pix / ffgpxv + (fitsfile *fptr, int datatype, long *fpixel, LONGLONG nelements, + DTYPE *nulval, > DTYPE *array, int *anynul, int *status) + + int fits_read_pixll / ffgpxvll + (fitsfile *fptr, int datatype, LONGLONG *fpixel, LONGLONG nelements, + DTYPE *nulval, > DTYPE *array, int *anynul, int *status) + + int fits_read_pixnull / ffgpxf + (fitsfile *fptr, int datatype, long *fpixel, LONGLONG nelements, + > DTYPE *array, char *nullarray, int *anynul, int *status) + + int fits_read_pixnullll / ffgpxfll + (fitsfile *fptr, int datatype, LONGLONG *fpixel, LONGLONG nelements, + > DTYPE *array, char *nullarray, int *anynul, int *status) +\end{verbatim} + +\begin{description} +\item[10] Copy a rectangular section of an image and write it to a new + FITS primary image or image extension. The new image HDU is appended + to the end of the output file; all the keywords in the input image + will be copied to the output image. The common WCS keywords will + be updated if necessary to correspond to the coordinates of the section. + The format of the section expression is + same as specifying an image section using the extended file name + syntax (see "Image Section" in Chapter 10). + (Examples: "1:100,1:200", "1:100:2, 1:*:2", "*, -*"). + \label{ffcpimg} +\end{description} + +\begin{verbatim} + int fits_copy_image_section / ffcpimg + (fitsfile *infptr, fitsfile *outfptr, char *section, int *status) +\end{verbatim} + + +\section{Image Compression} + +CFITSIO transparently supports the 2 methods of image compression described +below. + +1) The entire FITS file may be externally compressed with the gzip or Unix +compress utility programs, producing a *.gz or *.Z file, respectively. When reading +compressed files of this type, CFITSIO first uncompresses the entire file +into memory before performing the requested read operations. Output files +can be directly written in the gzip compressed format if the user-specified +filename ends with `.gz'. In this case, CFITSIO initially writes the +uncompressed file in memory and then compresses it and writes it to disk +when the FITS file is closed, thus saving user disk space. Read and write +access to these compressed FITS files is generally quite fast since all the +I/O is performed in memory; the main limitation with this technique is that +there must be enough available memory (or swap space) to hold the entire +uncompressed FITS file. + +2) CFITSIO also supports the FITS tiled image compression convention in +which the image is subdivided into a grid of rectangular tiles, and each +tile of pixels is individually compressed. The details of this FITS +compression convention are described at the FITS Support Office web site at +http://fits.gsfc.nasa.gov/fits\_registry.html Basically, the compressed +image tiles are stored in rows of a variable length array column in a FITS +binary table, however CFITSIO recognizes that this binary table extension +contains an image and treats it as if it were an IMAGE extension. This +tile-compressed format is especially well suited for compressing very large +images because a) the FITS header keywords remain uncompressed for rapid +read access, and because b) it is possible to extract and uncompress +sections of the image without having to uncompress the entire image. This +format is also much more effective in compressing floating point images +than simply compressing the image using gzip or compress because it +approximates the floating point values with scaled integers which can then +be compressed more efficiently. + +Currently CFITSIO supports 3 general purpose compression algorithms plus +one other special-purpose compression technique that is designed for data +masks with positive integer pixel values. The 3 general purpose algorithms +are GZIP, Rice, and HCOMPRESS, and the special purpose algorithm is the +IRAF pixel list compression technique (PLIO). In principle, any number of +other compression algorithms could also be supported by the FITS tiled +image compression convention. + +The FITS image can be subdivided into any desired rectangular grid of +compression tiles. With the GZIP, Rice, and PLIO algorithms, the default +is to take each row of the image as a tile. The HCOMPRESS algorithm is +inherently 2-dimensional in nature, so the default in this case is to take +16 rows of the image per tile. In most cases it makes little difference what +tiling pattern is used, so the default tiles are usually adequate. In the +case of very small images, it could be more efficient to compress the whole +image as a single tile. Note that the image dimensions are not required to +be an integer multiple of the tile dimensions; if not, then the tiles at the +edges of the image will be smaller than the other tiles. + +The 4 supported image compression algorithms are all 'loss-less' when +applied to integer FITS images; the pixel values are preserved exactly with +no loss of information during the compression and uncompression process. In +addition, the HCOMPRESS algorithm supports a 'lossy' compression mode that +will produce +larger amount of image compression. This is achieved by specifying a non-zero +value for the HCOMPRESS ``scale'' +parameter. Since the amount of compression that is achieved depends directly +on the RMS noise in the image, it is usually more convention +to specify the HCOMPRESS scale factor relative to the RMS noise. +Setting s = 2.5 means use a scale factor that is 2.5 times the calculated RMS noise +in the image tile. In some cases +it may be desirable to specify the exact scaling to be used, +instead of specifying it relative to the calculated noise value. This may +be done by specifying the negative of desired scale value (typically +in the range -2 to -100). + +Very high compression factors (of 100 or more) can be +achieved by using large HCOMPRESS scale values, however, this can produce undesirable +``blocky'' artifacts in the compressed image. A variation of the HCOMPRESS +algorithm (called HSCOMPRESS) can be used in this case to apply a small +amount of smoothing of the image when it is uncompressed to help cover up +these artifacts. This smoothing is purely cosmetic and does not cause any +significant change to the image pixel values. + +Floating point FITS images (which have BITPIX = -32 or -64) usually contain +too much ``noise'' in the least significant bits of the mantissa of the +pixel values to be effectively compressed with any lossless algorithm. +Consequently, floating point images are first quantized into scaled integer +pixel values (and thus throwing away much of the noise) before being +compressed with the specified algorithm (either GZIP, Rice, or HCOMPRESS). +This technique produces much higher compression factors than +simply using the GZIP utility to externally compress the whole FITS file, but it also +means that the original floating value pixel values are not exactly +preserved. When done properly, this integer scaling technique will only +discard the insignificant noise while still preserving all the real +information in the image. The amount of precision that is retained in the +pixel values is controlled by the "quantization level" parameter, q. Larger +values of q will result in compressed images whose pixels more closely match +the floating point pixel values, but at the same time the amount of +compression that is achieved will be reduced. Users should experiment with +different values for this parameter to determine the optimal value that +preserves all the useful information in the image, without needlessly +preserving all the ``noise'' which will hurt the compression efficiency. + +The default value for the quantization scale factor is 16., which means that +scaled integer pixel values will be quantized such that the difference +between adjacent integer values will be 1/16th of the noise level in the +image background. CFITSIO uses an optimized algorithm to accurately estimate +the noise in the image. As an example, if the RMS noise in the background +pixels of an image = 32.0, then the spacing between adjacent scaled +integer pixel values will equal 2.0 by default. Note that the RMS noise is +independently calculated for each tile of the image, so the resulting +integer scaling factor may fluctuate slightly for each tile. In some cases +it may be desirable to specify the exact quantization level to be used, +instead of specifying it relative to the calculated noise value. This may +be done by specifying the negative of desired quantization level for the +value of q. In the previous example, one could specify q = -2.0 so that the +quantized integer levels differ by 2.0. Larger negative values for q means +that the levels are more coarsely spaced, and will produce higher +compression factors. + +There are 2 methods for specifying all the parameters needed to write a FITS +image in the tile compressed format. The parameters may either be specified +at run time as part of the file name of the output compressed FITS file, or +the writing program may call a set of helper CFITSIO subroutines that are provided +for specifying the parameter values, as described below: + +1) At run time, when specifying the name of the output FITS file to be +created, the user can indicate that images should be +written in tile-compressed format by enclosing the compression +parameters in square brackets following the root disk file name +in the following format: + +\begin{verbatim} + [compress NAME T1,T2; q QLEVEL, s HSCALE] +\end{verbatim} +where + +\begin{verbatim} + NAME = algorithm name: GZIP, Rice, HCOMPRESS, HSCOMPRSS or PLIO + may be abbreviated to the first letter (or HS for HSCOMPRESS) + T1,T2 = tile dimension (e.g. 100,100 for square tiles 100 pixels wide) + QLEVEL = quantization level for floating point FITS images + HSCALE = HCOMPRESS scale factor; default = 0 which is lossless. +\end{verbatim} + +Here are a few examples of this extended syntax: + + +\begin{verbatim} + myfile.fit[compress] - use the default compression algorithm (Rice) + and the default tile size (row by row) + + myfile.fit[compress GZIP] - use the specified compression algorithm; + myfile.fit[compress Rice] only the first letter of the algorithm + myfile.fit[compress PLIO] name is required. + myfile.fit[compress HCOMP] + + myfile.fit[compress R 100,100] - use Rice and 100 x 100 pixel tiles + + myfile.fit[compress R; q 10.0] - quantization level = (RMS-noise) / 10. + myfile.fit[compress HS; s 2.0] - HSCOMPRESS (with smoothing) + and scale = 2.0 * RMS-noise +\end{verbatim} + +2) Before calling the CFITSIO routine to write the image header +keywords (e.g., fits\_create\_image) the programmer can call the +routines described below to specify the compression algorithm and the +tiling pattern that is to be used. There are routines for specifying +the various compression parameters and similar routines to +return the current values of the parameters: +\label{ffsetcomp} \label{ffgetcomp} + +\begin{verbatim} + int fits_set_compression_type(fitsfile *fptr, int comptype, int *status) + int fits_set_tile_dim(fitsfile *fptr, int ndim, long *tilesize, 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) + Set smooth = 1 to apply smoothing when uncompressing the image + + int fits_get_compression_type(fitsfile *fptr, int *comptype, int *status) + int fits_get_tile_dim(fitsfile *fptr, int ndim, long *tilesize, int *status) + int fits_get_quantize_level(fitsfile *fptr, float *level, int *status) + int fits_get_hcomp_scale(fitsfile *fptr, float *scale, int *status) + int fits_get_hcomp_smooth(fitsfile *fptr, int *smooth, int *status) +\end{verbatim} +4 symbolic constants are defined for use as the value of the +`comptype' parameter: GZIP\_1, RICE\_1, HCOMPRESS\_1 or PLIO\_1. +Entering NULL for +comptype will turn off the tile-compression and cause normal FITS +images to be written. + + +No special action is required by software when read tile-compressed images because +all the CFITSIO routines that read normal uncompressed FITS images also +transparently read images in the tile-compressed format; CFITSIO essentially +treats the binary table that contains the compressed tiles as if +it were an IMAGE extension. + + +The following 2 routines are available for compressing or +or decompressing an image: + +\begin{verbatim} + int fits_img_compress(fitsfile *infptr, fitsfile *outfptr, int *status); + int fits_img_decompress (fitsfile *infptr, fitsfile *outfptr, int *status); +\end{verbatim} +Before calling the compression routine, the compression parameters must +first be defined in one of the 2 way described in the previous paragraphs. +There is also a routine to determine if the current HDU contains +a tile compressed image (it returns 1 or 0): + +\begin{verbatim} + int fits_is_compressed_image(fitsfile *fptr, int *status); +\end{verbatim} +A small example program called 'imcopy' is included with CFITSIO that +can be used to compress (or uncompress) any FITS image. This +program can be used to experiment with the various compression options +on existing FITS images as shown in these examples: + +\begin{verbatim} +1) imcopy infile.fit 'outfile.fit[compress]' + + This will use the default compression algorithm (Rice) and the + default tile size (row by row) + +2) imcopy infile.fit 'outfile.fit[compress GZIP]' + + This will use the GZIP compression algorithm and the default + tile size (row by row). The allowed compression algorithms are + Rice, GZIP, and PLIO. Only the first letter of the algorithm + name needs to be specified. + +3) imcopy infile.fit 'outfile.fit[compress G 100,100]' + + This will use the GZIP compression algorithm and 100 X 100 pixel + tiles. + +4) imcopy infile.fit 'outfile.fit[compress R 100,100; q 10.0]' + + This will use the Rice compression algorithm, 100 X 100 pixel + tiles, and quantization level = RMSnoise / 10.0 (assuming the + input image has a floating point data type). + +5) imcopy infile.fit outfile.fit + + If the input file is in tile-compressed format, then it will be + uncompressed to the output file. Otherwise, it simply copies + the input image to the output image. + +6) imcopy 'infile.fit[1001:1500,2001:2500]' outfile.fit + + This extracts a 500 X 500 pixel section of the much larger + input image (which may be in tile-compressed format). The + output is a normal uncompressed FITS image. + +7) imcopy 'infile.fit[1001:1500,2001:2500]' outfile.fit.gz + + Same as above, except the output file is externally compressed + using the gzip algorithm. + +\end{verbatim} + +\section{ASCII and Binary Table Routines} + +These routines perform read and write operations on columns of data in +FITS ASCII or Binary tables. Note that in the following discussions, +the first row and column in a table is at position 1 not 0. + +Users should also read the following chapter on the CFITSIO iterator +function which provides a more `object oriented' method of reading and +writing table columns. The iterator function is a little more +complicated to use, but the advantages are that it usually takes less +code to perform the same operation, and the resulting program often +runs faster because the FITS files are read and written using the most +efficient block size. + + +\subsection{Create New Table} + + +\begin{description} +\item[1 ]Create a new ASCII or bintable table extension. If + the FITS file is currently empty then a dummy primary array will be + created before appending the table extension to it. The tbltype + parameter defines the type of table and can have values of + ASCII\_TBL or BINARY\_TBL. The naxis2 parameter gives the initial + number of rows to be created in the table, and should normally be + set = 0. CFITSIO will automatically increase the size of the table + as additional rows are written. A non-zero number of rows may be + specified to reserve space for that many rows, even if a fewer + number of rows will be written. The tunit and extname parameters + are optional and a null pointer may be given if they are not + defined. The FITS Standard recommends that only letters, digits, + and the underscore character be used in column names (the ttype + parameter) with no embedded spaces. Trailing blank characters are + not significant. \label{ffcrtb} +\end{description} + +\begin{verbatim} + int fits_create_tbl / ffcrtb + (fitsfile *fptr, int tbltype, LONGLONG naxis2, int tfields, char *ttype[], + char *tform[], char *tunit[], char *extname, int *status) +\end{verbatim} + +\subsection{Column Information Routines} + + +\begin{description} +\item[1 ] Get the number of rows or columns in the current FITS table. + The number of rows is given by the NAXIS2 keyword and the + number of columns is given by the TFIELDS keyword in the header + of the table. \label{ffgnrw} +\end{description} + +\begin{verbatim} + int fits_get_num_rows / ffgnrw + (fitsfile *fptr, > long *nrows, int *status); + + int fits_get_num_rowsll / ffgnrwll + (fitsfile *fptr, > LONGLONG *nrows, int *status); + + int fits_get_num_cols / ffgncl + (fitsfile *fptr, > int *ncols, int *status); +\end{verbatim} + + +\begin{description} +\item[2 ] Get the table column number (and name) of the column whose name +matches an input template name. If casesen = CASESEN then the column +name match will be case-sensitive, whereas if casesen = CASEINSEN then +the case will be ignored. As a general rule, the column names should +be treated as case INsensitive. + +The input column name template may be either the exact name of the +column to be searched for, or it may contain wild card characters (*, +?, or \#), or it may contain the integer number of the desired column +(with the first column = 1). The `*' wild card character matches any +sequence of characters (including zero characters) and the `?' +character matches any single character. The \# wildcard will match any +consecutive string of decimal digits (0-9). If more than one column +name in the table matches the template string, then the first match is +returned and the status value will be set to COL\_NOT\_UNIQUE as a +warning that a unique match was not found. To find the other cases +that match the template, call the routine again leaving the input +status value equal to COL\_NOT\_UNIQUE and the next matching name will +then be returned. Repeat this process until a status = +COL\_NOT\_FOUND is returned. + +The FITS Standard recommends that only letters, digits, and the +underscore character be used in column names (with no embedded +spaces). Trailing blank characters are not significant. + \label{ffgcno} \label{ffgcnn} +\end{description} + +\begin{verbatim} + int fits_get_colnum / ffgcno + (fitsfile *fptr, int casesen, char *templt, > int *colnum, + int *status) + + int fits_get_colname / ffgcnn + (fitsfile *fptr, int casesen, char *templt, > char *colname, + int *colnum, int *status) +\end{verbatim} + +\begin{description} +\item[3 ] Return the data type, vector repeat value, and the width in bytes + of a column in an ASCII or binary table. Allowed values for the + data type in ASCII tables are: TSTRING, TSHORT, TLONG, TFLOAT, and + TDOUBLE. Binary tables also support these types: TLOGICAL, TBIT, + TBYTE, TCOMPLEX and TDBLCOMPLEX. The negative of the data type code + value is returned if it is a variable length array column. Note + that in the case of a 'J' 32-bit integer binary table column, this + routine will return data type = TINT32BIT (which in fact is + equivalent to TLONG). With most current C compilers, a value in a + 'J' column has the same size as an 'int' variable, and may not be + equivalent to a 'long' variable, which is 64-bits long on an + increasing number of compilers. + + The 'repeat' parameter returns the vector repeat count on the binary + table TFORMn keyword value. (ASCII table columns always have repeat + = 1). The 'width' parameter returns the width in bytes of a single + column element (e.g., a '10D' binary table column will have width = + 8, an ASCII table 'F12.2' column will have width = 12, and a binary + table'60A' character string column will have width = 60); Note that + CFITSIO supports the local convention for specifying arrays of + fixed length strings within a binary table character column using + the syntax TFORM = 'rAw' where 'r' is the total number of characters + (= the width of the column) and 'w' is the width of a unit string + within the column. Thus if the column has TFORM = '60A12' then this + means that each row of the table contains 5 12-character substrings + within the 60-character field, and thus in this case this routine will + return typecode = TSTRING, repeat = 60, and width = 12. (The TDIMn + keyword may also be used to specify the unit string length; The pair + of keywords TFORMn = '60A' and TDIMn = '(12,5)' would have the + same effect as TFORMn = '60A12'). The number + of substrings in any binary table character string field can be + calculated by (repeat/width). A null pointer may be given for any of + the output parameters that are not needed. + + The second routine, fit\_get\_eqcoltype is similar except that in + the case of scaled integer columns it returns the 'equivalent' data + type that is needed to store the scaled values, and not necessarily + the physical data type of the unscaled values as stored in the FITS + table. For example if a '1I' column in a binary table has TSCALn = + 1 and TZEROn = 32768, then this column effectively contains unsigned + short integer values, and thus the returned value of typecode will + be TUSHORT, not TSHORT. Similarly, if a column has TTYPEn = '1I' + and TSCALn = 0.12, then the returned typecode + will be TFLOAT. \label{ffgtcl} +\end{description} + +\begin{verbatim} + int fits_get_coltype / ffgtcl + (fitsfile *fptr, int colnum, > int *typecode, long *repeat, + long *width, int *status) + + int fits_get_coltypell / ffgtclll + (fitsfile *fptr, int colnum, > int *typecode, LONGLONG *repeat, + LONGLONG *width, int *status) + + int fits_get_eqcoltype / ffeqty + (fitsfile *fptr, int colnum, > int *typecode, long *repeat, + long *width, int *status) + + int fits_get_eqcoltypell / ffeqtyll + (fitsfile *fptr, int colnum, > int *typecode, LONGLONG *repeat, + LONGLONG *width, int *status) +\end{verbatim} + +\begin{description} +\item[4 ] Return the display width of a column. This is the length + of the string that will be returned by the fits\_read\_col routine + when reading the column as a formatted string. The display width is + determined by the TDISPn keyword, if present, otherwise by the data + type of the column. \label{ffgcdw} +\end{description} + +\begin{verbatim} + int fits_get_col_display_width / ffgcdw + (fitsfile *fptr, int colnum, > int *dispwidth, int *status) +\end{verbatim} + + +\begin{description} +\item[5 ] Return the number of and size of the dimensions of a table column in + a binary table. Normally this information is given by the TDIMn keyword, + but if this keyword is not present then this routine returns naxis = 1 + and naxes[0] equal to the repeat count in the TFORM keyword. \label{ffgtdm} +\end{description} + +\begin{verbatim} + int fits_read_tdim / ffgtdm + (fitsfile *fptr, int colnum, int maxdim, > int *naxis, + long *naxes, int *status) + + int fits_read_tdimll / ffgtdmll + (fitsfile *fptr, int colnum, int maxdim, > int *naxis, + LONGLONG *naxes, int *status) +\end{verbatim} + +\begin{description} +\item[6 ] Decode the input TDIMn keyword string (e.g. '(100,200)') and return the + number of and size of the dimensions of a binary table column. If the input + tdimstr character string is null, then this routine returns naxis = 1 + and naxes[0] equal to the repeat count in the TFORM keyword. This routine + is called by fits\_read\_tdim. \label{ffdtdm} +\end{description} + +\begin{verbatim} + int fits_decode_tdim / ffdtdm + (fitsfile *fptr, char *tdimstr, int colnum, int maxdim, > int *naxis, + long *naxes, int *status) + + int fits_decode_tdimll / ffdtdmll + (fitsfile *fptr, char *tdimstr, int colnum, int maxdim, > int *naxis, + LONGLONG *naxes, int *status) +\end{verbatim} + +\begin{description} +\item[7 ] Write a TDIMn keyword whose value has the form '(l,m,n...)' + where l, m, n... are the dimensions of a multidimensional array + column in a binary table. \label{ffptdm} +\end{description} + +\begin{verbatim} + int fits_write_tdim / ffptdm + (fitsfile *fptr, int colnum, int naxis, long *naxes, > int *status) + + int fits_write_tdimll / ffptdmll + (fitsfile *fptr, int colnum, int naxis, LONGLONG *naxes, > int *status) +\end{verbatim} + + +\subsection{Routines to Edit Rows or Columns} + + +\begin{description} +\item[1 ] Insert or delete rows in an ASCII or binary table. When inserting rows + all the rows following row FROW are shifted down by NROWS rows; if + FROW = 0 then the blank rows are inserted at the beginning of the + table. Note that it is *not* necessary to insert rows in a table before + writing data to those rows (indeed, it would be inefficient to do so). + Instead one may simply write data to any row of the table, whether that + row of data already exists or not. + + The first delete routine deletes NROWS consecutive rows + starting with row FIRSTROW. The second delete routine takes an + input string that lists the rows or row ranges (e.g., + '5-10,12,20-30'), whereas the third delete routine takes an input + integer array that specifies each individual row to be deleted. In + both latter cases, the input list of rows to delete must be sorted + in ascending order. These routines update the NAXIS2 keyword to + reflect the new number of rows in the + table. \label{ffirow} \label{ffdrow} \label{ffdrws} \label{ffdrrg} +\end{description} + +\begin{verbatim} + int fits_insert_rows / ffirow + (fitsfile *fptr, LONGLONG firstrow, LONGLONG nrows, > int *status) + + int fits_delete_rows / ffdrow + (fitsfile *fptr, LONGLONG firstrow, LONGLONG nrows, > int *status) + + int fits_delete_rowrange / ffdrrg + (fitsfile *fptr, char *rangelist, > int *status) + + int fits_delete_rowlist / ffdrws + (fitsfile *fptr, long *rowlist, long nrows, > int *status) + + int fits_delete_rowlistll / ffdrwsll + (fitsfile *fptr, LONGLONG *rowlist, LONGLONG nrows, > int *status) +\end{verbatim} + +\begin{description} +\item[2 ] Insert or delete column(s) in an ASCII or binary + table. When inserting, COLNUM specifies the column number that the + (first) new column should occupy in the table. NCOLS specifies how + many columns are to be inserted. Any existing columns from this + position and higher are shifted over to allow room for the new + column(s). The index number on all the following keywords will be + incremented or decremented if necessary to reflect the new position + of the column(s) in the table: TBCOLn, TFORMn, TTYPEn, TUNITn, + TNULLn, TSCALn, TZEROn, TDISPn, TDIMn, TLMINn, TLMAXn, TDMINn, + TDMAXn, TCTYPn, TCRPXn, TCRVLn, TCDLTn, TCROTn, + and TCUNIn. \label{fficol} \label{fficls} \label{ffdcol} +\end{description} + +\begin{verbatim} + int fits_insert_col / fficol + (fitsfile *fptr, int colnum, char *ttype, char *tform, + > int *status) + + int fits_insert_cols / fficls + (fitsfile *fptr, int colnum, int ncols, char **ttype, + char **tform, > int *status) + + int fits_delete_col / ffdcol(fitsfile *fptr, int colnum, > int *status) +\end{verbatim} + +\begin{description} +\item[3 ] Copy a column from one HDU to another (or to the same HDU). If + create\_col = TRUE, then a new column will be inserted in the output + table, at position `outcolumn', otherwise the existing output column will + be overwritten (in which case it must have a compatible data type). + If outcolnum is greater than the number of column in the table, then + the new column will be appended to the end of the table. + Note that the first column in a table is at colnum = 1. + The standard indexed keywords that related to the column (e.g., TDISPn, + TUNITn, TCRPXn, TCDLTn, etc.) will also be copied. \label{ffcpcl} +\end{description} + +\begin{verbatim} + int fits_copy_col / ffcpcl + (fitsfile *infptr, fitsfile *outfptr, int incolnum, int outcolnum, + int create_col, > int *status); +\end{verbatim} + +\begin{description} +\item[4 ] Copy 'nrows' consecutive rows from one table to another, beginning + with row 'firstrow'. These rows will be appended to any existing + rows in the output table. + Note that the first row in a table is at row = 1. \label{ffcprw} +\end{description} + +\begin{verbatim} + int fits_copy_rows / ffcprw + (fitsfile *infptr, fitsfile *outfptr, LONGLONG firstrow, + LONGLONG nrows, > int *status); +\end{verbatim} + +\begin{description} +\item[5 ] Modify the vector length of a binary table column (e.g., + change a column from TFORMn = '1E' to '20E'). The vector + length may be increased or decreased from the current value. \label{ffmvec} +\end{description} + +\begin{verbatim} + int fits_modify_vector_len / ffmvec + (fitsfile *fptr, int colnum, LONGLONG newveclen, > int *status) +\end{verbatim} + +\subsection{Read and Write Column Data Routines} + +The following routines write or read data values in the current ASCII +or binary table extension. If a write operation extends beyond the +current size of the table, then the number of rows in the table will +automatically be increased and the NAXIS2 keyword value will be +updated. Attempts to read beyond the end of the table will result in +an error. + +Automatic data type conversion is performed for numerical data types +(only) if the data type of the column (defined by the TFORMn keyword) +differs from the data type of the array in the calling routine. ASCII and binary +tables support the following data type values: TSTRING, TBYTE, TSBYTE, TSHORT, +TUSHORT, TINT, TUINT, TLONG, TLONGLONG, TULONG, TFLOAT, or TDOUBLE. +Binary tables also support TLOGICAL (internally mapped to the `char' +data type), TCOMPLEX, and TDBLCOMPLEX. + +Note that it is *not* necessary to insert rows in a table before +writing data to those rows (indeed, it would be inefficient to do so). +Instead, one may simply write data to any row of the table, whether that +row of data already exists or not. + +Individual bits in a binary table 'X' or 'B' column may be read/written +to/from a *char array by specifying the TBIT datatype. The *char +array will be interpreted as an array of logical TRUE (1) or FALSE (0) +values that correspond to the value of each bit in the FITS 'X' or 'B' column. +Alternatively, the values in a binary table 'X' column may be read/written +8 bits at a time to/from an array of 8-bit integers by specifying the +TBYTE datatype. + +Note that within the context of these routines, the TSTRING data type +corresponds to a C 'char**' data type, i.e., a pointer to an array of +pointers to an array of characters. This is different from the keyword +reading and writing routines where TSTRING corresponds to a C 'char*' +data type, i.e., a single pointer to an array of characters. When +reading strings from a table, the char arrays obviously must have been +allocated long enough to hold the whole FITS table string. + +Numerical data values are automatically scaled by the TSCALn and TZEROn +keyword values (if they exist). + +In the case of binary tables with vector elements, the 'felem' +parameter defines the starting element (beginning with 1, not 0) within +the cell (a cell is defined as the intersection of a row and a column +and may contain a single value or a vector of values). The felem +parameter is ignored when dealing with ASCII tables. Similarly, in the +case of binary tables the 'nelements' parameter specifies the total +number of vector values to be read or written (continuing on subsequent +rows if required) and not the number of table cells. + + +\begin{description} +\item[1 ] Write elements into an ASCII or binary table column. +\end{description} + The first routine simply writes the array of values to the FITS file + (doing data type conversion if necessary) whereas the second routine + will substitute the appropriate FITS null value for all elements + which are equal to the input value of nulval (note that this + parameter gives the address of nulval, not the null value + itself). For integer columns the FITS null value is defined by the + TNULLn keyword (an error is returned if the keyword doesn't exist). + For floating point columns the special IEEE NaN (Not-a-Number) + value will be written into the FITS file. If a null pointer is + entered for nulval, then the null value is ignored and this routine + behaves the same as the first routine. The third routine + simply writes undefined pixel values to the column. The fourth routine + fills every column in the table with null values, in the specified + rows (ignoring any columns that do not have a defined null value). + \label{ffpcl} \label{ffpcn} \label{ffpclu} + +\begin{verbatim} + int fits_write_col / ffpcl + (fitsfile *fptr, int datatype, int colnum, LONGLONG firstrow, + LONGLONG firstelem, LONGLONG nelements, DTYPE *array, > int *status) + + int fits_write_colnull / ffpcn + (fitsfile *fptr, int datatype, int colnum, LONGLONG firstrow, + LONGLONG firstelem, LONGLONG nelements, DTYPE *array, DTYPE *nulval, + > int *status) + + int fits_write_col_null / ffpclu + (fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem, + LONGLONG nelements, > int *status) + + int fits_write_nullrows / ffprwu + (fitsfile *fptr, LONGLONG firstrow, LONGLONG nelements, > int *status) +\end{verbatim} + +\begin{description} +\item[2 ] Read elements from an ASCII or binary table column. The data type + parameter specifies the data type of the `nulval' and `array' pointers; + Undefined array elements will be returned with a value = *nullval, + (note that this parameter gives the address of the null value, not the + null value itself) unless nulval = 0 or *nulval = 0, in which case + no checking for undefined pixels will be performed. The second + routine is similar except that any undefined pixels will have the + corresponding nullarray element set equal to TRUE (= 1). + + Any column, regardless of it's intrinsic data type, may be read as a + string. It should be noted however that reading a numeric column + as a string is 10 - 100 times slower than reading the same column + as a number due to the large overhead in constructing the formatted + strings. The display format of the returned strings will be + determined by the TDISPn keyword, if it exists, otherwise by the + data type of the column. The length of the returned strings (not + including the null terminating character) can be determined with + the fits\_get\_col\_display\_width routine. The following TDISPn + display formats are currently supported: + +\begin{verbatim} + Iw.m Integer + Ow.m Octal integer + Zw.m Hexadecimal integer + Fw.d Fixed floating point + Ew.d Exponential floating point + Dw.d Exponential floating point + Gw.d General; uses Fw.d if significance not lost, else Ew.d +\end{verbatim} + where w is the width in characters of the displayed values, m is the minimum + number of digits displayed, and d is the number of digits to the right of the + decimal. The .m field is optional. + \label{ffgcv} \label{ffgcf} +\end{description} + +\begin{verbatim} + int fits_read_col / ffgcv + (fitsfile *fptr, int datatype, int colnum, LONGLONG firstrow, LONGLONG firstelem, + LONGLONG nelements, DTYPE *nulval, DTYPE *array, int *anynul, int *status) + + int fits_read_colnull / ffgcf + (fitsfile *fptr, int datatype, int colnum, LONGLONG firstrow, LONGLONG firstelem, + LONGLONG nelements, DTYPE *array, char *nullarray, int *anynul, int *status) +\end{verbatim} + + +\subsection{Row Selection and Calculator Routines} + +These routines all parse and evaluate an input string containing a user +defined arithmetic expression. The first 3 routines select rows in a +FITS table, based on whether the expression evaluates to true (not +equal to zero) or false (zero). The other routines evaluate the +expression and calculate a value for each row of the table. The +allowed expression syntax is described in the row filter section in the +`Extended File Name Syntax' chapter of this document. The expression +may also be written to a text file, and the name of the file, prepended +with a '@' character may be supplied for the 'expr' parameter (e.g. +'@filename.txt'). The expression in the file can be arbitrarily +complex and extend over multiple lines of the file. Lines that begin +with 2 slash characters ('//') will be ignored and may be used to add +comments to the file. + + +\begin{description} +\item[1 ] Evaluate a boolean expression over the indicated rows, returning an + array of flags indicating which rows evaluated to TRUE/FALSE. + Upon return, + *n\_good\_rows contains the number of rows that evaluate to TRUE. \label{fffrow} +\end{description} + +\begin{verbatim} + int fits_find_rows / fffrow + (fitsfile *fptr, char *expr, long firstrow, long nrows, + > long *n_good_rows, char *row_status, int *status) +\end{verbatim} + +\begin{description} +\item[2 ] Find the first row which satisfies the input boolean expression \label{ffffrw} +\end{description} + +\begin{verbatim} + int fits_find_first_row / ffffrw + (fitsfile *fptr, char *expr, > long *rownum, int *status) +\end{verbatim} + +\begin{description} +\item[3 ]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 output +table is not empty, then this routine will append the new +selected rows after the existing rows. If the +files are the same, delete the FALSE rows (preserve the TRUE rows). \label{ffsrow} +\end{description} + +\begin{verbatim} + int fits_select_rows / ffsrow + (fitsfile *infptr, fitsfile *outfptr, char *expr, > int *status ) +\end{verbatim} + +\begin{description} +\item[4 ] 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 fits\_test\_expr to obtain +the dimensions of the results. \label{ffcrow} +\end{description} + +\begin{verbatim} + int fits_calc_rows / ffcrow + (fitsfile *fptr, int datatype, char *expr, long firstrow, + long nelements, void *nulval, > void *array, int *anynul, int *status) +\end{verbatim} + +\begin{description} +\item[5 ]Evaluate an expression and write the result either to a column (if +the expression is a function of other columns in the table) or to a +keyword (if the expression evaluates to a constant and is not a +function of other columns in the table). In the former case, the +parName parameter is the name of the column (which may or may not already +exist) into which to write the results, and parInfo contains an +optional TFORM keyword value if a new column is being created. If a +TFORM value is not specified then a default format will be used, +depending on the expression. If the expression evaluates to a constant, +then the result will be written to the keyword name given by the +parName parameter, and the parInfo parameter may be used to supply an +optional comment for the keyword. If the keyword does not already +exist, then the name of the keyword must be preceded with a '\#' character, + otherwise the result will be written to a column with that name. \label{ffcalc} +\end{description} + +\begin{verbatim} + int fits_calculator / ffcalc + (fitsfile *infptr, char *expr, fitsfile *outfptr, char *parName, + char *parInfo, > int *status) +\end{verbatim} + +\begin{description} +\item[6 ] This calculator routine is similar to the previous routine, except +that the expression is only evaluated over the specified +row ranges. nranges specifies the number of row ranges, and firstrow +and lastrow give the starting and ending row number of each range. \label{ffcalcrng} +\end{description} + +\begin{verbatim} + int fits_calculator_rng / ffcalc_rng + (fitsfile *infptr, char *expr, fitsfile *outfptr, char *parName, + char *parInfo, int nranges, long *firstrow, long *lastrow + > int *status) +\end{verbatim} + +\begin{description} +\item[7 ]Evaluate the given expression and return dimension and type information +on the result. The returned dimensions correspond to a single row entry +of the requested expression, and are equivalent to the result of fits\_read\_tdim(). +Note that strings are considered to be one element regardless of string length. +If maxdim == 0, then naxes is optional. \label{fftexp} +\end{description} + +\begin{verbatim} + int fits_test_expr / fftexp + (fitsfile *fptr, char *expr, int maxdim > int *datatype, long *nelem, int *naxis, + long *naxes, int *status) +\end{verbatim} + + +\subsection{Column Binning or Histogramming Routines} + +The following routines may be useful when performing histogramming operations on +column(s) of a table to generate an image in a primary array or image extension. + + +\begin{description} +\item[1 ] Calculate the histogramming parameters (min, max, and bin size +for each axis of the histogram, based on a variety of possible input parameters. +If the input names of the columns to be binned are null, then the routine will first +look for the CPREF = "NAME1, NAME2, ..." keyword which lists the preferred +columns. If not present, then the routine will assume the column names X, Y, Z, and T +for up to 4 axes (as specified by the NAXIS parameter). + +MININ and MAXIN are input arrays that give the minimum and maximum value for +the histogram, along each axis. Alternatively, the name of keywords that give +the min, max, and binsize may be give with the MINNAME, MAXNAME, and BINNAME +array parameters. If the value = DOUBLENULLVALUE and no keyword names are +given, then the routine will use the TLMINn and TLMAXn keywords, if present, or the +actual min and/or max values in the column. + +BINSIZEIN is an array giving the binsize along each axis. +If the value = +DOUBLENULLVALUE, and a keyword name is not specified with BINNAME, +then this routine will first look for the TDBINn keyword, or else will +use a binsize = 1, or a binsize that produces 10 histogram bins, which ever +is smaller. + \label{calcbinning} +\end{description} + +\begin{verbatim} + int fits_calc_binning + Input parameters: + (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 */ + Output parameters: + int *colnum, /* O - column numbers, to be binned */ + long *naxes, /* 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) +\end{verbatim} + + +\begin{description} +\item[2 ] Copy the relevant keywords from the header of the table that is being +binned, to the the header of the output histogram image. This will not +copy the table structure keywords (e.g., NAXIS, TFORMn, TTYPEn, etc.) nor +will it copy the keywords that apply to other columns of the table that are +not used to create the histogram. This routine will translate the names of +the World Coordinate System (WCS) keywords for the binned columns into the +form that is need for a FITS image (e.g., the TCTYPn table keyword will +be translated to the CTYPEn image keyword). + \label{copypixlist2image} +\end{description} + +\begin{verbatim} + int fits_copy_pixlist2image + (fitsfile *infptr, /* I - pointer to input HDU */ + fitsfile *outfptr, /* I - pointer to output HDU */ + int firstkey, /* I - first HDU keyword 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 */ +\end{verbatim} + + +\begin{description} +\item[3 ] Write a set of default WCS keywords to the histogram header, IF the +WCS keywords do not already exist. This will create a linear WCS where +the coordinate types are equal to the original column names. + \label{writekeyshisto} +\end{description} + +\begin{verbatim} + 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 of the binned columns */ + int *status) +\end{verbatim} + + +\begin{description} +\item[4 ] Update the WCS keywords in a histogram image header that give the location +of the reference pixel (CRPIXn), and the pixel size (CDELTn), in the binned +image. + \label{rebinwcs} +\end{description} + +\begin{verbatim} + 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) +\end{verbatim} + + +\begin{description} +\item[5 ] Bin the values in the input table columns, and write the histogram +array to the output FITS image (histptr). + \label{makehist} +\end{description} + +\begin{verbatim} + int fits_make_hist + (fitsfile *fptr, /* I - 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 (FLOATNULLVALUE */ + /* for no weighting) */ + int wtcolnum, /* I - keyword or col for weight (or NULL) */ + int recip, /* I - use reciprocal of the weight? 0 or 1 */ + 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) +\end{verbatim} + + + +\section{Utility Routines} + + +\subsection{File Checksum Routines} + +The following routines either compute or validate the checksums for the +CHDU. The DATASUM keyword is used to store the numerical value of the +32-bit, 1's complement checksum for the data unit alone. If there is +no data unit then the value is set to zero. The numerical value is +stored as an ASCII string of digits, enclosed in quotes, because the +value may be too large to represent as a 32-bit signed integer. The +CHECKSUM keyword is used to store the ASCII encoded COMPLEMENT of the +checksum for the entire HDU. Storing the complement, rather than the +actual checksum, forces the checksum for the whole HDU to equal zero. +If the file has been modified since the checksums were computed, then +the HDU checksum will usually not equal zero. These checksum keyword +conventions are based on a paper by Rob Seaman published in the +proceedings of the ADASS IV conference in Baltimore in November 1994 +and a later revision in June 1995. See Appendix B for the definition +of the parameters used in these routines. + + +\begin{description} +\item[1 ] Compute and write the DATASUM and CHECKSUM keyword values for the CHDU + into the current header. If the keywords already exist, their values + will be updated only if necessary (i.e., if the file + has been modified since the original keyword + values were computed). \label{ffpcks} +\end{description} + +\begin{verbatim} + int fits_write_chksum / ffpcks + (fitsfile *fptr, > int *status) +\end{verbatim} + +\begin{description} +\item[2 ] Update the CHECKSUM keyword value in the CHDU, assuming that the + DATASUM keyword exists and already has the correct value. This routine + calculates the new checksum for the current header unit, adds it to the + data unit checksum, encodes the value into an ASCII string, and writes + the string to the CHECKSUM keyword. \label{ffupck} +\end{description} + +\begin{verbatim} + int fits_update_chksum / ffupck + (fitsfile *fptr, > int *status) +\end{verbatim} + +\begin{description} +\item[3 ] Verify the CHDU by computing the checksums and comparing + them with the keywords. The data unit is verified correctly + if the computed checksum equals the value of the DATASUM + keyword. The checksum for the entire HDU (header plus data unit) is + correct if it equals zero. The output DATAOK and HDUOK parameters + in this routine are integers which will have a value = 1 + if the data or HDU is verified correctly, a value = 0 + if the DATASUM or CHECKSUM keyword is not present, or value = -1 + if the computed checksum is not correct. \label{ffvcks} +\end{description} + +\begin{verbatim} + int fits_verify_chksum / ffvcks + (fitsfile *fptr, > int *dataok, int *hduok, int *status) +\end{verbatim} + +\begin{description} +\item[4 ] Compute and return the checksum values for the CHDU + without creating or modifying the + CHECKSUM and DATASUM keywords. This routine is used internally by + ffvcks, but may be useful in other situations as well. \label{ffgcks} +\end{description} + +\begin{verbatim} + int fits_get_chksum/ /ffgcks + (fitsfile *fptr, > unsigned long *datasum, unsigned long *hdusum, + int *status) +\end{verbatim} + +\begin{description} +\item[5 ] Encode a checksum value + into a 16-character string. If complm is non-zero (true) then the 32-bit + sum value will be complemented before encoding. \label{ffesum} +\end{description} + +\begin{verbatim} + int fits_encode_chksum / ffesum + (unsigned long sum, int complm, > char *ascii); +\end{verbatim} + +\begin{description} +\item[6 ] Decode a 16-character checksum string into a unsigned long value. + If is non-zero (true). then the 32-bit sum value will be complemented + after decoding. The checksum value is also returned as the + value of the function. \label{ffdsum} +\end{description} + +\begin{verbatim} + unsigned long fits_decode_chksum / ffdsum + (char *ascii, int complm, > unsigned long *sum); +\end{verbatim} + + +\subsection{Date and Time Utility Routines} + +The following routines help to construct or parse the FITS date/time +strings. Starting in the year 2000, the FITS DATE keyword values (and +the values of other `DATE-' keywords) must have the form 'YYYY-MM-DD' +(date only) or 'YYYY-MM-DDThh:mm:ss.ddd...' (date and time) where the +number of decimal places in the seconds value is optional. These times +are in UTC. The older 'dd/mm/yy' date format may not be used for dates +after 01 January 2000. See Appendix B for the definition of the +parameters used in these routines. + + +\begin{description} +\item[1 ] Get the current system date. C already provides standard + library routines for getting the current date and time, + but this routine is provided for compatibility with + the Fortran FITSIO library. The returned year has 4 digits + (1999, 2000, etc.) \label{ffgsdt} +\end{description} + +\begin{verbatim} + int fits_get_system_date/ffgsdt + ( > int *day, int *month, int *year, int *status ) +\end{verbatim} + + +\begin{description} +\item[2 ] Get the current system date and time string ('YYYY-MM-DDThh:mm:ss'). +The time will be in UTC/GMT if available, as indicated by a returned timeref +value = 0. If the returned value of timeref = 1 then this indicates that +it was not possible to convert the local time to UTC, and thus the local +time was returned. +\end{description} + +\begin{verbatim} + int fits_get_system_time/ffgstm + (> char *datestr, int *timeref, int *status) +\end{verbatim} + + +\begin{description} +\item[3 ] Construct a date string from the input date values. If the year +is between 1900 and 1998, inclusive, then the returned date string will +have the old FITS format ('dd/mm/yy'), otherwise the date string will +have the new FITS format ('YYYY-MM-DD'). Use fits\_time2str instead + to always return a date string using the new FITS format. \label{ffdt2s} +\end{description} + +\begin{verbatim} + int fits_date2str/ffdt2s + (int year, int month, int day, > char *datestr, int *status) +\end{verbatim} + + +\begin{description} +\item[4 ] Construct a new-format date + time string ('YYYY-MM-DDThh:mm:ss.ddd...'). + If the year, month, and day values all = 0 then only the time is encoded + with format 'hh:mm:ss.ddd...'. The decimals parameter specifies how many + decimal places of fractional seconds to include in the string. If `decimals' + is negative, then only the date will be return ('YYYY-MM-DD'). +\end{description} + +\begin{verbatim} + int fits_time2str/fftm2s + (int year, int month, int day, int hour, int minute, double second, + int decimals, > char *datestr, int *status) +\end{verbatim} + + +\begin{description} +\item[5 ] Return the date as read from the input string, where the string may be +in either the old ('dd/mm/yy') or new ('YYYY-MM-DDThh:mm:ss' or +'YYYY-MM-DD') FITS format. Null pointers may be supplied for any + unwanted output date parameters. +\end{description} + +\begin{verbatim} + int fits_str2date/ffs2dt + (char *datestr, > int *year, int *month, int *day, int *status) +\end{verbatim} + + +\begin{description} +\item[6 ] Return the date and time as read from the input string, where the +string may be in either the old or new FITS format. The returned hours, +minutes, and seconds values will be set to zero if the input string +does not include the time ('dd/mm/yy' or 'YYYY-MM-DD') . Similarly, +the returned year, month, and date values will be set to zero if the +date is not included in the input string ('hh:mm:ss.ddd...'). Null +pointers may be supplied for any unwanted output date and time +parameters. +\end{description} + +\begin{verbatim} + int fits_str2time/ffs2tm + (char *datestr, > int *year, int *month, int *day, int *hour, + int *minute, double *second, int *status) +\end{verbatim} + + +\subsection{General Utility Routines} + +The following utility routines may be useful for certain applications. + + +\begin{description} +\item[1 ] Return the revision number of the CFITSIO library. + The revision number will be incremented with each new + release of CFITSIO. \label{ffvers} +\end{description} + +\begin{verbatim} + float fits_get_version / ffvers ( > float *version) +\end{verbatim} + +\begin{description} +\item[2 ] Write an 80-character message to the CFITSIO error stack. Application + programs should not normally write to the stack, but there may be + some situations where this is desirable. \label{ffpmsg} +\end{description} + +\begin{verbatim} + void fits_write_errmsg / ffpmsg (char *err_msg) +\end{verbatim} + +\begin{description} +\item[3 ] Convert a character string to uppercase (operates in place). \label{ffupch} +\end{description} + +\begin{verbatim} + void fits_uppercase / ffupch (char *string) +\end{verbatim} + +\begin{description} +\item[4 ] Compare the input template string against the reference string + to see if they match. The template string may contain wildcard + characters: '*' will match any sequence of characters (including + zero characters) and '?' will match any single character in the + reference string. The '\#' character will match any consecutive string + of decimal digits (0 - 9). If casesen = CASESEN = TRUE then the match will + be case sensitive, otherwise the case of the letters will be ignored + if casesen = CASEINSEN = FALSE. The returned MATCH parameter will be + TRUE if the 2 strings match, and EXACT will be TRUE if the match is + exact (i.e., if no wildcard characters were used in the match). + Both strings must be 68 characters or less in length. \label{ffcmps} +\end{description} + +\begin{verbatim} + void fits_compare_str / ffcmps + (char *templt, char *string, int casesen, > int *match, int *exact) +\end{verbatim} + +\begin{description} +\item[5 ]Split a string containing a list of names (typically file names or column + names) into individual name tokens by a sequence of calls to + fits\_split\_names. The names in the list 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 null terminator 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. \label{splitnames} +\end{description} + +\begin{verbatim} + char *fits_split_names(char *namelist) +\end{verbatim} + The following example shows how a string would be split into 3 names: + +\begin{verbatim} + myfile[1][bin (x,y)=4], file2.fits file3.fits + ^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^ + 1st name 2nd name 3rd name +\end{verbatim} + +\begin{description} +\item[6 ] Test that the keyword name contains only legal characters (A-Z,0-9, + hyphen, and underscore) or that the keyword record contains only legal + printable ASCII characters \label{fftkey} \label{fftrec} +\end{description} + +\begin{verbatim} + int fits_test_keyword / fftkey (char *keyname, > int *status) + + int fits_test_record / fftrec (char *card, > int *status) +\end{verbatim} + +\begin{description} +\item[7 ] Test whether the current header contains any NULL (ASCII 0) characters. + These characters are illegal in the header, but they will go undetected + by most of the CFITSIO keyword header routines, because the null is + interpreted as the normal end-of-string terminator. This routine returns + the position of the first null character in the header, or zero if there + are no nulls. For example a returned value of 110 would indicate that + the first NULL is located in the 30th character of the second keyword + in the header (recall that each header record is 80 characters long). + Note that this is one of the few CFITSIO routines in which the returned + value is not necessarily equal to the status value). \label{ffnchk} +\end{description} + +\begin{verbatim} + int fits_null_check / ffnchk (char *card, > int *status) +\end{verbatim} + +\begin{description} +\item[8 ] Parse a header keyword record and return the name of the keyword, + and the length of the name. + The keyword name normally occupies the first 8 characters of the + record, except under the HIERARCH convention where the name can + be up to 70 characters in length. \label{ffgknm} +\end{description} + +\begin{verbatim} + int fits_get_keyname / ffgknm + (char *card, > char *keyname, int *keylength, int *status) +\end{verbatim} + +\begin{description} +\item[9 ] Parse a header keyword record, returning the value (as + a literal character string) and comment strings. If the keyword has no + value (columns 9-10 not equal to '= '), then a null value string is + returned and the comment string is set equal to column 9 - 80 of the + input string. \label{ffpsvc} +\end{description} + +\begin{verbatim} + int fits_parse_value / ffpsvc + (char *card, > char *value, char *comment, int *status) +\end{verbatim} + +\begin{description} +\item[10] Construct an array indexed keyword name (ROOT + nnn). + This routine appends the sequence number to the root string to create + a keyword name (e.g., 'NAXIS' + 2 = 'NAXIS2') \label{ffkeyn} +\end{description} + +\begin{verbatim} + int fits_make_keyn / ffkeyn + (char *keyroot, int value, > char *keyname, int *status) +\end{verbatim} + +\begin{description} +\item[11] Construct a sequence keyword name (n + ROOT). + This routine concatenates the sequence number to the front of the + root string to create a keyword name (e.g., 1 + 'CTYP' = '1CTYP') \label{ffnkey} +\end{description} + +\begin{verbatim} + int fits_make_nkey / ffnkey + (int value, char *keyroot, > char *keyname, int *status) +\end{verbatim} + +\begin{description} +\item[12] Determine the data type of a keyword value string. This routine + parses the keyword value string to determine its data type. + Returns 'C', 'L', 'I', 'F' or 'X', for character string, logical, + integer, floating point, or complex, respectively. \label{ffdtyp} +\end{description} + +\begin{verbatim} + int fits_get_keytype / ffdtyp + (char *value, > char *dtype, int *status) +\end{verbatim} + +\begin{description} +\item[13] Determine the integer data type of an integer keyword value string. + The returned datatype value is the minimum integer datatype (starting + from top of the following list and working down) required + to store the integer value: +\end{description} + +\begin{verbatim} + Data Type Range + TSBYTE: -128 to 127 + TBYTE: 128 to 255 + TSHORT: -32768 to 32767 + TUSHORT: 32768 to 65535 + TINT -2147483648 to 2147483647 + TUINT 2147483648 to 4294967295 + TLONGLONG -9223372036854775808 to 9223372036854775807 +\end{verbatim} + +\begin{description} +\item[ ] The *neg parameter returns 1 if the input value is + negative and returns 0 if it is non-negative.\label{ffinttyp} +\end{description} + +\begin{verbatim} + int fits_get_inttype / ffinttyp + (char *value, > int *datatype, int *neg, int *status) +\end{verbatim} + +\begin{description} +\item[14] Return the class of an input header record. The record is classified + into one of the following categories (the class values are + defined in fitsio.h). Note that this is one of the few CFITSIO + routines that does not return a status value. \label{ffgkcl} +\end{description} + +\begin{verbatim} + Class Value Keywords + TYP_STRUC_KEY 10 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 20 The experimental keywords used in the compressed + image format ZIMAGE, ZCMPTYPE, ZNAMEn, ZVALn, + ZTILEn, ZBITPIX, ZNAXISn, ZSCALE, ZZERO, ZBLANK + TYP_SCAL_KEY 30 BSCALE, BZERO, TSCALn, TZEROn + TYP_NULL_KEY 40 BLANK, TNULLn + TYP_DIM_KEY 50 TDIMn + TYP_RANG_KEY 60 TLMINn, TLMAXn, TDMINn, TDMAXn, DATAMIN, DATAMAX + TYP_UNIT_KEY 70 BUNIT, TUNITn + TYP_DISP_KEY 80 TDISPn + TYP_HDUID_KEY 90 EXTNAME, EXTVER, EXTLEVEL, HDUNAME, HDUVER, HDULEVEL + TYP_CKSUM_KEY 100 CHECKSUM, DATASUM + TYP_WCS_KEY 110 WCS keywords defined in the the WCS papers, including: + CTYPEn, CUNITn, CRVALn, CRPIXn, CROTAn, CDELTn + CDj_is, PVj_ms, LONPOLEs, LATPOLEs + TCTYPn, TCTYns, TCUNIn, TCUNns, TCRVLn, TCRVns, TCRPXn, + TCRPks, TCDn_k, TCn_ks, TPVn_m, TPn_ms, TCDLTn, TCROTn + jCTYPn, jCTYns, jCUNIn, jCUNns, jCRVLn, jCRVns, iCRPXn, + iCRPns, jiCDn, jiCDns, jPVn_m, jPn_ms, jCDLTn, jCROTn + (i,j,m,n are integers, s is any letter) + TYP_REFSYS_KEY 120 EQUINOXs, EPOCH, MJD-OBSs, RADECSYS, RADESYSs, DATE-OBS + TYP_COMM_KEY 130 COMMENT, HISTORY, (blank keyword) + TYP_CONT_KEY 140 CONTINUE + TYP_USER_KEY 150 all other keywords + + int fits_get_keyclass / ffgkcl (char *card) +\end{verbatim} + +\begin{description} +\item[15] Parse the 'TFORM' binary table column format string. + This routine parses the input TFORM character string and returns the + integer data type code, the repeat count of the field, and, in the case + of character string fields, the length of the unit string. See Appendix + B for the allowed values for the returned typecode parameter. A + null pointer may be given for any output parameters that are not needed. \label{ffbnfm} +\end{description} + +\begin{verbatim} + int fits_binary_tform / ffbnfm + (char *tform, > int *typecode, long *repeat, long *width, + int *status) + + int fits_binary_tformll / ffbnfmll + (char *tform, > int *typecode, LONGLONG *repeat, long *width, + int *status) +\end{verbatim} + +\begin{description} +\item[16] Parse the 'TFORM' keyword value that defines the column format in + an ASCII table. This routine parses the input TFORM character + string and returns the data type code, the width of the column, + and (if it is a floating point column) the number of decimal places + to the right of the decimal point. The returned data type codes are + the same as for the binary table, with the following + additional rules: integer columns that are between 1 and 4 characters + wide are defined to be short integers (code = TSHORT). Wider integer + columns are defined to be regular integers (code = TLONG). Similarly, + Fixed decimal point columns (with TFORM = 'Fw.d') are defined to + be single precision reals (code = TFLOAT) if w is between 1 and 7 characters + wide, inclusive. Wider 'F' columns will return a double precision + data code (= TDOUBLE). 'Ew.d' format columns will have datacode = TFLOAT, + and 'Dw.d' format columns will have datacode = TDOUBLE. A null + pointer may be given for any output parameters that are not needed. \label{ffasfm} +\end{description} + +\begin{verbatim} + int fits_ascii_tform / ffasfm + (char *tform, > int *typecode, long *width, int *decimals, + int *status) +\end{verbatim} + +\begin{description} +\item[17] Calculate the starting column positions and total ASCII table width + based on the input array of ASCII table TFORM values. The SPACE input + parameter defines how many blank spaces to leave between each column + (it is recommended to have one space between columns for better human + readability). \label{ffgabc} +\end{description} + +\begin{verbatim} + int fits_get_tbcol / ffgabc + (int tfields, char **tform, int space, > long *rowlen, + long *tbcol, int *status) +\end{verbatim} + +\begin{description} +\item[18] Parse a template header record and return a formatted 80-character string + suitable for appending to (or deleting from) a FITS header file. + This routine is useful for parsing lines from an ASCII template file + and reformatting them into legal FITS header records. The formatted + string may then be passed to the fits\_write\_record, ffmcrd, or + fits\_delete\_key routines + to append or modify a FITS header record. \label{ffgthd} +\end{description} + +\begin{verbatim} + int fits_parse_template / ffgthd + (char *templt, > char *card, int *keytype, int *status) +\end{verbatim} + The input templt character string generally should contain 3 tokens: + (1) the KEYNAME, (2) the VALUE, and (3) the COMMENT string. The + TEMPLATE string must adhere to the following format: + + +\begin{description} +\item[- ] The KEYNAME token must begin in columns 1-8 and be a maximum of 8 + characters long. A legal FITS keyword name may only + contain the characters A-Z, 0-9, and '-' (minus sign) and + underscore. This routine will automatically convert any lowercase + characters to uppercase in the output string. If the first 8 characters + of the template line are + blank then the remainder of the line is considered to be a FITS comment + (with a blank keyword name). +\end{description} + + +\begin{description} +\item[- ] The VALUE token must be separated from the KEYNAME token by one or more + spaces and/or an '=' character. The data type of the VALUE token + (numeric, logical, or character string) is automatically determined + and the output CARD string is formatted accordingly. The value + token may be forced to be interpreted as a string (e.g. if it is a + string of numeric digits) by enclosing it in single quotes. + If the value token is a character string that contains 1 or more + embedded blank space characters or slash ('/') characters then the + entire character string must be enclosed in single quotes. +\end{description} + + +\begin{description} +\item[- ] The COMMENT token is optional, but if present must be separated from + the VALUE token by a blank space or a '/' character. +\end{description} + + +\begin{description} +\item[- ] One exception to the above rules is that if the first non-blank + character in the first 8 characters of the template string is a + minus sign ('-') followed + by a single token, or a single token followed by an equal sign, + then it is interpreted as the name of a keyword which is to be + deleted from the FITS header. +\end{description} + + +\begin{description} +\item[- ] The second exception is that if the template string starts with + a minus sign and is followed by 2 tokens (without an equals sign between + them) then the second token + is interpreted as the new name for the keyword specified by + first token. In this case the old keyword name (first token) + is returned in characters 1-8 of the returned CARD string, and + the new keyword name (the second token) is returned in characters + 41-48 of the returned CARD string. These old and new names + may then be passed to the ffmnam routine which will change + the keyword name. +\end{description} + + The keytype output parameter indicates how the returned CARD string + should be interpreted: + +\begin{verbatim} + keytype interpretation + ------- ------------------------------------------------- + -2 Rename the keyword with name = the first 8 characters of CARD + to the new name given in characters 41 - 48 of CARD. + + -1 delete the keyword with this name from the FITS header. + + 0 append the CARD string to the FITS header if the + keyword does not already exist, otherwise update + the keyword value and/or comment field if is already exists. + + 1 This is a HISTORY or COMMENT keyword; append it to the header + + 2 END record; do not explicitly write it to the FITS file. +\end{verbatim} + EXAMPLES: The following lines illustrate valid input template strings: + +\begin{verbatim} + INTVAL 7 / This is an integer keyword + RVAL 34.6 / This is a floating point keyword + EVAL=-12.45E-03 / This is a floating point keyword in exponential notation + lval F / This is a boolean keyword + This is a comment keyword with a blank keyword name + SVAL1 = 'Hello world' / this is a string keyword + SVAL2 '123.5' this is also a string keyword + sval3 123+ / this is also a string keyword with the value '123+ ' + # the following template line deletes the DATE keyword + - DATE + # the following template line modifies the NAME keyword to OBJECT + - NAME OBJECT +\end{verbatim} + +\begin{description} +\item[19] Translate a keyword name into a new name, based on a set of patterns. +This routine is useful for translating keywords in cases such as +adding or deleting columns in +a table, or copying a column from one table to another, or extracting +an array from a cell in a binary table column into an image extension. In +these cases, it is necessary to translate the names of the keywords associated +with the original table column(s) into the appropriate keyword name in the final +file. For example, if column 2 is deleted from a table, +then the value of 'n' in all the +TFORMn and TTYPEn keywords for columns 3 and higher must be decremented +by 1. Even more complex translations are sometimes needed to convert the +WCS keywords when extracting an image out of a table column cell into +a separate image extension. + +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: + +\begin{verbatim} + 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 +\end{verbatim} +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: + + "-" - 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.\label{translatekey} +\end{description} + +\begin{verbatim} +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 */ +\end{verbatim} + +\begin{description} +\item[ ] Here is an example of some of the patterns used to convert the keywords associated +with an image in a cell of a table column into the keywords appropriate for +an IMAGE extension: +\end{description} + +\begin{verbatim} + 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"}}; +\end{verbatim} + +\begin{description} +\item[20] Translate the keywords in the input HDU into the keywords that are +appropriate for the output HDU. This is a driver routine that calls +the previously described routine. +\end{description} + +\begin{verbatim} +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 */ +\end{verbatim} + + +\begin{description} +\item[21] Parse the input string containing a list of rows or row ranges, and + return integer arrays containing the first and last row in each + range. For example, if rowlist = "3-5, 6, 8-9" then it will + return numranges = 3, rangemin = 3, 6, 8 and rangemax = 5, 6, 9. + At most, 'maxranges' number of ranges will be returned. 'maxrows' + is the maximum number of rows in the table; any rows or ranges + larger than this will be ignored. The rows must be specified in + increasing order, and the ranges must not overlap. A minus sign + may be use to specify all the rows to the upper or lower bound, so + "50-" means all the rows from 50 to the end of the table, and "-" + means all the rows in the table, from 1 - maxrows. + \label{ffrwrg} +\end{description} + +\begin{verbatim} + int fits_parse_range / ffrwrg(char *rowlist, LONGLONG maxrows, int maxranges, > + int *numranges, long *rangemin, long *rangemax, int *status) + + int fits_parse_rangell / ffrwrgll(char *rowlist, LONGLONG maxrows, int maxranges, > + int *numranges, LONGLONG *rangemin, LONGLONG *rangemax, int *status) +\end{verbatim} + +\begin{description} +\item[22] Check that the Header fill bytes (if any) are all blank. These are the bytes + that may follow END keyword and before the beginning of data unit, + or the end of the HDU if there is no data unit. + \label{ffchfl} +\end{description} + +\begin{verbatim} + int ffchfl(fitsfile *fptr, > int *status) +\end{verbatim} + +\begin{description} +\item[23] Check that the Data fill bytes (if any) are all zero (for IMAGE or + BINARY Table HDU) or all blanks (for ASCII table HDU). These file + bytes may be located after the last valid data byte in the HDU and + before the physical end of the HDU. + \label{ffcdfl} +\end{description} + +\begin{verbatim} + int ffcdfl(fitsfile *fptr, > int *status) +\end{verbatim} + +\begin{description} +\item[24] Estimate the root-mean-squared (RMS) noise in an image. +These routines are mainly for use with the Hcompress image compression +algorithm. They return an estimate of the RMS noise in the background +pixels of the image. This robust algorithm (written by Richard +White, STScI) first attempts to estimate the RMS value +as 1.68 times the median of the absolute differences between successive +pixels in the image. If the median = 0, then the +algorithm falls back to computing the RMS of the difference between successive +pixels, after several N-sigma rejection cycles to remove +extreme values. The input parameters are: the array of image pixel values +(either float or short values), the number of values in the array, +the value that is used to represent null pixels (enter a very +large number if there are no null pixels). \label{imageRMS} +\end{description} + +\begin{verbatim} + int fits_rms_float (float fdata[], int npix, float in_null_value, + > double *rms, int *status) + int fits_rms_short (short fdata[], int npix, short in_null_value, + > double *rms, int *status) +\end{verbatim} + +\begin{description} +\item[25] Was CFITSIO compiled with the -D\_REENTRANT directive +so that it may be safely used in multi-threaded environments? +The following function returns 1 if yes, 0 if no. Note, however, +that even if the -D\_REENTRANT directive was specified, this does +not guarantee that the CFITSIO routines are thread-safe, because +some compilers may not support this feature.\label{reentrant} +\end{description} + +\begin{verbatim} +int fits_is_reentrant(void) +\end{verbatim} + +\chapter{ The CFITSIO Iterator Function } + +The fits\_iterate\_data function in CFITSIO provides a unique method of +executing an arbitrary user-supplied `work' function that operates on +rows of data in FITS tables or on pixels in FITS images. Rather than +explicitly reading and writing the FITS images or columns of data, one +instead calls the CFITSIO iterator routine, passing to it the name of +the user's work function that is to be executed along with a list of +all the table columns or image arrays that are to be passed to the work +function. The CFITSIO iterator function then does all the work of +allocating memory for the arrays, reading the input data from the FITS +file, passing them to the work function, and then writing any output +data back to the FITS file after the work function exits. Because +it is often more efficient to process only a subset of the total table +rows at one time, the iterator function can determine the optimum +amount of data to pass in each iteration and repeatedly call the work +function until the entire table been processed. + +For many applications this single CFITSIO iterator function can +effectively replace all the other CFITSIO routines for reading or +writing data in FITS images or tables. Using the iterator has several +important advantages over the traditional method of reading and writing +FITS data files: + +\begin{itemize} +\item +It cleanly separates the data I/O from the routine that operates on +the data. This leads to a more modular and `object oriented' +programming style. + +\item +It simplifies the application program by eliminating the need to allocate +memory for the data arrays and eliminates most of the calls to the CFITSIO +routines that explicitly read and write the data. + +\item +It ensures that the data are processed as efficiently as possible. +This is especially important when processing tabular data since +the iterator function will calculate the most efficient number +of rows in the table to be passed at one time to the user's work +function on each iteration. + +\item +Makes it possible for larger projects to develop a library of work +functions that all have a uniform calling sequence and are all +independent of the details of the FITS file format. + +\end{itemize} + +There are basically 2 steps in using the CFITSIO iterator function. +The first step is to design the work function itself which must have a +prescribed set of input parameters. One of these parameters is a +structure containing pointers to the arrays of data; the work function +can perform any desired operations on these arrays and does not need to +worry about how the input data were read from the file or how the +output data get written back to the file. + +The second step is to design the driver routine that opens all the +necessary FITS files and initializes the input parameters to the +iterator function. The driver program calls the CFITSIO iterator +function which then reads the data and passes it to the user's work +function. + +The following 2 sections describe these steps in more detail. There +are also several example programs included with the CFITSIO +distribution which illustrate how to use the iterator function. + + +\section{The Iterator Work Function} + +The user-supplied iterator work function must have the following set of +input parameters (the function can be given any desired name): + + +\begin{verbatim} + int user_fn( long totaln, long offset, long firstn, long nvalues, + int narrays, iteratorCol *data, void *userPointer ) +\end{verbatim} + +\begin{itemize} + +\item + totaln -- the total number of table rows or image pixels + that will be passed to the work function + during 1 or more iterations. + +\item + offset -- the offset applied to the first table row or image + pixel to be passed to the work function. In other + words, this is the number of rows or pixels that + are skipped over before starting the iterations. If + offset = 0, then all the table rows or image pixels + will be passed to the work function. + +\item + firstn -- the number of the first table row or image pixel + (starting with 1) that is being passed in this + particular call to the work function. + +\item + nvalues -- the number of table rows or image pixels that are + being passed in this particular call to the work + function. nvalues will always be less than or + equal to totaln and will have the same value on + each iteration, except possibly on the last + call which may have a smaller value. + +\item + narrays -- the number of arrays of data that are being passed + to the work function. There is one array for each + image or table column. + +\item + *data -- array of structures, one for each + column or image. Each structure contains a pointer + to the array of data as well as other descriptive + parameters about that array. + +\item + *userPointer -- a user supplied pointer that can be used + to pass ancillary information from the driver function + to the work function. + This pointer is passed to the CFITSIO iterator function + which then passes it on to the + work function without any modification. + It may point to a single number, to an array of values, + to a structure containing an arbitrary set of parameters + of different types, + or it may be a null pointer if it is not needed. + The work function must cast this pointer to the + appropriate data type before using it it. +\end{itemize} + +The totaln, offset, narrays, data, and userPointer parameters are +guaranteed to have the same value on each iteration. Only firstn, +nvalues, and the arrays of data pointed to by the data structures may +change on each iterative call to the work function. + +Note that the iterator treats an image as a long 1-D array of pixels +regardless of it's intrinsic dimensionality. The total number of +pixels is just the product of the size of each dimension, and the order +of the pixels is the same as the order that they are stored in the FITS +file. If the work function needs to know the number and size of the +image dimensions then these parameters can be passed via the +userPointer structure. + +The iteratorCol structure is currently defined as follows: + +\begin{verbatim} +typedef struct /* structure for the iterator function column information */ +{ + /* structure elements required as input to fits_iterate_data: */ + + fitsfile *fptr; /* pointer to the HDU containing the column or image */ + int colnum; /* column number in the table; ignored for images */ + char colname[70]; /* name (TTYPEn) of the column; null for images */ + int datatype; /* output data type (converted if necessary) */ + int iotype; /* type: InputCol, InputOutputCol, or OutputCol */ + + /* output structure 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; set */ + /* equal to 1 for images */ + long tlmin; /* legal minimum data value, if any */ + long tlmax; /* legal maximum data value, if any */ + char unit[70]; /* physical unit string (BUNIT or TUNITn) */ + char tdisp[70]; /* suggested display format; null if none */ + +} iteratorCol; +\end{verbatim} + +Instead of directly reading or writing the elements in this structure, +it is recommended that programmers use the access functions that are +provided for this purpose. + +The first five elements in this structure must be initially defined by +the driver routine before calling the iterator routine. The CFITSIO +iterator routine uses this information to determine what column or +array to pass to the work function, and whether the array is to be +input to the work function, output from the work function, or both. +The CFITSIO iterator function fills in the values of the remaining +structure elements before passing it to the work function. + +The array structure element is a pointer to the actual data array and +it must be cast to the correct data type before it is used. The +`repeat' structure element give the number of data values in each row +of the table, so that the total number of data values in the array is +given by repeat * nvalues. In the case of image arrays and ASCII +tables, repeat will always be equal to 1. When the data type is a +character string, the array pointer is actually a pointer to an array +of string pointers (i.e., char **array). The other output structure +elements are provided for convenience in case that information is +needed within the work function. Any other information may be passed +from the driver routine to the work function via the userPointer +parameter. + +Upon completion, the work routine must return an integer status value, +with 0 indicating success and any other value indicating an error which +will cause the iterator function to immediately exit at that point. Return status +values in the range 1 -- 1000 should be avoided since these are +reserved for use by CFITSIO. A return status value of -1 may be used to +force the CFITSIO iterator function to stop at that point and return +control to the driver routine after writing any output arrays to the +FITS file. CFITSIO does not considered this to be an error condition, +so any further processing by the application program will continue normally. + + +\section{The Iterator Driver Function} + +The iterator driver function must open the necessary FITS files and +position them to the correct HDU. It must also initialize the following +parameters in the iteratorCol structure (defined above) for each +column or image before calling the CFITSIO iterator function. +Several `constructor' routines are provided in CFITSIO for this +purpose. + +\begin{itemize} +\item + *fptr -- The fitsfile pointer to the table or image. +\item +colnum -- the number of the column in the table. This value is ignored + in the case of images. If colnum equals 0, then the column name + will be used to identify the column to be passed to the + work function. + +\item +colname -- the name (TTYPEn keyword) of the column. This is + only required if colnum = 0 and is ignored for images. +\item +datatype -- The desired data type of the array to be passed to the + work function. For numerical data the data type does + not need to be the same as the actual data type in the + FITS file, in which case CFITSIO will do the conversion. + Allowed values are: TSTRING, TLOGICAL, TBYTE, TSBYTE, TSHORT, TUSHORT, + TINT, TLONG, TULONG, TFLOAT, TDOUBLE. If the input + value of data type equals 0, then the existing + data type of the column or image will be used without + any conversion. + +\item +iotype -- defines whether the data array is to be input to the + work function (i.e, read from the FITS file), or output + from the work function (i.e., written to the FITS file) or + both. Allowed values are InputCol, OutputCol, or InputOutputCol. + Variable-length array columns are supported as InputCol or + InputOutputCol types, but may not be used for an OutputCol type. +\end{itemize} + +After the driver routine has initialized all these parameters, it +can then call the CFITSIO iterator function: + + +\begin{verbatim} + int fits_iterate_data(int narrays, iteratorCol *data, long offset, + long nPerLoop, int (*workFn)( ), void *userPointer, int *status); +\end{verbatim} + +\begin{itemize} +\item + + narrays -- the number of columns or images that are to be passed + to the work function. +\item + *data -- pointer to array of structures containing information + about each column or image. + +\item + offset -- if positive, this number of rows at the + beginning of the table (or pixels in the image) + will be skipped and will not be passed to the work + function. + +\item + nPerLoop - specifies the number of table rows (or number of + image pixels) that are to be passed to the work + function on each iteration. If nPerLoop = 0 + then CFITSIO will calculate the optimum number + for greatest efficiency. + If nPerLoop is negative, then all the rows + or pixels will be passed at one time, and the work + function will only be called once. If any variable + length arrays are being processed, then the nPerLoop + value is ignored, and the iterator will always process + one row of the table at a time. + +\item + *workFn - the name (actually the address) of the work function + that is to be called by fits\_iterate\_data. + +\item + *userPointer - this is a user supplied pointer that can be used + to pass ancillary information from the driver routine + to the work function. It may point to a single number, + an array, or to a structure containing an arbitrary set + of parameters. + +\item + *status - The CFITSIO error status. Should = 0 on input; + a non-zero output value indicates an error. +\end{itemize} + +When fits\_iterate\_data is called it first allocates memory to hold +all the requested columns of data or image pixel arrays. It then reads +the input data from the FITS tables or images into the arrays then +passes the structure with pointers to these data arrays to the work +function. After the work function returns, the iterator function +writes any output columns of data or images back to the FITS files. It +then repeats this process for any remaining sets of rows or image +pixels until it has processed the entire table or image or until the +work function returns a non-zero status value. The iterator then frees +the memory that it initially allocated and returns control to the +driver routine that called it. + + +\section{Guidelines for Using the Iterator Function} + +The totaln, offset, firstn, and nvalues parameters that are passed to +the work function are useful for determining how much of the data has +been processed and how much remains left to do. On the very first call +to the work function firstn will be equal to offset + 1; the work +function may need to perform various initialization tasks before +starting to process the data. Similarly, firstn + nvalues - 1 will be +equal to totaln on the last iteration, at which point the work function +may need to perform some clean up operations before exiting for the +last time. The work function can also force an early termination of +the iterations by returning a status value = -1. + +The narrays and iteratorCol.datatype arguments allow the work function +to double check that the number of input arrays and their data types +have the expected values. The iteratorCol.fptr and iteratorCol.colnum +structure elements can be used if the work function needs to read or +write the values of other keywords in the FITS file associated with +the array. This should generally only be done during the +initialization step or during the clean up step after the last set of +data has been processed. Extra FITS file I/O during the main +processing loop of the work function can seriously degrade the speed of +the program. + +If variable-length array columns are being processed, then the iterator +will operate on one row of the table at a time. In this case the +the repeat element in the interatorCol structure will be set equal to +the number of elements in the current row that is being processed. + +One important feature of the iterator is that the first element in each +array that is passed to the work function gives the value that is used +to represent null or undefined values in the array. The real data then +begins with the second element of the array (i.e., array[1], not +array[0]). If the first array element is equal to zero, then this +indicates that all the array elements have defined values and there are +no undefined values. If array[0] is not equal to zero, then this +indicates that some of the data values are undefined and this value +(array[0]) is used to represent them. In the case of output arrays +(i.e., those arrays that will be written back to the FITS file by the +iterator function after the work function exits) the work function must +set the first array element to the desired null value if necessary, +otherwise the first element should be set to zero to indicate that +there are no null values in the output array. CFITSIO defines 2 +values, FLOATNULLVALUE and DOUBLENULLVALUE, that can be used as default +null values for float and double data types, respectively. In the case +of character string data types, a null string is always used to +represent undefined strings. + +In some applications it may be necessary to recursively call the iterator +function. An example of this is given by one of the example programs +that is distributed with CFITSIO: it first calls a work function that +writes out a 2D histogram image. That work function in turn calls +another work function that reads the `X' and `Y' columns in a table to +calculate the value of each 2D histogram image pixel. Graphically, the +program structure can be described as: + +\begin{verbatim} + driver --> iterator --> work1_fn --> iterator --> work2_fn +\end{verbatim} + +Finally, it should be noted that the table columns or image arrays that +are passed to the work function do not all have to come from the same +FITS file and instead may come from any combination of sources as long +as they have the same length. The length of the first table column or +image array is used by the iterator if they do not all have the same +length. + + +\section{Complete List of Iterator Routines} + +All of the iterator routines are listed below. Most of these routines +do not have a corresponding short function name. + + +\begin{description} +\item[1 ] Iterator `constructor' functions that set + the value of elements in the iteratorCol structure + that define the columns or arrays. These set the fitsfile + pointer, column name, column number, datatype, and iotype, + respectively. The last 2 routines allow all the parameters + to be set with one function call (one supplies the column + name, the other the column number). \label{ffiterset} +\end{description} + + +\begin{verbatim} + 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); + + 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); +\end{verbatim} + +\begin{description} +\item[2 ] Iterator `accessor' functions that return the value of the + element in the iteratorCol structure + that describes a particular data column or array \label{ffiterget} +\end{description} + +\begin{verbatim} + 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); +\end{verbatim} + +\begin{description} +\item[3 ] The CFITSIO iterator function \label{ffiter} +\end{description} + +\begin{verbatim} + int fits_iterate_data(int narrays, 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); +\end{verbatim} + +\chapter{ World Coordinate System Routines } + +The FITS community has adopted a set of keyword conventions that define +the transformations needed to convert between pixel locations in an +image and the corresponding celestial coordinates on the sky, or more +generally, that define world coordinates that are to be associated with +any pixel location in an n-dimensional FITS array. CFITSIO is distributed +with a a few self-contained World Coordinate System (WCS) routines, +however, these routines DO NOT support all the latest WCS conventions, +so it is STRONGLY RECOMMENDED that software developers use a more robust +external WCS library. Several recommended libraries are: + + +\begin{verbatim} + WCSLIB - supported by Mark Calabretta + WCSTools - supported by Doug Mink + AST library - developed by the U.K. Starlink project +\end{verbatim} + +More information about the WCS keyword conventions and links to all of +these WCS libraries can be found on the FITS Support Office web site at +http://fits.gsfc.nasa.gov under the WCS link. + +The functions provided in these external WCS libraries will need +access to the WCS keywords contained in the FITS file headers. +One convenient way to pass this information to the external library is +to use the fits\_hdr2str routine in CFITSIO (defined below) to copy the +header keywords into one long string, and then pass this string to an +interface routine in the external library that will extract +the necessary WCS information (e.g., the 'wcspih' routine in the WCSLIB +library and the 'astFitsChan' and 'astPutCards' functions in the AST +library). + + +\begin{description} +\item[1 ] Concatenate the header keywords in the CHDU into a single long + string of characters. Each 80-character fixed-length keyword + record is appended to the output character string, in order, with + no intervening separator or terminating characters. The last header + record is terminated with a NULL character. This routine allocates + memory for the returned character array, so the calling program must + free the memory when finished. + + There are 2 related routines: fits\_hdr2str simply concatenates all + the existing keywords in the header; fits\_convert\_hdr2str is similar, + except that if the CHDU is a tile compressed image (stored in a binary + table) then it will first convert that header back to that of a + normal FITS image before concatenating the keywords. + + Selected keywords may be excluded from the returned character string. + If the second parameter (nocomments) is TRUE (nonzero) then any + COMMENT, HISTORY, or blank keywords in the header will not be copied + to the output string. + + The 'exclist' parameter may be used to supply a list of keywords + that are to be excluded from the output character string. Wild card + characters (*, ?, and \#) may be used in the excluded keyword names. + If no additional keywords are to be excluded, then set nexc = 0 and + specify NULL for the the **exclist parameter. \label{hdr2str} +\end{description} + +\begin{verbatim} + int fits_hdr2str + (fitsfile *fptr, int nocomments, char **exclist, int nexc, + > char **header, int *nkeys, int *status) + + int fits_convert_hdr2str / ffcnvthdr2str + (fitsfile *fptr, int nocomments, char **exclist, int nexc, + > char **header, int *nkeys, int *status) +\end{verbatim} + + +\begin{description} +\item[2 ] The following CFITSIO routine is specifically designed for use +in conjunction with the WCSLIB library. It is not expected that +applications programmers will call this routine directly, but it +is documented here for completeness. This routine extracts arrays +from a binary table that contain WCS information using the -TAB table +lookup convention. See the documentation provided with the WCSLIB + library for more information. \label{wcstab} +\end{description} + +\begin{verbatim} + int fits_read_wcstab + (fitsfile *fptr, int nwtb, wtbarr *wtb, int *status); +\end{verbatim} + +\section{ Self-contained WCS Routines} + +The following routines DO NOT support the more recent WCS conventions +that have been approved as part of the FITS standard. Consequently, +the following routines ARE NOW DEPRECATED. It is STRONGLY RECOMMENDED +that software developers not use these routines, and instead use an +external WCS library, as described in the previous section. + +These routines are included mainly for backward compatibility with +existing software. They support the following standard map +projections: -SIN, -TAN, -ARC, -NCP, -GLS, -MER, and -AIT (these are the +legal values for the coordtype parameter). These routines are based +on similar functions in Classic AIPS. All the angular quantities are +given in units of degrees. + + +\begin{description} +\item[1 ] Get the values of the basic set of standard FITS celestial coordinate + system keywords from the header of a FITS image (i.e., the primary + array or an IMAGE extension). These values may then be passed to + the fits\_pix\_to\_world and fits\_world\_to\_pix routines that + perform the coordinate transformations. If any or all of the WCS + keywords are not present, then default values will be returned. If + the first coordinate axis is the declination-like coordinate, then + this routine will swap them so that the longitudinal-like coordinate + is returned as the first axis. + + The first routine (ffgics) returns + the primary WCS, whereas the second routine returns the particular + version of the WCS specified by the 'version' parameter, which much + be a character ranging from 'A' to 'Z' (or a blank character, which is + equivalent to calling ffgics). + + If the file uses the newer 'CDj\_i' WCS transformation matrix + keywords instead of old style 'CDELTn' and 'CROTA2' keywords, then + this routine will calculate and return the values of the equivalent + old-style keywords. Note that the conversion from the new-style + keywords to the old-style values is sometimes only an + approximation, so if the approximation is larger than an internally + defined threshold level, then CFITSIO will still return the + approximate WCS keyword values, but will also return with status = + APPROX\_WCS\_KEY, to warn the calling program that approximations + have been made. It is then up to the calling program to decide + whether the approximations are sufficiently accurate for the + particular application, or whether more precise WCS transformations + must be performed using new-style WCS keywords directly. \label{ffgics} +\end{description} + +\begin{verbatim} + int fits_read_img_coord / ffgics + (fitsfile *fptr, > double *xrefval, double *yrefval, + double *xrefpix, double *yrefpix, double *xinc, double *yinc, + double *rot, char *coordtype, int *status) + + int fits_read_img_coord_version / ffgicsa + (fitsfile *fptr, char version, > double *xrefval, double *yrefval, + double *xrefpix, double *yrefpix, double *xinc, double *yinc, + double *rot, char *coordtype, int *status) +\end{verbatim} + +\begin{description} +\item[2 ] Get the values of the standard FITS celestial coordinate system + keywords from the header of a FITS table where the X and Y (or RA + and DEC) coordinates are stored in 2 separate columns of the table + (as in the Event List table format that is often used by high energy + astrophysics missions). These values may then be passed to the + fits\_pix\_to\_world and fits\_world\_to\_pix routines that perform + the coordinate transformations. \label{ffgtcs} +\end{description} + +\begin{verbatim} + int fits_read_tbl_coord / ffgtcs + (fitsfile *fptr, int xcol, int ycol, > double *xrefval, + double *yrefval, double *xrefpix, double *yrefpix, double *xinc, + double *yinc, double *rot, char *coordtype, int *status) +\end{verbatim} + +\begin{description} +\item[3 ] Calculate the celestial coordinate corresponding to the input + X and Y pixel location in the image. \label{ffwldp} +\end{description} + +\begin{verbatim} + int fits_pix_to_world / ffwldp + (double xpix, double ypix, double xrefval, double yrefval, + double xrefpix, double yrefpix, double xinc, double yinc, + double rot, char *coordtype, > double *xpos, double *ypos, + int *status) +\end{verbatim} + +\begin{description} +\item[4 ] Calculate the X and Y pixel location corresponding to the input + celestial coordinate in the image. \label{ffxypx} +\end{description} + +\begin{verbatim} + int fits_world_to_pix / ffxypx + (double xpos, double ypos, double xrefval, double yrefval, + double xrefpix, double yrefpix, double xinc, double yinc, + double rot, char *coordtype, > double *xpix, double *ypix, + int *status) +\end{verbatim} + + +\chapter{ Hierarchical Grouping Routines } + +These functions allow for the creation and manipulation of FITS HDU +Groups, as defined in "A Hierarchical Grouping Convention for FITS" by +Jennings, Pence, Folk and Schlesinger: + +http://fits.gsfc.nasa.gov/group.html + +A group is a +collection of HDUs whose association is defined by a {\it grouping +table}. HDUs which are part of a group are referred to as {\it member +HDUs} or simply as {\it members}. Grouping table member HDUs may +themselves be grouping tables, thus allowing for the construction of +open-ended hierarchies of HDUs. + +Grouping tables contain one row for each member HDU. The grouping table +columns provide identification information that allows applications to +reference or "point to" the member HDUs. Member HDUs are expected, but +not required, to contain a set of GRPIDn/GRPLCn keywords in their +headers for each grouping table that they are referenced by. In this +sense, the GRPIDn/GRPLCn keywords "link" the member HDU back to its +Grouping table. Note that a member HDU need not reside in the same FITS +file as its grouping table, and that a given HDU may be referenced by +up to 999 grouping tables simultaneously. + +Grouping tables are implemented as FITS binary tables with up to six +pre-defined column TTYPEn values: 'MEMBER\_XTENSION', 'MEMBER\_NAME', +'MEMBER\_VERSION', 'MEMBER\_POSITION', 'MEMBER\_URI\_TYPE' and 'MEMBER\_LOCATION'. +The first three columns allow member HDUs to be identified by reference to +their XTENSION, EXTNAME and EXTVER keyword values. The fourth column allows +member HDUs to be identified by HDU position within their FITS file. +The last two columns identify the FITS file in which the member HDU resides, +if different from the grouping table FITS file. + +Additional user defined "auxiliary" columns may also be included with any +grouping table. When a grouping table is copied or modified the presence of +auxiliary columns is always taken into account by the grouping support +functions; however, the grouping support functions cannot directly +make use of this data. + +If a grouping table column is defined but the corresponding member HDU +information is unavailable then a null value of the appropriate data type +is inserted in the column field. Integer columns (MEMBER\_POSITION, +MEMBER\_VERSION) are defined with a TNULLn value of zero (0). Character field +columns (MEMBER\_XTENSION, MEMBER\_NAME, MEMBER\_URI\_TYPE, MEMBER\_LOCATION) +utilize an ASCII null character to denote a null field value. + +The grouping support functions belong to two basic categories: those that +work with grouping table HDUs (ffgt**) and those that work with member HDUs +(ffgm**). Two functions, fits\_copy\_group() and fits\_remove\_group(), have the +option to recursively copy/delete entire groups. Care should be taken when +employing these functions in recursive mode as poorly defined groups could +cause unpredictable results. The problem of a grouping table directly or +indirectly referencing itself (thus creating an infinite loop) is protected +against; in fact, neither function will attempt to copy or delete an HDU +twice. + + +\section{Grouping Table Routines} + + +\begin{description} +\item[1 ]Create (append) a grouping table at the end of the current FITS file + pointed to by fptr. The grpname parameter provides the grouping table + name (GRPNAME keyword value) and may be set to NULL if no group name + is to be specified. The grouptype parameter specifies the desired + structure of the grouping table and may take on the values: + GT\_ID\_ALL\_URI (all columns created), GT\_ID\_REF (ID by reference columns), + GT\_ID\_POS (ID by position columns), GT\_ID\_ALL (ID by reference and + position columns), GT\_ID\_REF\_URI (ID by reference and FITS file URI + columns), and GT\_ID\_POS\_URI (ID by position and FITS file URI columns). \label{ffgtcr} +\end{description} + +\begin{verbatim} + int fits_create_group / ffgtcr + (fitsfile *fptr, char *grpname, int grouptype, > int *status) +\end{verbatim} + +\begin{description} +\item[2 ]Create (insert) a grouping table just after the CHDU of the current FITS + file pointed to by fptr. All HDUs below the the insertion point will be + shifted downwards to make room for the new HDU. The grpname parameter + provides the grouping table name (GRPNAME keyword value) and may be set to + NULL if no group name is to be specified. The grouptype parameter specifies + the desired structure of the grouping table and may take on the values: + GT\_ID\_ALL\_URI (all columns created), GT\_ID\_REF (ID by reference columns), + GT\_ID\_POS (ID by position columns), GT\_ID\_ALL (ID by reference and + position columns), GT\_ID\_REF\_URI (ID by reference and FITS file URI + columns), and GT\_ID\_POS\_URI (ID by position and FITS file URI columns) \label{ffgtis}. +\end{description} + +\begin{verbatim} + int fits_insert_group / ffgtis + (fitsfile *fptr, char *grpname, int grouptype, > int *status) +\end{verbatim} + +\begin{description} +\item[3 ]Change the structure of an existing grouping table pointed to by + gfptr. The grouptype parameter (see fits\_create\_group() for valid + parameter values) specifies the new structure of the grouping table. This + function 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 null values appropriate to the + column type. \label{ffgtch} +\end{description} + +\begin{verbatim} + int fits_change_group / ffgtch + (fitsfile *gfptr, int grouptype, > int *status) +\end{verbatim} + +\begin{description} +\item[4 ]Remove the group defined by the grouping table pointed to by gfptr, and + optionally all the group member HDUs. The rmopt parameter specifies the + action to be taken for + all members of the group defined by the grouping table. Valid values are: + OPT\_RM\_GPT (delete only the grouping table) and OPT\_RM\_ALL (recursively + delete all HDUs that belong to the group). Any groups containing the + grouping table gfptr as a member are updated, and if rmopt == OPT\_RM\_GPT + all members have their GRPIDn and GRPLCn keywords updated accordingly. + If rmopt == OPT\_RM\_ALL, then other groups that contain the deleted members + of gfptr are updated to reflect the deletion accordingly. \label{ffgtrm} +\end{description} + +\begin{verbatim} + int fits_remove_group / ffgtrm + (fitsfile *gfptr, int rmopt, > int *status) +\end{verbatim} + +\begin{description} +\item[5 ]Copy (append) the group defined by the grouping table pointed to by infptr, + and optionally all group member HDUs, to the FITS file pointed to by + outfptr. The cpopt parameter specifies the action to be taken for all + members of the group infptr. Valid values are: OPT\_GCP\_GPT (copy only + the grouping table) and OPT\_GCP\_ALL (recursively copy ALL the HDUs that + belong to the group defined by infptr). If the cpopt == OPT\_GCP\_GPT then + the members of infptr have their GRPIDn and GRPLCn keywords updated to + reflect the existence of the new grouping table outfptr, since they now + belong to the new group. If cpopt == OPT\_GCP\_ALL then the new + grouping table outfptr only contains pointers to the copied member HDUs + and not the original member HDUs of infptr. Note that, when + cpopt == OPT\_GCP\_ALL, all members of the group defined by infptr will be + copied to a single FITS file pointed to by outfptr regardless of their + file distribution in the original group. \label{ffgtcp} +\end{description} + +\begin{verbatim} + int fits_copy_group / ffgtcp + (fitsfile *infptr, fitsfile *outfptr, int cpopt, > int *status) +\end{verbatim} + +\begin{description} +\item[6 ] Merge the two groups defined by the grouping table HDUs infptr and outfptr + by combining their members into a single grouping table. All member HDUs + (rows) are copied from infptr to outfptr. If mgopt == OPT\_MRG\_COPY then + infptr continues to exist unaltered after the merge. If the mgopt == + OPT\_MRG\_MOV then infptr is deleted after the merge. In both cases, + the GRPIDn and GRPLCn keywords of the member HDUs are updated accordingly. \label{ffgtmg} +\end{description} + +\begin{verbatim} + int fits_merge_groups / ffgtmg + (fitsfile *infptr, fitsfile *outfptr, int mgopt, > int *status) +\end{verbatim} + +\begin{description} +\item[7 ]"Compact" the group defined by grouping table pointed to by gfptr. The + compaction is achieved by merging (via fits\_merge\_groups()) all direct + member HDUs of gfptr that are themselves grouping tables. The cmopt + parameter defines whether the merged grouping table HDUs remain after + merging (cmopt == OPT\_CMT\_MBR) or if they are deleted after merging + (cmopt == OPT\_CMT\_MBR\_DEL). If the grouping table contains no direct + member HDUs that are themselves grouping tables then this function + does nothing. Note that this function is not recursive, i.e., only the + direct member HDUs of gfptr are considered for merging. \label{ffgtcm} +\end{description} + +\begin{verbatim} + int fits_compact_group / ffgtcm + (fitsfile *gfptr, int cmopt, > int *status) +\end{verbatim} + +\begin{description} +\item[8 ]Verify the integrity of the grouping table pointed to by gfptr to make + sure that all group members are accessible and that all links to other + grouping tables are valid. The firstfailed parameter returns the member + ID (row number) of the first member HDU to fail verification (if positive + value) or the first group link to fail (if negative value). If gfptr is + successfully verified then firstfailed contains a return value of 0. \label{ffgtvf} +\end{description} + +\begin{verbatim} + int fits_verify_group / ffgtvf + (fitsfile *gfptr, > long *firstfailed, int *status) +\end{verbatim} + +\begin{description} +\item[9 ] Open a grouping table that contains the member HDU pointed to by mfptr. + The grouping table to open is defined by the grpid parameter, which + contains the keyword index value of the GRPIDn/GRPLCn keyword(s) that + link the member HDU mfptr to the grouping table. If the grouping table + resides in a file other than the member HDUs file then an attempt is + first made to open the file readwrite, and failing that readonly. A + pointer to the opened grouping table HDU is returned in gfptr. + + Note that it is possible, although unlikely and undesirable, for the + GRPIDn/GRPLCn keywords in a member HDU 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. \label{ffgtop} +\end{description} + +\begin{verbatim} + int fits_open_group / ffgtop + (fitsfile *mfptr, int group, > fitsfile **gfptr, int *status) +\end{verbatim} + +\begin{description} +\item[10] Add a member HDU to an existing grouping table pointed to by gfptr. + The member HDU may either be pointed to mfptr (which must be positioned + to the member HDU) or, if mfptr == NULL, identified by the hdupos parameter + (the HDU position number, Primary array == 1) if both the grouping table + and the member HDU reside in the same FITS file. The new member HDU shall + have the appropriate GRPIDn and GRPLCn keywords created in its header. + Note that if the member HDU is already a member of the group then it will + not be added a second time. \label{ffgtam} +\end{description} + +\begin{verbatim} + int fits_add_group_member / ffgtam + (fitsfile *gfptr, fitsfile *mfptr, int hdupos, > int *status) +\end{verbatim} + + +\section{Group Member Routines} + + +\begin{description} +\item[1 ] Return the number of member HDUs in a grouping table gfptr. The number + member HDUs is just the NAXIS2 value (number of rows) of the grouping + table. \label{ffgtnm} +\end{description} + +\begin{verbatim} + int fits_get_num_members / ffgtnm + (fitsfile *gfptr, > long *nmembers, int *status) +\end{verbatim} + +\begin{description} +\item[2 ] Return the number of groups to which the HDU pointed to by mfptr is + linked, as defined by the number of GRPIDn/GRPLCn keyword records that + appear in its header. Note that each time this function is called, the + indices of the GRPIDn/GRPLCn keywords are checked to make sure they + are continuous (ie no gaps) and are re-enumerated to eliminate gaps if + found. \label{ffgmng} +\end{description} + +\begin{verbatim} + int fits_get_num_groups / ffgmng + (fitsfile *mfptr, > long *nmembers, int *status) +\end{verbatim} + +\begin{description} +\item[3 ] Open a member of the grouping table pointed to by gfptr. The member to + open is identified by its row number within the grouping table as given + by the parameter 'member' (first member == 1) . A fitsfile pointer to + the opened member HDU is returned as mfptr. Note that if the member HDU + resides in a FITS file different from the grouping table HDU then the + member file is first opened readwrite and, failing this, opened readonly. \label{ffgmop} +\end{description} + +\begin{verbatim} + int fits_open_member / ffgmop + (fitsfile *gfptr, long member, > fitsfile **mfptr, int *status) +\end{verbatim} + +\begin{description} +\item[4 ]Copy (append) a member HDU of the grouping table pointed to by gfptr. + The member HDU is identified by its row number within the grouping table + as given by the parameter 'member' (first member == 1). The copy of the + group member HDU will be appended to the FITS file pointed to by mfptr, + and upon return mfptr shall point to the copied member HDU. The cpopt + parameter may take on the following values: OPT\_MCP\_ADD which adds a new + entry in gfptr for the copied member HDU, OPT\_MCP\_NADD which does not add + an entry in gfptr for the copied member, and OPT\_MCP\_REPL which replaces + the original member entry with the copied member entry. \label{ffgmcp} +\end{description} + +\begin{verbatim} + int fits_copy_member / ffgmcp + (fitsfile *gfptr, fitsfile *mfptr, long member, int cpopt, > int *status) +\end{verbatim} + +\begin{description} +\item[5 ]Transfer a group member HDU from the grouping table pointed to by + infptr to the grouping table pointed to by outfptr. The member HDU to + transfer is identified by its row number within infptr as specified by + the parameter 'member' (first member == 1). If tfopt == OPT\_MCP\_ADD then + the member HDU is made + a member of outfptr and remains a member of infptr. If tfopt == OPT\_MCP\_MOV + then the member HDU is deleted from infptr after the transfer to outfptr. \label{ffgmtf} +\end{description} + +\begin{verbatim} + int fits_transfer_member / ffgmtf + (fitsfile *infptr, fitsfile *outfptr, long member, int tfopt, + > int *status) +\end{verbatim} + +\begin{description} +\item[6 ]Remove a member HDU from the grouping table pointed to by gfptr. The + member HDU to be deleted is identified by its row number in the grouping + table as specified by the parameter 'member' (first member == 1). The rmopt + parameter may take on the following values: OPT\_RM\_ENTRY which + removes the member HDU entry from the grouping table and updates the + member's GRPIDn/GRPLCn keywords, and OPT\_RM\_MBR which removes the member + HDU entry from the grouping table and deletes the member HDU itself. \label{ffgmrm} +\end{description} + +\begin{verbatim} + int fits_remove_member / ffgmrm + (fitsfile *fptr, long member, int rmopt, > int *status) +\end{verbatim} + +\chapter{ Specialized CFITSIO Interface Routines } + +The basic interface routines described previously are recommended +for most uses, but the routines described in this chapter +are also available if necessary. Some of these routines perform more +specialized function that cannot easily be done with the basic +interface routines while others duplicate the functionality of the +basic routines but have a slightly different calling sequence. +See Appendix B for the definition of each function parameter. + + +\section{FITS File Access Routines} + + +\begin{description} +\item[1 ] Open an existing FITS file residing in core computer memory. This +routine is analogous to fits\_open\_file. The 'filename' is +currently ignored by this routine and may be any arbitrary string. In +general, the application must have preallocated an initial block of +memory to hold the FITS file prior to calling this routine: 'memptr' +points to the starting address and 'memsize' gives the initial size of +the block of memory. 'mem\_realloc' is a pointer to an optional +function that CFITSIO can call to allocate additional memory, if needed +(only if mode = READWRITE), and is modeled after the standard C +'realloc' function; a null pointer may be given if the initial +allocation of memory is all that will be required (e.g., if the file is +opened with mode = READONLY). The 'deltasize' parameter may be used to +suggest a minimum amount of additional memory that should be allocated +during each call to the memory reallocation function. By default, +CFITSIO will reallocate enough additional space to hold the entire +currently defined FITS file (as given by the NAXISn keywords) or 1 FITS +block (= 2880 bytes), which ever is larger. Values of deltasize less +than 2880 will be ignored. Since the memory reallocation operation can +be computationally expensive, allocating a larger initial block of +memory, and/or specifying a larger deltasize value may help to reduce +the number of reallocation calls and make the application program run +faster. Note that values of the memptr and memsize pointers will be updated +by CFITSIO if the location or size of the FITS file in memory +should change as a result of allocating more memory. \label{ffomem} +\end{description} + +\begin{verbatim} + int fits_open_memfile / ffomem + (fitsfile **fptr, const char *filename, int mode, void **memptr, + size_t *memsize, size_t deltasize, + void *(*mem_realloc)(void *p, size_t newsize), int *status) +\end{verbatim} + +\begin{description} +\item[2 ] Create a new FITS file residing in core computer memory. This +routine is analogous to fits\_create\_file. In general, the +application must have preallocated an initial block of memory to hold +the FITS file prior to calling this routine: 'memptr' points to the +starting address and 'memsize' gives the initial size of the block of +memory. 'mem\_realloc' is a pointer to an optional function that +CFITSIO can call to allocate additional memory, if needed, and is +modeled after the standard C 'realloc' function; a null pointer may be +given if the initial allocation of memory is all that will be +required. The 'deltasize' parameter may be used to suggest a minimum +amount of additional memory that should be allocated during each call +to the memory reallocation function. By default, CFITSIO will +reallocate enough additional space to hold 1 FITS block (= 2880 bytes) +and values of deltasize less than 2880 will be ignored. Since the +memory reallocation operation can be computationally expensive, +allocating a larger initial block of memory, and/or specifying a larger +deltasize value may help to reduce the number of reallocation calls +and make the application program run +faster. Note that values of the memptr and memsize pointers will be updated +by CFITSIO if the location or size of the FITS file in memory +should change as a result of allocating more memory. \label{ffimem} +\end{description} + +\begin{verbatim} + int fits_create_memfile / ffimem + (fitsfile **fptr, void **memptr, + size_t *memsize, size_t deltasize, + void *(*mem_realloc)(void *p, size_t newsize), int *status) +\end{verbatim} + +\begin{description} +\item[3 ] Reopen a FITS file that was previously opened with + fits\_open\_file or fits\_create\_file. The new fitsfile + pointer may then be treated as a separate file, and one may + simultaneously read or write to 2 (or more) different extensions in + the same file. The fits\_open\_file routine (above) automatically + detects cases where a previously opened file is being opened again, + and then internally call fits\_reopen\_file, so programs should rarely + need to explicitly call this routine. +\label{ffreopen} +\end{description} + +\begin{verbatim} + int fits_reopen_file / ffreopen + (fitsfile *openfptr, fitsfile **newfptr, > int *status) +\end{verbatim} + + +\begin{description} +\item[4 ] Create a new FITS file, using a template file to define its + initial size and structure. The template may be another FITS HDU + or an ASCII template file. If the input template file name pointer + is null, then this routine behaves the same as fits\_create\_file. + The currently supported format of the ASCII template file is described + under the fits\_parse\_template routine (in the general Utilities + section) +\label{fftplt} +\end{description} + +\begin{verbatim} + int fits_create_template / fftplt + (fitsfile **fptr, char *filename, char *tpltfile > int *status) +\end{verbatim} + + +\begin{description} +\item[5 ] Parse the input filename or URL into its component parts, namely: +\begin{itemize} +\item +the file type (file://, ftp://, http://, etc), +\item +the base input file name, +\item +the name of the output file that the input file is to be copied to prior +to opening, +\item +the HDU or extension specification, +\item +the filtering specifier, +\item +the binning specifier, +\item +the column specifier, +\item +and the +image pixel filtering specifier. +\end{itemize} +A null pointer (0) may be be specified for any of the output string arguments +that are not needed. Null strings will be returned for any components that are not +present in the input file name. The calling routine must allocate sufficient +memory to hold the returned character strings. Allocating the string lengths +equal to FLEN\_FILENAME is guaranteed to be safe. +These routines are mainly for internal use +by other CFITSIO routines. \label{ffiurl} +\end{description} + +\begin{verbatim} + int fits_parse_input_url / ffiurl + (char *filename, > char *filetype, char *infile, char *outfile, char + *extspec, char *filter, char *binspec, char *colspec, int *status) + + int fits_parse_input_filename / ffifile + (char *filename, > char *filetype, char *infile, char *outfile, char + *extspec, char *filter, char *binspec, char *colspec, char *pixspec, + int *status) +\end{verbatim} + +\begin{description} +\item[6 ] Parse the input filename and return the HDU number that would be +moved to if the file were opened with fits\_open\_file. The returned +HDU number begins with 1 for the primary array, so for example, if the +input filename = `myfile.fits[2]' then hdunum = 3 will be returned. +CFITSIO does not open the file to check if the extension actually +exists if an extension number is specified. If an extension name is +included in the file name specification (e.g. `myfile.fits[EVENTS]' +then this routine will have to open the FITS file and look for the +position of the named extension, then close file again. This is not +possible if the file is being read from the stdin stream, and an error +will be returned in this case. If the filename does not specify an +explicit extension (e.g. 'myfile.fits') then hdunum = -99 will be +returned, which is functionally equivalent to hdunum = 1. This routine +is mainly used for backward compatibility in the ftools software +package and is not recommended for general use. It is generally better +and more efficient to first open the FITS file with fits\_open\_file, +then use fits\_get\_hdu\_num to determine which HDU in the file has +been opened, rather than calling fits\_parse\_input\_url followed by a +call to fits\_open\_file. + \label{ffextn} +\end{description} + +\begin{verbatim} + int fits_parse_extnum / ffextn + (char *filename, > int *hdunum, int *status) +\end{verbatim} + +\begin{description} +\item[7 ]Parse the input file name and return the root file name. The root +name includes the file type if specified, (e.g. 'ftp://' or 'http://') +and the full path name, to the extent that it is specified in the input +filename. It does not include the HDU name or number, or any filtering +specifications. The calling routine must allocate sufficient +memory to hold the returned rootname character string. Allocating the length +equal to FLEN\_FILENAME is guaranteed to be safe. + \label{ffrtnm} +\end{description} + +\begin{verbatim} + int fits_parse_rootname / ffrtnm + (char *filename, > char *rootname, int *status); +\end{verbatim} + +\begin{description} +\item[8 ]Test if the input file or a compressed version of the file (with +a .gz, .Z, .z, or .zip extension) exists on disk. The returned value of +the 'exists' parameter will have 1 of the 4 following values: + +\begin{verbatim} + 2: the file does not exist, but a compressed version does exist + 1: the disk file does exist + 0: neither the file nor a compressed version of the file exist + -1: the input file name is not a disk file (could be a ftp, http, + smem, or mem file, or a file piped in on the STDIN stream) +\end{verbatim} + + \label{ffexist} +\end{description} + +\begin{verbatim} + int fits_file_exists / ffexist + (char *filename, > int *exists, int *status); +\end{verbatim} + +\begin{description} +\item[9 ]Flush any internal buffers of data to the output FITS file. These + routines rarely need to be called, but can be useful in cases where + other processes need to access the same FITS file in real time, + either on disk or in memory. These routines also help to ensure + that if the application program subsequently aborts then the FITS + file will have been closed properly. The first routine, + fits\_flush\_file is more rigorous and completely closes, then + reopens, the current HDU, before flushing the internal buffers, thus + ensuring that the output FITS file is identical to what would be + produced if the FITS was closed at that point (i.e., with a call to + fits\_close\_file). The second routine, fits\_flush\_buffer simply + flushes the internal CFITSIO buffers of data to the output FITS + file, without updating and closing the current HDU. This is much + faster, but there may be circumstances where the flushed file does + not completely reflect the final state of the file as it will exist + when the file is actually closed. + + A typical use of these routines would be to flush the state of a + FITS table to disk after each row of the table is written. It is + recommend that fits\_flush\_file be called after the first row is + written, then fits\_flush\_buffer may be called after each + subsequent row is written. Note that this latter routine will not + automatically update the NAXIS2 keyword which records the number of + rows of data in the table, so this keyword must be explicitly + updated by the application program after each row is written. + \label{ffflus} +\end{description} + +\begin{verbatim} + int fits_flush_file / ffflus + (fitsfile *fptr, > int *status) + + int fits_flush_buffer / ffflsh + (fitsfile *fptr, 0, > int *status) + + (Note: The second argument must be 0). +\end{verbatim} + + +\section{HDU Access Routines} + + +\begin{description} +\item[1 ] Get the byte offsets in the FITS file to the start of the header + and the start and end of the data in the CHDU. The difference + between headstart and dataend equals the size of the CHDU. If the + CHDU is the last HDU in the file, then dataend is also equal to the + size of the entire FITS file. Null pointers may be input for any + of the address parameters if their values are not needed. \label{ffghad} +\end{description} + +\begin{verbatim} + int fits_get_hduaddr / ffghad (only supports files up to 2.1 GB in size) + (fitsfile *fptr, > long *headstart, long *datastart, long *dataend, + int *status) + + int fits_get_hduaddrll / ffghadll (supports large files) + (fitsfile *fptr, > LONGLONG *headstart, LONGLONG *datastart, + LONGLONG *dataend, int *status) +\end{verbatim} + +\begin{description} +\item[2 ] Create (append) a new empty HDU at the end of the FITS file. + This is now the CHDU but it is completely empty and has + no header keywords. It is recommended that fits\_create\_img or + fits\_create\_tbl be used instead of this routine. \label{ffcrhd} +\end{description} + +\begin{verbatim} + int fits_create_hdu / ffcrhd + (fitsfile *fptr, > int *status) +\end{verbatim} + +\begin{description} +\item[3 ] Insert a new IMAGE extension immediately following the CHDU, or + insert a new Primary Array at the beginning of the file. Any + following extensions in the file will be shifted down to make room + for the new extension. If the CHDU is the last HDU in the file + then the new image extension will simply be appended to the end of + the file. One can force a new primary array to be inserted at the + beginning of the FITS file by setting status = PREPEND\_PRIMARY prior + to calling the routine. In this case the old primary array will be + converted to an IMAGE extension. The new extension (or primary + array) will become the CHDU. Refer to Chapter 9 for a list of + pre-defined bitpix values. \label{ffiimg} +\end{description} + +\begin{verbatim} + int fits_insert_img / ffiimg + (fitsfile *fptr, int bitpix, int naxis, long *naxes, > int *status) + + int fits_insert_imgll / ffiimgll + (fitsfile *fptr, int bitpix, int naxis, LONGLONG *naxes, > int *status) +\end{verbatim} + +\begin{description} +\item[4 ] Insert a new ASCII or binary table extension immediately following the CHDU. + Any following extensions will be shifted down to make room for the + new extension. If there are no other following extensions then the + new table extension will simply be appended to the end of the + file. If the FITS file is currently empty then this routine will + create a dummy primary array before appending the table to it. The + new extension will become the CHDU. The tunit and extname + parameters are optional and a null pointer may be given if they are + not defined. When inserting an ASCII table with + fits\_insert\_atbl, a null pointer may given for the *tbcol + parameter in which case each column of the table will be separated + by a single space character. Similarly, if the input value of + rowlen is 0, then CFITSIO will calculate the default rowlength + based on the tbcol and ttype values. Under normal circumstances, + the nrows + paramenter should have a value of 0; CFITSIO will automatically update + the number of rows as data is written to the table. When inserting a binary table + with fits\_insert\_btbl, if there are following extensions in the + file and if the table contains variable length array columns then + pcount must specify the expected final size of the data heap, + otherwise pcount must = 0. \label{ffitab} \label{ffibin} +\end{description} + +\begin{verbatim} + int fits_insert_atbl / ffitab + (fitsfile *fptr, LONGLONG rowlen, LONGLONG nrows, int tfields, char *ttype[], + long *tbcol, char *tform[], char *tunit[], char *extname, > int *status) + + int fits_insert_btbl / ffibin + (fitsfile *fptr, LONGLONG nrows, int tfields, char **ttype, + char **tform, char **tunit, char *extname, long pcount, > int *status) +\end{verbatim} + +\begin{description} +\item[5 ] Modify the size, dimensions, and/or data type of the current + primary array or image extension. If the new image, as specified + by the input arguments, is larger than the current existing image + in the FITS file then zero fill data will be inserted at the end + of the current image and any following extensions will be moved + further back in the file. Similarly, if the new image is + smaller than the current image then any following extensions + will be shifted up towards the beginning of the FITS file + and the image data will be truncated to the new size. + This routine rewrites the BITPIX, NAXIS, and NAXISn keywords + with the appropriate values for the new image. \label{ffrsim} +\end{description} + +\begin{verbatim} + int fits_resize_img / ffrsim + (fitsfile *fptr, int bitpix, int naxis, long *naxes, > int *status) + + int fits_resize_imgll / ffrsimll + (fitsfile *fptr, int bitpix, int naxis, LONGLONG *naxes, > int *status) +\end{verbatim} + +\begin{description} +\item[6 ] Copy the data (and not the header) from the CHDU associated with infptr + to the CHDU associated with outfptr. This will overwrite any data + previously in the output CHDU. This low level routine is used by + fits\_copy\_hdu, but it may also be useful in certain application programs + that want to copy the data from one FITS file to another but also + want to modify the header keywords. The required FITS header keywords + which define the structure of the HDU must be written to the + output CHDU before calling this routine. \label{ffcpdt} +\end{description} + +\begin{verbatim} + int fits_copy_data / ffcpdt + (fitsfile *infptr, fitsfile *outfptr, > int *status) +\end{verbatim} + +\begin{description} +\item[7 ] Read or write a specified number of bytes starting at the specified byte + offset from the start of the extension data unit. These low + level routine are intended mainly for accessing the data in + non-standard, conforming extensions, and should not be used for standard + IMAGE, TABLE, or BINTABLE extensions. \label{ffgextn} +\end{description} + +\begin{verbatim} + int fits_read_ext / ffgextn + (fitsfile *fptr, LONGLONG offset, LONGLONG nbytes, void *buffer) + int fits_write_ext / ffpextn + (fitsfile *fptr, LONGLONG offset, LONGLONG nbytes, void *buffer) +\end{verbatim} + +\begin{description} +\item[8 ] This routine forces CFITSIO to rescan the current header keywords that + define the structure of the HDU (such as the NAXIS and BITPIX + keywords) so that it reinitializes the internal buffers that + describe the HDU structure. This routine is useful for + reinitializing the structure of an HDU if any of the required + keywords (e.g., NAXISn) have been modified. In practice it should + rarely be necessary to call this routine because CFITSIO + internally calls it in most situations. \label{ffrdef} +\end{description} + +\begin{verbatim} + int fits_set_hdustruc / ffrdef + (fitsfile *fptr, > int *status) (DEPRECATED) +\end{verbatim} + +\section{Specialized Header Keyword Routines} + + +\subsection{Header Information Routines} + + +\begin{description} +\item[1 ] Reserve space in the CHU for MOREKEYS more header keywords. + This routine may be called to allocate space for additional keywords + at the time the header is created (prior to writing any data). + CFITSIO can dynamically add more space to the header when needed, + however it is more efficient to preallocate the required space + if the size is known in advance. \label{ffhdef} +\end{description} + +\begin{verbatim} + int fits_set_hdrsize / ffhdef + (fitsfile *fptr, int morekeys, > int *status) +\end{verbatim} + +\begin{description} +\item[2 ] Return the number of keywords in the header (not counting the END + keyword) and the current position + in the header. The position is the number of the keyword record that + will be read next (or one greater than the position of the last keyword + that was read). A value of 1 is returned if the pointer is + positioned at the beginning of the header. \label{ffghps} +\end{description} + +\begin{verbatim} + int fits_get_hdrpos / ffghps + (fitsfile *fptr, > int *keysexist, int *keynum, int *status) +\end{verbatim} + + +\subsection{Read and Write the Required Keywords} + + +\begin{description} +\item[1 ] Write the required extension header keywords into the CHU. + These routines are not required, and instead the appropriate + header may be constructed by writing each individual keyword in the + proper sequence. + + The simpler fits\_write\_imghdr routine is equivalent to calling + fits\_write\_grphdr with the default values of simple = TRUE, pcount + = 0, gcount = 1, and extend = TRUE. The PCOUNT, GCOUNT and EXTEND + keywords are not required in the primary header and are only written + if pcount is not equal to zero, gcount is not equal to zero or one, + and if extend is TRUE, respectively. When writing to an IMAGE + extension, the SIMPLE and EXTEND parameters are ignored. It is + recommended that fits\_create\_image or fits\_create\_tbl be used + instead of these routines to write the + required header keywords. The general fits\_write\_exthdr routine + may be used to write the header of any conforming FITS + extension. \label{ffphpr} \label{ffphps} +\end{description} + +\begin{verbatim} + int fits_write_imghdr / ffphps + (fitsfile *fptr, int bitpix, int naxis, long *naxes, > int *status) + + int fits_write_imghdrll / ffphpsll + (fitsfile *fptr, int bitpix, int naxis, LONGLONG *naxes, > int *status) + + int fits_write_grphdr / ffphpr + (fitsfile *fptr, int simple, int bitpix, int naxis, long *naxes, + LONGLONG pcount, LONGLONG gcount, int extend, > int *status) + + int fits_write_grphdrll / ffphprll + (fitsfile *fptr, int simple, int bitpix, int naxis, LONGLONG *naxes, + LONGLONG pcount, LONGLONG gcount, int extend, > int *status) + + int fits_write_exthdr /ffphext + (fitsfile *fptr, char *xtension, int bitpix, int naxis, long *naxes, + LONGLONG pcount, LONGLONG gcount, > int *status) + +\end{verbatim} + +\begin{description} +\item[2 ] Write the ASCII table header keywords into the CHU. The optional + TUNITn and EXTNAME keywords are written only if the input pointers + are not null. A null pointer may given for the + *tbcol parameter in which case a single space will be inserted + between each column of the table. Similarly, if rowlen is + given = 0, then CFITSIO will calculate the default rowlength based on + the tbcol and ttype values. \label{ffphtb} +\end{description} + +\begin{verbatim} + int fits_write_atblhdr / ffphtb + (fitsfile *fptr, LONGLONG rowlen, LONGLONG nrows, int tfields, char **ttype, + long *tbcol, char **tform, char **tunit, char *extname, > int *status) +\end{verbatim} + +\begin{description} +\item[3 ] Write the binary table header keywords into the CHU. The optional + TUNITn and EXTNAME keywords are written only if the input pointers + are not null. The pcount parameter, which specifies the + size of the variable length array heap, should initially = 0; + CFITSIO will automatically update the PCOUNT keyword value if any + variable length array data is written to the heap. The TFORM keyword + value for variable length vector columns should have the form 'Pt(len)' + or '1Pt(len)' where `t' is the data type code letter (A,I,J,E,D, etc.) + and `len' is an integer specifying the maximum length of the vectors + in that column (len must be greater than or equal to the longest + vector in the column). If `len' is not specified when the table is + created (e.g., the input TFORMn value is just '1Pt') then CFITSIO will + scan the column when the table is first closed and will append the + maximum length to the TFORM keyword value. Note that if the table + is subsequently modified to increase the maximum length of the vectors + then the modifying program is responsible for also updating the TFORM + keyword value. \label{ffphbn} +\end{description} + +\begin{verbatim} + int fits_write_btblhdr / ffphbn + (fitsfile *fptr, LONGLONG nrows, int tfields, char **ttype, + char **tform, char **tunit, char *extname, LONGLONG pcount, > int *status) +\end{verbatim} + +\begin{description} +\item[4 ] Read the required keywords from the CHDU (image or table). When + reading from an IMAGE extension the SIMPLE and EXTEND parameters are + ignored. A null pointer may be supplied for any of the returned + parameters that are not needed. \label{ffghpr} \label{ffghtb} \label{ffghbn} +\end{description} + +\begin{verbatim} + int fits_read_imghdr / ffghpr + (fitsfile *fptr, int maxdim, > int *simple, int *bitpix, int *naxis, + long *naxes, long *pcount, long *gcount, int *extend, int *status) + + int fits_read_imghdrll / ffghprll + (fitsfile *fptr, int maxdim, > int *simple, int *bitpix, int *naxis, + LONGLONG *naxes, long *pcount, long *gcount, int *extend, int *status) + + int fits_read_atblhdr / ffghtb + (fitsfile *fptr,int maxdim, > long *rowlen, long *nrows, + int *tfields, char **ttype, LONGLONG *tbcol, char **tform, char **tunit, + char *extname, int *status) + + int fits_read_atblhdrll / ffghtbll + (fitsfile *fptr,int maxdim, > LONGLONG *rowlen, LONGLONG *nrows, + int *tfields, char **ttype, long *tbcol, char **tform, char **tunit, + char *extname, int *status) + + int fits_read_btblhdr / ffghbn + (fitsfile *fptr, int maxdim, > long *nrows, int *tfields, + char **ttype, char **tform, char **tunit, char *extname, + long *pcount, int *status) + + int fits_read_btblhdrll / ffghbnll + (fitsfile *fptr, int maxdim, > LONGLONG *nrows, int *tfields, + char **ttype, char **tform, char **tunit, char *extname, + long *pcount, int *status) +\end{verbatim} + +\subsection{Write Keyword Routines} + +These routines simply append a new keyword to the header and do not +check to see if a keyword with the same name already exists. In +general it is preferable to use the fits\_update\_key routine to ensure +that the same keyword is not written more than once to the header. See +Appendix B for the definition of the parameters used in these +routines. + + + +\begin{description} +\item[1 ] Write (append) a new keyword of the appropriate data type into the CHU. + A null pointer may be entered for the comment parameter, which + will cause the comment field of the keyword to be left blank. The + flt, dbl, cmp, and dblcmp versions of this routine have the added + feature that if the 'decimals' parameter is negative, then the 'G' + display format rather then the 'E' format will be used when + constructing the keyword value, taking the absolute value of + 'decimals' for the precision. This will suppress trailing zeros, + and will use a fixed format rather than an exponential format, + depending on the magnitude of the value. \label{ffpkyx} +\end{description} + +\begin{verbatim} + int fits_write_key_str / ffpkys + (fitsfile *fptr, char *keyname, char *value, char *comment, + > int *status) + + int fits_write_key_[log, lng] / ffpky[lj] + (fitsfile *fptr, char *keyname, DTYPE numval, char *comment, + > int *status) + + int fits_write_key_[flt, dbl, fixflg, fixdbl] / ffpky[edfg] + (fitsfile *fptr, char *keyname, DTYPE numval, int decimals, + char *comment, > int *status) + + int fits_write_key_[cmp, dblcmp, fixcmp, fixdblcmp] / ffpk[yc,ym,fc,fm] + (fitsfile *fptr, char *keyname, DTYPE *numval, int decimals, + char *comment, > int *status) +\end{verbatim} + +\begin{description} +\item[2 ] Write (append) a string valued keyword into the CHU which may be longer + than 68 characters in length. This uses the Long String Keyword + convention that is described in the`Local FITS Conventions' section + in Chapter 4. Since this uses a non-standard FITS convention to + encode the long keyword string, programs which use this routine + should also call the fits\_write\_key\_longwarn routine to add some + COMMENT keywords to warn users of the FITS file that this + convention is being used. The fits\_write\_key\_longwarn routine + also writes a keyword called LONGSTRN to record the version of the + longstring convention that has been used, in case a new convention + is adopted at some point in the future. If the LONGSTRN keyword + is already present in the header, then fits\_write\_key\_longwarn + will + simply return without doing anything. \label{ffpkls} \label{ffplsw} +\end{description} + +\begin{verbatim} + int fits_write_key_longstr / ffpkls + (fitsfile *fptr, char *keyname, char *longstr, char *comment, + > int *status) + + int fits_write_key_longwarn / ffplsw + (fitsfile *fptr, > int *status) +\end{verbatim} + +\begin{description} +\item[3 ] Write (append) a numbered sequence of keywords into the CHU. The + starting index number (nstart) must be greater than 0. One may + append the same comment to every keyword (and eliminate the need + to have an array of identical comment strings, one for each keyword) by + including the ampersand character as the last non-blank character in the + (first) COMMENTS string parameter. This same string + will then be used for the comment field in all the keywords. + One may also enter a null pointer for the comment parameter to + leave the comment field of the keyword blank. \label{ffpknx} +\end{description} + +\begin{verbatim} + int fits_write_keys_str / ffpkns + (fitsfile *fptr, char *keyroot, int nstart, int nkeys, + char **value, char **comment, > int *status) + + int fits_write_keys_[log, lng] / ffpkn[lj] + (fitsfile *fptr, char *keyroot, int nstart, int nkeys, + DTYPE *numval, char **comment, int *status) + + int fits_write_keys_[flt, dbl, fixflg, fixdbl] / ffpkne[edfg] + (fitsfile *fptr, char *keyroot, int nstart, int nkey, + DTYPE *numval, int decimals, char **comment, > int *status) +\end{verbatim} + +\begin{description} +\item[4 ]Copy an indexed keyword from one HDU to another, modifying + the index number of the keyword name in the process. For example, + this routine could read the TLMIN3 keyword from the input HDU + (by giving keyroot = `TLMIN' and innum = 3) and write it to the + output HDU with the keyword name TLMIN4 (by setting outnum = 4). + If the input keyword does not exist, then this routine simply + returns without indicating an error. \label{ffcpky} +\end{description} + +\begin{verbatim} + int fits_copy_key / ffcpky + (fitsfile *infptr, fitsfile *outfptr, int innum, int outnum, + char *keyroot, > int *status) +\end{verbatim} + +\begin{description} +\item[5 ]Write (append) a `triple precision' keyword into the CHU in F28.16 format. + The floating point keyword value is constructed by concatenating the + input integer value with the input double precision fraction value + (which must have a value between 0.0 and 1.0). The ffgkyt routine should + be used to read this keyword value, because the other keyword reading + routines will not preserve the full precision of the value. \label{ffpkyt} +\end{description} + +\begin{verbatim} + int fits_write_key_triple / ffpkyt + (fitsfile *fptr, char *keyname, long intval, double frac, + char *comment, > int *status) +\end{verbatim} + +\begin{description} +\item[6 ]Write keywords to the CHDU that are defined in an ASCII template file. + The format of the template file is described under the fits\_parse\_template + routine. \label{ffpktp} +\end{description} + +\begin{verbatim} + int fits_write_key_template / ffpktp + (fitsfile *fptr, const char *filename, > int *status) +\end{verbatim} + +\subsection{Insert Keyword Routines} + +These insert routines are somewhat less efficient than the `update' or +`write' keyword routines because the following keywords in the header +must be shifted down to make room for the inserted keyword. See +Appendix B for the definition of the parameters used in these +routines. + + +\begin{description} +\item[1 ] Insert a new keyword record into the CHU at the specified position + (i.e., immediately preceding the (keynum)th keyword in the header.) + \label{ffirec} +\end{description} + +\begin{verbatim} + int fits_insert_record / ffirec + (fitsfile *fptr, int keynum, char *card, > int *status) +\end{verbatim} + +\begin{description} +\item[2 ] Insert a new keyword into the CHU. The new keyword is inserted + immediately following the last keyword that has been read from the + header. The `longstr' version has the same functionality as the + `str' version except that it also supports the local long string + keyword convention for strings longer than 68 characters. A null + pointer may be entered for the comment parameter which will cause + the comment field to be left blank. The flt, dbl, cmp, and dblcmp + versions of this routine have the added + feature that if the 'decimals' parameter is negative, then the 'G' + display format rather then the 'E' format will be used when + constructing the keyword value, taking the absolute value of + 'decimals' for the precision. This will suppress trailing zeros, + and will use a fixed format rather than an exponential format, + depending on the magnitude of the value. \label{ffikyx} +\end{description} + +\begin{verbatim} + int fits_insert_card / ffikey + (fitsfile *fptr, char *card, > int *status) + + int fits_insert_key_[str, longstr] / ffi[kys, kls] + (fitsfile *fptr, char *keyname, char *value, char *comment, + > int *status) + + int fits_insert_key_[log, lng] / ffiky[lj] + (fitsfile *fptr, char *keyname, DTYPE numval, char *comment, + > int *status) + + int fits_insert_key_[flt, fixflt, dbl, fixdbl] / ffiky[edfg] + (fitsfile *fptr, char *keyname, DTYPE numval, int decimals, + char *comment, > int *status) + + int fits_insert_key_[cmp, dblcmp, fixcmp, fixdblcmp] / ffik[yc,ym,fc,fm] + (fitsfile *fptr, char *keyname, DTYPE *numval, int decimals, + char *comment, > int *status) +\end{verbatim} + +\begin{description} +\item[3 ] Insert a new keyword with an undefined, or null, value into the CHU. + The value string of the keyword is left blank in this case. \label{ffikyu} +\end{description} + +\begin{verbatim} + int fits_insert_key_null / ffikyu + (fitsfile *fptr, char *keyname, char *comment, > int *status) +\end{verbatim} + + +\subsection{Read Keyword Routines} + +Wild card characters may be used when specifying the name of the +keyword to be read. + + +\begin{description} +\item[1 ] Read a keyword value (with the appropriate data type) and comment from + the CHU. If a NULL comment pointer is given on input, then the comment + string will not be returned. If the value of the keyword is not defined + (i.e., the value field is blank) then an error status = VALUE\_UNDEFINED + will be returned and the input value will not be changed (except that + ffgkys will reset the value to a null string). + \label{ffgkyx} \label{ffgkls} +\end{description} + +\begin{verbatim} + int fits_read_key_str / ffgkys + (fitsfile *fptr, char *keyname, > char *value, char *comment, + int *status); + + NOTE: after calling the following routine, programs must explicitly free + the memory allocated for 'longstr' after it is no longer needed or + call fits_free_memory. + + int fits_read_key_longstr / ffgkls + (fitsfile *fptr, char *keyname, > char **longstr, char *comment, + int *status) + + int fits_free_memory / fffree + (char *longstr, int *status); + + int fits_read_key_[log, lng, flt, dbl, cmp, dblcmp] / ffgky[ljedcm] + (fitsfile *fptr, char *keyname, > DTYPE *numval, char *comment, + int *status) + + int fits_read_key_lnglng / ffgkyjj + (fitsfile *fptr, char *keyname, > LONGLONG *numval, char *comment, + int *status) +\end{verbatim} + +\begin{description} +\item[2 ] Read a sequence of indexed keyword values (e.g., NAXIS1, NAXIS2, ...). + The input starting index number (nstart) must be greater than 0. + If the value of any of the keywords is not defined (i.e., the value + field is blank) then an error status = VALUE\_UNDEFINED will be + returned and the input value for the undefined keyword(s) will not + be changed. These routines do not support wild card characters in + the root name. If there are no indexed keywords in the header with + the input root name then these routines do not return a non-zero + status value and instead simply return nfound = 0. \label{ffgknx} +\end{description} + +\begin{verbatim} + int fits_read_keys_str / ffgkns + (fitsfile *fptr, char *keyname, int nstart, int nkeys, + > char **value, int *nfound, int *status) + + int fits_read_keys_[log, lng, flt, dbl] / ffgkn[ljed] + (fitsfile *fptr, char *keyname, int nstart, int nkeys, + > DTYPE *numval, int *nfound, int *status) +\end{verbatim} + +\begin{description} +\item[3 ] Read the value of a floating point keyword, returning the integer and + fractional parts of the value in separate routine arguments. + This routine may be used to read any keyword but is especially + useful for reading the 'triple precision' keywords written by ffpkyt. + \label{ffgkyt} +\end{description} + +\begin{verbatim} + int fits_read_key_triple / ffgkyt + (fitsfile *fptr, char *keyname, > long *intval, double *frac, + char *comment, int *status) +\end{verbatim} + +\subsection{Modify Keyword Routines} + +These routines modify the value of an existing keyword. An error is +returned if the keyword does not exist. Wild card characters may be +used when specifying the name of the keyword to be modified. See +Appendix B for the definition of the parameters used in these +routines. + + +\begin{description} +\item[1 ] Modify (overwrite) the nth 80-character header record in the CHU. \label{ffmrec} +\end{description} + +\begin{verbatim} + int fits_modify_record / ffmrec + (fitsfile *fptr, int keynum, char *card, > int *status) +\end{verbatim} + +\begin{description} +\item[2 ] Modify (overwrite) the 80-character header record for the named keyword + in the CHU. This can be used to overwrite the name of the keyword as + well as its value and comment fields. \label{ffmcrd} +\end{description} + +\begin{verbatim} + int fits_modify_card / ffmcrd + (fitsfile *fptr, char *keyname, char *card, > int *status) +\end{verbatim} + +\begin{description} +\item[5 ] Modify the value and comment fields of an existing keyword in the CHU. + The `longstr' version has the same functionality as the `str' + version except that it also supports the local long string keyword + convention for strings longer than 68 characters. Optionally, one + may modify only the value field and leave the comment field + unchanged by setting the input COMMENT parameter equal to the + ampersand character (\&) or by entering a null pointer for the + comment parameter. The flt, dbl, cmp, and dblcmp versions of this + routine have the added feature that if the 'decimals' parameter is + negative, then the 'G' display format rather then the 'E' format + will be used when constructing the keyword value, taking the + absolute value of 'decimals' for the precision. This will suppress + trailing zeros, and will use a fixed format rather than an + exponential format, + depending on the magnitude of the value. \label{ffmkyx} +\end{description} + +\begin{verbatim} + int fits_modify_key_[str, longstr] / ffm[kys, kls] + (fitsfile *fptr, char *keyname, char *value, char *comment, + > int *status); + + int fits_modify_key_[log, lng] / ffmky[lj] + (fitsfile *fptr, char *keyname, DTYPE numval, char *comment, + > int *status) + + int fits_modify_key_[flt, dbl, fixflt, fixdbl] / ffmky[edfg] + (fitsfile *fptr, char *keyname, DTYPE numval, int decimals, + char *comment, > int *status) + + int fits_modify_key_[cmp, dblcmp, fixcmp, fixdblcmp] / ffmk[yc,ym,fc,fm] + (fitsfile *fptr, char *keyname, DTYPE *numval, int decimals, + char *comment, > int *status) +\end{verbatim} + +\begin{description} +\item[6 ] Modify the value of an existing keyword to be undefined, or null. + The value string of the keyword is set to blank. + Optionally, one may leave the comment field unchanged by setting the + input COMMENT parameter equal to + the ampersand character (\&) or by entering a null pointer. \label{ffmkyu} +\end{description} + +\begin{verbatim} + int fits_modify_key_null / ffmkyu + (fitsfile *fptr, char *keyname, char *comment, > int *status) +\end{verbatim} + +\subsection{Update Keyword Routines} + + +\begin{description} +\item[1 ] These update routines modify the value, and optionally the comment field, + of the keyword if it already exists, otherwise the new keyword is + appended to the header. A separate routine is provided for each + keyword data type. The `longstr' version has the same functionality + as the `str' version except that it also supports the local long + string keyword convention for strings longer than 68 characters. A + null pointer may be entered for the comment parameter which will + leave the comment field unchanged or blank. The flt, dbl, cmp, and + dblcmp versions of this routine have the added feature that if the + 'decimals' parameter is negative, then the 'G' display format + rather then the 'E' format will be used when constructing the + keyword value, taking the absolute value of 'decimals' for the + precision. This will suppress trailing zeros, and will use a fixed + format rather than an exponential format, + depending on the magnitude of the value. \label{ffukyx} +\end{description} + +\begin{verbatim} + int fits_update_key_[str, longstr] / ffu[kys, kls] + (fitsfile *fptr, char *keyname, char *value, char *comment, + > int *status) + + int fits_update_key_[log, lng] / ffuky[lj] + (fitsfile *fptr, char *keyname, DTYPE numval, char *comment, + > int *status) + + int fits_update_key_[flt, dbl, fixflt, fixdbl] / ffuky[edfg] + (fitsfile *fptr, char *keyname, DTYPE numval, int decimals, + char *comment, > int *status) + + int fits_update_key_[cmp, dblcmp, fixcmp, fixdblcmp] / ffuk[yc,ym,fc,fm] + (fitsfile *fptr, char *keyname, DTYPE *numval, int decimals, + char *comment, > int *status) +\end{verbatim} + + +\section{Define Data Scaling and Undefined Pixel Parameters} + +These routines set or modify the internal parameters used by CFITSIO +to either scale the data or to represent undefined pixels. Generally +CFITSIO will scale the data according to the values of the BSCALE and +BZERO (or TSCALn and TZEROn) keywords, however these routines may be +used to override the keyword values. This may be useful when one wants +to read or write the raw unscaled values in the FITS file. Similarly, +CFITSIO generally uses the value of the BLANK or TNULLn keyword to +signify an undefined pixel, but these routines may be used to override +this value. These routines do not create or modify the corresponding +header keyword values. See Appendix B for the definition of the +parameters used in these routines. + + +\begin{description} +\item[1 ] Reset the scaling factors in the primary array or image extension; does + not change the BSCALE and BZERO keyword values and only affects the + automatic scaling performed when the data elements are written/read + to/from the FITS file. When reading from a FITS file the returned + data value = (the value given in the FITS array) * BSCALE + BZERO. + The inverse formula is used when writing data values to the FITS + file. \label{ffpscl} +\end{description} + +\begin{verbatim} + int fits_set_bscale / ffpscl + (fitsfile *fptr, double scale, double zero, > int *status) +\end{verbatim} + +\begin{description} +\item[2 ] Reset the scaling parameters for a table column; does not change + the TSCALn or TZEROn keyword values and only affects the automatic + scaling performed when the data elements are written/read to/from + the FITS file. When reading from a FITS file the returned data + value = (the value given in the FITS array) * TSCAL + TZERO. The + inverse formula is used when writing data values to the FITS file. + \label{fftscl} +\end{description} + +\begin{verbatim} + int fits_set_tscale / fftscl + (fitsfile *fptr, int colnum, double scale, double zero, + > int *status) +\end{verbatim} + +\begin{description} +\item[3 ] Define the integer value to be used to signify undefined pixels in the + primary array or image extension. This is only used if BITPIX = 8, 16, + or 32. This does not create or change the value of the BLANK keyword in + the header. \label{ffpnul} +\end{description} + +\begin{verbatim} + int fits_set_imgnull / ffpnul + (fitsfile *fptr, LONGLONG nulval, > int *status) +\end{verbatim} + +\begin{description} +\item[4 ] Define the string to be used to signify undefined pixels in + a column in an ASCII table. This does not create or change the value + of the TNULLn keyword. \label{ffsnul} +\end{description} + +\begin{verbatim} + int fits_set_atblnull / ffsnul + (fitsfile *fptr, int colnum, char *nulstr, > int *status) +\end{verbatim} + +\begin{description} +\item[5 ] Define the value to be used to signify undefined pixels in + an integer column in a binary table (where TFORMn = 'B', 'I', or 'J'). + This does not create or change the value of the TNULLn keyword. + \label{fftnul} +\end{description} + +\begin{verbatim} + int fits_set_btblnull / fftnul + (fitsfile *fptr, int colnum, LONGLONG nulval, > int *status) +\end{verbatim} + + +\section{Specialized FITS Primary Array or IMAGE Extension I/O Routines} + +These routines read or write data values in the primary data array +(i.e., the first HDU in the FITS file) or an IMAGE extension. +Automatic data type conversion is performed for if the data type of the +FITS array (as defined by the BITPIX keyword) differs from the data +type of the array in the calling routine. The data values are +automatically scaled by the BSCALE and BZERO header values as they are +being written or read from the FITS array. Unlike the basic routines +described in the previous chapter, most of these routines specifically +support the FITS random groups format. See Appendix B for the +definition of the parameters used in these routines. + +The more primitive reading and writing routines (i. e., ffppr\_, +ffppn\_, ffppn, ffgpv\_, or ffgpf\_) simply treat the primary array as +a long 1-dimensional array of pixels, ignoring the intrinsic +dimensionality of the array. When dealing with a 2D image, for +example, the application program must calculate the pixel offset in the +1-D array that corresponds to any particular X, Y coordinate in the +image. C programmers should note that the ordering of arrays in FITS +files, and hence in all the CFITSIO calls, is more similar to the +dimensionality of arrays in Fortran rather than C. For instance if a +FITS image has NAXIS1 = 100 and NAXIS2 = 50, then a 2-D array just +large enough to hold the image should be declared as array[50][100] and +not as array[100][50]. + +For convenience, higher-level routines are also provided to specifically +deal with 2D images (ffp2d\_ and ffg2d\_) and 3D data cubes (ffp3d\_ +and ffg3d\_). The dimensionality of the FITS image is passed by the +naxis1, naxis2, and naxis3 parameters and the declared dimensions of +the program array are passed in the dim1 and dim2 parameters. Note +that the dimensions of the program array may be larger than the +dimensions of the FITS array. For example if a FITS image with NAXIS1 += NAXIS2 = 400 is read into a program array which is dimensioned as 512 +x 512 pixels, then the image will just fill the lower left corner of +the array with pixels in the range 1 - 400 in the X an Y directions. +This has the effect of taking a contiguous set of pixel value in the +FITS array and writing them to a non-contiguous array in program memory +(i.e., there are now some blank pixels around the edge of the image in +the program array). + +The most general set of routines (ffpss\_, ffgsv\_, and ffgsf\_) may be +used to transfer a rectangular subset of the pixels in a FITS +N-dimensional image to or from an array which has been declared in the +calling program. The fpixel and lpixel parameters are integer arrays +which specify the starting and ending pixel coordinate in each dimension +(starting with 1, not 0) of the FITS image that is to be read or +written. It is important to note that these are the starting and +ending pixels in the FITS image, not in the declared array in the +program. The array parameter in these routines is treated simply as a +large one-dimensional array of the appropriate data type containing the +pixel values; The pixel values in the FITS array are read/written +from/to this program array in strict sequence without any gaps; it is +up to the calling routine to correctly interpret the dimensionality of +this array. The two FITS reading routines (ffgsv\_ and ffgsf\_ ) also +have an `inc' parameter which defines the data sampling interval in +each dimension of the FITS array. For example, if inc[0]=2 and +inc[1]=3 when reading a 2-dimensional FITS image, then only every other +pixel in the first dimension and every 3rd pixel in the second +dimension will be returned to the 'array' parameter. + +Two types of routines are provided to read the data array which differ in +the way undefined pixels are handled. The first type of routines (e.g., +ffgpv\_) simply return an array of data elements in which undefined +pixels are set equal to a value specified by the user in the `nulval' +parameter. An additional feature of these routines is that if the user +sets nulval = 0, then no checks for undefined pixels will be performed, +thus reducing the amount of CPU processing. The second type of routines +(e.g., ffgpf\_) returns the data element array and, in addition, a char +array that indicates whether the value of the corresponding data pixel +is undefined (= 1) or defined (= 0). The latter type of routines may +be more convenient to use in some circumstances, however, it requires +an additional array of logical values which can be unwieldy when working +with large data arrays. + + +\begin{description} +\item[1 ] Write elements into the FITS data array. + \label{ffppr} \label{ffpprx} \label{ffppn} \label{ffppnx} +\end{description} + +\begin{verbatim} + int fits_write_img / ffppr + (fitsfile *fptr, int datatype, LONGLONG firstelem, LONGLONG nelements, + DTYPE *array, int *status); + + int fits_write_img_[byt, sht, usht, int, uint, lng, ulng, lnglng, flt, dbl] / + ffppr[b,i,ui,k,uk,j,uj,jj,e,d] + (fitsfile *fptr, long group, LONGLONG firstelem, LONGLONG nelements, + DTYPE *array, > int *status); + + int fits_write_imgnull / ffppn + (fitsfile *fptr, int datatype, LONGLONG firstelem, LONGLONG nelements, + DTYPE *array, DTYPE *nulval, > int *status); + + int fits_write_imgnull_[byt, sht, usht, int, uint, lng, ulng, lnglng, flt, dbl] / + ffppn[b,i,ui,k,uk,j,uj,jj,e,d] + (fitsfile *fptr, long group, LONGLONG firstelem, + LONGLONG nelements, DTYPE *array, DTYPE nulval, > int *status); +\end{verbatim} + +\begin{description} +\item[2 ]Set data array elements as undefined. \label{ffppru} +\end{description} + +\begin{verbatim} + int fits_write_img_null / ffppru + (fitsfile *fptr, long group, LONGLONG firstelem, LONGLONG nelements, + > int *status) +\end{verbatim} + +\begin{description} +\item[3 ] Write values into group parameters. This routine only applies + to the `Random Grouped' FITS format which has been used for + applications in radio interferometry, but is officially deprecated + for future use. \label{ffpgpx} +\end{description} + +\begin{verbatim} + int fits_write_grppar_[byt, sht, usht, int, uint, lng, ulng, lnglng, flt, dbl] / + ffpgp[b,i,ui,k,uk,j,uj,jj,e,d] + (fitsfile *fptr, long group, long firstelem, long nelements, + > DTYPE *array, int *status) +\end{verbatim} + +\begin{description} +\item[4 ] Write a 2-D or 3-D image into the data array. \label{ffp2dx} \label{ffp3dx} +\end{description} + +\begin{verbatim} + int fits_write_2d_[byt, sht, usht, int, uint, lng, ulng, lnglng, flt, dbl] / + ffp2d[b,i,ui,k,uk,j,uj,jj,e,d] + (fitsfile *fptr, long group, LONGLONG dim1, LONGLONG naxis1, + LONGLONG naxis2, DTYPE *array, > int *status) + + int fits_write_3d_[byt, sht, usht, int, uint, lng, ulng, lnglng, flt, dbl] / + ffp3d[b,i,ui,k,uk,j,uj,jj,e,d] + (fitsfile *fptr, long group, LONGLONG dim1, LONGLONG dim2, LONGLONG naxis1, + LONGLONG naxis2, LONGLONG naxis3, DTYPE *array, > int *status) +\end{verbatim} + +\begin{description} +\item[5 ] Write an arbitrary data subsection into the data array. \label{ffpssx} +\end{description} + +\begin{verbatim} + int fits_write_subset_[byt, sht, usht, int, uint, lng, ulng, lnglng, flt, dbl] / + ffpss[b,i,ui,k,uk,j,uj,jj,e,d] + (fitsfile *fptr, long group, long naxis, long *naxes, + long *fpixel, long *lpixel, DTYPE *array, > int *status) +\end{verbatim} + +\begin{description} +\item[6 ] Read elements from the FITS data array. + \label{ffgpv} \label{ffgpvx} \label{ffgpf} \label{ffgpfx} +\end{description} + +\begin{verbatim} + int fits_read_img / ffgpv + (fitsfile *fptr, int datatype, long firstelem, long nelements, + DTYPE *nulval, > DTYPE *array, int *anynul, int *status) + + int fits_read_img_[byt, sht, usht, int, uint, lng, ulng, lnglng, flt, dbl] / + ffgpv[b,i,ui,k,uk,j,uj,jj,e,d] + (fitsfile *fptr, long group, long firstelem, long nelements, + DTYPE nulval, > DTYPE *array, int *anynul, int *status) + + int fits_read_imgnull / ffgpf + (fitsfile *fptr, int datatype, long firstelem, long nelements, + > DTYPE *array, char *nullarray, int *anynul, int *status) + + int fits_read_imgnull_[byt, sht, usht, int, uint, lng, ulng, flt, dbl] / + ffgpf[b,i,ui,k,uk,j,uj,jj,e,d] + (fitsfile *fptr, long group, long firstelem, long nelements, + > DTYPE *array, char *nullarray, int *anynul, int *status) +\end{verbatim} + +\begin{description} +\item[7 ] Read values from group parameters. This routine only applies + to the `Random Grouped' FITS format which has been used for + applications in radio interferometry, but is officially deprecated + for future use. \label{ffggpx} +\end{description} + +\begin{verbatim} + int fits_read_grppar_[byt, sht, usht, int, uint, lng, ulng, lnglng, flt, dbl] / + ffggp[b,i,ui,k,uk,j,uj,jj,e,d] + (fitsfile *fptr, long group, long firstelem, long nelements, + > DTYPE *array, int *status) +\end{verbatim} + +\begin{description} +\item[8 ] Read 2-D or 3-D image from the data array. Undefined + pixels in the array will be set equal to the value of 'nulval', + unless nulval=0 in which case no testing for undefined pixels will + be performed. \label{ffg2dx} \label{ffg3dx} +\end{description} + +\begin{verbatim} + int fits_read_2d_[byt, sht, usht, int, uint, lng, ulng, lnglng, flt, dbl] / + ffg2d[b,i,ui,k,uk,j,uj,jj,e,d] + (fitsfile *fptr, long group, DTYPE nulval, LONGLONG dim1, LONGLONG naxis1, + LONGLONG naxis2, > DTYPE *array, int *anynul, int *status) + + int fits_read_3d_[byt, sht, usht, int, uint, lng, ulng, lnglng, flt, dbl] / + ffg3d[b,i,ui,k,uk,j,uj,jj,e,d] + (fitsfile *fptr, long group, DTYPE nulval, LONGLONG dim1, + LONGLONG dim2, LONGLONG naxis1, LONGLONG naxis2, LONGLONG naxis3, + > DTYPE *array, int *anynul, int *status) +\end{verbatim} + +\begin{description} +\item[9 ] Read an arbitrary data subsection from the data array. + \label{ffgsvx} \label{ffgsfx} +\end{description} + +\begin{verbatim} + int fits_read_subset_[byt, sht, usht, int, uint, lng, ulng, lnglng, flt, dbl] / + ffgsv[b,i,ui,k,uk,j,uj,jj,e,d] + (fitsfile *fptr, int group, int naxis, long *naxes, + long *fpixel, long *lpixel, long *inc, DTYPE nulval, + > DTYPE *array, int *anynul, int *status) + + int fits_read_subsetnull_[byt, sht, usht, int, uint, lng, ulng, lnglng, flt, dbl] / + ffgsf[b,i,ui,k,uk,j,uj,jj,e,d] + (fitsfile *fptr, int group, int naxis, long *naxes, + long *fpixel, long *lpixel, long *inc, > DTYPE *array, + char *nullarray, int *anynul, int *status) +\end{verbatim} + + +\section{Specialized FITS ASCII and Binary Table Routines} + + +\subsection{General Column Routines} + + +\begin{description} +\item[1 ] Get information about an existing ASCII or binary table column. A null + pointer may be given for any of the output parameters that are not + needed. DATATYPE is a character string which returns the data type + of the column as defined by the TFORMn keyword (e.g., 'I', 'J','E', + 'D', etc.). In the case of an ASCII character column, typecode + will have a value of the form 'An' where 'n' is an integer + expressing the width of the field in characters. For example, if + TFORM = '160A8' then ffgbcl will return typechar='A8' and + repeat=20. All the returned parameters are scalar quantities. + \label{ffgacl} \label{ffgbcl} +\end{description} + +\begin{verbatim} + int fits_get_acolparms / ffgacl + (fitsfile *fptr, int colnum, > char *ttype, long *tbcol, + char *tunit, char *tform, double *scale, double *zero, + char *nulstr, char *tdisp, int *status) + + int fits_get_bcolparms / ffgbcl + (fitsfile *fptr, int colnum, > char *ttype, char *tunit, + char *typechar, long *repeat, double *scale, double *zero, + long *nulval, char *tdisp, int *status) + + int fits_get_bcolparmsll / ffgbclll + (fitsfile *fptr, int colnum, > char *ttype, char *tunit, + char *typechar, LONGLONG *repeat, double *scale, double *zero, + LONGLONG *nulval, char *tdisp, int *status) +\end{verbatim} + +\begin{description} +\item[2 ] Return optimal number of rows to read or write at one time for + maximum I/O efficiency. Refer to the + ``Optimizing Code'' section in Chapter 5 for more discussion on how + to use this routine. \label{ffgrsz} +\end{description} + +\begin{verbatim} + int fits_get_rowsize / ffgrsz + (fitsfile *fptr, long *nrows, *status) +\end{verbatim} + +\begin{description} +\item[3 ] Define the zero indexed byte offset of the 'heap' measured from + the start of the binary table data. By default the heap is assumed + to start immediately following the regular table data, i.e., at + location NAXIS1 x NAXIS2. This routine is only relevant for + binary tables which contain variable length array columns (with + TFORMn = 'Pt'). This routine also automatically writes + the value of theap to a keyword in the extension header. This + routine must be called after the required keywords have been + written (with ffphbn) + but before any data is written to the table. \label{ffpthp} +\end{description} + +\begin{verbatim} + int fits_write_theap / ffpthp + (fitsfile *fptr, long theap, > int *status) +\end{verbatim} + +\begin{description} +\item[4 ] Test the contents of the binary table variable array heap, returning + the size of the heap, the number of unused bytes that are not currently + pointed to by any of the descriptors, and the number of bytes which are + pointed to by multiple descriptors. It also returns valid = FALSE if + any of the descriptors point to invalid addresses out of range of the + heap. \label{fftheap} +\end{description} + +\begin{verbatim} + int fits_test_heap / fftheap + (fitsfile *fptr, > LONGLONG *heapsize, LONGLONG *unused, LONGLONG *overlap, + int *validheap, int *status) +\end{verbatim} + +\begin{description} +\item[5 ] Re-pack the vectors in the binary table variable array heap to recover + any unused space. Normally, when a vector in a variable length + array column is rewritten the previously written array remains in + the heap as wasted unused space. This routine will repack the + arrays that are still in use, thus eliminating any bytes in the + heap that are no longer in use. Note that if several vectors point + to the same bytes in the heap, then this routine will make + duplicate copies of the bytes for each vector, which will actually + expand the size of the heap. \label{ffcmph} +\end{description} + +\begin{verbatim} + int fits_compress_heap / ffcmph + (fitsfile *fptr, > int *status) +\end{verbatim} + + +\subsection{Low-Level Table Access Routines} + +The following 2 routines provide low-level access to the data in ASCII +or binary tables and are mainly useful as an efficient way to copy all +or part of a table from one location to another. These routines simply +read or write the specified number of consecutive bytes in an ASCII or +binary table, without regard for column boundaries or the row length in +the table. These routines do not perform any machine dependent data +conversion or byte swapping. See Appendix B for the definition of the +parameters used in these routines. + + +\begin{description} +\item[1 ] Read or write a consecutive array of bytes from an ASCII or binary + table \label{ffgtbb} \label{ffptbb} +\end{description} + +\begin{verbatim} + int fits_read_tblbytes / ffgtbb + (fitsfile *fptr, LONGLONG firstrow, LONGLONG firstchar, LONGLONG nchars, + > unsigned char *values, int *status) + + int fits_write_tblbytes / ffptbb + (fitsfile *fptr, LONGLONG firstrow, LONGLONG firstchar, LONGLONG nchars, + unsigned char *values, > int *status) +\end{verbatim} + + +\subsection{Write Column Data Routines} + + +\begin{description} +\item[1 ] Write elements into an ASCII or binary table column (in the CDU). + The data type of the array is implied by the suffix of the + routine name. \label{ffpcls} +\end{description} + +\begin{verbatim} + int fits_write_col_str / ffpcls + (fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem, + LONGLONG nelements, char **array, > int *status) + + int fits_write_col_[log,byt,sht,usht,int,uint,lng,ulng,lnglng,flt,dbl,cmp,dblcmp] / + ffpcl[l,b,i,ui,k,uk,j,uj,jj,e,d,c,m] + (fitsfile *fptr, int colnum, LONGLONG firstrow, + LONGLONG firstelem, LONGLONG nelements, DTYPE *array, > int *status) +\end{verbatim} + +\begin{description} +\item[2 ] Write elements into an ASCII or binary table column + substituting the appropriate FITS null value for any elements that + are equal to the nulval parameter. \label{ffpcnx} +\end{description} + +\begin{verbatim} + int fits_write_colnull_[log, byt, sht, usht, int, uint, lng, ulng, lnglng, flt, dbl] / + ffpcn[l,b,i,ui,k,uk,j,uj,jj,e,d] + (fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem, + LONGLONG nelements, DTYPE *array, DTYPE nulval, > int *status) +\end{verbatim} + +\begin{description} +\item[3 ] Write string elements into a binary table column (in the CDU) + substituting the FITS null value for any elements that + are equal to the nulstr string. \label{ffpcns} +\end{description} + +\begin{verbatim} + int fits_write_colnull_str / ffpcns + (fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem, + LONGLONG nelements, char **array, char *nulstr, > int *status) +\end{verbatim} + +\begin{description} +\item[4 ] Write bit values into a binary byte ('B') or bit ('X') table column (in + the CDU). Larray is an array of characters corresponding to the + sequence of bits to be written. If an element of larray is true + (not equal to zero) then the corresponding bit in the FITS table is + set to 1, otherwise the bit is set to 0. The 'X' column in a FITS + table is always padded out to a multiple of 8 bits where the bit + array starts with the most significant bit of the byte and works + down towards the 1's bit. For example, a '4X' array, with the + first bit = 1 and the remaining 3 bits = 0 is equivalent to the 8-bit + unsigned byte decimal value of 128 ('1000 0000B'). In the case of + 'X' columns, CFITSIO can write to all 8 bits of each byte whether + they are formally valid or not. Thus if the column is defined as + '4X', and one calls ffpclx with firstbit=1 and nbits=8, then all + 8 bits will be written into the first byte (as opposed to writing + the first 4 bits into the first row and then the next 4 bits into + the next row), even though the last 4 bits of each byte are formally + not defined and should all be set = 0. It should also be noted that + it is more efficient to write 'X' columns an entire byte at a time, + instead of bit by bit. Any of the CFITSIO routines that write to + columns (e.g. fits\_write\_col\_byt) may be used for this purpose. + These routines will interpret 'X' columns as though they were 'B' + columns (e.g., '1X' through '8X' is equivalent + to '1B', and '9X' through '16X' is equivalent to '2B'). \label{ffpclx} +\end{description} + +\begin{verbatim} + int fits_write_col_bit / ffpclx + (fitsfile *fptr, int colnum, LONGLONG firstrow, long firstbit, + long nbits, char *larray, > int *status) +\end{verbatim} + +\begin{description} +\item[5 ] Write the descriptor for a variable length column in a binary table. + This routine can be used in conjunction with ffgdes to enable + 2 or more arrays to point to the same storage location to save + storage space if the arrays are identical. \label{ffpdes} +\end{description} + +\begin{verbatim} + int fits_write_descript / ffpdes + (fitsfile *fptr, int colnum, LONGLONG rownum, LONGLONG repeat, + LONGLONG offset, > int *status) +\end{verbatim} + +\subsection{Read Column Data Routines} + +Two types of routines are provided to get the column data which differ +in the way undefined pixels are handled. The first set of routines +(ffgcv) simply return an array of data elements in which undefined +pixels are set equal to a value specified by the user in the 'nullval' +parameter. If nullval = 0, then no checks for undefined pixels will be +performed, thus increasing the speed of the program. The second set of +routines (ffgcf) returns the data element array and in addition a +logical array of flags which defines whether the corresponding data +pixel is undefined. See Appendix B for the definition of the +parameters used in these routines. + + Any column, regardless of it's intrinsic data type, may be read as a + string. It should be noted however that reading a numeric column as + a string is 10 - 100 times slower than reading the same column as a number + due to the large overhead in constructing the formatted strings. + The display format of the returned strings will be + determined by the TDISPn keyword, if it exists, otherwise by the + data type of the column. The length of the returned strings (not + including the null terminating character) can be determined with + the fits\_get\_col\_display\_width routine. The following TDISPn + display formats are currently supported: + +\begin{verbatim} + Iw.m Integer + Ow.m Octal integer + Zw.m Hexadecimal integer + Fw.d Fixed floating point + Ew.d Exponential floating point + Dw.d Exponential floating point + Gw.d General; uses Fw.d if significance not lost, else Ew.d +\end{verbatim} + where w is the width in characters of the displayed values, m is + the minimum number of digits displayed, and d is the number of + digits to the right of the decimal. The .m field is optional. + + +\begin{description} +\item[1 ] Read elements from an ASCII or binary table column (in the CDU). These + routines return the values of the table column array elements. Undefined + array elements will be returned with a value = nulval, unless nulval = 0 + (or = ' ' for ffgcvs) in which case no checking for undefined values will + be performed. The ANYF parameter is set to true if any of the returned + elements are undefined. \label{ffgcvx} +\end{description} + +\begin{verbatim} + int fits_read_col_str / ffgcvs + (fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem, + LONGLONG nelements, char *nulstr, > char **array, int *anynul, + int *status) + + int fits_read_col_[log,byt,sht,usht,int,uint,lng,ulng, lnglng, flt, dbl, cmp, dblcmp] / + ffgcv[l,b,i,ui,k,uk,j,uj,jj,e,d,c,m] + (fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem, + LONGLONG nelements, DTYPE nulval, > DTYPE *array, int *anynul, + int *status) +\end{verbatim} + +\begin{description} +\item[2 ] Read elements and null flags from an ASCII or binary table column (in the + CHDU). These routines return the values of the table column array elements. + Any undefined array elements will have the corresponding nullarray element + set equal to TRUE. The anynul parameter is set to true if any of the + returned elements are undefined. \label{ffgcfx} +\end{description} + +\begin{verbatim} + int fits_read_colnull_str / ffgcfs + (fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem, + LONGLONG nelements, > char **array, char *nullarray, int *anynul, + int *status) + + int fits_read_colnull_[log,byt,sht,usht,int,uint,lng,ulng,lnglng,flt,dbl,cmp,dblcmp] / + ffgcf[l,b,i,ui,k,uk,j,uj,jj,e,d,c,m] + (fitsfile *fptr, int colnum, LONGLONG firstrow, + LONGLONG firstelem, LONGLONG nelements, > DTYPE *array, + char *nullarray, int *anynul, int *status) +\end{verbatim} + +\begin{description} +\item[3 ] Read an arbitrary data subsection from an N-dimensional array + in a binary table vector column. Undefined pixels + in the array will be set equal to the value of 'nulval', + unless nulval=0 in which case no testing for undefined pixels will + be performed. The first and last rows in the table to be read + are specified by fpixel(naxis+1) and lpixel(naxis+1), and hence + are treated as the next higher dimension of the FITS N-dimensional + array. The INC parameter specifies the sampling interval in + each dimension between the data elements that will be returned. \label{ffgsvx2} +\end{description} + +\begin{verbatim} + int fits_read_subset_[byt, sht, usht, int, uint, lng, ulng, lnglng, flt, dbl] / + ffgsv[b,i,ui,k,uk,j,uj,jj,e,d] + (fitsfile *fptr, int colnum, int naxis, long *naxes, long *fpixel, + long *lpixel, long *inc, DTYPE nulval, > DTYPE *array, int *anynul, + int *status) +\end{verbatim} + +\begin{description} +\item[4 ] Read an arbitrary data subsection from an N-dimensional array + in a binary table vector column. Any Undefined + pixels in the array will have the corresponding 'nullarray' + element set equal to TRUE. The first and last rows in the table + to be read are specified by fpixel(naxis+1) and lpixel(naxis+1), + and hence are treated as the next higher dimension of the FITS + N-dimensional array. The INC parameter specifies the sampling + interval in each dimension between the data elements that will be + returned. \label{ffgsfx2} +\end{description} + +\begin{verbatim} + int fits_read_subsetnull_[byt, sht, usht, int, uint, lng, ulng, lnglng, flt, dbl] / + ffgsf[b,i,ui,k,uk,j,uj,jj,e,d] + (fitsfile *fptr, int colnum, int naxis, long *naxes, + long *fpixel, long *lpixel, long *inc, > DTYPE *array, + char *nullarray, int *anynul, int *status) +\end{verbatim} + +\begin{description} +\item[5 ] Read bit values from a byte ('B') or bit (`X`) table column (in the + CDU). Larray is an array of logical values corresponding to the + sequence of bits to be read. If larray is true then the + corresponding bit was set to 1, otherwise the bit was set to 0. + The 'X' column in a FITS table is always padded out to a multiple + of 8 bits where the bit array starts with the most significant bit + of the byte and works down towards the 1's bit. For example, a + '4X' array, with the first bit = 1 and the remaining 3 bits = 0 is + equivalent to the 8-bit unsigned byte value of 128. + Note that in the case of 'X' columns, CFITSIO can read all 8 bits + of each byte whether they are formally valid or not. Thus if the + column is defined as '4X', and one calls ffgcx with firstbit=1 and + nbits=8, then all 8 bits will be read from the first byte (as + opposed to reading the first 4 bits from the first row and then the + first 4 bits from the next row), even though the last 4 bits of + each byte are formally not defined. It should also be noted that + it is more efficient to read 'X' columns an entire byte at a time, + instead of bit by bit. Any of the CFITSIO routines that read + columns (e.g. fits\_read\_col\_byt) may be used for this + purpose. These routines will interpret 'X' columns as though they + were 'B' columns (e.g., '8X' is equivalent to '1B', and '16X' is + equivalent to '2B'). \label{ffgcx} +\end{description} + +\begin{verbatim} + int fits_read_col_bit / ffgcx + (fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstbit, + LONGLONG nbits, > char *larray, int *status) +\end{verbatim} + +\begin{description} +\item[6 ] Read any consecutive set of bits from an 'X' or 'B' column and + interpret them as an unsigned n-bit integer. nbits must be less + than 16 or 32 in ffgcxui and ffgcxuk, respectively. If nrows + is greater than 1, then the same set of bits will be read from + each row, starting with firstrow. The bits are numbered with + 1 = the most significant bit of the first element of the column. + \label{ffgcxui} +\end{description} + +\begin{verbatim} + int fits_read_col_bit_[usht, uint] / ffgcx[ui,uk] + (fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG, nrows, + long firstbit, long nbits, > DTYPE *array, int *status) +\end{verbatim} + +\begin{description} +\item[7 ] Return the descriptor for a variable length column in a binary table. + The descriptor consists of 2 integer parameters: the number of elements + in the array and the starting offset relative to the start of the heap. + The first pair of routine returns a single descriptor whereas the second + pair of routine + returns the descriptors for a range of rows in the table. The only + difference between the 2 routines in each pair is that one returns + the parameters as 'long' integers, whereas the other returns the values + as 64-bit 'LONGLONG' integers. + \label{ffgdes} +\end{description} + +\begin{verbatim} + int fits_read_descript / ffgdes + (fitsfile *fptr, int colnum, LONGLONG rownum, > long *repeat, + long *offset, int *status) + + int fits_read_descriptll / ffgdesll + (fitsfile *fptr, int colnum, LONGLONG rownum, > LONGLONG *repeat, + LONGLONG *offset, int *status) + + int fits_read_descripts / ffgdess + (fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG nrows + > long *repeat, long *offset, int *status) + + int fits_read_descriptsll / ffgdessll + (fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG nrows + > LONGLONG *repeat, LONGLONG *offset, int *status) +\end{verbatim} + +\chapter{ Extended File Name Syntax } + + +\section{Overview} + +CFITSIO supports an extended syntax when specifying the name of the +data file to be opened or created that includes the following +features: + +\begin{itemize} +\item +CFITSIO can read IRAF format images which have header file names that +end with the '.imh' extension, as well as reading and writing FITS +files, This feature is implemented in CFITSIO by first converting the +IRAF image into a temporary FITS format file in memory, then opening +the FITS file. Any of the usual CFITSIO routines then may be used to +read the image header or data. Similarly, raw binary data arrays can +be read by converting them on the fly into virtual FITS images. + +\item +FITS files on the Internet can be read (and sometimes written) using the FTP, +HTTP, or ROOT protocols. + +\item +FITS files can be piped between tasks on the stdin and stdout streams. + +\item +FITS files can be read and written in shared memory. This can +potentially achieve better data I/O performance compared to reading and +writing the same FITS files on magnetic disk. + +\item +Compressed FITS files in gzip or Unix COMPRESS format can be directly read. + +\item +Output FITS files can be written directly in compressed gzip format, +thus saving disk space. + +\item +FITS table columns can be created, modified, or deleted 'on-the-fly' as +the table is opened by CFITSIO. This creates a virtual FITS file containing +the modifications that is then opened by the application program. + +\item +Table rows may be selected, or filtered out, on the fly when the table +is opened by CFITSIO, based on an user-specified expression. +Only rows for which the expression evaluates to 'TRUE' are retained +in the copy of the table that is opened by the application program. + +\item +Histogram images may be created on the fly by binning the values in +table columns, resulting in a virtual N-dimensional FITS image. The +application program then only sees the FITS image (in the primary +array) instead of the original FITS table. +\end{itemize} + +The latter 3 table filtering features in particular add very powerful +data processing capabilities directly into CFITSIO, and hence into +every task that uses CFITSIO to read or write FITS files. For example, +these features transform a very simple program that just copies an +input FITS file to a new output file (like the `fitscopy' program that +is distributed with CFITSIO) into a multipurpose FITS file processing +tool. By appending fairly simple qualifiers onto the name of the input +FITS file, the user can perform quite complex table editing operations +(e.g., create new columns, or filter out rows in a table) or create +FITS images by binning or histogramming the values in table columns. +In addition, these functions have been coded using new state-of-the art +algorithms that are, in some cases, 10 - 100 times faster than previous +widely used implementations. + +Before describing the complete syntax for the extended FITS file names +in the next section, here are a few examples of FITS file names that +give a quick overview of the allowed syntax: + +\begin{itemize} +\item +{\tt myfile.fits}: the simplest case of a FITS file on disk in the current +directory. + +\item +{\tt myfile.imh}: opens an IRAF format image file and converts it on the +fly into a temporary FITS format image in memory which can then be read with +any other CFITSIO routine. + +\item +{\tt rawfile.dat[i512,512]}: opens a raw binary data array (a 512 x 512 +short integer array in this case) and converts it on the fly into a +temporary FITS format image in memory which can then be read with any +other CFITSIO routine. + +\item +{\tt myfile.fits.gz}: if this is the name of a new output file, the '.gz' +suffix will cause it to be compressed in gzip format when it is written to +disk. + +\item +{\tt myfile.fits.gz[events, 2]}: opens and uncompresses the gzipped file +myfile.fits then moves to the extension with the keywords EXTNAME += 'EVENTS' and EXTVER = 2. + +\item +{\tt -}: a dash (minus sign) signifies that the input file is to be read +from the stdin file stream, or that the output file is to be written to +the stdout stream. See also the stream:// driver which provides a +more efficient, but more restricted method of reading or writing to +the stdin or stdout streams. + +\item +{\tt ftp://legacy.gsfc.nasa.gov/test/vela.fits}: FITS files in any ftp +archive site on the Internet may be directly opened with read-only +access. + +\item +{\tt http://legacy.gsfc.nasa.gov/software/test.fits}: any valid URL to a +FITS file on the Web may be opened with read-only access. + +\item +{\tt root://legacy.gsfc.nasa.gov/test/vela.fits}: similar to ftp access +except that it provides write as well as read access to the files +across the network. This uses the root protocol developed at CERN. + +\item +{\tt shmem://h2[events]}: opens the FITS file in a shared memory segment and +moves to the EVENTS extension. + +\item +{\tt mem://}: creates a scratch output file in core computer memory. The +resulting 'file' will disappear when the program exits, so this +is mainly useful for testing purposes when one does not want a +permanent copy of the output file. + +\item +{\tt myfile.fits[3; Images(10)]}: opens a copy of the image contained in the +10th row of the 'Images' column in the binary table in the 3th extension +of the FITS file. The virtual file that is opened by the application just +contains this single image in the primary array. + +\item +{\tt myfile.fits[1:512:2, 1:512:2]}: opens a section of the input image +ranging from the 1st to the 512th pixel in X and Y, and selects every +second pixel in both dimensions, resulting in a 256 x 256 pixel input image +in this case. + +\item +{\tt myfile.fits[EVENTS][col Rad = sqrt(X**2 + Y**2)]}: creates and opens +a virtual file on the fly that is identical to +myfile.fits except that it will contain a new column in the EVENTS +extension called 'Rad' whose value is computed using the indicated +expression which is a function of the values in the X and Y columns. + +\item +{\tt myfile.fits[EVENTS][PHA > 5]}: creates and opens a virtual FITS +files that is identical to 'myfile.fits' except that the EVENTS table +will only contain the rows that have values of the PHA column greater +than 5. In general, any arbitrary boolean expression using a C or +Fortran-like syntax, which may combine AND and OR operators, +may be used to select rows from a table. + +\item +{\tt myfile.fits[EVENTS][bin (X,Y)=1,2048,4]}: creates a temporary FITS +primary array image which is computed on the fly by binning (i.e, +computing the 2-dimensional histogram) of the values in the X and Y +columns of the EVENTS extension. In this case the X and Y coordinates +range from 1 to 2048 and the image pixel size is 4 units in both +dimensions, so the resulting image is 512 x 512 pixels in size. + +\item +The final example combines many of these feature into one complex +expression (it is broken into several lines for clarity): + +\begin{verbatim} + ftp://legacy.gsfc.nasa.gov/data/sample.fits.gz[EVENTS] + [col phacorr = pha * 1.1 - 0.3][phacorr >= 5.0 && phacorr <= 14.0] + [bin (X,Y)=32] +\end{verbatim} +In this case, CFITSIO (1) copies and uncompresses the FITS file from +the ftp site on the legacy machine, (2) moves to the 'EVENTS' +extension, (3) calculates a new column called 'phacorr', (4) selects +the rows in the table that have phacorr in the range 5 to 14, and +finally (5) bins the remaining rows on the X and Y column coordinates, +using a pixel size = 32 to create a 2D image. All this processing is +completely transparent to the application program, which simply sees +the final 2-D image in the primary array of the opened file. +\end{itemize} + +The full extended CFITSIO FITS file name can contain several different +components depending on the context. These components are described in +the following sections: + +\begin{verbatim} +When creating a new file: + filetype://BaseFilename(templateName)[compress] + +When opening an existing primary array or image HDU: + filetype://BaseFilename(outName)[HDUlocation][ImageSection][pixFilter] + +When opening an existing table HDU: + filetype://BaseFilename(outName)[HDUlocation][colFilter][rowFilter][binSpec] +\end{verbatim} +The filetype, BaseFilename, outName, HDUlocation, ImageSection, and pixFilter +components, if present, must be given in that order, but the colFilter, +rowFilter, and binSpec specifiers may follow in any order. Regardless +of the order, however, the colFilter specifier, if present, will be +processed first by CFITSIO, followed by the rowFilter specifier, and +finally by the binSpec specifier. + + +\section{Filetype} + +The type of file determines the medium on which the file is located +(e.g., disk or network) and, hence, which internal device driver is used by +CFITSIO to read and/or write the file. Currently supported types are + +\begin{verbatim} + file:// - file on local magnetic disk (default) + ftp:// - a readonly file accessed with the anonymous FTP protocol. + It also supports ftp://username:password@hostname/... + for accessing password-protected ftp sites. + http:// - a readonly file accessed with the HTTP protocol. It + supports username:password just like the ftp driver. + Proxy HTTP servers are supported using the http_proxy + environment variable (see following note). + stream:// - special driver to read an input FITS file from the stdin + stream, and/or write an output FITS file to the stdout + stream. This driver is fragile and has limited + functionality (see the following note). + gsiftp:// - access files on a computational grid using the gridftp + protocol in the Globus toolkit (see following note). + root:// - uses the CERN root protocol for writing as well as + reading files over the network (see following note). + shmem:// - opens or creates a file which persists in the computer's + shared memory (see following note). + mem:// - opens a temporary file in core memory. The file + disappears when the program exits so this is mainly + useful for test purposes when a permanent output file + is not desired. +\end{verbatim} +If the filetype is not specified, then type file:// is assumed. +The double slashes '//' are optional and may be omitted in most cases. + + +\subsection{Notes about HTTP proxy servers} + +A proxy HTTP server may be used by defining the address (URL) and port +number of the proxy server with the http\_proxy environment variable. +For example + +\begin{verbatim} + setenv http_proxy http://heasarc.gsfc.nasa.gov:3128 +\end{verbatim} +will cause CFITSIO to use port 3128 on the heasarc proxy server whenever +reading a FITS file with HTTP. + + +\subsection{Notes about the stream filetype driver} + +The stream driver can be used to efficiently read a FITS file from the stdin +file stream or write a FITS to the stdout file stream. However, because these +input and output streams must be accessed sequentially, the FITS file reading or +writing application must also read and write the file sequentially, at least +within the tolerances described below. + +CFITSIO supports 2 different methods for accessing FITS files on the stdin and +stdout streams. The original method, which is invoked by specifying a dash +character, "-", as the name of the file when opening or creating it, works by +storing a complete copy of the entire FITS file in memory. In this case, when +reading from stdin, CFITSIO will copy the entire stream into memory before doing +any processing of the file. Similarly, when writing to stdout, CFITSIO will +create a copy of the entire FITS file in memory, before finally flushing it out +to the stdout stream when the FITS file is closed. Buffering the entire FITS +file in this way allows the application to randomly access any part of the FITS +file, in any order, but it also requires that the user have sufficient available +memory (or virtual memory) to store the entire file, which may not be possible +in the case of very large files. + +The newer stream filetype provides a more memory-efficient method of accessing +FITS files on the stdin or stdout streams. Instead of storing a copy of the +entire FITS file in memory, CFITSIO only uses a set of internal buffer which by +default can store 40 FITS blocks, or about 100K bytes of the FITS file. The +application program must process the FITS file sequentially from beginning to +end, within this 100K buffer. Generally speaking the application program must +conform to the following restrictions: + +\begin{itemize} +\item +The program must finish reading or writing the header keywords +before reading or writing any data in the HDU. +\item +The HDU can contain at most about 1400 header keywords. This is the +maximum that can fit in the nominal 40 FITS block buffer. In principle, +this limit could be increased by recompiling CFITSIO with a larger +buffer limit, which is set by the NIOBUF parameter in fitsio2.h. +\item +The program must read or write the data in a sequential manner from the +beginning to the end of the HDU. Note that CFITSIO's internal +100K buffer allows a little latitude in meeting this requirement. +\item +The program cannot move back to a previous HDU in the FITS file. +\item +Reading or writing of variable length array columns in binary tables is not +supported on streams, because this requires moving back and forth between the +fixed-length portion of the binary table and the following heap area where the +arrays are actually stored. +\item +Reading or writing of tile-compressed images is not supported on streams, +because the images are internally stored using variable length arrays. +\end{itemize} + + +\subsection{Notes about the gsiftp filetype} + +DEPENDENCIES: Globus toolkit (2.4.3 or higher) (GT) should be installed. +There are two different ways to install GT: + +1) goto the globus toolkit web page www.globus.org and follow the + download and compilation instructions; + +2) goto the Virtual Data Toolkit web page http://vdt.cs.wisc.edu/ + and follow the instructions (STRONGLY SUGGESTED); + +Once a globus client has been installed in your system with a specific flavour +it is possible to compile and install the CFITSIO libraries. +Specific configuration flags must be used: + +1) --with-gsiftp[[=PATH]] Enable Globus Toolkit gsiftp protocol support + PATH=GLOBUS\_LOCATION i.e. the location of your globus installation + +2) --with-gsiftp-flavour[[=PATH] defines the specific Globus flavour + ex. gcc32 + +Both the flags must be used and it is mandatory to set both the PATH and the +flavour. + +USAGE: To access files on a gridftp server it is necessary to use a gsiftp prefix: + +example: gsiftp://remote\_server\_fqhn/directory/filename + +The gridftp driver uses a local buffer on a temporary file the file is located +in the /tmp directory. If you have special permissions on /tmp or you do not have a /tmp +directory, it is possible to force another location setting the GSIFTP\_TMPFILE environment +variable (ex. export GSIFTP\_TMPFILE=/your/location/yourtmpfile). + +Grid FTP supports multi channel transfer. By default a single channel transmission is +available. However, it is possible to modify this behavior setting the GSIFTP\_STREAMS +environment variable (ex. export GSIFTP\_STREAMS=8). + + + +\subsection{Notes about the root filetype} + +The original rootd server can be obtained from: +\verb-ftp://root.cern.ch/root/rootd.tar.gz- +but, for it to work correctly with CFITSIO one has to use a modified +version which supports a command to return the length of the file. +This modified version is available in rootd subdirectory +in the CFITSIO ftp area at + +\begin{verbatim} + ftp://legacy.gsfc.nasa.gov/software/fitsio/c/root/rootd.tar.gz. +\end{verbatim} + +This small server is started either by inetd when a client requests a +connection to a rootd server or by hand (i.e. from the command line). +The rootd server works with the ROOT TNetFile class. It allows remote +access to ROOT database files in either read or write mode. By default +TNetFile assumes port 432 (which requires rootd to be started as root). +To run rootd via inetd add the following line to /etc/services: + +\begin{verbatim} + rootd 432/tcp +\end{verbatim} +and to /etc/inetd.conf, add the following line: + +\begin{verbatim} + rootd stream tcp nowait root /user/rdm/root/bin/rootd rootd -i +\end{verbatim} +Force inetd to reread its conf file with \verb+kill -HUP +. +You can also start rootd by hand running directly under your private +account (no root system privileges needed). For example to start +rootd listening on port 5151 just type: \verb+rootd -p 5151+ +Notice that no \& is needed. Rootd will go into background by itself. + +\begin{verbatim} + Rootd arguments: + -i says we were started by inetd + -p port# specifies a different port to listen on + -d level level of debug info written to syslog + 0 = no debug (default) + 1 = minimum + 2 = medium + 3 = maximum +\end{verbatim} +Rootd can also be configured for anonymous usage (like anonymous ftp). +To setup rootd to accept anonymous logins do the following (while being +logged in as root): + +\begin{verbatim} + - Add the following line to /etc/passwd: + + rootd:*:71:72:Anonymous rootd:/var/spool/rootd:/bin/false + + where you may modify the uid, gid (71, 72) and the home directory + to suite your system. + + - Add the following line to /etc/group: + + rootd:*:72:rootd + + where the gid must match the gid in /etc/passwd. + + - Create the directories: + + mkdir /var/spool/rootd + mkdir /var/spool/rootd/tmp + chmod 777 /var/spool/rootd/tmp + + Where /var/spool/rootd must match the rootd home directory as + specified in the rootd /etc/passwd entry. + + - To make writeable directories for anonymous do, for example: + + mkdir /var/spool/rootd/pub + chown rootd:rootd /var/spool/rootd/pub +\end{verbatim} +That's all. Several additional remarks: you can login to an anonymous +server either with the names "anonymous" or "rootd". The password should +be of type user@host.do.main. Only the @ is enforced for the time +being. In anonymous mode the top of the file tree is set to the rootd +home directory, therefore only files below the home directory can be +accessed. Anonymous mode only works when the server is started via +inetd. + + +\subsection{Notes about the shmem filetype:} + +Shared memory files are currently supported on most Unix platforms, +where the shared memory segments are managed by the operating system +kernel and `live' independently of processes. They are not deleted (by +default) when the process which created them terminates, although they +will disappear if the system is rebooted. Applications can create +shared memory files in CFITSIO by calling: + +\begin{verbatim} + fit_create_file(&fitsfileptr, "shmem://h2", &status); +\end{verbatim} +where the root `file' names are currently restricted to be 'h0', 'h1', +'h2', 'h3', etc., up to a maximum number defined by the the value of +SHARED\_MAXSEG (equal to 16 by default). This is a prototype +implementation of the shared memory interface and a more robust +interface, which will have fewer restrictions on the number of files +and on their names, may be developed in the future. + +When opening an already existing FITS file in shared memory one calls +the usual CFITSIO routine: + +\begin{verbatim} + fits_open_file(&fitsfileptr, "shmem://h7", mode, &status) +\end{verbatim} +The file mode can be READWRITE or READONLY just as with disk files. +More than one process can operate on READONLY mode files at the same +time. CFITSIO supports proper file locking (both in READONLY and +READWRITE modes), so calls to fits\_open\_file may be locked out until +another other process closes the file. + +When an application is finished accessing a FITS file in a shared +memory segment, it may close it (and the file will remain in the +system) with fits\_close\_file, or delete it with fits\_delete\_file. +Physical deletion is postponed until the last process calls +ffclos/ffdelt. fits\_delete\_file tries to obtain a READWRITE lock on +the file to be deleted, thus it can be blocked if the object was not +opened in READWRITE mode. + +A shared memory management utility program called `smem', is included +with the CFITSIO distribution. It can be built by typing `make smem'; +then type `smem -h' to get a list of valid options. Executing smem +without any options causes it to list all the shared memory segments +currently residing in the system and managed by the shared memory +driver. To get a list of all the shared memory objects, run the system +utility program `ipcs [-a]'. + + +\section{Base Filename} + +The base filename is the name of the file optionally including the +director/subdirectory path, and in the case of `ftp', `http', and `root' +filetypes, the machine identifier. Examples: + +\begin{verbatim} + myfile.fits + !data.fits + /data/myfile.fits + fits.gsfc.nasa.gov/ftp/sampledata/myfile.fits.gz +\end{verbatim} + +When creating a new output file on magnetic disk (of type file://) if +the base filename begins with an exclamation point (!) then any +existing file with that same basename will be deleted prior to creating +the new FITS file. Otherwise if the file to be created already exists, +then CFITSIO will return an error and will not overwrite the existing +file. Note that the exclamation point, '!', is a special UNIX +character, so if it is used on the command line rather than entered at +a task prompt, it must be preceded by a backslash to force the UNIX +shell to pass it verbatim to the application program. + +If the output disk file name ends with the suffix '.gz', then CFITSIO +will compress the file using the gzip compression algorithm before +writing it to disk. This can reduce the amount of disk space used by +the file. Note that this feature requires that the uncompressed file +be constructed in memory before it is compressed and written to disk, +so it can fail if there is insufficient available memory. + +An input FITS file may be compressed with the gzip or Unix compress +algorithms, in which case CFITSIO will uncompress the file on the fly +into a temporary file (in memory or on disk). Compressed files may +only be opened with read-only permission. When specifying the name of +a compressed FITS file it is not necessary to append the file suffix +(e.g., `.gz' or `.Z'). If CFITSIO cannot find the input file name +without the suffix, then it will automatically search for a compressed +file with the same root name. In the case of reading ftp and http type +files, CFITSIO generally looks for a compressed version of the file +first, before trying to open the uncompressed file. By default, +CFITSIO copies (and uncompressed if necessary) the ftp or http FITS +file into memory on the local machine before opening it. This will +fail if the local machine does not have enough memory to hold the whole +FITS file, so in this case, the output filename specifier (see the next +section) can be used to further control how CFITSIO reads ftp and http +files. + +If the input file is an IRAF image file (*.imh file) then CFITSIO will +automatically convert it on the fly into a virtual FITS image before it +is opened by the application program. IRAF images can only be opened +with READONLY file access. + +Similarly, if the input file is a raw binary data array, then CFITSIO +will convert it on the fly into a virtual FITS image with the basic set +of required header keywords before it is opened by the application +program (with READONLY access). In this case the data type and +dimensions of the image must be specified in square brackets following +the filename (e.g. rawfile.dat[ib512,512]). The first character (case +insensitive) defines the data type of the array: + +\begin{verbatim} + b 8-bit unsigned byte + i 16-bit signed integer + u 16-bit unsigned integer + j 32-bit signed integer + r or f 32-bit floating point + d 64-bit floating point +\end{verbatim} +An optional second character specifies the byte order of the array +values: b or B indicates big endian (as in FITS files and the native +format of SUN UNIX workstations and Mac PCs) and l or L indicates +little endian (native format of DEC OSF workstations and IBM PCs). If +this character is omitted then the array is assumed to have the native +byte order of the local machine. These data type characters are then +followed by a series of one or more integer values separated by commas +which define the size of each dimension of the raw array. Arrays with +up to 5 dimensions are currently supported. Finally, a byte offset to +the position of the first pixel in the data file may be specified by +separating it with a ':' from the last dimension value. If omitted, it +is assumed that the offset = 0. This parameter may be used to skip +over any header information in the file that precedes the binary data. +Further examples: + +\begin{verbatim} + raw.dat[b10000] 1-dimensional 10000 pixel byte array + raw.dat[rb400,400,12] 3-dimensional floating point big-endian array + img.fits[ib512,512:2880] reads the 512 x 512 short integer array in + a FITS file, skipping over the 2880 byte header +\end{verbatim} + +One special case of input file is where the filename = `-' (a dash or +minus sign) or 'stdin' or 'stdout', which signifies that the input file +is to be read from the stdin stream, or written to the stdout stream if +a new output file is being created. In the case of reading from stdin, +CFITSIO first copies the whole stream into a temporary FITS file (in +memory or on disk), and subsequent reading of the FITS file occurs in +this copy. When writing to stdout, CFITSIO first constructs the whole +file in memory (since random access is required), then flushes it out +to the stdout stream when the file is closed. In addition, if the +output filename = '-.gz' or 'stdout.gz' then it will be gzip compressed +before being written to stdout. + +This ability to read and write on the stdin and stdout steams allows +FITS files to be piped between tasks in memory rather than having to +create temporary intermediate FITS files on disk. For example if task1 +creates an output FITS file, and task2 reads an input FITS file, the +FITS file may be piped between the 2 tasks by specifying + +\begin{verbatim} + task1 - | task2 - +\end{verbatim} +where the vertical bar is the Unix piping symbol. This assumes that the 2 +tasks read the name of the FITS file off of the command line. + + +\section{Output File Name when Opening an Existing File} + +An optional output filename may be specified in parentheses immediately +following the base file name to be opened. This is mainly useful in +those cases where CFITSIO creates a temporary copy of the input FITS +file before it is opened and passed to the application program. This +happens by default when opening a network FTP or HTTP-type file, when +reading a compressed FITS file on a local disk, when reading from the +stdin stream, or when a column filter, row filter, or binning specifier +is included as part of the input file specification. By default this +temporary file is created in memory. If there is not enough memory to +create the file copy, then CFITSIO will exit with an error. In these +cases one can force a permanent file to be created on disk, instead of +a temporary file in memory, by supplying the name in parentheses +immediately following the base file name. The output filename can +include the '!' clobber flag. + +Thus, if the input filename to CFITSIO is: +\verb+file1.fits.gz(file2.fits)+ +then CFITSIO will uncompress `file1.fits.gz' into the local disk file +`file2.fits' before opening it. CFITSIO does not automatically delete +the output file, so it will still exist after the application program +exits. + +The output filename "mem://" is also allowed, which will write the +output file into memory, and also allow write access to the file. This +'file' will disappear when it is closed, but this may be useful for +some applications which only need to modify a temporary copy of the file. + +In some cases, several different temporary FITS files will be created +in sequence, for instance, if one opens a remote file using FTP, then +filters rows in a binary table extension, then create an image by +binning a pair of columns. In this case, the remote file will be +copied to a temporary local file, then a second temporary file will be +created containing the filtered rows of the table, and finally a third +temporary file containing the binned image will be created. In cases +like this where multiple files are created, the outfile specifier will +be interpreted the name of the final file as described below, in descending +priority: + +\begin{itemize} +\item +as the name of the final image file if an image within a single binary +table cell is opened or if an image is created by binning a table column. +\item +as the name of the file containing the filtered table if a column filter +and/or a row filter are specified. +\item +as the name of the local copy of the remote FTP or HTTP file. +\item +as the name of the uncompressed version of the FITS file, if a +compressed FITS file on local disk has been opened. +\item +otherwise, the output filename is ignored. +\end{itemize} + +The output file specifier is useful when reading FTP or HTTP-type +FITS files since it can be used to create a local disk copy of the file +that can be reused in the future. If the output file name = `*' then a +local file with the same name as the network file will be created. +Note that CFITSIO will behave differently depending on whether the +remote file is compressed or not as shown by the following examples: +\begin{itemize} +\item +\verb+ftp://remote.machine/tmp/myfile.fits.gz(*)+ - the remote compressed +file is copied to the local compressed file `myfile.fits.gz', which +is then uncompressed in local memory before being opened and passed +to the application program. + +\item +\verb+ftp://remote.machine/tmp/myfile.fits.gz(myfile.fits)+ - the +remote compressed file is copied and uncompressed into the local file +`myfile.fits'. This example requires less local memory than the +previous example since the file is uncompressed on disk instead of in +memory. + +\item +\verb+ftp://remote.machine/tmp/myfile.fits(myfile.fits.gz)+ - this will +usually produce an error since CFITSIO itself cannot compress files. +\end{itemize} + +The exact behavior of CFITSIO in the latter case depends on the type of +ftp server running on the remote machine and how it is configured. In +some cases, if the file `myfile.fits.gz' exists on the remote machine, +then the server will copy it to the local machine. In other cases the +ftp server will automatically create and transmit a compressed version +of the file if only the uncompressed version exists. This can get +rather confusing, so users should use a certain amount of caution when +using the output file specifier with FTP or HTTP file types, to make +sure they get the behavior that they expect. + + +\section{Template File Name when Creating a New File} + +When a new FITS file is created with a call to fits\_create\_file, the +name of a template file may be supplied in parentheses immediately +following the name of the new file to be created. This template is +used to define the structure of one or more HDUs in the new file. The +template file may be another FITS file, in which case the newly created +file will have exactly the same keywords in each HDU as in the template +FITS file, but all the data units will be filled with zeros. The +template file may also be an ASCII text file, where each line (in +general) describes one FITS keyword record. The format of the ASCII +template file is described in the following Template Files chapter. + + +\section{Image Tile-Compression Specification} + +When specifying the name of the output FITS file to be created, the +user can indicate that images should be written in tile-compressed +format (see section 5.5, ``Primary Array or IMAGE Extension I/O +Routines'') by enclosing the compression parameters in square brackets +following the root disk file name. Here are some examples of the +syntax for specifying tile-compressed output images: + +\begin{verbatim} + myfile.fit[compress] - use Rice algorithm and default tile size + + myfile.fit[compress GZIP] - use the specified compression algorithm; + myfile.fit[compress Rice] only the first letter of the algorithm + myfile.fit[compress PLIO] name is required. + + myfile.fit[compress Rice 100,100] - use 100 x 100 pixel tile size + myfile.fit[compress Rice 100,100;2] - as above, and use noisebits = 2 +\end{verbatim} + + +\section{HDU Location Specification} + +The optional HDU location specifier defines which HDU (Header-Data +Unit, also known as an `extension') within the FITS file to initially +open. It must immediately follow the base file name (or the output +file name if present). If it is not specified then the first HDU (the +primary array) is opened. The HDU location specifier is required if +the colFilter, rowFilter, or binSpec specifiers are present, because +the primary array is not a valid HDU for these operations. The HDU may +be specified either by absolute position number, starting with 0 for +the primary array, or by reference to the HDU name, and optionally, the +version number and the HDU type of the desired extension. The location +of an image within a single cell of a binary table may also be +specified, as described below. + +The absolute position of the extension is specified either by enclosed +the number in square brackets (e.g., `[1]' = the first extension +following the primary array) or by preceded the number with a plus sign +(`+1'). To specify the HDU by name, give the name of the desired HDU +(the value of the EXTNAME or HDUNAME keyword) and optionally the +extension version number (value of the EXTVER keyword) and the +extension type (value of the XTENSION keyword: IMAGE, ASCII or TABLE, +or BINTABLE), separated by commas and all enclosed in square brackets. +If the value of EXTVER and XTENSION are not specified, then the first +extension with the correct value of EXTNAME is opened. The extension +name and type are not case sensitive, and the extension type may be +abbreviated to a single letter (e.g., I = IMAGE extension or primary +array, A or T = ASCII table extension, and B = binary table BINTABLE +extension). If the HDU location specifier is equal to `[PRIMARY]' or +`[P]', then the primary array (the first HDU) will be opened. + +An optional pound sign character ("\#") may be appended to the extension +name or number to signify that any other extensions in the file should +be ignored during any subsequent file filtering operations. For example, +when doing row filtering operations on a table extension, CFITSIO normally +creates a copy of the filtered table in memory, along with a verbatim +copy of all the other extensions in the input FITS file. If the pound +sign is appended to the table extension name, then only that extension, +and none of the other extensions in the file, will by copied to memory, +as in the following example: + +\begin{verbatim} + myfile.fit[events#][TIME > 10000] +\end{verbatim} + +FITS images are most commonly stored in the primary array or an image +extension, but images can also be stored as a vector in a single cell +of a binary table (i.e. each row of the vector column contains a +different image). Such an image can be opened with CFITSIO by +specifying the desired column name and the row number after the binary +table HDU specifier as shown in the following examples. The column name +is separated from the HDU specifier by a semicolon and the row number +is enclosed in parentheses. In this case CFITSIO copies the image from +the table cell into a temporary primary array before it is opened. The +application program then just sees the image in the primary array, +without any extensions. The particular row to be opened may be +specified either by giving an absolute integer row number (starting +with 1 for the first row), or by specifying a boolean expression that +evaluates to TRUE for the desired row. The first row that satisfies +the expression will be used. The row selection expression has the same +syntax as described in the Row Filter Specifier section, below. + + Examples: + +\begin{verbatim} + myfile.fits[3] - open the 3rd HDU following the primary array + myfile.fits+3 - same as above, but using the FTOOLS-style notation + myfile.fits[EVENTS] - open the extension that has EXTNAME = 'EVENTS' + myfile.fits[EVENTS, 2] - same as above, but also requires EXTVER = 2 + myfile.fits[events,2,b] - same, but also requires XTENSION = 'BINTABLE' + myfile.fits[3; images(17)] - opens the image in row 17 of the 'images' + column in the 3rd extension of the file. + myfile.fits[3; images(exposure > 100)] - as above, but opens the image + in the first row that has an 'exposure' column value + greater than 100. +\end{verbatim} + + +\section{Image Section} + +A virtual file containing a rectangular subsection of an image can be +extracted and opened by specifying the range of pixels (start:end) +along each axis to be extracted from the original image. One can also +specify an optional pixel increment (start:end:step) for each axis of +the input image. A pixel step = 1 will be assumed if it is not +specified. If the start pixel is larger then the end pixel, then the +image will be flipped (producing a mirror image) along that dimension. +An asterisk, '*', may be used to specify the entire range of an axis, +and '-*' will flip the entire axis. The input image can be in the +primary array, in an image extension, or contained in a vector cell of +a binary table. In the later 2 cases the extension name or number must +be specified before the image section specifier. + + Examples: + +\begin{verbatim} + myfile.fits[1:512:2, 2:512:2] - open a 256x256 pixel image + consisting of the odd numbered columns (1st axis) and + the even numbered rows (2nd axis) of the image in the + primary array of the file. + + myfile.fits[*, 512:256] - open an image consisting of all the columns + in the input image, but only rows 256 through 512. + The image will be flipped along the 2nd axis since + the starting pixel is greater than the ending pixel. + + myfile.fits[*:2, 512:256:2] - same as above but keeping only + every other row and column in the input image. + + myfile.fits[-*, *] - copy the entire image, flipping it along + the first axis. + + myfile.fits[3][1:256,1:256] - opens a subsection of the image that + is in the 3rd extension of the file. + + myfile.fits[4; images(12)][1:10,1:10] - open an image consisting + of the first 10 pixels in both dimensions. The original + image resides in the 12th row of the 'images' vector + column in the table in the 4th extension of the file. +\end{verbatim} + +When CFITSIO opens an image section it first creates a temporary file +containing the image section plus a copy of any other HDUs in the +file. (If a `\#' character is appended to the name or number of the +image HDU, as in "myfile.fits[1\#][1:200,1:200]", then the other +HDUs in the input file will not be copied into memory). +This temporary file is then opened by the application program, +so it is not possible to write to or modify the input file when +specifying an image section. Note that CFITSIO automatically updates +the world coordinate system keywords in the header of the image +section, if they exist, so that the coordinate associated with each +pixel in the image section will be computed correctly. + + +\section{Image Transform Filters} + +CFITSIO can apply a user-specified mathematical function to the value +of every pixel in a FITS image, thus creating a new virtual image +in computer memory that is then opened and read by the application +program. The original FITS image is not modified by this process. + +The image transformation specifier is appended to the input +FITS file name and is enclosed in square brackets. It begins with the +letters 'PIX' to distinguish it from other types of FITS file filters +that are recognized by CFITSIO. The image transforming function may +use any of the mathematical operators listed in the following +'Row Filtering Specification' section of this document. +Some examples of image transform filters are: + +\begin{verbatim} + [pix X * 2.0] - multiply each pixel by 2.0 + [pix sqrt(X)] - take the square root of each pixel + [pix X + #ZEROPT - add the value of the ZEROPT keyword + [pix X>0 ? log10(X) : -99.] - if the pixel value is greater + than 0, compute the base 10 log, + else set the pixel = -99. +\end{verbatim} +Use the letter 'X' in the expression to represent the current pixel value +in the image. The expression is evaluated +independently for each pixel in the image and may be a function of 1) the +original pixel value, 2) the value of other pixels in the image at +a given relative offset from the position of the pixel that is being +evaluated, and 3) the value of +any header keywords. Header keyword values are represented +by the name of the keyword preceded by the '\#' sign. + + +To access the the value of adjacent pixels in the image, +specify the (1-D) offset from the current pixel in curly brackets. +For example + +\begin{verbatim} + [pix (x{-1} + x + x{+1}) / 3] +\end{verbatim} +will replace each pixel value with the running mean of the values of that +pixel and it's 2 neighboring pixels. Note that in this notation the image +is treated as a 1-D array, where each row of the image (or higher dimensional +cube) is appended one after another in one long array of pixels. +It is possible to refer to pixels +in the rows above or below the current pixel by using the value of the +NAXIS1 header keyword. For example + +\begin{verbatim} + [pix (x{-#NAXIS1} + x + x{#NAXIS1}) / 3] +\end{verbatim} +will compute the mean of each image pixel and the pixels immediately +above and below it in the adjacent rows of the image. +The following more complex example +creates a smoothed virtual image where each pixel +is a 3 x 3 boxcar average of the input image pixels: + +\begin{verbatim} + [pix (X + X{-1} + X{+1} + + X{-#NAXIS1} + X{-#NAXIS1 - 1} + X{-#NAXIS1 + 1} + + X{#NAXIS1} + X{#NAXIS1 - 1} + X{#NAXIS1 + 1}) / 9.] +\end{verbatim} +If the pixel offset +extends beyond the first or last pixel in the image, the function will +evaluate to undefined, or NULL. + +For complex or commonly used image filtering operations, +one can write the expression into an external text file and +then import it into the +filter using the syntax '[pix @filename.txt]'. The mathematical +expression can +extend over multiple lines of text in the file. +Any lines in the external text file +that begin with 2 slash characters ('//') will be ignored and may be +used to add comments into the file. + +By default, the datatype of the resulting image will be the same as +the original image, but one may force a different datatype by appended +a code letter to the 'pix' keyword: + +\begin{verbatim} + pixb - 8-bit byte image with BITPIX = 8 + pixi - 16-bit integer image with BITPIX = 16 + pixj - 32-bit integer image with BITPIX = 32 + pixr - 32-bit float image with BITPIX = -32 + pixd - 64-bit float image with BITPIX = -64 +\end{verbatim} +Also by default, any other HDUs in the input file will be copied without +change to the +output virtual FITS file, but one may discard the other HDUs by adding +the number '1' to the 'pix' keyword (and following any optional datatype code +letter). For example: + +\begin{verbatim} + myfile.fits[3][pixr1 sqrt(X)] +\end{verbatim} +will create a virtual FITS file containing only a primary array image +with 32-bit floating point pixels that have a value equal to the square +root of the pixels in the image that is in the 3rd extension +of the 'myfile.fits' file. + + + +\section{Column and Keyword Filtering Specification} + +The optional column/keyword filtering specifier is used to modify the +column structure and/or the header keywords in the HDU that was +selected with the previous HDU location specifier. This filtering +specifier must be enclosed in square brackets and can be distinguished +from a general row filter specifier (described below) by the fact that +it begins with the string 'col ' and is not immediately followed by an +equals sign. The original file is not changed by this filtering +operation, and instead the modifications are made on a copy of the +input FITS file (usually in memory), which also contains a copy of all +the other HDUs in the file. (If a `\#' character is appended to the name +or number of the +table HDU then only the primary array, and none of the other +HDUs in the input file will be copied into memory). +This temporary file is passed to the +application program and will persist only until the file is closed or +until the program exits, unless the outfile specifier (see above) is +also supplied. + +The column/keyword filter can be used to perform the following +operations. More than one operation may be specified by separating +them with commas or semi-colons. + +\begin{itemize} + +\item +Copy only a specified list of columns columns to the filtered input file. +The list of column name should be separated by semi-colons. Wild card +characters may be used in the column names to match multiple columns. +If the expression contains both a list of columns to be included and +columns to be deleted, then all the columns in the original table +except the explicitly deleted columns will appear in the filtered +table (i.e., there is no need to explicitly list the columns to +be included if any columns are being deleted). + +\item +Delete a column or keyword by listing the name preceded by a minus sign +or an exclamation mark (!), e.g., '-TIME' will delete the TIME column +if it exists, otherwise the TIME keyword. An error is returned if +neither a column nor keyword with this name exists. Note that the +exclamation point, '!', is a special UNIX character, so if it is used +on the command line rather than entered at a task prompt, it must be +preceded by a backslash to force the UNIX shell to ignore it. + +\item +Rename an existing column or keyword with the syntax 'NewName == +OldName'. An error is returned if neither a column nor keyword with +this name exists. + +\item +Append a new column or keyword to the table. To create a column, +give the new name, optionally followed by the data type in parentheses, +followed by a single equals sign and an expression to be used to +compute the value (e.g., 'newcol(1J) = 0' will create a new 32-bit +integer column called 'newcol' filled with zeros). The data type is +specified using the same syntax that is allowed for the value of the +FITS TFORMn keyword (e.g., 'I', 'J', 'E', 'D', etc. for binary tables, +and 'I8', F12.3', 'E20.12', etc. for ASCII tables). If the data type is +not specified then an appropriate data type will be chosen depending on +the form of the expression (may be a character string, logical, bit, long +integer, or double column). An appropriate vector count (in the case +of binary tables) will also be added if not explicitly specified. + +When creating a new keyword, the keyword name must be preceded by a +pound sign '\#', and the expression must evaluate to a scalar +(i.e., cannot have a column name in the expression). The comment +string for the keyword may be specified in parentheses immediately +following the keyword name (instead of supplying a data type as in +the case of creating a new column). If the keyword name ends with a +pound sign '\#', then cfitsio will substitute the number of the +most recently referenced column for the \# character . +This is especially useful when writing +a column-related keyword like TUNITn for a newly created column, +as shown in the following examples. + +\item +Recompute (overwrite) the values in an existing column or keyword by +giving the name followed by an equals sign and an arithmetic +expression. +\end{itemize} + +The expression that is used when appending or recomputing columns or +keywords can be arbitrarily complex and may be a function of other +header keyword values and other columns (in the same row). The full +syntax and available functions for the expression are described below +in the row filter specification section. + +If the expression contains both a list of columns to be included and +columns to be deleted, then all the columns in the original table +except the explicitly deleted columns will appear in the filtered +table. If no columns to be deleted are specified, then only the +columns that are explicitly listed will be included in the filtered +output table. To include all the columns, add the '*' wildcard +specifier at the end of the list, as shown in the examples. + +For complex or commonly used operations, one can place the +operations into an external text file and import it into the column +filter using the syntax '[col @filename.txt]'. The operations can +extend over multiple lines of the file, but multiple operations must +still be separated by semicolons. Any lines in the external text file +that begin with 2 slash characters ('//') will be ignored and may be +used to add comments into the file. + +Examples: + +\begin{verbatim} + [col Time; rate] - only the Time and rate columns will + appear in the filtered input file. + + [col Time, *raw] - include the Time column and any other + columns whose name ends with 'raw'. + + [col -TIME; Good == STATUS] - deletes the TIME column and + renames the status column to 'Good' + + [col PI=PHA * 1.1 + 0.2; #TUNIT#(column units) = 'counts';*] + - creates new PI column from PHA values + and also writes the TUNITn keyword + for the new column. The final '*' + expression means preserve all the + columns in the input table in the + virtual output table; without the '*' + the output table would only contain + the single 'PI' column. + + [col rate = rate/exposure; TUNIT#(&) = 'counts/s';*] + - recomputes the rate column by dividing + it by the EXPOSURE keyword value. This + also modifies the value of the TUNITn + keyword for this column. The use of the + '&' character for the keyword comment + string means preserve the existing + comment string for that keyword. The + final '*' preserves all the columns + in the input table in the virtual + output table. +\end{verbatim} + + +\section{Row Filtering Specification} + + When entering the name of a FITS table that is to be opened by a + program, an optional row filter may be specified to select a subset + of the rows in the table. A temporary new FITS file is created on + the fly which contains only those rows for which the row filter + expression evaluates to true. The primary array and any other + extensions in the input file are also copied to the temporary + file. +(If a `\#' character is appended to the name +or number of the +table HDU then only the primary array, and none of the other +HDUs in the input file will be copied into the temporary file). + The original FITS file is closed and the new virtual file + is opened by the application program. The row filter expression is + enclosed in square brackets following the file name and extension + name (e.g., 'file.fits[events][GRADE==50]' selects only those rows + where the GRADE column value equals 50). When dealing with tables + where each row has an associated time and/or 2D spatial position, + the row filter expression can also be used to select rows based on + the times in a Good Time Intervals (GTI) extension, or on spatial + position as given in a SAO-style region file. + + +\subsection{General Syntax} + + The row filtering expression can be an arbitrarily complex series + of operations performed on constants, keyword values, and column + data taken from the specified FITS TABLE extension. The expression + must evaluate to a boolean value for each row of the table, where + a value of FALSE means that the row will be excluded. + + For complex or commonly used filters, one can place the expression + into a text file and import it into the row filter using the syntax + '[@filename.txt]'. The expression can be arbitrarily complex and + extend over multiple lines of the file. Any lines in the external + text file that begin with 2 slash characters ('//') will be ignored + and may be used to add comments into the file. + + Keyword and column data are referenced by name. Any string of + characters not surrounded by quotes (ie, a constant string) or + followed by an open parentheses (ie, a function name) will be + initially interpreted as a column name and its contents for the + current row inserted into the expression. If no such column exists, + a keyword of that name will be searched for and its value used, if + found. To force the name to be interpreted as a keyword (in case + there is both a column and keyword with the same name), precede the + keyword name with a single pound sign, '\#', as in '\#NAXIS2'. Due to + the generalities of FITS column and keyword names, if the column or + keyword name contains a space or a character which might appear as + an arithmetic term then enclose the name in '\$' characters as in + \$MAX PHA\$ or \#\$MAX-PHA\$. Names are case insensitive. + + To access a table entry in a row other than the current one, follow + the column's name with a row offset within curly braces. For + example, 'PHA\{-3\}' will evaluate to the value of column PHA, 3 rows + above the row currently being processed. One cannot specify an + absolute row number, only a relative offset. Rows that fall outside + the table will be treated as undefined, or NULLs. + + Boolean operators can be used in the expression in either their + Fortran or C forms. The following boolean operators are available: + +\begin{verbatim} + "equal" .eq. .EQ. == "not equal" .ne. .NE. != + "less than" .lt. .LT. < "less than/equal" .le. .LE. <= =< + "greater than" .gt. .GT. > "greater than/equal" .ge. .GE. >= => + "or" .or. .OR. || "and" .and. .AND. && + "negation" .not. .NOT. ! "approx. equal(1e-7)" ~ +\end{verbatim} + +Note that the exclamation +point, '!', is a special UNIX character, so if it is used on the +command line rather than entered at a task prompt, it must be preceded +by a backslash to force the UNIX shell to ignore it. + + The expression may also include arithmetic operators and functions. + Trigonometric functions use radians, not degrees. The following + arithmetic operators and functions can be used in the expression + (function names are case insensitive). A null value will be returned + in case of illegal operations such as divide by zero, sqrt(negative) + log(negative), log10(negative), arccos(.gt. 1), arcsin(.gt. 1). + + +\begin{verbatim} + "addition" + "subtraction" - + "multiplication" * "division" / + "negation" - "exponentiation" ** ^ + "absolute value" abs(x) "cosine" cos(x) + "sine" sin(x) "tangent" tan(x) + "arc cosine" arccos(x) "arc sine" arcsin(x) + "arc tangent" arctan(x) "arc tangent" arctan2(y,x) + "hyperbolic cos" cosh(x) "hyperbolic sin" sinh(x) + "hyperbolic tan" tanh(x) "round to nearest int" round(x) + "round down to int" floor(x) "round up to int" ceil(x) + "exponential" exp(x) "square root" sqrt(x) + "natural log" log(x) "common log" log10(x) + "modulus" x % y "random # [0.0,1.0)" random() + "random Gaussian" randomn() "random Poisson" randomp(x) + "minimum" min(x,y) "maximum" max(x,y) + "cumulative sum" accum(x) "sequential difference" seqdiff(x) + "if-then-else" b?x:y + "angular separation" angsep(ra1,dec1,ra2,de2) (all in degrees) + "substring" strmid(s,p,n) "string search" strstr(s,r) +\end{verbatim} +Three different random number functions are provided: random(), with +no arguments, produces a uniform random deviate between 0 and 1; +randomn(), also with no arguments, produces a normal (Gaussian) random +deviate with zero mean and unit standard deviation; randomp(x) +produces a Poisson random deviate whose expected number of counts is +X. X may be any positive real number of expected counts, including +fractional values, but the return value is an integer. + +When the random functions are used in a vector expression, by default +the same random value will be used when evaluating each element of the vector. +If different random numbers are desired, then the name of a vector +column should be supplied as the single argument to the random +function (e.g., "flux + 0.1 * random(flux)", where "flux' is the +name of a vector column). This will create a vector of +random numbers that will be used in sequence when evaluating each +element of the vector expression. + +An alternate syntax for the min and max functions has only a single +argument which should be a vector value (see below). The result +will be the minimum/maximum element contained within the vector. + +The accum(x) function forms the cumulative sum of x, element by element. +Vector columns are supported simply by performing the summation process +through all the values. Null values are treated as 0. The seqdiff(x) +function forms the sequential difference of x, element by element. +The first value of seqdiff is the first value of x. A single null +value in x causes a pair of nulls in the output. The seqdiff and +accum functions are functional inverses, i.e., seqdiff(accum(x)) == x +as long as no null values are present. + +In the if-then-else expression, "b?x:y", b is an explicit boolean +value or expression. There is no automatic type conversion from +numeric to boolean values, so one needs to use "iVal!=0" instead of +merely "iVal" as the boolean argument. x and y can be any scalar data +type (including string). + +The angsep function computes the angular separation in degrees +between 2 celestial positions, where the first 2 parameters +give the RA-like and Dec-like coordinates (in decimal degrees) +of the first position, and the 3rd and 4th parameters give the +coordinates of the second position. + +The substring function strmid(S,P,N) extracts a substring from S, +starting at string position P, with a substring length N. The first +character position in S is labeled as 1. If P is 0, or refers to a +position beyond the end of S, then the extracted substring will be +NULL. S, P, and N may be functions of other columns. + +The string search function strstr(S,R) searches for the first occurrence +of the substring R in S. The result is an integer, indicating the +character position of the first match (where 1 is the first character +position of S). If no match is found, then strstr() returns a NULL +value. + +The following type casting operators are available, where the +inclosing parentheses are required and taken from the C language +usage. Also, the integer to real casts values to double precision: + +\begin{verbatim} + "real to integer" (int) x (INT) x + "integer to real" (float) i (FLOAT) i +\end{verbatim} + + In addition, several constants are built in for use in numerical + expressions: + + +\begin{verbatim} + #pi 3.1415... #e 2.7182... + #deg #pi/180 #row current row number + #null undefined value #snull undefined string +\end{verbatim} + + A string constant must be enclosed in quotes as in 'Crab'. The + "null" constants are useful for conditionally setting table values + to a NULL, or undefined, value (eg., "col1==-99 ? \#NULL : col1"). + + There is also a function for testing if two values are close to + each other, i.e., if they are "near" each other to within a user + specified tolerance. The arguments, value\_1 and value\_2 can be + integer or real and represent the two values who's proximity is + being tested to be within the specified tolerance, also an integer + or real: + +\begin{verbatim} + near(value_1, value_2, tolerance) +\end{verbatim} + When a NULL, or undefined, value is encountered in the FITS table, + the expression will evaluate to NULL unless the undefined value is + not actually required for evaluation, e.g. "TRUE .or. NULL" + evaluates to TRUE. The following two functions allow some NULL + detection and handling: + +\begin{verbatim} + "a null value?" ISNULL(x) + "define a value for null" DEFNULL(x,y) +\end{verbatim} + The former + returns a boolean value of TRUE if the argument x is NULL. The + later "defines" a value to be substituted for NULL values; it + returns the value of x if x is not NULL, otherwise it returns the + value of y. + + + + +\subsection{Bit Masks} + + Bit masks can be used to select out rows from bit columns (TFORMn = + \#X) in FITS files. To represent the mask, binary, octal, and hex + formats are allowed: + + +\begin{verbatim} + binary: b0110xx1010000101xxxx0001 + octal: o720x1 -> (b111010000xxx001) + hex: h0FxD -> (b00001111xxxx1101) +\end{verbatim} + + In all the representations, an x or X is allowed in the mask as a + wild card. Note that the x represents a different number of wild + card bits in each representation. All representations are case + insensitive. + + To construct the boolean expression using the mask as the boolean + equal operator described above on a bit table column. For example, + if you had a 7 bit column named flags in a FITS table and wanted + all rows having the bit pattern 0010011, the selection expression + would be: + + +\begin{verbatim} + flags == b0010011 + or + flags .eq. b10011 +\end{verbatim} + + It is also possible to test if a range of bits is less than, less + than equal, greater than and greater than equal to a particular + boolean value: + + +\begin{verbatim} + flags <= bxxx010xx + flags .gt. bxxx100xx + flags .le. b1xxxxxxx +\end{verbatim} + + Notice the use of the x bit value to limit the range of bits being + compared. + + It is not necessary to specify the leading (most significant) zero + (0) bits in the mask, as shown in the second expression above. + + Bit wise AND, OR and NOT operations are also possible on two or + more bit fields using the '\&'(AND), '$|$'(OR), and the '!'(NOT) + operators. All of these operators result in a bit field which can + then be used with the equal operator. For example: + + +\begin{verbatim} + (!flags) == b1101100 + (flags & b1000001) == bx000001 +\end{verbatim} + + Bit fields can be appended as well using the '+' operator. Strings + can be concatenated this way, too. + + +\subsection{Vector Columns} + + Vector columns can also be used in building the expression. No + special syntax is required if one wants to operate on all elements + of the vector. Simply use the column name as for a scalar column. + Vector columns can be freely intermixed with scalar columns or + constants in virtually all expressions. The result will be of the + same dimension as the vector. Two vectors in an expression, though, + need to have the same number of elements and have the same + dimensions. + + Arithmetic and logical operations are all performed on an element by + element basis. Comparing two vector columns, eg "COL1 == COL2", + thus results in another vector of boolean values indicating which + elements of the two vectors are equal. + + Eight functions are available that operate on a vector and return a + scalar result: + +\begin{verbatim} + "minimum" MIN(V) "maximum" MAX(V) + "average" AVERAGE(V) "median" MEDIAN(V) + "summation" SUM(V) "standard deviation" STDDEV(V) + "# of values" NELEM(V) "# of non-null values" NVALID(V) +\end{verbatim} + where V represents the name of a vector column or a manually + constructed vector using curly brackets as described below. The + first 6 of these functions ignore any null values in the vector when + computing the result. The STDDEV() function computes the sample + standard deviation, i.e. it is proportional to 1/SQRT(N-1) instead + of 1/SQRT(N), where N is NVALID(V). + + The SUM function literally sums all the elements in x, returning a + scalar value. If V is a boolean vector, SUM returns the number + of TRUE elements. The NELEM function returns the number of elements + in vector V whereas NVALID return the number of non-null elements in + the vector. (NELEM also operates on bit and string columns, + returning their column widths.) As an example, to test whether all + elements of two vectors satisfy a given logical comparison, one can + use the expression + +\begin{verbatim} + SUM( COL1 > COL2 ) == NELEM( COL1 ) +\end{verbatim} + + which will return TRUE if all elements of COL1 are greater than + their corresponding elements in COL2. + + To specify a single element of a vector, give the column name + followed by a comma-separated list of coordinates enclosed in + square brackets. For example, if a vector column named PHAS exists + in the table as a one dimensional, 256 component list of numbers + from which you wanted to select the 57th component for use in the + expression, then PHAS[57] would do the trick. Higher dimensional + arrays of data may appear in a column. But in order to interpret + them, the TDIMn keyword must appear in the header. Assuming that a + (4,4,4,4) array is packed into each row of a column named ARRAY4D, + the (1,2,3,4) component element of each row is accessed by + ARRAY4D[1,2,3,4]. Arrays up to dimension 5 are currently + supported. Each vector index can itself be an expression, although + it must evaluate to an integer value within the bounds of the + vector. Vector columns which contain spaces or arithmetic operators + must have their names enclosed in "\$" characters as with + \$ARRAY-4D\$[1,2,3,4]. + + A more C-like syntax for specifying vector indices is also + available. The element used in the preceding example alternatively + could be specified with the syntax ARRAY4D[4][3][2][1]. Note the + reverse order of indices (as in C), as well as the fact that the + values are still ones-based (as in Fortran -- adopted to avoid + ambiguity for 1D vectors). With this syntax, one does not need to + specify all of the indices. To extract a 3D slice of this 4D + array, use ARRAY4D[4]. + + Variable-length vector columns are not supported. + + Vectors can be manually constructed within the expression using a + comma-separated list of elements surrounded by curly braces ('\{\}'). + For example, '\{1,3,6,1\}' is a 4-element vector containing the values + 1, 3, 6, and 1. The vector can contain only boolean, integer, and + real values (or expressions). The elements will be promoted to the + highest data type present. Any elements which are themselves + vectors, will be expanded out with each of its elements becoming an + element in the constructed vector. + + +\subsection{Good Time Interval Filtering} + + A common filtering method involves selecting rows which have a time + value which lies within what is called a Good Time Interval or GTI. + The time intervals are defined in a separate FITS table extension + which contains 2 columns giving the start and stop time of each + good interval. The filtering operation accepts only those rows of + the input table which have an associated time which falls within + one of the time intervals defined in the GTI extension. A high + level function, gtifilter(a,b,c,d), is available which evaluates + each row of the input table and returns TRUE or FALSE depending + whether the row is inside or outside the good time interval. The + syntax is + +\begin{verbatim} + gtifilter( [ "gtifile" [, expr [, "STARTCOL", "STOPCOL" ] ] ] ) + or + gtifilter( [ 'gtifile' [, expr [, 'STARTCOL', 'STOPCOL' ] ] ] ) +\end{verbatim} + where each "[]" demarks optional parameters. Note that the quotes + around the gtifile and START/STOP column are required. Either single + or double quotes may be used. In cases where this expression is + entered on the Unix command line, enclose the entire expression in + double quotes, and then use single quotes within the expression to + enclose the 'gtifile' and other terms. It is also usually possible + to do the reverse, and enclose the whole expression in single quotes + and then use double quotes within the expression. The gtifile, + if specified, can be blank ("") which will mean to use the first + extension with the name "*GTI*" in the current file, a plain + extension specifier (eg, "+2", "[2]", or "[STDGTI]") which will be + used to select an extension in the current file, or a regular + filename with or without an extension specifier which in the latter + case will mean to use the first extension with an extension name + "*GTI*". Expr can be any arithmetic expression, including simply + the time column name. A vector time expression will produce a + vector boolean result. STARTCOL and STOPCOL are the names of the + START/STOP columns in the GTI extension. If one of them is + specified, they both must be. + + In its simplest form, no parameters need to be provided -- default + values will be used. The expression "gtifilter()" is equivalent to + +\begin{verbatim} + gtifilter( "", TIME, "*START*", "*STOP*" ) +\end{verbatim} + This will search the current file for a GTI extension, filter the + TIME column in the current table, using START/STOP times taken from + columns in the GTI extension with names containing the strings + "START" and "STOP". The wildcards ('*') allow slight variations in + naming conventions such as "TSTART" or "STARTTIME". The same + default values apply for unspecified parameters when the first one + or two parameters are specified. The function automatically + searches for TIMEZERO/I/F keywords in the current and GTI + extensions, applying a relative time offset, if necessary. + + +\subsection{Spatial Region Filtering} + + Another common filtering method selects rows based on whether the + spatial position associated with each row is located within a given + 2-dimensional region. The syntax for this high-level filter is + +\begin{verbatim} + regfilter( "regfilename" [ , Xexpr, Yexpr [ , "wcs cols" ] ] ) +\end{verbatim} + where each "[]" demarks optional parameters. The region file name + is required and must be enclosed in quotes. The remaining + parameters are optional. There are 2 supported formats for the + region file: ASCII file or FITS binary table. The region file + contains a list of one or more geometric shapes (circle, + ellipse, box, etc.) which defines a region on the celestial sphere + or an area within a particular 2D image. The region file is + typically generated using an image display program such as fv/POW + (distribute by the HEASARC), or ds9 (distributed by the Smithsonian + Astrophysical Observatory). Users should refer to the documentation + provided with these programs for more details on the syntax used in + the region files. The FITS region file format is defined in a document + available from the FITS Support Office at + http://fits.gsfc.nasa.gov/ registry/ region.html + + In its simplest form, (e.g., regfilter("region.reg") ) the + coordinates in the default 'X' and 'Y' columns will be used to + determine if each row is inside or outside the area specified in + the region file. Alternate position column names, or expressions, + may be entered if needed, as in + +\begin{verbatim} + regfilter("region.reg", XPOS, YPOS) +\end{verbatim} + Region filtering can be applied most unambiguously if the positions + in the region file and in the table to be filtered are both give in + terms of absolute celestial coordinate units. In this case the + locations and sizes of the geometric shapes in the region file are + specified in angular units on the sky (e.g., positions given in + R.A. and Dec. and sizes in arcseconds or arcminutes). Similarly, + each row of the filtered table will have a celestial coordinate + associated with it. This association is usually implemented using + a set of so-called 'World Coordinate System' (or WCS) FITS keywords + that define the coordinate transformation that must be applied to + the values in the 'X' and 'Y' columns to calculate the coordinate. + + Alternatively, one can perform spatial filtering using unitless + 'pixel' coordinates for the regions and row positions. In this + case the user must be careful to ensure that the positions in the 2 + files are self-consistent. A typical problem is that the region + file may be generated using a binned image, but the unbinned + coordinates are given in the event table. The ROSAT events files, + for example, have X and Y pixel coordinates that range from 1 - + 15360. These coordinates are typically binned by a factor of 32 to + produce a 480x480 pixel image. If one then uses a region file + generated from this image (in image pixel units) to filter the + ROSAT events file, then the X and Y column values must be converted + to corresponding pixel units as in: + +\begin{verbatim} + regfilter("rosat.reg", X/32.+.5, Y/32.+.5) +\end{verbatim} + Note that this binning conversion is not necessary if the region + file is specified using celestial coordinate units instead of pixel + units because CFITSIO is then able to directly compare the + celestial coordinate of each row in the table with the celestial + coordinates in the region file without having to know anything + about how the image may have been binned. + + The last "wcs cols" parameter should rarely be needed. If supplied, + this string contains the names of the 2 columns (space or comma + separated) which have the associated WCS keywords. If not supplied, + the filter will scan the X and Y expressions for column names. + If only one is found in each expression, those columns will be + used, otherwise an error will be returned. + + These region shapes are supported (names are case insensitive): + +\begin{verbatim} + Point ( X1, Y1 ) <- One pixel square region + Line ( X1, Y1, X2, Y2 ) <- One pixel wide region + Polygon ( X1, Y1, X2, Y2, ... ) <- Rest are interiors with + Rectangle ( X1, Y1, X2, Y2, A ) | boundaries considered + Box ( Xc, Yc, Wdth, Hght, A ) V within the region + Diamond ( Xc, Yc, Wdth, Hght, A ) + Circle ( Xc, Yc, R ) + Annulus ( Xc, Yc, Rin, Rout ) + Ellipse ( Xc, Yc, Rx, Ry, A ) + Elliptannulus ( Xc, Yc, Rinx, Riny, Routx, Routy, Ain, Aout ) + Sector ( Xc, Yc, Amin, Amax ) +\end{verbatim} + where (Xc,Yc) is the coordinate of the shape's center; (X\#,Y\#) are + the coordinates of the shape's edges; Rxxx are the shapes' various + Radii or semimajor/minor axes; and Axxx are the angles of rotation + (or bounding angles for Sector) in degrees. For rotated shapes, the + rotation angle can be left off, indicating no rotation. Common + alternate names for the regions can also be used: rotbox = box; + rotrectangle = rectangle; (rot)rhombus = (rot)diamond; and pie + = sector. When a shape's name is preceded by a minus sign, '-', + the defined region is instead the area *outside* its boundary (ie, + the region is inverted). All the shapes within a single region + file are OR'd together to create the region, and the order is + significant. The overall way of looking at region files is that if + the first region is an excluded region then a dummy included region + of the whole detector is inserted in the front. Then each region + specification as it is processed overrides any selections inside of + that region specified by previous regions. Another way of thinking + about this is that if a previous excluded region is completely + inside of a subsequent included region the excluded region is + ignored. + + The positional coordinates may be given either in pixel units, + decimal degrees or hh:mm:ss.s, dd:mm:ss.s units. The shape sizes + may be given in pixels, degrees, arcminutes, or arcseconds. Look + at examples of region file produced by fv/POW or ds9 for further + details of the region file format. + + There are three low-level functions that are primarily for use with + regfilter function, but they can be called directly. They + return a boolean true or false depending on whether a two + dimensional point is in the region or not. The positional coordinates + must be given in pixel units: + +\begin{verbatim} + "point in a circular region" + circle(xcntr,ycntr,radius,Xcolumn,Ycolumn) + + "point in an elliptical region" + ellipse(xcntr,ycntr,xhlf_wdth,yhlf_wdth,rotation,Xcolumn,Ycolumn) + + "point in a rectangular region" + box(xcntr,ycntr,xfll_wdth,yfll_wdth,rotation,Xcolumn,Ycolumn) + + where + (xcntr,ycntr) are the (x,y) position of the center of the region + (xhlf_wdth,yhlf_wdth) are the (x,y) half widths of the region + (xfll_wdth,yfll_wdth) are the (x,y) full widths of the region + (radius) is half the diameter of the circle + (rotation) is the angle(degrees) that the region is rotated with + respect to (xcntr,ycntr) + (Xcoord,Ycoord) are the (x,y) coordinates to test, usually column + names + NOTE: each parameter can itself be an expression, not merely a + column name or constant. +\end{verbatim} + + +\subsection{Example Row Filters} + +\begin{verbatim} + [ binary && mag <= 5.0] - Extract all binary stars brighter + than fifth magnitude (note that + the initial space is necessary to + prevent it from being treated as a + binning specification) + + [#row >= 125 && #row <= 175] - Extract row numbers 125 through 175 + + [IMAGE[4,5] .gt. 100] - Extract all rows that have the + (4,5) component of the IMAGE column + greater than 100 + + [abs(sin(theta * #deg)) < 0.5] - Extract all rows having the + absolute value of the sine of theta + less than a half where the angles + are tabulated in degrees + + [SUM( SPEC > 3*BACKGRND )>=1] - Extract all rows containing a + spectrum, held in vector column + SPEC, with at least one value 3 + times greater than the background + level held in a keyword, BACKGRND + + [VCOL=={1,4,2}] - Extract all rows whose vector column + VCOL contains the 3-elements 1, 4, and + 2. + + [@rowFilter.txt] - Extract rows using the expression + contained within the text file + rowFilter.txt + + [gtifilter()] - Search the current file for a GTI + extension, filter the TIME + column in the current table, using + START/STOP times taken from + columns in the GTI extension + + [regfilter("pow.reg")] - Extract rows which have a coordinate + (as given in the X and Y columns) + within the spatial region specified + in the pow.reg region file. + + [regfilter("pow.reg", Xs, Ys)] - Same as above, except that the + Xs and Ys columns will be used to + determine the coordinate of each + row in the table. +\end{verbatim} + + +\section{ Binning or Histogramming Specification} + +The optional binning specifier is enclosed in square brackets and can +be distinguished from a general row filter specification by the fact +that it begins with the keyword 'bin' not immediately followed by an +equals sign. When binning is specified, a temporary N-dimensional FITS +primary array is created by computing the histogram of the values in +the specified columns of a FITS table extension. After the histogram +is computed the input FITS file containing the table is then closed and +the temporary FITS primary array is opened and passed to the +application program. Thus, the application program never sees the +original FITS table and only sees the image in the new temporary file +(which has no additional extensions). Obviously, the application +program must be expecting to open a FITS image and not a FITS table in +this case. + +The data type of the FITS histogram image may be specified by appending +'b' (for 8-bit byte), 'i' (for 16-bit integers), 'j' (for 32-bit +integer), 'r' (for 32-bit floating points), or 'd' (for 64-bit double +precision floating point) to the 'bin' keyword (e.g. '[binr X]' +creates a real floating point image). If the data type is not +explicitly specified then a 32-bit integer image will be created by +default, unless the weighting option is also specified in which case +the image will have a 32-bit floating point data type by default. + +The histogram image may have from 1 to 4 dimensions (axes), depending +on the number of columns that are specified. The general form of the +binning specification is: + +\begin{verbatim} + [bin{bijrd} Xcol=min:max:binsize, Ycol= ..., Zcol=..., Tcol=...; weight] +\end{verbatim} +in which up to 4 columns, each corresponding to an axis of the image, +are listed. The column names are case insensitive, and the column +number may be given instead of the name, preceded by a pound sign +(e.g., [bin \#4=1:512]). If the column name is not specified, then +CFITSIO will first try to use the 'preferred column' as specified by +the CPREF keyword if it exists (e.g., 'CPREF = 'DETX,DETY'), otherwise +column names 'X', 'Y', 'Z', and 'T' will be assumed for each of the 4 +axes, respectively. In cases where the column name could be confused +with an arithmetic expression, enclose the column name in parentheses to +force the name to be interpreted literally. + +Each column name may be followed by an equals sign and then the lower +and upper range of the histogram, and the size of the histogram bins, +separated by colons. Spaces are allowed before and after the equals +sign but not within the 'min:max:binsize' string. The min, max and +binsize values may be integer or floating point numbers, or they may be +the names of keywords in the header of the table. If the latter, then +the value of that keyword is substituted into the expression. + +Default values for the min, max and binsize quantities will be +used if not explicitly given in the binning expression as shown +in these examples: + +\begin{verbatim} + [bin x = :512:2] - use default minimum value + [bin x = 1::2] - use default maximum value + [bin x = 1:512] - use default bin size + [bin x = 1:] - use default maximum value and bin size + [bin x = :512] - use default minimum value and bin size + [bin x = 2] - use default minimum and maximum values + [bin x] - use default minimum, maximum and bin size + [bin 4] - default 2-D image, bin size = 4 in both axes + [bin] - default 2-D image +\end{verbatim} +CFITSIO will use the value of the TLMINn, TLMAXn, and TDBINn keywords, +if they exist, for the default min, max, and binsize, respectively. If +they do not exist then CFITSIO will use the actual minimum and maximum +values in the column for the histogram min and max values. The default +binsize will be set to 1, or (max - min) / 10., whichever is smaller, +so that the histogram will have at least 10 bins along each axis. + +A shortcut notation is allowed if all the columns/axes have the same +binning specification. In this case all the column names may be listed +within parentheses, followed by the (single) binning specification, as +in: + +\begin{verbatim} + [bin (X,Y)=1:512:2] + [bin (X,Y) = 5] +\end{verbatim} + +The optional weighting factor is the last item in the binning specifier +and, if present, is separated from the list of columns by a +semi-colon. As the histogram is accumulated, this weight is used to +incremented the value of the appropriated bin in the histogram. If the +weighting factor is not specified, then the default weight = 1 is +assumed. The weighting factor may be a constant integer or floating +point number, or the name of a keyword containing the weighting value. +Or the weighting factor may be the name of a table column in which case +the value in that column, on a row by row basis, will be used. + +In some cases, the column or keyword may give the reciprocal of the +actual weight value that is needed. In this case, precede the weight +keyword or column name by a slash '/' to tell CFITSIO to use the +reciprocal of the value when constructing the histogram. + +For complex or commonly used histograms, one can also place its +description into a text file and import it into the binning +specification using the syntax [bin @filename.txt]. The file's +contents can extend over multiple lines, although it must still +conform to the no-spaces rule for the min:max:binsize syntax and each +axis specification must still be comma-separated. Any lines in the +external text file that begin with 2 slash characters ('//') will be +ignored and may be used to add comments into the file. + + Examples: + + +\begin{verbatim} + [bini detx, dety] - 2-D, 16-bit integer histogram + of DETX and DETY columns, using + default values for the histogram + range and binsize + + [bin (detx, dety)=16; /exposure] - 2-D, 32-bit real histogram of DETX + and DETY columns with a bin size = 16 + in both axes. The histogram values + are divided by the EXPOSURE keyword + value. + + [bin time=TSTART:TSTOP:0.1] - 1-D lightcurve, range determined by + the TSTART and TSTOP keywords, + with 0.1 unit size bins. + + [bin pha, time=8000.:8100.:0.1] - 2-D image using default binning + of the PHA column for the X axis, + and 1000 bins in the range + 8000. to 8100. for the Y axis. + + [bin @binFilter.txt] - Use the contents of the text file + binFilter.txt for the binning + specifications. + +\end{verbatim} +\chapter{Template Files } + +When a new FITS file is created with a call to fits\_create\_file, the +name of a template file may be supplied in parentheses immediately +following the name of the new file to be created. This template is +used to define the structure of one or more HDUs in the new file. The +template file may be another FITS file, in which case the newly created +file will have exactly the same keywords in each HDU as in the template +FITS file, but all the data units will be filled with zeros. The +template file may also be an ASCII text file, where each line (in +general) describes one FITS keyword record. The format of the ASCII +template file is described in the following sections. + + +\section{Detailed Template Line Format} + +The format of each ASCII template line closely follows the format of a +FITS keyword record: + +\begin{verbatim} + KEYWORD = KEYVALUE / COMMENT +\end{verbatim} +except that free format may be used (e.g., the equals sign may appear +at any position in the line) and TAB characters are allowed and are +treated the same as space characters. The KEYVALUE and COMMENT fields +are optional. The equals sign character is also optional, but it is +recommended that it be included for clarity. Any template line that +begins with the pound '\#' character is ignored by the template parser +and may be use to insert comments into the template file itself. + +The KEYWORD name field is limited to 8 characters in length and only +the letters A-Z, digits 0-9, and the hyphen and underscore characters +may be used, without any embedded spaces. Lowercase letters in the +template keyword name will be converted to uppercase. Leading spaces +in the template line preceding the keyword name are generally ignored, +except if the first 8 characters of a template line are all blank, then +the entire line is treated as a FITS comment keyword (with a blank +keyword name) and is copied verbatim into the FITS header. + +The KEYVALUE field may have any allowed FITS data type: character +string, logical, integer, real, complex integer, or complex real. The +character string values need not be enclosed in single quote characters +unless they are necessary to distinguish the string from a different +data type (e.g. 2.0 is a real but '2.0' is a string). The keyword has +an undefined (null) value if the template record only contains blanks +following the "=" or between the "=" and the "/" comment field +delimiter. + +String keyword values longer than 68 characters (the maximum length +that will fit in a single FITS keyword record) are permitted using the +CFITSIO long string convention. They can either be specified as a +single long line in the template, or by using multiple lines where the +continuing lines contain the 'CONTINUE' keyword, as in this example: + +\begin{verbatim} + LONGKEY = 'This is a long string value that is contin&' + CONTINUE 'ued over 2 records' / comment field goes here +\end{verbatim} +The format of template lines with CONTINUE keyword is very strict: 3 +spaces must follow CONTINUE and the rest of the line is copied verbatim +to the FITS file. + +The start of the optional COMMENT field must be preceded by "/", which +is used to separate it from the keyword value field. Exceptions are if +the KEYWORD name field contains COMMENT, HISTORY, CONTINUE, or if the +first 8 characters of the template line are blanks. + +More than one Header-Data Unit (HDU) may be defined in the template +file. The start of an HDU definition is denoted with a SIMPLE or +XTENSION template line: + +1) SIMPLE begins a Primary HDU definition. SIMPLE may only appear as +the first keyword in the template file. If the template file begins +with XTENSION instead of SIMPLE, then a default empty Primary HDU is +created, and the template is then assumed to define the keywords +starting with the first extension following the Primary HDU. + +2) XTENSION marks the beginning of a new extension HDU definition. The +previous HDU will be closed at this point and processing of the next +extension begins. + + +\section{Auto-indexing of Keywords} + +If a template keyword name ends with a "\#" character, it is said to be +'auto-indexed'. Each "\#" character will be replaced by the current +integer index value, which gets reset = 1 at the start of each new HDU +in the file (or 7 in the special case of a GROUP definition). The +FIRST indexed keyword in each template HDU definition is used as the +'incrementor'; each subsequent occurrence of this SAME keyword will +cause the index value to be incremented. This behavior can be rather +subtle, as illustrated in the following examples in which the TTYPE +keyword is the incrementor in both cases: + +\begin{verbatim} + TTYPE# = TIME + TFORM# = 1D + TTYPE# = RATE + TFORM# = 1E +\end{verbatim} +will create TTYPE1, TFORM1, TTYPE2, and TFORM2 keywords. But if the +template looks like, + +\begin{verbatim} + TTYPE# = TIME + TTYPE# = RATE + TFORM# = 1D + TFORM# = 1E +\end{verbatim} +this results in a FITS files with TTYPE1, TTYPE2, TFORM2, and TFORM2, +which is probably not what was intended! + + +\section{Template Parser Directives} + +In addition to the template lines which define individual keywords, the +template parser recognizes 3 special directives which are each preceded +by the backslash character: \verb+ \include, \group+, and \verb+ \end+. + +The 'include' directive must be followed by a filename. It forces the +parser to temporarily stop reading the current template file and begin +reading the include file. Once the parser reaches the end of the +include file it continues parsing the current template file. Include +files can be nested, and HDU definitions can span multiple template +files. + +The start of a GROUP definition is denoted with the 'group' directive, +and the end of a GROUP definition is denoted with the 'end' directive. +Each GROUP contains 0 or more member blocks (HDUs or GROUPs). Member +blocks of type GROUP can contain their own member blocks. The GROUP +definition itself occupies one FITS file HDU of special type (GROUP +HDU), so if a template specifies 1 group with 1 member HDU like: + +\begin{verbatim} +\group +grpdescr = 'demo' +xtension bintable +# this bintable has 0 cols, 0 rows +\end +\end{verbatim} +then the parser creates a FITS file with 3 HDUs : + +\begin{verbatim} +1) dummy PHDU +2) GROUP HDU (has 1 member, which is bintable in HDU number 3) +3) bintable (member of GROUP in HDU number 2) +\end{verbatim} +Technically speaking, the GROUP HDU is a BINTABLE with 6 columns. Applications +can define additional columns in a GROUP HDU using TFORMn and TTYPEn +(where n is 7, 8, ....) keywords or their auto-indexing equivalents. + +For a more complicated example of a template file using the group directives, +look at the sample.tpl file that is included in the CFITSIO distribution. + + +\section{Formal Template Syntax} + +The template syntax can formally be defined as follows: + +\begin{verbatim} + TEMPLATE = BLOCK [ BLOCK ... ] + + BLOCK = { HDU | GROUP } + + GROUP = \GROUP [ BLOCK ... ] \END + + HDU = XTENSION [ LINE ... ] { XTENSION | \GROUP | \END | EOF } + + LINE = [ KEYWORD [ = ] ] [ VALUE ] [ / COMMENT ] + + X ... - X can be present 1 or more times + { X | Y } - X or Y + [ X ] - X is optional +\end{verbatim} + +At the topmost level, the template defines 1 or more template blocks. Blocks +can be either HDU (Header Data Unit) or a GROUP. For each block the parser +creates 1 (or more for GROUPs) FITS file HDUs. + + + +\section{Errors} + +In general the fits\_execute\_template() function tries to be as atomic +as possible, so either everything is done or nothing is done. If an +error occurs during parsing of the template, fits\_execute\_template() +will (try to) delete the top level BLOCK (with all its children if any) +in which the error occurred, then it will stop reading the template file +and it will return with an error. + + +\section{Examples} + +1. This template file will create a 200 x 300 pixel image, with 4-byte +integer pixel values, in the primary HDU: + +\begin{verbatim} + SIMPLE = T + BITPIX = 32 + NAXIS = 2 / number of dimensions + NAXIS1 = 100 / length of first axis + NAXIS2 = 200 / length of second axis + OBJECT = NGC 253 / name of observed object +\end{verbatim} +The allowed values of BITPIX are 8, 16, 32, -32, or -64, +representing, respectively, 8-bit integer, 16-bit integer, 32-bit +integer, 32-bit floating point, or 64 bit floating point pixels. + +2. To create a FITS table, the template first needs to include +XTENSION = TABLE or BINTABLE to define whether it is an ASCII or binary +table, and NAXIS2 to define the number of rows in the table. Two +template lines are then needed to define the name (TTYPEn) and FITS data +format (TFORMn) of the columns, as in this example: + +\begin{verbatim} + xtension = bintable + naxis2 = 40 + ttype# = Name + tform# = 10a + ttype# = Npoints + tform# = j + ttype# = Rate + tunit# = counts/s + tform# = e +\end{verbatim} +The above example defines a null primary array followed by a 40-row +binary table extension with 3 columns called 'Name', 'Npoints', and +'Rate', with data formats of '10A' (ASCII character string), '1J' +(integer) and '1E' (floating point), respectively. Note that the other +required FITS keywords (BITPIX, NAXIS, NAXIS1, PCOUNT, GCOUNT, TFIELDS, +and END) do not need to be explicitly defined in the template because +their values can be inferred from the other keywords in the template. +This example also illustrates that the templates are generally +case-insensitive (the keyword names and TFORMn values are converted to +upper-case in the FITS file) and that string keyword values generally +do not need to be enclosed in quotes. + +\chapter{ Local FITS Conventions } + +CFITSIO supports several local FITS conventions which are not +defined in the official NOST FITS standard and which are not +necessarily recognized or supported by other FITS software packages. +Programmers should be cautious about using these features, especially +if the FITS files that are produced are expected to be processed by +other software systems which do not use the CFITSIO interface. + + +\section{64-Bit Long Integers} + +CFITSIO supports reading and writing FITS images or table columns containing +64-bit integer data values. Support for 64-bit integers was added to the +official FITS Standard in December 2005. + FITS 64-bit images have BITPIX = +64, and the 64-bit binary table columns have TFORMn = 'K'. CFITSIO also +supports the 'Q' variable-length array table column format which is +analogous to the 'P' column format except that the array descriptor +is stored as a pair of 64-bit integers. + +For the convenience of C programmers, the fitsio.h include file +defines (with a typedef statement) the 'LONGLONG' datatype to be +equivalent to an appropriate 64-bit integer datatype on each platform. +Since there is currently no universal standard +for the name of the 64-bit integer datatype (it might be defined as +'long long', 'long', or '\_\_int64' depending on the platform) +C programmers may prefer to use the 'LONGLONG' datatype when +declaring or allocating 64-bit integer quantities when writing +code which needs to run on multiple platforms. +Note that CFITSIO will implicitly convert the datatype when reading +or writing FITS 64-bit integer images and columns with data arrays of +a different integer or floating point datatype, but there is an +increased risk of loss of numerical precision or +numerical overflow in this case. + + +\section{Long String Keyword Values.} + +The length of a standard FITS string keyword is limited to 68 +characters because it must fit entirely within a single FITS header +keyword record. In some instances it is necessary to encode strings +longer than this limit, so CFITSIO supports a local convention in which +the string value is continued over multiple keywords. This +continuation convention uses an ampersand character at the end of each +substring to indicate that it is continued on the next keyword, and the +continuation keywords all have the name CONTINUE without an equal sign +in column 9. The string value may be continued in this way over as many +additional CONTINUE keywords as is required. The following lines +illustrate this continuation convention which is used in the value of +the STRKEY keyword: + +\begin{verbatim} +LONGSTRN= 'OGIP 1.0' / The OGIP Long String Convention may be used. +STRKEY = 'This is a very long string keyword&' / Optional Comment +CONTINUE ' value that is continued over 3 keywords in the & ' +CONTINUE 'FITS header.' / This is another optional comment. +\end{verbatim} +It is recommended that the LONGSTRN keyword, as shown here, always be +included in any HDU that uses this longstring convention as a warning +to any software that must read the keywords. A routine called fits\_write\_key\_longwarn +has been provided in CFITSIO to write this keyword if it does not +already exist. + +This long string convention is supported by the following CFITSIO +routines: + +\begin{verbatim} + fits_write_key_longstr - write a long string keyword value + fits_insert_key_longstr - insert a long string keyword value + fits_modify_key_longstr - modify a long string keyword value + fits_update_key_longstr - modify a long string keyword value + fits_read_key_longstr - read a long string keyword value + fits_delete_key - delete a keyword +\end{verbatim} +The fits\_read\_key\_longstr routine is unique among all the CFITSIO +routines in that it internally allocates memory for the long string +value; all the other CFITSIO routines that deal with arrays require +that the calling program pre-allocate adequate space to hold the array +of data. Consequently, programs which use the fits\_read\_key\_longstr +routine must be careful to free the allocated memory for the string +when it is no longer needed. + +The following 2 routines also have limited support for this long string +convention, + +\begin{verbatim} + fits_modify_key_str - modify an existing string keyword value + fits_update_key_str - update a string keyword value +\end{verbatim} +in that they will correctly overwrite an existing long string value, +but the new string value is limited to a maximum of 68 characters in +length. + +The more commonly used CFITSIO routines to write string valued keywords +(fits\_update\_key and fits\_write\_key) do not support this long +string convention and only support strings up to 68 characters in +length. This has been done deliberately to prevent programs from +inadvertently writing keywords using this non-standard convention +without the explicit intent of the programmer or user. The +fits\_write\_key\_longstr routine must be called instead to write long +strings. This routine can also be used to write ordinary string values +less than 68 characters in length. + + +\section{Arrays of Fixed-Length Strings in Binary Tables} + +CFITSIO supports 2 ways to specify that a character column in a binary +table contains an array of fixed-length strings. The first way, which +is officially supported by the FITS Standard document, uses the TDIMn keyword. +For example, if TFORMn = '60A' and TDIMn = '(12,5)' then that +column will be interpreted as containing an array of 5 strings, each 12 +characters long. + +CFITSIO also supports a +local convention for the format of the TFORMn keyword value of the form +'rAw' where 'r' is an integer specifying the total width in characters +of the column, and 'w' is an integer specifying the (fixed) length of +an individual unit string within the vector. For example, TFORM1 = +'120A10' would indicate that the binary table column is 120 characters +wide and consists of 12 10-character length strings. This convention +is recognized by the CFITSIO routines that read or write strings in +binary tables. The Binary Table definition document specifies that +other optional characters may follow the data type code in the TFORM +keyword, so this local convention is in compliance with the +FITS standard although other FITS readers may not +recognize this convention. + +The Binary Table definition document that was approved by the IAU in +1994 contains an appendix describing an alternate convention for +specifying arrays of fixed or variable length strings in a binary table +character column (with the form 'rA:SSTRw/nnn)'. This appendix was not +officially voted on by the IAU and hence is still provisional. CFITSIO +does not currently support this proposal. + + +\section{Keyword Units Strings} + +One limitation of the current FITS Standard is that it does not define +a specific convention for recording the physical units of a keyword +value. The TUNITn keyword can be used to specify the physical units of +the values in a table column, but there is no analogous convention for +keyword values. The comment field of the keyword is often used for +this purpose, but the units are usually not specified in a well defined +format that FITS readers can easily recognize and extract. + +To solve this problem, CFITSIO uses a local convention in which the +keyword units are enclosed in square brackets as the first token in the +keyword comment field; more specifically, the opening square bracket +immediately follows the slash '/' comment field delimiter and a single +space character. The following examples illustrate keywords that use +this convention: + + +\begin{verbatim} +EXPOSURE= 1800.0 / [s] elapsed exposure time +V_HELIO = 16.23 / [km s**(-1)] heliocentric velocity +LAMBDA = 5400. / [angstrom] central wavelength +FLUX = 4.9033487787637465E-30 / [J/cm**2/s] average flux +\end{verbatim} + +In general, the units named in the IAU(1988) Style Guide are +recommended, with the main exception that the preferred unit for angle +is 'deg' for degrees. + +The fits\_read\_key\_unit and fits\_write\_key\_unit routines in +CFITSIO read and write, respectively, the keyword unit strings in an +existing keyword. + + +\section{HIERARCH Convention for Extended Keyword Names} + +CFITSIO supports the HIERARCH keyword convention which allows keyword +names that are longer then 8 characters and may contain the full range +of printable ASCII text characters. This convention +was developed at the European Southern Observatory (ESO) to support +hierarchical FITS keyword such as: + +\begin{verbatim} +HIERARCH ESO INS FOCU POS = -0.00002500 / Focus position +\end{verbatim} +Basically, this convention uses the FITS keyword 'HIERARCH' to indicate +that this convention is being used, then the actual keyword name +({\tt'ESO INS FOCU POS'} in this example) begins in column 10 and can +contain any printable ASCII text characters, including spaces. The +equals sign marks the end of the keyword name and is followed by the +usual value and comment fields just as in standard FITS keywords. +Further details of this convention are described at +http://arcdev.hq.eso.org/dicb/dicd/dic-1-1.4.html (search for +HIERARCH). + +This convention allows a much broader range of keyword names +than is allowed by the FITS Standard. Here are more examples +of such keywords: + +\begin{verbatim} +HIERARCH LongKeyword = 47.5 / Keyword has > 8 characters, and mixed case +HIERARCH XTE$TEMP = 98.6 / Keyword contains the '$' character +HIERARCH Earth is a star = F / Keyword contains embedded spaces +\end{verbatim} +CFITSIO will transparently read and write these keywords, so application +programs do not in general need to know anything about the specific +implementation details of the HIERARCH convention. In particular, +application programs do not need to specify the `HIERARCH' part of the +keyword name when reading or writing keywords (although it +may be included if desired). When writing a keyword, CFITSIO first +checks to see if the keyword name is legal as a standard FITS keyword +(no more than 8 characters long and containing only letters, digits, or +a minus sign or underscore). If so it writes it as a standard FITS +keyword, otherwise it uses the hierarch convention to write the +keyword. The maximum keyword name length is 67 characters, which +leaves only 1 space for the value field. A more practical limit is +about 40 characters, which leaves enough room for most keyword values. +CFITSIO returns an error if there is not enough room for both the +keyword name and the keyword value on the 80-character card, except for +string-valued keywords which are simply truncated so that the closing +quote character falls in column 80. In the current implementation, +CFITSIO preserves the case of the letters when writing the keyword +name, but it is case-insensitive when reading or searching for a +keyword. The current implementation allows any ASCII text character +(ASCII 32 to ASCII 126) in the keyword name except for the '=' +character. A space is also required on either side of the equal sign. + + +\section{Tile-Compressed Image Format} + +CFITSIO supports a convention for compressing n-dimensional images and +storing the resulting byte stream in a variable-length column in a FITS +binary table. The general principle used in this convention is to +first divide the n-dimensional image into a rectangular grid of +subimages or `tiles'. Each tile is then compressed as a continuous +block of data, and the resulting compressed byte stream is stored in a +row of a variable length column in a FITS binary table. By dividing the +image into tiles it is generally possible to extract and uncompress +subsections of the image without having to uncompress the whole image. +The default tiling pattern treats each row of a 2-dimensional image (or +higher dimensional cube) as a tile, such that each tile contains NAXIS1 +pixels (except the default with the HCOMPRESS algorithm is to +compress the whole 2D image as a single tile). Any other rectangular +tiling pattern may also be defined. In +the case of relatively small images it may be sufficient to compress +the entire image as a single tile, resulting in an output binary table +with 1 row. In the case of 3-dimensional data cubes, it may be +advantageous to treat each plane of the cube as a separate tile if +application software typically needs to access the cube on a plane by +plane basis. + +See section 5.6 ``Image Compression'' +for more information on using this tile-compressed image format. + +\chapter{ Optimizing Programs } + +CFITSIO has been carefully designed to obtain the highest possible +speed when reading and writing FITS files. In order to achieve the +best performance, however, application programmers must be careful to +call the CFITSIO routines appropriately and in an efficient sequence; +inappropriate usage of CFITSIO routines can greatly slow down the +execution speed of a program. + +The maximum possible I/O speed of CFITSIO depends of course on the type +of computer system that it is running on. As a rough guide, the +current generation of workstations can achieve speeds of 2 -- 10 MB/s +when reading or writing FITS images and similar, or slightly slower +speeds with FITS binary tables. Reading of FITS files can occur at +even higher rates (30MB/s or more) if the FITS file is still cached in +system memory following a previous read or write operation on the same +file. To more accurately predict the best performance that is possible +on any particular system, a diagnostic program called ``speed.c'' is +included with the CFITSIO distribution which can be run to +approximately measure the maximum possible speed of writing and reading +a test FITS file. + +The following 2 sections provide some background on how CFITSIO +internally manages the data I/O and describes some strategies that may +be used to optimize the processing speed of software that uses +CFITSIO. + + +\section{How CFITSIO Manages Data I/O} + +Many CFITSIO operations involve transferring only a small number of +bytes to or from the FITS file (e.g, reading a keyword, or writing a +row in a table); it would be very inefficient to physically read or +write such small blocks of data directly in the FITS file on disk, +therefore CFITSIO maintains a set of internal Input--Output (IO) +buffers in RAM memory that each contain one FITS block (2880 bytes) of +data. Whenever CFITSIO needs to access data in the FITS file, it first +transfers the FITS block containing those bytes into one of the IO +buffers in memory. The next time CFITSIO needs to access bytes in the +same block it can then go to the fast IO buffer rather than using a +much slower system disk access routine. The number of available IO +buffers is determined by the NIOBUF parameter (in fitsio2.h) and is +currently set to 40 by default. + +Whenever CFITSIO reads or writes data it first checks to see if that +block of the FITS file is already loaded into one of the IO buffers. +If not, and if there is an empty IO buffer available, then it will load +that block into the IO buffer (when reading a FITS file) or will +initialize a new block (when writing to a FITS file). If all the IO +buffers are already full, it must decide which one to reuse (generally +the one that has been accessed least recently), and flush the contents +back to disk if it has been modified before loading the new block. + +The one major exception to the above process occurs whenever a large +contiguous set of bytes are accessed, as might occur when reading or +writing a FITS image. In this case CFITSIO bypasses the internal IO +buffers and simply reads or writes the desired bytes directly in the +disk file with a single call to a low-level file read or write +routine. The minimum threshold for the number of bytes to read or +write this way is set by the MINDIRECT parameter and is currently set +to 3 FITS blocks = 8640 bytes. This is the most efficient way to read +or write large chunks of data and can achieve IO transfer rates of +5 -- 10MB/s or greater. Note that this fast direct IO process is not +applicable when accessing columns of data in a FITS table because the +bytes are generally not contiguous since they are interleaved by the +other columns of data in the table. This explains why the speed for +accessing FITS tables is generally slower than accessing +FITS images. + +Given this background information, the general strategy for efficiently +accessing FITS files should be apparent: when dealing with FITS +images, read or write large chunks of data at a time so that the direct +IO mechanism will be invoked; when accessing FITS headers or FITS +tables, on the other hand, once a particular FITS block has been +loading into one of the IO buffers, try to access all the needed +information in that block before it gets flushed out of the IO buffer. +It is important to avoid the situation where the same FITS block is +being read then flushed from a IO buffer multiple times. + +The following section gives more specific suggestions for optimizing +the use of CFITSIO. + + +\section{Optimization Strategies} + +1. Because the data in FITS files is always stored in "big-endian" byte order, +where the first byte of numeric values contains the most significant bits and the +last byte contains the least significant bits, CFITSIO must swap the order of the bytes +when reading or writing FITS files when running on little-endian machines (e.g., +Linux and Microsoft Windows operating systems running on PCs with x86 CPUs). + +On fairly new CPUs that support "SSSE3" machine instructions +(e.g., starting with Intel Core 2 CPUs in 2007, and in AMD CPUs +beginning in 2011) significantly faster 4-byte and 8-byte swapping +algorithms are available. These faster byte swapping functions are +not used by default in CFITSIO (because of the potential code +portablility issues), but users can enable them on supported +platforms by adding the appropriate compiler flags (-mssse3 with gcc +or icc on linux) when compiling the swapproc.c source file, which will +allow the compiler to generate code using the SSSE3 instruction set. +A convenient way to do this is to configure the CFITSIO library +with the following command: + +\begin{verbatim} + > ./configure --enable-ssse3 +\end{verbatim} +Note, however, that a binary executable file that is +created using these faster functions will only run on +machines that support the SSSE3 machine instructions. It will +crash on machines that do not support them. + +For faster 2-byte swaps on virtually all x86-64 CPUs (even those that +do not support SSSE3), a variant using only SSE2 instructions exists. +SSE2 is enabled by default on x86\_64 CPUs with 64-bit operating systems +(and is also automatically enabled by the --enable-ssse3 flag). +When running on x86\_64 CPUs with 32-bit operating systems, these faster +2-byte swapping algorithms are not used by default in CFITSIO, but can be +enabled explicitly with: + +\begin{verbatim} +./configure --enable-sse2 +\end{verbatim} +Preliminary testing indicates that these SSSE3 and SSE2 based +byte-swapping algorithms can boost the CFITSIO performance when +reading or writing FITS images by 20\% - 30\% or more. +It is important to note, however, that compiler optimization must be +turned on (e.g., by using the -O1 or -O2 flags in gcc) when building +programs that use these fast byte-swapping algorithms in order +to reap the full benefit of the SSSE3 and SSE2 instructions; without +optimization, the code may actually run slower than when using +more traditional byte-swapping techniques. + +2. When dealing with a FITS primary array or IMAGE extension, it is +more efficient to read or write large chunks of the image at a time +(at least 3 FITS blocks = 8640 bytes) so that the direct IO mechanism +will be used as described in the previous section. Smaller chunks of +data are read or written via the IO buffers, which is somewhat less +efficient because of the extra copy operation and additional +bookkeeping steps that are required. In principle it is more efficient +to read or write as big an array of image pixels at one time as +possible, however, if the array becomes so large that the operating +system cannot store it all in RAM, then the performance may be degraded +because of the increased swapping of virtual memory to disk. + +3. When dealing with FITS tables, the most important efficiency factor +in the software design is to read or write the data in the FITS file in +a single pass through the file. An example of poor program design +would be to read a large, 3-column table by sequentially reading the +entire first column, then going back to read the 2nd column, and +finally the 3rd column; this obviously requires 3 passes through the +file which could triple the execution time of an IO limited program. +For small tables this is not important, but when reading multi-megabyte +sized tables these inefficiencies can become significant. The more +efficient procedure in this case is to read or write only as many rows +of the table as will fit into the available internal IO buffers, then +access all the necessary columns of data within that range of rows. +Then after the program is completely finished with the data in those +rows it can move on to the next range of rows that will fit in the +buffers, continuing in this way until the entire file has been +processed. By using this procedure of accessing all the columns of a +table in parallel rather than sequentially, each block of the FITS file +will only be read or written once. + +The optimal number of rows to read or write at one time in a given +table depends on the width of the table row and on the number of IO +buffers that have been allocated in CFITSIO. The CFITSIO Iterator routine +will automatically use the optimal-sized buffer, but there is also a +CFITSIO routine that will return the optimal number of rows for a given +table: fits\_get\_rowsize. It is not critical to use exactly the +value of nrows returned by this routine, as long as one does not exceed +it. Using a very small value however can also lead to poor performance +because of the overhead from the larger number of subroutine calls. + +The optimal number of rows returned by fits\_get\_rowsize is valid only +as long as the application program is only reading or writing data in +the specified table. Any other calls to access data in the table +header would cause additional blocks of data +to be loaded into the IO buffers displacing data from the original +table, and should be avoided during the critical period while the table +is being read or written. + +4. Use the CFITSIO Iterator routine. This routine provides a +more `object oriented' way of reading and writing FITS files +which automatically uses the most appropriate data buffer size +to achieve the maximum I/O throughput. + +5. Use binary table extensions rather than ASCII table +extensions for better efficiency when dealing with tabular data. The +I/O to ASCII tables is slower because of the overhead in formatting or +parsing the ASCII data fields and because ASCII tables are about twice +as large as binary tables that have the same information content. + +6. Design software so that it reads the FITS header keywords in the +same order in which they occur in the file. When reading keywords, +CFITSIO searches forward starting from the position of the last keyword +that was read. If it reaches the end of the header without finding the +keyword, it then goes back to the start of the header and continues the +search down to the position where it started. In practice, as long as +the entire FITS header can fit at one time in the available internal IO +buffers, then the header keyword access will be relatively fast and it makes +little difference which order they are accessed. + +7. Avoid the use of scaling (by using the BSCALE and BZERO or TSCAL and +TZERO keywords) in FITS files since the scaling operations add to the +processing time needed to read or write the data. In some cases it may +be more efficient to temporarily turn off the scaling (using fits\_set\_bscale or +fits\_set\_tscale) and then read or write the raw unscaled values in the FITS +file. + +8. Avoid using the `implicit data type conversion' capability in +CFITSIO. For instance, when reading a FITS image with BITPIX = -32 +(32-bit floating point pixels), read the data into a single precision +floating point data array in the program. Forcing CFITSIO to convert +the data to a different data type can slow the program. + +9. Where feasible, design FITS binary tables using vector column +elements so that the data are written as a contiguous set of bytes, +rather than as single elements in multiple rows. For example, it is +faster to access the data in a table that contains a single row +and 2 columns with TFORM keywords equal to '10000E' and '10000J', than +it is to access the same amount of data in a table with 10000 rows +which has columns with the TFORM keywords equal to '1E' and '1J'. In +the former case the 10000 floating point values in the first column are +all written in a contiguous block of the file which can be read or +written quickly, whereas in the second case each floating point value +in the first column is interleaved with the integer value in the second +column of the same row so CFITSIO has to explicitly move to the +position of each element to be read or written. + +10. Avoid the use of variable length vector columns in binary tables, +since any reading or writing of these data requires that CFITSIO first +look up or compute the starting address of each row of data in the +heap. In practice, this is probably not a significant efficiency issue. + +11. When copying data from one FITS table to another, it is faster to +transfer the raw bytes instead of reading then writing each column of +the table. The CFITSIO routines fits\_read\_tblbytes and +fits\_write\_tblbytes will perform low-level reads or writes of any +contiguous range of bytes in a table extension. These routines can be +used to read or write a whole row (or multiple rows for even greater +efficiency) of a table with a single function call. These routines +are fast because they bypass all the usual data scaling, error checking +and machine dependent data conversion that is normally done by CFITSIO, +and they allow the program to write the data to the output file in +exactly the same byte order. For these same reasons, these routines +can corrupt the FITS data file if used incorrectly because no +validation or machine dependent conversion is performed by these +routines. These routines are only recommended for optimizing critical +pieces of code and should only be used by programmers who thoroughly +understand the internal format of the FITS tables they are reading or +writing. + +12. Another strategy for improving the speed of writing a FITS table, +similar to the previous one, is to directly construct the entire byte +stream for a whole table row (or multiple rows) within the application +program and then write it to the FITS file with +fits\_write\_tblbytes. This avoids all the overhead normally present +in the column-oriented CFITSIO write routines. This technique should +only be used for critical applications because it makes the code more +difficult to understand and maintain, and it makes the code more system +dependent (e.g., do the bytes need to be swapped before writing to the +FITS file?). + +13. Finally, external factors such as the speed of the data storage device, +the size of the data cache, the amount of disk fragmentation, and the amount of +RAM available on the system can all have a significant impact on +overall I/O efficiency. For critical applications, the entire hardware +and software system should be reviewed to identify any +potential I/O bottlenecks. + + +\appendix +\chapter{Index of Routines } +\begin{tabular}{lr} +fits\_add\_group\_member & \pageref{ffgtam} \\ +fits\_ascii\_tform & \pageref{ffasfm} \\ +fits\_binary\_tform & \pageref{ffbnfm} \\ +fits\_calculator & \pageref{ffcalc} \\ +fits\_calculator\_rng & \pageref{ffcalcrng} \\ +fits\_calc\_binning & \pageref{calcbinning} \\ +fits\_calc\_rows & \pageref{ffcrow} \\ +fits\_change\_group & \pageref{ffgtch} \\ +fits\_clear\_errmark & \pageref{ffpmrk} \\ +fits\_clear\_errmsg & \pageref{ffcmsg} \\ +fits\_close\_file & \pageref{ffclos} \\ +fits\_compact\_group & \pageref{ffgtcm} \\ +fits\_compare\_str & \pageref{ffcmps} \\ +fits\_compress\_heap & \pageref{ffcmph} \\ +fits\_convert\_hdr2str & \pageref{ffhdr2str}, \pageref{hdr2str} \\ +fits\_copy\_cell2image & \pageref{copycell} \\ +fits\_copy\_col & \pageref{ffcpcl} \\ +fits\_copy\_data & \pageref{ffcpdt} \\ +fits\_copy\_file & \pageref{ffcpfl} \\ +fits\_copy\_group & \pageref{ffgtcp} \\ +fits\_copy\_hdu & \pageref{ffcopy} \\ +fits\_copy\_header & \pageref{ffcphd} \\ +fits\_copy\_image2cell & \pageref{copycell} \\ +fits\_copy\_image\_section & \pageref{ffcpimg} \\ +fits\_copy\_key & \pageref{ffcpky} \\ +fits\_copy\_member & \pageref{ffgmcp} \\ +fits\_copy\_pixlist2image & \pageref{copypixlist2image} \\ +fits\_copy\_rows & \pageref{ffcprw} \\ +fits\_create\_diskfile & \pageref{ffinit} \\ +fits\_create\_file & \pageref{ffinit} \\ +fits\_create\_group & \pageref{ffgtcr} \\ +fits\_create\_hdu & \pageref{ffcrhd} \\ + +\end{tabular} +\begin{tabular}{lr} +fits\_create\_img & \pageref{ffcrim} \\ +fits\_create\_memfile & \pageref{ffimem} \\ +fits\_create\_tbl & \pageref{ffcrtb} \\ +fits\_create\_template & \pageref{fftplt} \\ +fits\_date2str & \pageref{ffdt2s} \\ +fits\_decode\_chksum & \pageref{ffdsum} \\ +fits\_decode\_tdim & \pageref{ffdtdm} \\ +fits\_delete\_col & \pageref{ffdcol} \\ +fits\_delete\_file & \pageref{ffdelt} \\ +fits\_delete\_hdu & \pageref{ffdhdu} \\ +fits\_delete\_key & \pageref{ffdkey} \\ +fits\_delete\_record & \pageref{ffdrec} \\ +fits\_delete\_rowlist & \pageref{ffdrws} \\ +fits\_delete\_rowrange & \pageref{ffdrrg} \\ +fits\_delete\_rows & \pageref{ffdrow} \\ +fits\_delete\_str & \pageref{ffdkey} \\ +fits\_encode\_chksum & \pageref{ffesum} \\ +fits\_file\_exists & \pageref{ffexist} \\ +fits\_file\_mode & \pageref{ffflmd} \\ +fits\_file\_name & \pageref{ffflnm} \\ +fits\_find\_first\_row & \pageref{ffffrw} \\ +fits\_find\_nextkey & \pageref{ffgnxk} \\ +fits\_find\_rows & \pageref{fffrow} \\ +fits\_flush\_buffer & \pageref{ffflus} \\ +fits\_flush\_file & \pageref{ffflus} \\ +fits\_free\_memory & \pageref{ffgkls} \\ +fits\_get\_acolparms & \pageref{ffgacl} \\ +fits\_get\_bcolparms & \pageref{ffgbcl} \\ +fits\_get\_chksum & \pageref{ffgcks} \\ +fits\_get\_col\_display\_width & \pageref{ffgcdw} \\ +fits\_get\_colname & \pageref{ffgcnn} \\ +fits\_get\_colnum & \pageref{ffgcno} \\ +\end{tabular} +\begin{tabular}{lr} +fits\_get\_coltype & \pageref{ffgtcl} \\ +fits\_get\_compression\_type & \pageref{ffgetcomp} \\ +fits\_get\_eqcoltype & \pageref{ffgtcl} \\ +fits\_get\_errstatus & \pageref{ffgerr} \\ +fits\_get\_hdrpos & \pageref{ffghps} \\ +fits\_get\_hdrspace & \pageref{ffghsp} \\ +fits\_get\_hdu\_num & \pageref{ffghdn} \\ +fits\_get\_hdu\_type & \pageref{ffghdt} \\ +fits\_get\_hduaddr & \pageref{ffghad} \\ +fits\_get\_hduaddrll & \pageref{ffghad} \\ +fits\_get\_img\_dim & \pageref{ffgidm} \\ +fits\_get\_img\_equivtype & \pageref{ffgidt} \\ +fits\_get\_img\_param & \pageref{ffgipr} \\ +fits\_get\_img\_size & \pageref{ffgisz} \\ +fits\_get\_img\_type & \pageref{ffgidt} \\ +fits\_get\_inttype & \pageref{ffinttyp} \\ +fits\_get\_keyclass & \pageref{ffgkcl} \\ +fits\_get\_keyname & \pageref{ffgknm} \\ +fits\_get\_keytype & \pageref{ffdtyp} \\ +fits\_get\_noise\_bits & \pageref{ffgetcomp} \\ +fits\_get\_num\_cols & \pageref{ffgnrw} \\ +fits\_get\_num\_groups & \pageref{ffgmng} \\ +fits\_get\_num\_hdus & \pageref{ffthdu} \\ +fits\_get\_num\_members & \pageref{ffgtnm} \\ +fits\_get\_num\_rows & \pageref{ffgnrw} \\ +fits\_get\_rowsize & \pageref{ffgrsz} \\ +fits\_get\_system\_time & \pageref{ffdt2s} \\ +fits\_get\_tile\_dim & \pageref{ffgetcomp} \\ +fits\_get\_tbcol & \pageref{ffgabc} \\ +fits\_get\_version & \pageref{ffvers} \\ +fits\_hdr2str & \pageref{ffhdr2str}, \pageref{hdr2str} \\ +fits\_insert\_atbl & \pageref{ffitab} \\ +\end{tabular} +\newpage +\begin{tabular}{lr} +fits\_insert\_btbl & \pageref{ffibin} \\ +fits\_insert\_col & \pageref{fficol} \\ +fits\_insert\_cols & \pageref{fficls} \\ +fits\_insert\_group & \pageref{ffgtis} \\ +fits\_insert\_img & \pageref{ffiimg} \\ +fits\_insert\_key\_null & \pageref{ffikyu} \\ +fits\_insert\_key\_TYP & \pageref{ffikyx} \\ +fits\_insert\_record & \pageref{ffirec} \\ +fits\_insert\_rows & \pageref{ffirow} \\ +fits\_is\_reentrant & \pageref{reentrant} \\ +fits\_iterate\_data & \pageref{ffiter} \\ +fits\_make\_hist & \pageref{makehist} \\ +fits\_make\_keyn & \pageref{ffkeyn} \\ +fits\_make\_nkey & \pageref{ffnkey} \\ +fits\_merge\_groups & \pageref{ffgtmg} \\ +fits\_modify\_card & \pageref{ffmcrd} \\ +fits\_modify\_comment & \pageref{ffmcom} \\ +fits\_modify\_key\_null & \pageref{ffmkyu} \\ +fits\_modify\_key\_TYP & \pageref{ffmkyx} \\ +fits\_modify\_name & \pageref{ffmnam} \\ +fits\_modify\_record & \pageref{ffmrec} \\ +fits\_modify\_vector\_len & \pageref{ffmvec} \\ +fits\_movabs\_hdu & \pageref{ffmahd} \\ +fits\_movnam\_hdu & \pageref{ffmnhd} \\ +fits\_movrel\_hdu & \pageref{ffmrhd} \\ +fits\_null\_check & \pageref{ffnchk} \\ +fits\_open\_data & \pageref{ffopen} \\ +fits\_open\_diskfile & \pageref{ffopen} \\ +fits\_open\_file & \pageref{ffopen} \\ +fits\_open\_image & \pageref{ffopen} \\ +fits\_open\_table & \pageref{ffopen} \\ +fits\_open\_group & \pageref{ffgtop} \\ +fits\_open\_member & \pageref{ffgmop} \\ +fits\_open\_memfile & \pageref{ffomem} \\ +fits\_parse\_extnum & \pageref{ffextn} \\ +fits\_parse\_input\_filename & \pageref{ffiurl} \\ +fits\_parse\_input\_url & \pageref{ffiurl} \\ +fits\_parse\_range & \pageref{ffrwrg} \\ +fits\_parse\_rootname & \pageref{ffrtnm} \\ +fits\_parse\_template & \pageref{ffgthd} \\ +fits\_parse\_value & \pageref{ffpsvc} \\ +fits\_pix\_to\_world & \pageref{ffwldp} \\ +fits\_read\_2d\_TYP & \pageref{ffg2dx} \\ +fits\_read\_3d\_TYP & \pageref{ffg3dx} \\ +fits\_read\_atblhdr & \pageref{ffghtb} \\ +fits\_read\_btblhdr & \pageref{ffghbn} \\ +fits\_read\_card & \pageref{ffgcrd} \\ +fits\_read\_col & \pageref{ffgcv} \\ +\end{tabular} +\begin{tabular}{lr} +fits\_read\_col\_bit\_ & \pageref{ffgcx} \\ +fits\_read\_col\_TYP & \pageref{ffgcvx} \\ +fits\_read\_colnull & \pageref{ffgcf} \\ +fits\_read\_colnull\_TYP & \pageref{ffgcfx} \\ +fits\_read\_descript & \pageref{ffgdes} \\ +fits\_read\_descripts & \pageref{ffgdes} \\ +fits\_read\_errmsg & \pageref{ffgmsg} \\ +fits\_read\_ext & \pageref{ffgextn} \\ +fits\_read\_grppar\_TYP & \pageref{ffggpx} \\ +fits\_read\_img & \pageref{ffgpv} \\ +fits\_read\_img\_coord & \pageref{ffgics} \\ +fits\_read\_img\_TYP & \pageref{ffgpvx} \\ +fits\_read\_imghdr & \pageref{ffghpr} \\ +fits\_read\_imgnull & \pageref{ffgpf} \\ +fits\_read\_imgnull\_TYP & \pageref{ffgpfx} \\ +fits\_read\_key & \pageref{ffgky} \\ +fits\_read\_key\_longstr & \pageref{ffgkls} \\ +fits\_read\_key\_triple & \pageref{ffgkyt} \\ +fits\_read\_key\_unit & \pageref{ffgunt} \\ +fits\_read\_key\_TYP & \pageref{ffgkyx} \\ +fits\_read\_keyn & \pageref{ffgkyn} \\ +fits\_read\_keys\_TYP & \pageref{ffgknx} \\ +fits\_read\_keyword & \pageref{ffgkey} \\ +fits\_read\_pix & \pageref{ffgpxv} \\ +fits\_read\_pixnull & \pageref{ffgpxf} \\ +fits\_read\_record & \pageref{ffgrec} \\ +fits\_read\_str & \pageref{ffgcrd} \\ +fits\_read\_subset\_TYP & \pageref{ffgsvx} \pageref{ffgsvx2}\\ +fits\_read\_subsetnull\_TYP & \pageref{ffgsfx} \pageref{ffgsfx2} \\ +fits\_read\_tbl\_coord & \pageref{ffgtcs} \\ +fits\_read\_tblbytes & \pageref{ffgtbb} \\ +fits\_read\_tdim & \pageref{ffgtdm} \\ +fits\_read\_wcstab & \pageref{wcstab} \\ +fits\_rebin\_wcs & \pageref{rebinwcs} \\ +fits\_remove\_group & \pageref{ffgtrm} \\ +fits\_remove\_member & \pageref{ffgmrm} \\ +fits\_reopen\_file & \pageref{ffreopen} \\ +fits\_report\_error & \pageref{ffrprt} \\ +fits\_resize\_img & \pageref{ffrsim} \\ +fits\_rms\_float & \pageref{imageRMS} \\ +fits\_rms\_short & \pageref{imageRMS} \\ +fits\_select\_rows & \pageref{ffsrow} \\ +fits\_set\_atblnull & \pageref{ffsnul} \\ +fits\_set\_bscale & \pageref{ffpscl} \\ +fits\_set\_btblnull & \pageref{fftnul} \\ +fits\_set\_compression\_type & \pageref{ffsetcomp} \\ +fits\_set\_hdrsize & \pageref{ffhdef} \\ +fits\_set\_hdustruc & \pageref{ffrdef} \\ +\end{tabular} +\begin{tabular}{lr} +fits\_set\_imgnull & \pageref{ffpnul} \\ +fits\_set\_noise\_bits & \pageref{ffsetcomp} \\ +fits\_set\_tile\_dim & \pageref{ffsetcomp} \\ +fits\_set\_tscale & \pageref{fftscl} \\ +fits\_split\_names & \pageref{splitnames} \\ +fits\_str2date & \pageref{ffdt2s} \\ +fits\_str2time & \pageref{ffdt2s} \\ +fits\_test\_expr & \pageref{fftexp} \\ +fits\_test\_heap & \pageref{fftheap} \\ +fits\_test\_keyword & \pageref{fftkey} \\ +fits\_test\_record & \pageref{fftrec} \\ +fits\_time2str & \pageref{ffdt2s} \\ +fits\_transfer\_member & \pageref{ffgmtf} \\ +fits\_translate\_keyword & \pageref{translatekey} \\ +fits\_update\_card & \pageref{ffucrd} \\ +fits\_update\_chksum & \pageref{ffupck} \\ +fits\_update\_key & \pageref{ffuky} \\ +fits\_update\_key\_longstr & \pageref{ffukyx} \\ +fits\_update\_key\_null & \pageref{ffukyu} \\ +fits\_update\_key\_TYP & \pageref{ffukyx} \\ +fits\_uppercase & \pageref{ffupch} \\ +fits\_url\_type & \pageref{ffurlt} \\ +fits\_verify\_chksum & \pageref{ffvcks} \\ +fits\_verify\_group & \pageref{ffgtvf} \\ +fits\_world\_to\_pix & \pageref{ffxypx} \\ +fits\_write\_2d\_TYP & \pageref{ffp2dx} \\ +fits\_write\_3d\_TYP & \pageref{ffp3dx} \\ +fits\_write\_atblhdr & \pageref{ffphtb} \\ +fits\_write\_btblhdr & \pageref{ffphbn} \\ +fits\_write\_chksum & \pageref{ffpcks} \\ +fits\_write\_col & \pageref{ffpcl} \\ +fits\_write\_col\_bit & \pageref{ffpclx} \\ +fits\_write\_col\_TYP & \pageref{ffpcls} \\ +fits\_write\_col\_null & \pageref{ffpclu} \\ +fits\_write\_colnull & \pageref{ffpcn} \\ +fits\_write\_colnull\_TYP & \pageref{ffpcnx} \\ +fits\_write\_comment & \pageref{ffpcom} \\ +fits\_write\_date & \pageref{ffpdat} \\ +fits\_write\_descript & \pageref{ffpdes} \\ +fits\_write\_errmark & \pageref{ffpmrk} \\ +fits\_write\_errmsg & \pageref{ffpmsg} \\ +fits\_write\_ext & \pageref{ffgextn} \\ +fits\_write\_exthdr & \pageref{ffphps} \\ +fits\_write\_grphdr & \pageref{ffphpr} \\ +fits\_write\_grppar\_TYP & \pageref{ffpgpx} \\ +fits\_write\_hdu & \pageref{ffwrhdu} \\ +fits\_write\_history & \pageref{ffphis} \\ +fits\_write\_img & \pageref{ffppr} \\ +\end{tabular} +\newpage +\begin{tabular}{lr} +fits\_write\_img\_null & \pageref{ffppru} \\ +fits\_write\_img\_TYP & \pageref{ffpprx} \\ +fits\_write\_imghdr & \pageref{ffphps} \\ +fits\_write\_imgnull & \pageref{ffppn} \\ +fits\_write\_imgnull\_TYP & \pageref{ffppnx} \\ +fits\_write\_key & \pageref{ffpky} \\ +fits\_write\_key\_longstr & \pageref{ffpkls} \\ +fits\_write\_key\_longwarn & \pageref{ffplsw} \\ +fits\_write\_key\_null & \pageref{ffpkyu} \\ +fits\_write\_key\_template & \pageref{ffpktp} \\ +fits\_write\_key\_triple & \pageref{ffpkyt} \\ +fits\_write\_key\_unit & \pageref{ffpunt} \\ +fits\_write\_key\_TYP & \pageref{ffpkyx} \\ +fits\_write\_keys\_TYP & \pageref{ffpknx} \\ +fits\_write\_keys\_histo & \pageref{writekeyshisto} \\ +fits\_write\_null\_img & \pageref{ffpprn} \\ +fits\_write\_nullrows & \pageref{ffpclu} \\ +fits\_write\_pix & \pageref{ffppx} \\ +fits\_write\_pixnull & \pageref{ffppxn} \\ +fits\_write\_record & \pageref{ffprec} \\ +fits\_write\_subset & \pageref{ffpss} \\ +fits\_write\_subset\_TYP & \pageref{ffpssx} \\ +fits\_write\_tblbytes & \pageref{ffptbb} \\ +fits\_write\_tdim & \pageref{ffptdm} \\ +fits\_write\_theap & \pageref{ffpthp} \\ +\end{tabular} +\newpage +\begin{tabular}{lr} +ffasfm & \pageref{ffasfm} \\ +ffbnfm & \pageref{ffbnfm} \\ +ffcalc & \pageref{ffcalc} \\ +ffcalc\_rng & \pageref{ffcalcrng} \\ +ffclos & \pageref{ffclos} \\ +ffcmph & \pageref{ffcmph} \\ +ffcmps & \pageref{ffcmps} \\ +ffcmrk & \pageref{ffpmrk} \\ +ffcmsg & \pageref{ffcmsg} \\ +ffcopy & \pageref{ffcopy} \\ +ffcpcl & \pageref{ffcpcl} \\ +ffcpdt & \pageref{ffcpdt} \\ +ffcpfl & \pageref{ffcpfl} \\ +ffcphd & \pageref{ffcphd} \\ +ffcpimg & \pageref{ffcpimg} \\ +ffcpky & \pageref{ffcpky} \\ +ffcprw & \pageref{ffcprw} \\ +ffcrhd & \pageref{ffcrhd} \\ +ffcrim & \pageref{ffcrim} \\ +ffcrow & \pageref{ffcrow} \\ +ffcrtb & \pageref{ffcrtb} \\ +ffdcol & \pageref{ffdcol} \\ +ffdelt & \pageref{ffdelt} \\ +ffdhdu & \pageref{ffdhdu} \\ +ffdkey & \pageref{ffdkey} \\ +ffdkinit & \pageref{ffinit} \\ +ffdkopen & \pageref{ffopen} \\ +ffdopn & \pageref{ffopen} \\ +ffdrec & \pageref{ffdrec} \\ +ffdrow & \pageref{ffdrow} \\ +ffdrrg & \pageref{ffdrrg} \\ +ffdrws & \pageref{ffdrws} \\ +ffdstr & \pageref{ffdkey} \\ +ffdsum & \pageref{ffdsum} \\ +ffdt2s & \pageref{ffdt2s} \\ +ffdtdm & \pageref{ffdtdm} \\ +ffdtyp & \pageref{ffdtyp} \\ +ffeqty & \pageref{ffgtcl} \\ +ffesum & \pageref{ffesum} \\ +ffexest & \pageref{ffexist} \\ +ffextn & \pageref{ffextn} \\ +ffffrw & \pageref{ffffrw} \\ +ffflmd & \pageref{ffflmd} \\ +ffflnm & \pageref{ffflnm} \\ +ffflsh & \pageref{ffflus} \\ +ffflus & \pageref{ffflus} \\ +fffree & \pageref{ffgkls} \\ +fffrow & \pageref{fffrow} \\ +\end{tabular} +\begin{tabular}{lr} +ffg2d\_ & \pageref{ffg2dx} \\ +ffg3d\_ & \pageref{ffg3dx} \\ +ffgabc & \pageref{ffgabc} \\ +ffgacl & \pageref{ffgacl} \\ +ffgbcl & \pageref{ffgbcl} \\ +ffgcdw & \pageref{ffgcdw} \\ +ffgcf & \pageref{ffgcf} \\ +ffgcf\_ & \pageref{ffgcfx} \\ +ffgcks & \pageref{ffgcks} \\ +ffgcnn & \pageref{ffgcnn} \\ +ffgcno & \pageref{ffgcno} \\ +ffgcrd & \pageref{ffgcrd} \\ +ffgcv & \pageref{ffgcv} \\ +ffgcv\_ & \pageref{ffgcvx} \\ +ffgcx & \pageref{ffgcx} \\ +ffgdes & \pageref{ffgdes} \\ +ffgdess & \pageref{ffgdes} \\ +ffgerr & \pageref{ffgerr} \\ +ffgextn & \pageref{ffgextn} \\ +ffggp\_ & \pageref{ffggpx} \\ +ffghad & \pageref{ffghad} \\ +ffghbn & \pageref{ffghbn} \\ +ffghdn & \pageref{ffghdn} \\ +ffghdt & \pageref{ffghdt} \\ +ffghpr & \pageref{ffghpr} \\ +ffghps & \pageref{ffghps} \\ +ffghsp & \pageref{ffghsp} \\ +ffghtb & \pageref{ffghtb} \\ +ffgics & \pageref{ffgics} \\ +ffgidm & \pageref{ffgidm} \\ +ffgidt & \pageref{ffgidt} \\ +ffgiet & \pageref{ffgidt} \\ +ffgipr & \pageref{ffgipr} \\ +ffgisz & \pageref{ffgisz} \\ +ffgkcl & \pageref{ffgkcl} \\ +ffgkey & \pageref{ffgkey} \\ +ffgkls & \pageref{ffgkls} \\ +ffgkn\_ & \pageref{ffgknx} \\ +ffgknm & \pageref{ffgknm} \\ +ffgky & \pageref{ffgky} \\ +ffgkyn & \pageref{ffgkyn} \\ +ffgkyt & \pageref{ffgkyt} \\ +ffgky\_ & \pageref{ffgkyx} \\ +ffgmcp & \pageref{ffgmcp} \\ +ffgmng & \pageref{ffgmng} \\ +ffgmop & \pageref{ffgmop} \\ +ffgmrm & \pageref{ffgmrm} \\ +ffgmsg & \pageref{ffgmsg} \\ + +\end{tabular} +\begin{tabular}{lr} +ffgmtf & \pageref{ffgmtf} \\ +ffgncl & \pageref{ffgnrw} \\ +ffgnrw & \pageref{ffgnrw} \\ +ffgnxk & \pageref{ffgnxk} \\ +ffgpf & \pageref{ffgpf} \\ +ffgpf\_ & \pageref{ffgpfx} \\ +ffgpv & \pageref{ffgpv} \\ +ffgpv\_ & \pageref{ffgpvx} \\ +ffgpxv & \pageref{ffgpxv} \\ +ffgpxf & \pageref{ffgpxf} \\ +ffgrec & \pageref{ffgrec} \\ +ffgrsz & \pageref{ffgrsz} \\ +ffgsdt & \pageref{ffdt2s} \\ +ffgsf\_ & \pageref{ffgsfx} \pageref{ffgsfx2} \\ +ffgstm & \pageref{ffdt2s} \\ +ffgstr & \pageref{ffgcrd} \\ +ffgsv\_ & \pageref{ffgsvx} \pageref{ffgsvx2}\\ +ffgtam & \pageref{ffgtam} \\ +ffgtbb & \pageref{ffgtbb} \\ +ffgtch & \pageref{ffgtch} \\ +ffgtcl & \pageref{ffgtcl} \\ +ffgtcm & \pageref{ffgtcm} \\ +ffgtcp & \pageref{ffgtcp} \\ +ffgtcr & \pageref{ffgtcr} \\ +ffgtcs & \pageref{ffgtcs} \\ +ffgtdm & \pageref{ffgtdm} \\ +ffgthd & \pageref{ffgthd} \\ +ffgtis & \pageref{ffgtis} \\ +ffgtmg & \pageref{ffgtmg} \\ +ffgtnm & \pageref{ffgtnm} \\ +ffgtop & \pageref{ffgtop} \\ +ffgtrm & \pageref{ffgtrm} \\ +ffgtvf & \pageref{ffgtvf} \\ +ffgunt & \pageref{ffgunt} \\ +ffhdef & \pageref{ffhdef} \\ +ffibin & \pageref{ffibin} \\ +fficls & \pageref{fficls} \\ +fficol & \pageref{fficol} \\ +ffifile & \pageref{ffiurl} \\ +ffiimg & \pageref{ffiimg} \\ +ffikls & \pageref{ffikyx} \\ +ffikyu & \pageref{ffikyu} \\ +ffiky\_ & \pageref{ffikyx} \\ +ffimem & \pageref{ffimem} \\ +ffinit & \pageref{ffinit} \\ +ffinttyp & \pageref{ffinttyp} \\ +ffiopn & \pageref{ffopen} \\ +ffirec & \pageref{ffirec} \\ + +\end{tabular} +\begin{tabular}{lr} + +ffirow & \pageref{ffirow} \\ +ffitab & \pageref{ffitab} \\ +ffiter & \pageref{ffiter} \\ +ffiurl & \pageref{ffiurl} \\ +ffkeyn & \pageref{ffkeyn} \\ +ffmahd & \pageref{ffmahd} \\ +ffmcom & \pageref{ffmcom} \\ +ffmcrd & \pageref{ffmcrd} \\ +ffmkls & \pageref{ffmkyx} \\ +ffmkyu & \pageref{ffmkyu} \\ +ffmky\_ & \pageref{ffmkyx} \\ +ffmnam & \pageref{ffmnam} \\ +ffmnhd & \pageref{ffmnhd} \\ +ffmrec & \pageref{ffmrec} \\ +ffmrhd & \pageref{ffmrhd} \\ +ffmvec & \pageref{ffmvec} \\ +ffnchk & \pageref{ffnchk} \\ +ffnkey & \pageref{ffnkey} \\ +ffomem & \pageref{ffomem} \\ +ffopen & \pageref{ffopen} \\ +ffp2d\_ & \pageref{ffp2dx} \\ +ffp3d\_ & \pageref{ffp3dx} \\ +ffpcks & \pageref{ffpcks} \\ +ffpcl & \pageref{ffpcl} \\ +ffpcls & \pageref{ffpcls} \\ +ffpcl\_ & \pageref{ffpclx} \\ +ffpclu & \pageref{ffpclu} \\ +ffpcn & \pageref{ffpcn} \\ +ffpcn\_ & \pageref{ffpcnx} \\ +ffpcom & \pageref{ffpcom} \\ +ffpdat & \pageref{ffpdat} \\ +ffpdes & \pageref{ffpdes} \\ +ffpextn & \pageref{ffgextn} \\ +ffpgp\_ & \pageref{ffpgpx} \\ +ffphbn & \pageref{ffphbn} \\ +ffphext & \pageref{ffphpr} \\ +ffphis & \pageref{ffphis} \\ +ffphpr & \pageref{ffphpr} \\ +ffphps & \pageref{ffphps} \\ +ffphtb & \pageref{ffphtb} \\ +ffpkls & \pageref{ffpkls} \\ +ffpkn\_ & \pageref{ffpknx} \\ +ffpktp & \pageref{ffpktp} \\ +ffpky & \pageref{ffpky} \\ +ffpkyt & \pageref{ffpkyt} \\ +ffpkyu & \pageref{ffpkyu} \\ +ffpky\_ & \pageref{ffpkyx} \\ +ffplsw & \pageref{ffplsw} \\ + +\end{tabular} +\begin{tabular}{lr} + +ffpmrk & \pageref{ffpmrk} \\ +ffpmsg & \pageref{ffpmsg} \\ +ffpnul & \pageref{ffpnul} \\ +ffppn & \pageref{ffppn} \\ +ffppn\_ & \pageref{ffppnx} \\ +ffppr & \pageref{ffppr} \\ +ffpprn & \pageref{ffpprn} \\ +ffppru & \pageref{ffppru} \\ +ffppr\_ & \pageref{ffpprx} \\ +ffppx & \pageref{ffppx} \\ +ffppxn & \pageref{ffppxn} \\ +ffprec & \pageref{ffprec} \\ +ffprwu & \pageref{ffpclu} \\ +ffpscl & \pageref{ffpscl} \\ +ffpss & \pageref{ffpss} \\ +ffpss\_ & \pageref{ffpssx} \\ +ffpsvc & \pageref{ffpsvc} \\ +ffptbb & \pageref{ffptbb} \\ +ffptdm & \pageref{ffptdm} \\ +ffpthp & \pageref{ffpthp} \\ +ffpunt & \pageref{ffpunt} \\ +ffrdef & \pageref{ffrdef} \\ +ffreopen & \pageref{ffreopen} \\ +ffrprt & \pageref{ffrprt} \\ +ffrsim & \pageref{ffrsim} \\ +ffrtnm & \pageref{ffrtnm} \\ +ffrwrg & \pageref{ffrwrg} \\ +ffs2dt & \pageref{ffdt2s} \\ +ffs2tm & \pageref{ffdt2s} \\ +ffsnul & \pageref{ffsnul} \\ +ffsrow & \pageref{ffsrow} \\ +fftexp & \pageref{fftexp} \\ +ffthdu & \pageref{ffthdu} \\ +fftheap & \pageref{fftheap} \\ +fftkey & \pageref{fftkey} \\ +fftm2s & \pageref{ffdt2s} \\ +fftnul & \pageref{fftnul} \\ +fftopn & \pageref{ffopen} \\ +fftplt & \pageref{fftplt} \\ +fftrec & \pageref{fftrec} \\ +fftscl & \pageref{fftscl} \\ +ffucrd & \pageref{ffucrd} \\ +ffukls & \pageref{ffukyx} \\ +ffuky & \pageref{ffuky} \\ +ffukyu & \pageref{ffukyu} \\ +ffuky\_ & \pageref{ffukyx} \\ +ffupch & \pageref{ffupch} \\ +ffupck & \pageref{ffupck} \\ + +\end{tabular} +\newpage +\begin{tabular}{lr} + +ffurlt & \pageref{ffurlt} \\ +ffvcks & \pageref{ffvcks} \\ +ffvers & \pageref{ffvers} \\ +ffwldp & \pageref{ffwldp} \\ +ffwrhdu & \pageref{ffwrhdu} \\ +ffxypx & \pageref{ffxypx} \\ + +\end{tabular} + + +\chapter{Parameter Definitions } + +\begin{verbatim} +anynul - set to TRUE (=1) if any returned values are undefined, else FALSE +array - array of numerical data values to read or write +ascii - encoded checksum string +binspec - the input table binning specifier +bitpix - bits per pixel. The following symbolic mnemonics are predefined: + BYTE_IMG = 8 (unsigned char) + SHORT_IMG = 16 (signed short integer) + LONG_IMG = 32 (signed long integer) + LONGLONG_IMG = 64 (signed long 64-bit integer) + FLOAT_IMG = -32 (float) + DOUBLE_IMG = -64 (double). + The LONGLONG_IMG type is experimental and is not officially + recognized in the FITS Standard document. + Two additional values, USHORT_IMG and ULONG_IMG are also available + for creating unsigned integer images. These are equivalent to + creating a signed integer image with BZERO offset keyword values + of 32768 or 2147483648, respectively, which is the convention that + FITS uses to store unsigned integers. +card - header record to be read or written (80 char max, null-terminated) +casesen - CASESEN (=1) for case-sensitive string matching, else CASEINSEN (=0) +cmopt - grouping table "compact" option parameter. Allowed values are: + OPT_CMT_MBR and OPT_CMT_MBR_DEL. +colname - name of the column (null-terminated) +colnum - column number (first column = 1) +colspec - the input file column specification; used to delete, create, or rename + table columns +comment - the keyword comment field (72 char max, null-terminated) +complm - should the checksum be complemented? +comptype - compression algorithm to use: GZIP_1, RICE_1, HCOMPRESS_1, or PLIO_1 +coordtype- type of coordinate projection (-SIN, -TAN, -ARC, -NCP, + -GLS, -MER, or -AIT) +cpopt - grouping table copy option parameter. Allowed values are: + OPT_GCP_GPT, OPT_GCP_MBR, OPT_GCP_ALL, OPT_MCP_ADD, OPT_MCP_NADD, + OPT_MCP_REPL, amd OPT_MCP_MOV. +create_col- If TRUE, then insert a new column in the table, otherwise + overwrite the existing column. +current - if TRUE, then the current HDU will be copied +dataok - was the data unit verification successful (=1) or + not (= -1). Equals zero if the DATASUM keyword is not present. +datasum - 32-bit 1's complement checksum for the data unit +dataend - address (in bytes) of the end of the HDU +datastart- address (in bytes) of the start of the data unit +datatype - specifies the data type of the value. Allowed value are: TSTRING, + TLOGICAL, TBYTE, TSBYTE, TSHORT, TUSHORT, TINT, TUINT, TLONG, TULONG, + TFLOAT, TDOUBLE, TCOMPLEX, and TDBLCOMPLEX +datestr - FITS date/time string: 'YYYY-MM-DDThh:mm:ss.ddd', 'YYYY-MM-dd', + or 'dd/mm/yy' +day - calendar day (UTC) (1-31) +decimals - number of decimal places to be displayed +deltasize - increment for allocating more memory +dim1 - declared size of the first dimension of the image or cube array +dim2 - declared size of the second dimension of the data cube array +dispwidth - display width of a column = length of string that will be read +dtype - data type of the keyword ('C', 'L', 'I', 'F' or 'X') + C = character string + L = logical + I = integer + F = floating point number + X = complex, e.g., "(1.23, -4.56)" +err_msg - error message on the internal stack (80 chars max) +err_text - error message string corresponding to error number (30 chars max) +exact - TRUE (=1) if the strings match exactly; + FALSE (=0) if wildcards are used +exclist - array of pointers to keyword names to be excluded from search +exists - flag indicating whether the file or compressed file exists on disk +expr - boolean or arithmetic expression +extend - TRUE (=1) if FITS file may have extensions, else FALSE (=0) +extname - value of the EXTNAME keyword (null-terminated) +extspec - the extension or HDU specifier; a number or name, version, and type +extver - value of the EXTVER keyword = integer version number +filename - full name of the FITS file, including optional HDU and filtering specs +filetype - type of file (file://, ftp://, http://, etc.) +filter - the input file filtering specifier +firstchar- starting byte in the row (first byte of row = 1) +firstfailed - member HDU ID (if positive) or grouping table GRPIDn index + value (if negative) that failed grouping table verification. +firstelem- first element in a vector (ignored for ASCII tables) +firstrow - starting row number (first row of table = 1) +following- if TRUE, any HDUs following the current HDU will be copied +fpixel - coordinate of the first pixel to be read or written in the + FITS array. The array must be of length NAXIS and have values such + that fpixel[0] is in the range 1 to NAXIS1, fpixel[1] is in the + range 1 to NAXIS2, etc. +fptr - pointer to a 'fitsfile' structure describing the FITS file. +frac - factional part of the keyword value +gcount - number of groups in the primary array (usually = 1) +gfptr - fitsfile* pointer to a grouping table HDU. +group - GRPIDn/GRPLCn index value identifying a grouping table HDU, or + data group number (=0 for non-grouped data) +grouptype - Grouping table parameter that specifies the columns to be + created in a grouping table HDU. Allowed values are: GT_ID_ALL_URI, + GT_ID_REF, GT_ID_POS, GT_ID_ALL, GT_ID_REF_URI, and GT_ID_POS_URI. +grpname - value to use for the GRPNAME keyword value. +hdunum - sequence number of the HDU (Primary array = 1) +hduok - was the HDU verification successful (=1) or + not (= -1). Equals zero if the CHECKSUM keyword is not present. +hdusum - 32 bit 1's complement checksum for the entire CHDU +hdutype - HDU type: IMAGE_HDU (0), ASCII_TBL (1), BINARY_TBL (2), ANY_HDU (-1) +header - returned character string containing all the keyword records +headstart- starting address (in bytes) of the CHDU +heapsize - size of the binary table heap, in bytes +history - the HISTORY keyword comment string (70 char max, null-terminated) +hour - hour within day (UTC) (0 - 23) +inc - sampling interval for pixels in each FITS dimension +inclist - array of pointers to matching keyword names +incolnum - input column number; range = 1 to TFIELDS +infile - the input filename, including path if specified +infptr - pointer to a 'fitsfile' structure describing the input FITS file. +intval - integer part of the keyword value +iomode - file access mode: either READONLY (=0) or READWRITE (=1) +keyname - name of a keyword (8 char max, null-terminated) +keynum - position of keyword in header (1st keyword = 1) +keyroot - root string for the keyword name (5 char max, null-terminated) +keysexist- number of existing keyword records in the CHU +keytype - header record type: -1=delete; 0=append or replace; + 1=append; 2=this is the END keyword +longstr - arbitrarily long string keyword value (null-terminated) +lpixel - coordinate of the last pixel to be read or written in the + FITS array. The array must be of length NAXIS and have values such + that lpixel[0] is in the range 1 to NAXIS1, lpixel[1] is in the + range 1 to NAXIS2, etc. +match - TRUE (=1) if the 2 strings match, else FALSE (=0) +maxdim - maximum number of values to return +member - row number of a grouping table member HDU. +memptr - pointer to the a FITS file in memory +mem_realloc - pointer to a function for reallocating more memory +memsize - size of the memory block allocated for the FITS file +mfptr - fitsfile* pointer to a grouping table member HDU. +mgopt - grouping table merge option parameter. Allowed values are: + OPT_MRG_COPY, and OPT_MRG_MOV. +minute - minute within hour (UTC) (0 - 59) +month - calendar month (UTC) (1 - 12) +morekeys - space in the header for this many more keywords +n_good_rows - number of rows evaluating to TRUE +namelist - string containing a comma or space delimited list of names +naxes - size of each dimension in the FITS array +naxis - number of dimensions in the FITS array +naxis1 - length of the X/first axis of the FITS array +naxis2 - length of the Y/second axis of the FITS array +naxis3 - length of the Z/third axis of the FITS array +nbytes - number of bytes or characters to read or write +nchars - number of characters to read or write +nelements- number of data elements to read or write +newfptr - returned pointer to the reopened file +newveclen- new value for the column vector repeat parameter +nexc - number of names in the exclusion list (may = 0) +nfound - number of keywords found (highest keyword number) +nkeys - number of keywords in the sequence +ninc - number of names in the inclusion list +nmembers - Number of grouping table members (NAXIS2 value). +nmove - number of HDUs to move (+ or -), relative to current position +nocomments - if equal to TRUE, then no commentary keywords will be copied +noisebits- number of bits to ignore when compressing floating point images +nrows - number of rows in the table +nstart - first integer value +nullarray- set to TRUE (=1) if corresponding data element is undefined +nulval - numerical value to represent undefined pixels +nulstr - character string used to represent undefined values in ASCII table +numval - numerical data value, of the appropriate data type +offset - byte offset in the heap or data unit to the first element of the vector +openfptr - pointer to a currently open FITS file +overlap - number of bytes in the binary table heap pointed to by more than 1 + descriptor +outcolnum- output column number; range = 1 to TFIELDS + 1 +outfile - and optional output filename; the input file will be copied to this prior + to opening the file +outfptr - pointer to a 'fitsfile' structure describing the output FITS file. +pcount - value of the PCOUNT keyword = size of binary table heap +previous - if TRUE, any previous HDUs in the input file will be copied. +repeat - length of column vector (e.g. 12J); == 1 for ASCII table +rmopt - grouping table remove option parameter. Allowed values are: + OPT_RM_GPT, OPT_RM_ENTRY, OPT_RM_MBR, and OPT_RM_ALL. +rootname - root filename, minus any extension or filtering specifications +rot - celestial coordinate rotation angle (degrees) +rowlen - length of a table row, in characters or bytes +rowlist - sorted list of row numbers to be deleted from the table +rownum - number of the row (first row = 1) +rowrange - list of rows or row ranges: '3,6-8,12,56-80' or '500-' +row_status - array of True/False results for each row that was evaluated +scale - linear scaling factor; true value = (FITS value) * scale + zero +second - second within minute (0 - 60.9999999999) (leap second!) +section - section of image to be copied (e.g. 21:80,101:200) +simple - TRUE (=1) if FITS file conforms to the Standard, else FALSE (=0) +space - number of blank spaces to leave between ASCII table columns +status - returned error status code (0 = OK) +sum - 32 bit unsigned checksum value +tbcol - byte position in row to start of column (1st col has tbcol = 1) +tdisp - Fortran style display format for the table column +tdimstr - the value of the TDIMn keyword +templt - template string used in comparison (null-terminated) +tfields - number of fields (columns) in the table +tfopt - grouping table member transfer option parameter. Allowed values are: + OPT_MCP_ADD, and OPT_MCP_MOV. +tform - format of the column (null-terminated); allowed values are: + ASCII tables: Iw, Aw, Fww.dd, Eww.dd, or Dww.dd + Binary tables: rL, rX, rB, rI, rJ, rA, rAw, rE, rD, rC, rM + where 'w'=width of the field, 'd'=no. of decimals, 'r'=repeat count. + Variable length array columns are denoted by a '1P' before the data type + character (e.g., '1PJ'). When creating a binary table, 2 addition tform + data type codes are recognized by CFITSIO: 'rU' and 'rV' for unsigned + 16-bit and unsigned 32-bit integer, respectively. + +theap - zero indexed byte offset of starting address of the heap + relative to the beginning of the binary table data +tilesize - array of length NAXIS that specifies the dimensions of + the image compression tiles +ttype - label or name for table column (null-terminated) +tunit - physical unit for table column (null-terminated) +typechar - symbolic code of the table column data type +typecode - data type code of the table column. The negative of + the value indicates a variable length array column. + Datatype typecode Mnemonic + bit, X 1 TBIT + byte, B 11 TBYTE + logical, L 14 TLOGICAL + ASCII character, A 16 TSTRING + short integer, I 21 TSHORT + integer, J 41 TINT32BIT (same as TLONG) + long long integer, K 81 TLONGLONG + real, E 42 TFLOAT + double precision, D 82 TDOUBLE + complex, C 83 TCOMPLEX + double complex, M 163 TDBLCOMPLEX +unit - the physical unit string (e.g., 'km/s') for a keyword +unused - number of unused bytes in the binary table heap +urltype - the file type of the FITS file (file://, ftp://, mem://, etc.) +validheap- returned value = FALSE if any of the variable length array + address are outside the valid range of addresses in the heap +value - the keyword value string (70 char max, null-terminated) +version - current version number of the CFITSIO library +width - width of the character string field +xcol - number of the column containing the X coordinate values +xinc - X axis coordinate increment at reference pixel (deg) +xpix - X axis pixel location +xpos - X axis celestial coordinate (usually RA) (deg) +xrefpix - X axis reference pixel array location +xrefval - X axis coordinate value at the reference pixel (deg) +ycol - number of the column containing the X coordinate values +year - calendar year (e.g. 1999, 2000, etc) +yinc - Y axis coordinate increment at reference pixel (deg) +ypix - y axis pixel location +ypos - y axis celestial coordinate (usually DEC) (deg) +yrefpix - Y axis reference pixel array location +yrefval - Y axis coordinate value at the reference pixel (deg) +zero - scaling offset; true value = (FITS value) * scale + zero +\end{verbatim} + +\chapter{CFITSIO Error Status Codes } + +The following table lists all the error status codes used by CFITSIO. +Programmers are encouraged to use the symbolic mnemonics (defined in +the file fitsio.h) rather than the actual integer status values to +improve the readability of their code. + +\begin{verbatim} + Symbolic Const Value Meaning + -------------- ----- ----------------------------------------- + 0 OK, no error + SAME_FILE 101 input and output files are the same + TOO_MANY_FILES 103 tried to open too many FITS files at once + FILE_NOT_OPENED 104 could not open the named file + FILE_NOT_CREATED 105 could not create the named file + WRITE_ERROR 106 error writing to FITS file + END_OF_FILE 107 tried to move past end of file + READ_ERROR 108 error reading from FITS file + FILE_NOT_CLOSED 110 could not close the file + ARRAY_TOO_BIG 111 array dimensions exceed internal limit + READONLY_FILE 112 Cannot write to readonly file + MEMORY_ALLOCATION 113 Could not allocate memory + BAD_FILEPTR 114 invalid fitsfile pointer + NULL_INPUT_PTR 115 NULL input pointer to routine + SEEK_ERROR 116 error seeking position in file + + BAD_URL_PREFIX 121 invalid URL prefix on file name + TOO_MANY_DRIVERS 122 tried to register too many IO drivers + DRIVER_INIT_FAILED 123 driver initialization failed + NO_MATCHING_DRIVER 124 matching driver is not registered + URL_PARSE_ERROR 125 failed to parse input file URL + RANGE_PARSE_ERROR 126 parse error in range list + + SHARED_BADARG 151 bad argument in shared memory driver + SHARED_NULPTR 152 null pointer passed as an argument + SHARED_TABFULL 153 no more free shared memory handles + SHARED_NOTINIT 154 shared memory driver is not initialized + SHARED_IPCERR 155 IPC error returned by a system call + SHARED_NOMEM 156 no memory in shared memory driver + SHARED_AGAIN 157 resource deadlock would occur + SHARED_NOFILE 158 attempt to open/create lock file failed + SHARED_NORESIZE 159 shared memory block cannot be resized at the moment + + HEADER_NOT_EMPTY 201 header already contains keywords + KEY_NO_EXIST 202 keyword not found in header + KEY_OUT_BOUNDS 203 keyword record number is out of bounds + VALUE_UNDEFINED 204 keyword value field is blank + NO_QUOTE 205 string is missing the closing quote + BAD_INDEX_KEY 206 illegal indexed keyword name (e.g. 'TFORM1000') + BAD_KEYCHAR 207 illegal character in keyword name or card + BAD_ORDER 208 required keywords out of order + NOT_POS_INT 209 keyword value is not a positive integer + NO_END 210 couldn't find END keyword + BAD_BITPIX 211 illegal BITPIX keyword value + BAD_NAXIS 212 illegal NAXIS keyword value + BAD_NAXES 213 illegal NAXISn keyword value + BAD_PCOUNT 214 illegal PCOUNT keyword value + BAD_GCOUNT 215 illegal GCOUNT keyword value + BAD_TFIELDS 216 illegal TFIELDS keyword value + NEG_WIDTH 217 negative table row size + NEG_ROWS 218 negative number of rows in table + COL_NOT_FOUND 219 column with this name not found in table + BAD_SIMPLE 220 illegal value of SIMPLE keyword + NO_SIMPLE 221 Primary array doesn't start with SIMPLE + NO_BITPIX 222 Second keyword not BITPIX + NO_NAXIS 223 Third keyword not NAXIS + NO_NAXES 224 Couldn't find all the NAXISn keywords + NO_XTENSION 225 HDU doesn't start with XTENSION keyword + NOT_ATABLE 226 the CHDU is not an ASCII table extension + NOT_BTABLE 227 the CHDU is not a binary table extension + NO_PCOUNT 228 couldn't find PCOUNT keyword + NO_GCOUNT 229 couldn't find GCOUNT keyword + NO_TFIELDS 230 couldn't find TFIELDS keyword + NO_TBCOL 231 couldn't find TBCOLn keyword + NO_TFORM 232 couldn't find TFORMn keyword + NOT_IMAGE 233 the CHDU is not an IMAGE extension + BAD_TBCOL 234 TBCOLn keyword value < 0 or > rowlength + NOT_TABLE 235 the CHDU is not a table + COL_TOO_WIDE 236 column is too wide to fit in table + COL_NOT_UNIQUE 237 more than 1 column name matches template + BAD_ROW_WIDTH 241 sum of column widths not = NAXIS1 + UNKNOWN_EXT 251 unrecognizable FITS extension type + UNKNOWN_REC 252 unknown record; 1st keyword not SIMPLE or XTENSION + END_JUNK 253 END keyword is not blank + BAD_HEADER_FILL 254 Header fill area contains non-blank chars + BAD_DATA_FILL 255 Illegal data fill bytes (not zero or blank) + BAD_TFORM 261 illegal TFORM format code + BAD_TFORM_DTYPE 262 unrecognizable TFORM data type code + BAD_TDIM 263 illegal TDIMn keyword value + BAD_HEAP_PTR 264 invalid BINTABLE heap pointer is out of range + + BAD_HDU_NUM 301 HDU number < 1 + BAD_COL_NUM 302 column number < 1 or > tfields + NEG_FILE_POS 304 tried to move to negative byte location in file + NEG_BYTES 306 tried to read or write negative number of bytes + BAD_ROW_NUM 307 illegal starting row number in table + BAD_ELEM_NUM 308 illegal starting element number in vector + NOT_ASCII_COL 309 this is not an ASCII string column + NOT_LOGICAL_COL 310 this is not a logical data type column + BAD_ATABLE_FORMAT 311 ASCII table column has wrong format + BAD_BTABLE_FORMAT 312 Binary table column has wrong format + NO_NULL 314 null value has not been defined + NOT_VARI_LEN 317 this is not a variable length column + BAD_DIMEN 320 illegal number of dimensions in array + BAD_PIX_NUM 321 first pixel number greater than last pixel + ZERO_SCALE 322 illegal BSCALE or TSCALn keyword = 0 + NEG_AXIS 323 illegal axis length < 1 + + NOT_GROUP_TABLE 340 Grouping function error + HDU_ALREADY_MEMBER 341 + MEMBER_NOT_FOUND 342 + GROUP_NOT_FOUND 343 + BAD_GROUP_ID 344 + TOO_MANY_HDUS_TRACKED 345 + HDU_ALREADY_TRACKED 346 + BAD_OPTION 347 + IDENTICAL_POINTERS 348 + BAD_GROUP_ATTACH 349 + BAD_GROUP_DETACH 350 + + NGP_NO_MEMORY 360 malloc failed + NGP_READ_ERR 361 read error from file + NGP_NUL_PTR 362 null pointer passed as an argument. + Passing null pointer as a name of + template file raises this error + NGP_EMPTY_CURLINE 363 line read seems to be empty (used + internally) + NGP_UNREAD_QUEUE_FULL 364 cannot unread more then 1 line (or single + line twice) + NGP_INC_NESTING 365 too deep include file nesting (infinite + loop, template includes itself ?) + NGP_ERR_FOPEN 366 fopen() failed, cannot open template file + NGP_EOF 367 end of file encountered and not expected + NGP_BAD_ARG 368 bad arguments passed. Usually means + internal parser error. Should not happen + NGP_TOKEN_NOT_EXPECT 369 token not expected here + + BAD_I2C 401 bad int to formatted string conversion + BAD_F2C 402 bad float to formatted string conversion + BAD_INTKEY 403 can't interpret keyword value as integer + BAD_LOGICALKEY 404 can't interpret keyword value as logical + BAD_FLOATKEY 405 can't interpret keyword value as float + BAD_DOUBLEKEY 406 can't interpret keyword value as double + BAD_C2I 407 bad formatted string to int conversion + BAD_C2F 408 bad formatted string to float conversion + BAD_C2D 409 bad formatted string to double conversion + BAD_DATATYPE 410 illegal datatype code value + BAD_DECIM 411 bad number of decimal places specified + NUM_OVERFLOW 412 overflow during data type conversion + DATA_COMPRESSION_ERR 413 error compressing image + DATA_DECOMPRESSION_ERR 414 error uncompressing image + + BAD_DATE 420 error in date or time conversion + + PARSE_SYNTAX_ERR 431 syntax error in parser expression + PARSE_BAD_TYPE 432 expression did not evaluate to desired type + PARSE_LRG_VECTOR 433 vector result too large to return in array + PARSE_NO_OUTPUT 434 data parser failed not sent an out column + PARSE_BAD_COL 435 bad data encounter while parsing column + PARSE_BAD_OUTPUT 436 Output file not of proper type + + ANGLE_TOO_BIG 501 celestial angle too large for projection + BAD_WCS_VAL 502 bad celestial coordinate or pixel value + WCS_ERROR 503 error in celestial coordinate calculation + BAD_WCS_PROJ 504 unsupported type of celestial projection + NO_WCS_KEY 505 celestial coordinate keywords not found + APPROX_WCS_KEY 506 approximate wcs keyword values were returned +\end{verbatim} +\end{document} + diff --git a/external/cfitsio/cfitsio_mac.sit.hqx b/external/cfitsio/cfitsio_mac.sit.hqx new file mode 100644 index 0000000..0a3dbdf --- /dev/null +++ b/external/cfitsio/cfitsio_mac.sit.hqx @@ -0,0 +1 @@ +(This file must be converted with BinHex 4.0) :$f0QDA4cD@pIE@&M,R0TG!"6594%8dP8)3#3"%11!*!%4HY6593K!!%!!%11FNa KG3*6!*!$&ZlZ)#!,BfCTG(0TEepYB@-!N"AR#BB"&J!9!U)"eJ#3!`-!N!q'!!! $([q3"!2JYCR`K,@Cp[N!N!8"mK%!N!C$#2r`rrJ!!-0!!!"0Y`!0$N0'DA4cD@p 38%-ZE@0`!*!4Da3*KJ#3$aB!!$i$!*!$&[q3"%e08(*$9dP&!3#[H%fHYCRfmJ# 3"3(U0!#3"Md0!!"9XJ#3"[8H"[LUJEpCRC+&h2,Qp5XR2!PhHq4S2H%,ApKQqmJ LNkpp8AlddNfr[1A)MP#1Tai![I"-haEmadlZiRN5eLh2&pPQYb@hMpc#pMQahU[ MjF@A)`YEb',mbF)EN!#&l)[`r)9RmFZHm#-lbAEK#qr)l6MKLpjbR($&#Ur-Vr0 fK0r#Ma`RA-)l22Q&,13AFTY-&L1cK5jN"9K($6SEG(C1mZ4R0R4JEhDp%a[NjC! $GhCHIV6,b@DM((4ffHR#LbqH!ph[phRj4[C&&ShX)r[YiRdmbHkebmMq[(KN$cV cFa#2l)[X86b,,-plB#b!Vl2VCAikk,fqbH,(bq[*l53meJ,!&[`"jhIkI5f1'd% 3,!Q#dUUJp-!lJY+$p5"BH5ma(bKeP6LDVcDZZdeKNN4a8UiQm9CAbqCLA5QIqP[ XPi0Jl8H#i"hHrpUqXi,J3lqkj2T6Yh"H2r[XNdTd(rB03A$L`5"Bk[dc2RMIZPh "b@rE'34[ackdki2h"FF(`H@c#NqI)P4GS[!F,Pp*9ei&5TqF6!e5'Z+9D"fM`h3 SKb"@J#a`!MD',FIf`[E'9Q$lB2YLqf(lBdr#RS`GJ$d&1a!l&(XDGKKf1,B5HcV f$1`)l*RBNGLcX'GMcm'HKld!Hb&f0$D-(B3GV&F8@iSYXeH9XJm!'S'I``l"RSX p(cX+1aCEK4f((@pf`QYP)SqYmP[j5"8[%qfa(pAKIHX%hY3A6jljZ2T8+l$Y9P+ 9ZSABJ5Ud2KH'fm*+)da6PhC[$C1M&eJq8#d2EIU0kPcTFK1[N!#9EL'@!Z3P+,c -9icR6L[c8`j9I`iR[I25%D#SDkA4H$YaNp&-Gc1XG32c$IHfjC@#kiCLM)K%k9U d0C!!+Y"B$PJ09iKRp+T'ImNm&#%J!p"afpL5RV[@,,R`mrF[ZH#ElqrD[qq5V[h AhHR6KErIYIm6PcaA6"*I""A*,da(Ij9Gqfr-+elX!'`CYKcE#pX2#j!!mKDmMrF f[!P[`aGVCh(5hqLkFX8[lETZ[FY'fki@68BZ'4rrPF(qU1%Ur52RpBmU,(M&e-r [ZMjp9qrk4M`40Y,amE22fMMRMCjalUk29a)ACUirLCZM'DkYiq-#6)e-2)$BNji $[H19,-l5)CG-ZI*`jkGdGbVIp'Te-(q&F1rJqXfekDJZ"VeXY&TjXIkPJR"+B'p X,f`j0LE!&GXAfcq[XZ"cN!$HrL9`cYkY)A1@E%VP'l5+hlRcbk0[mTDf2KX`YaX e[DrL-$5@ED`1G2ZkcR%$Fa@`J+rA8&Ll`)9ePk4(B5"I%%P5e4$j5bD8ZL99"C' %"%)')C`36FJTK2"&@%JPj,)HqlYBb1JU,136iJN"K@"#6L'VI1!b4*@kJP$@BGG L4Gh4a",#Z4rl8L`BHMX@BVS-#r0EJEd4Za,lHpJef*Z`P0R0f&Z`eGL*@0jl&aD -K&[`XMABGGK*f-RBLl$)*TGJT`JBa8l$6XI1`0CLRiqGLGf"[3%,JGq$I3N@rVF HHkYa$AMM[GJAB+r&3Z`KlK"pb2NXib@3!(k)1%)!K2[P15X)"&GM)IQ)!4"pq!T #!&`#-@!$&R5![%2Qlm4HJpf%I4lfaGM0@05cV9J)r6EXE1`Fl+ABZGKjf-Z`ml% ,X*GM&f,"JLZ`,m3ZaYiPU$4)UGRfhGJP@0',!Vq$[4Ul&,YDF#,f+Zc[B+r%hS0 YCr+j6bUTE-2`KTkK[Zkqc5UCjSmUeDA%`6[rejF4)l,TiR`brVA3*Hm,%a+b-,* 3m2Vmm)Grm!5eR`CPMk%khMkK4`-Vr9'@4R'j@Uf8aebD5H$HN!#%3R@q1)AA(!- ,*PeY!S[6i&1kHG`*(+DSEhGqHe)rN3YqQb2j1!RK3Fcr%)dV5eHAPR@*'(UI%,* )f"hrVb$680&UjfB4V1HqC*df[aL&i!5iCF2hhkV$Xr!4rp,Nc`R6Uq!!q&D2TeQ B,2,38L0LY!''CqYC[k2Ei6&eaif6jJX[#J$6`Cm+2mfIND*raNQlmN!cYD6qDhR iehPEAiE`T##&'[YlPQU9DGfVFiN+aG3d"p9Cr"U,Il(jejT5GAfZ@k+UQ[MRfAQ c2FC,l,`Pec"4*3dBEV-mfqdq1r+cj1VIXf&dS&`T9q*@'MGFZEpRl,pCY8HMYE" hS,I"43%j5H+E+PXTX"5C[L@5"*hjT*ck(p+-T'pPA6eAME"lC``ZA"'DN9&,k4l 3V35k$q"r4B!'5-[V2ffqh+G,[2'9A[C+-mQ4Hce6diRfMY3c"p(TcH-lB&51Ndq [$2IfR9iq[EGRV)IMSS(+Q"TA(#UI-kB[e`fL%R@hP1SR6VjaNXdRhjpH,Bp,dX- 85VFXPH*+Ve+iDrm(e&0&Ve"NH8pG[9G[aNDV88Xp2hqKFUUVaZSTqNGXbG"!4Xq 8bTZ'GimiHUU8),CXb,ASJe,U*bV2S1[LSrHS&biT)+AqS0jakVLrZJPI4G4Sb2F KI(e'Ym6dEDP[iqYl396Ar5"4rBELEIL#kKRV6eqQj!ZUEqNNA2)f3I8iREc$9HK &#UVhD8!MdYX%2iA[&-[h-AbRCT8ahLri(A`$XmE&p*Q&T$F-LPS9hLDN(V6"PDa ",eG)Z[q3!%E()H'(e&meY,&XP$Z&e&pfQ[Lcq2i3hl$H*#3qp(Pm`mH5N!#qYG" Am9@Z)&"m2m3RI@k)kQ(eJCh4UMYU+kbqY4'4VK%i6Ve5CrSqeV#8FGk"12VGk!r $pa!(5f(#AqC!2adqDY(fiN@NcG"I4`Pc2VpVrar!jb2U@EXJ1+&1r868mdCr(MR S4k1h$YmIF"MYm5$bjrM'H$b)2)"[V-H$b02iUM`H409R0XlM395pGH-p(N69qcA "id&8Me[Ym5#UrVf*(JqLYq#VmAJ3r5+q54i2SUV,b4i2BY+4,[*i%"0ZAHca)#E "i"+2"c(edNhap4h6@dleH"$6@dlcH"#l(Crdl9&rXDrMQf(jrN8BD2#J4$ed-cd HP+JAF*E(Ja,9j@b2"bAUmC[MmD"%IAQAHM`S89h1pAK3mL9mmc`HP$b+lc+2"k8 5$HCl2#K92b5pGG,69kSq3hVXj+e,e50*Vaep[26@JFY5ck9-Cp#pGb+2P0l"'5b @[X(5KcR6FbFpJDA2F1DY"&r+K$(df8Q2AjNN#RV[D)AQE[4,#T%S8lI3e4Hj5kR 4-Y@Pp!U'`BNbeHAbJ@DEZLN6hUqSELU$ifArJ1rDLi,cHEpb[HA+4PY[@*lhGPD [8FYJZIM%kNEj21URA2fAer@A"lKRZCT**#re9hiG[M8HrmVr'YpDMhrPUX[Vam+ %ZLNAaXM!MNTk+hZT6h$pU-Y)dNYeZD'-8hc8aNBh'X*EHUQ2%8iL$6Qpe)F)&i" Z[)8$[!2I"cR3qiM[#3kf@kUh-'EEijG)[ZNYE"A9X6Fb@1qmVr06Tk9I`3FhZQ& UEINqI25GlR`m&i&k#f0f5C38hr[`lHETjEkpralI(QT,F+Zh4QcFL'STp95KZVa THQd2H&#KZYbl*48Z93J,ETDL+6lHlKDAPDRV#YAPmrhp+X4ME[AhUp#dNaHdfa( [A[(2q&iS495S%lfKYjhILAMR2Z*aq`+5L`pFS2F5HJ@hi!haJ@(dkq*l0`GkGI( "AH"Sq,l,!Ei$(D-h&@k)MjjFqPcaJ5P`,RcdfG)ELZr9(+K(I1!@ICriU(AH&Kr FL$jKB)aq9[J12VJ42Dri`$,H#Kq#*Afdq(kI!p0,m,fI`rhQq`+(9jM[D3k[p,l qkUGpPIRJ%16(4mm`Ei@2IPfCQ+*pVqG!MH0$#RfYq6l2!4c$pkmFb!-9T5rh!I2 "Ym"Y69(PS&[6a8H0[G&m$h*iNrR!@VJN[Up`J"FE1!m%0jZ2AQ[i1$kDcmL2Mcj TX!iIp3b'ib2[@mhh%3j[-aqm"$b"*S0rEcFII2%GjU-Iq*hQJr[#FI"4jqmb(hF $0r"pQ!1F%4riqV$hR5)qqKlc`6RIDcl`'mc%4rr`RjL2HRl%I0bCZXEhF3l`1(` #[`(K#45(RZX2Q)qHG$J&2[UBVAapUM$ecmd(VX%&m&&,F(Cme"rk!ckd"pj6[S' 5T)R&"pHKM["4hr"@I1!([!mI[%dQlfJIZ)$QJ)qhi`haJAr`GhcJabHmEj!!qVi rD6l`$jc("liqEMjkh)@(D"pi*ra%qkM$6jX2[Jk'ii0,`8IN'b`"L9Kmp,6rMIR SdIjEmr&qi"Xqm)Lh`NHGJ+[ii)*Ip,iK`Sm[Q3m0!Nk+$rjNCh)-%Ik"DILS[kq D$hcL,[LS5HS1hcFiJ#(b$C@fK#D!$r`!Zr$4RdiHI25pIp0mF",d%(aJ)PL"MjV mP[QHj2"YlcY0'XChc%G0`hI``5HrCcii#6`,(aJRlGRDalYc&rQ'5926ij6%"ip mbRa`EMJ12ZB4r,2ji#(8*Mk`$#d#(l8"*XJhA'm2VmG(EF&Pm-(RIQ3qCK[)j#E YJdZBKS2!,i06RZ3hR$VaY-&+HR)BCD0pdVR!U!cY%rk[V(HP%TkZE,j&*4LJV21 m%La6[PFR8+BfY1iRhqQ3!"5PZ32iC,k#XZ&"Ti-V5[L-6rLRm[e$J90d*p-E&$K &8kUBJB#21m%aj6Z$'+8q&(`bld'*fq)6,UM8RB42hPjTlJ3qiA)U*d[L%kaAiT, i"&18Y"hj4R"rTEN(q%6c8MD$B!6m@`N(m%R6K4)1ia0m9H+-q)5V+"0J4i#$5V- Em%QY+XePN!$[61+91#!qi@G+I!fID"h+jPbF#6iTm3Km`Tq8G#eme"ED*Ml"%@A khdMH9jRq0e,eD[@rNHKH-J(0q!5cPI!"Rf#U-[e[*&a#L42K%ka4QVdKheR5)Uh qGaEDJM,plbcTY,U[4Ac#GC8d"(c#Se6HkL)q`@@Pq4[bRDekdYd[iT2l+'N#q%5 I8MB)kQc9+0SJ2Z($5[-hm)&2k$Ri")Z9jQl)G`ieVc4r!jr-he$5ar#*0UE`Dap B`P`1I033A!DIm#*Pc86RJ*8UEp!@D96U5QPZ"ci4UT4`(KmD0CS92Z(j5T`&Rr! pC6hIjm)IP'CNb(FH'+5N1H160e,Lb2L%IfZY'jr`HbAZL8riZC)`KSpDK,2J%`k JT2R)GciiSU6riC2l+r%&I05LkB31R)NHSD6ri411UU6ri42HSU6rbAH"-"KHJ%p U3%Nc`3IfS2rK%ae(L8[L%be!5DI&*aa5D9B)2[#-H4hbA3Jf+Fe9`3G1S,AL%`e 'LI2L%ae%5EI%*aa4L8[L!hIYh+C4F!1PH5li`(+d3Aab0bAY!Kpe3lm"2Y%[P$J M2Z'd5[UEI+-e$3JY!TpJNl*T1+1&$I!eI+*Y+G2r4ZXH9[mE$3G@T[q0d6fXrMG '2!$p%"mD-9S%2UN6C3eBBlL2N[D,$fjN1pI(L"m`4`DID!R+QN2'S%FScB("ar[ "fI%*MeALJ2L%[bPV[KX,$e(LFIJ%aj8d0(b#IFSDmmH#"8VD&$jk)Y#kmFPE+-e N`5HkQa*ra`GqfbEqX@J$b[5rX@J0b[5rXA"eCIVI@''`eIr'`L&eMiCm9A!YCIT I&Ca%QIjA*IbcqPq9"K0BrDq+qP5Qre@T&Uhq9i@ZSdcrUd,E8UEr9D'c+02rUX4 6VIih6K1BV2ih$JkM62mETlYDr@mFpDa-raY(,QAkhcLpYpAraSN6@2e[($aFQIi h(XkQ62mE,jjKpEra`L5VrifRaT6TIq24pC6TIq24UT6TIq24!*6TIq09deErQb# 1CI@r#F*1Urp08(eBr@q#kX2UIa1)9kEr64"rYIVI"(36CITIYDCH@[f['JkT62q VKQmSdrqUK3G@rkY@69MpVaTG6jRq9bd-X2TIY6LheImQ`Vf9kAm6iA[+p,q*F!p PqYp%m@1Vrde869[pEb*kRc,pEk,iK0Ar*UVQV2jA)kjMpEmDiBI9rfVJ)FVd[aV a4D[reBMh@2f[4M9YpEmD0#aPqPm01SXbr@q5F06UIj2%JDcq0dPFfHTrNm"LCIV I*1'heImQkAj@rjXNc,$khb6d(@Akhf6a2k[r6BDl+Y2r*N[RX2VIC$#DbCY1Q3r PUGI)m,#a8r0&eSpX0(SE@2U'3ph*@k+**%a%KDVl"`Elb$c1cZ2Y2-(1ARb0R6I Cq@)lAf,R+ADHDZGTGTjZjaPfVVAc6$[2X[0X1mqamk9fRQ[RHADqc-lclEc!cTI EHD'G&pQjbXjAf(Q*RCIDq8SlAfARCADqfXlAf(QjR9IBq9SlVl6c+MZ[Y[0eGUk cmaSlVlAcHMY[X20'R4QRD2eEl,c9cY[X[(fKRS1[ca[ejqI&[%PGB)%ZZdBr1rG I5+G1BX"G+P[)XT6EZCHGbaDbPLe9`a@6MlL#66"J)8(jm[PHGbm&F`R9mMIP+G6 199HAPTr8cMVT@ppq8Y6+bT16mQpH03NBV@I5Bq8CNqdX16B3XA9FTSpccS'T%Dd 6a6TlfeeId-ZiA,F#p%CIKPll'2SfFYB3,HI`iJ#3!-`NXe%!81m40qN5ekU4Bcd c8mf`B!e[$H-fRA!IC'YVU"C)YdX4Y03&l6V*83$Z@a"ea3aSG0B-SN-(%cXH1`& EMDh"EX*HM,d%1`A,hJ(6X01a-l#ef*PBV@1*RB1p&$XA1`pl'ABqGJ(fFZa#l#* X&IB+l",X8Zb9f+Z`bl"ABkr",XHZ`&k,ABPGK9f0[3jEKef$ABYGMpf!hBMG)U! AZ`flAB43E"!E`SDa6F``*bPZAcaQVT!!#f(B[NrMM8l@TEAGAJP@lE`m1'lANU" mmEU6ejkQMR$h,Iph+Fb86idDE*i)l`JB3"QDfphM1@``eKN`"GY&mECQP3(DCC& 5D)qPlCDf@YTMDBZP(CD''0TZDBHPcC!!pN,D#QQ*T4@@&PKDAfPjTG@9&PGD@fP TTC@9'U$eN!#@3eS0D6'N$C2f5pSZDEHNMC-f6GSYDE1N[C)f6pSiDDpNE!MM3KJ 63SXPVC@d90*+53XPVC1d60)U5BXNVC'dJc*+K"%LM!jKC!JYXE6#dJ*,kbXYYE6 -d[T+bbZYVV6FdP*,UbXYN!#d2Y,b5,XVEDkdYp,@5MXVEDbdVp+f5VXUEDUdjY) @56XNEC!!M$pTfN(&@aQ#m3@-,@"F!@-6')[!Z!*[0aA'%M#1J$%%6EZQ-&l!@qH "-3+-9fKDhB(fFpV1D6HRcCcfGGV6D61R[CbfFYVED9rheQI`eQ9)VXS!2D)Yh&[ c)ENq"'h`Y(V6iZfY'q'Y%m+S$'3e2BP8V,GALaf*%Grki0Lq,ibZB'3&SbVS&'0 -"H-T["eJ[,8N''-!@$-@JE%(b48I'*X!BKpDpB%a!i`AS(Z-F3+-%8#@C)3!S`- B'F#S!-E)b2JBhHl2k"K'aL!m-'U'N6'-LQ&8#Qh"Y-VbCV6MmfkdS20fM)TK4)b -2p(M0'3X$+-dp!J0aX)`GS*DCK3-)f!BrF,)&aReSXF2b,JE4Q``@S14'S`BB*` 'Bc4S#kFGR$C`fVpTqkEPR*CbfVjTpkE0QjCd@XYT+DIeR(C[fVaTlkDYQhCZfVK ThkCYQhCY@YTT@DGGQcCYfV0TbkBGQcCXfU0TLkBGQMCSfTpT[DDeQ[CRfTjTGkB eQaCV@UYT`DEYQACRfTaTEkDYQACQfTKTAkCYQGCZ@VGT@kCGQ6CPfT0T5kBGQI% XM'9K(!)M@4M&`SJA4VJ`LS84,)aHB33-ia-BmD&l4aM43#XiidBBad(E-f-!"(F CYm*S$dCk-'k&-5Z-@'$%#U09'+R#+"9'U$#HJ6%bM2aJe!FM2KM"`(J2aRS`KS2 a'icGB0`')cdBfF'i$FCX-&k$N4q-lQ#X"b-f'+h"5!e'D6"#Jp%CM-aJ9!BM-KL 0`8J-4Q%`!S24&ibmB03&Bd)BFm&i#mCD--j#Ml&Jc)q-pp%M*aMY`dJI4J8a#SL 42ScbBB32Sd!BT`'2BI3'A)C4([!B4[N``SIa'A!D'G[$'"BpIS@a2B`'J'm`USI a#Y!#16#+4ir!B2b#m"l'XM#1K6%AM',4)eJB9F#S%PVA'Bh*q!hDj)@6-)U#F4' -L@!m"+-X'%R"+!T'9M!QJ[%3M)@JCB!a%)arB1`$iaiBmm!S$%CG-1D"m3k-G@# F!f-F[2%0MS)2rM2k16(CU!5M$ZalURYfpJNEUH*SqmBHf[c'ai8a"rBp!lkHEL0 b('hI1*(94Pbiim#q*lma1hZhM8&cXRdZXadr4,pU@[Y"cm$pfHF%DFjdKFb`pkU MH5Upr4`8A5*Lk$Uid6E,1l"-95+9lNcSbD1l&hVbk!k(RMad39aEj+&,BQq4Kmk *L88H1JPULMad)H`UmS#TZiXmG#rX+I,3q6#Tb%2Aa13L$jd9&a9jk,kiZ-K$KmB P44kk0kB8HHM`Q&VNS3YN@T%(pA9kNBFZNKP&(MT0DSXmG+2-,2,3XA*VNBHZPPP &(P6&'iSmG-IX,2,33I1#)JmG0V1,2(6Kc#Rbd+PcDC'(ETkj44kkIHB9HHJ)ZUc )3pI3r#)2R88,LMadF&eHj+(lDf'4Kik3!%9&(VU1VLMbd('fZ-K$pp4Y44kkS*B 8HHKkfeINSIYNDC'(lT8VLcaddYeHj+(ElUSL$aej0aGjk0DlXmK$4ppG44kkrZi ZmY!CH-qL!Zpp1)m$k(2,,8VMVYR"bUf`E&rk5br5U1BQAcTK2PdCMSIkG-p8)c- bjAMD2(-r@##5KrlrB88HCJ--,r)`,f$S5hRL%'TA(kdXmM"[B0P,#N3b$l-)4QR 2S)C)rGPamcS2X`Xb(45qdkcjSdUeC*C2I2eV58KIXPRcCmI5+Mh3CXfIG41G+4C b[90jDUXa2G"QcCrYH@Z,@I1RVUd9AS,IiES$rAh-QMm,`MCeDYEmf65c$(ABV2Q M5P&UpaiDCXY4qmfD2lZA9EAbMelcCdCBQfDjd-H8Cee@4CN+D6C"T4YC6aG"Q+H 1VrNcJRZIiHm6*PE2Zj!![[Z`9$lQBB$'c"SC5CjbZcGpj9,cBFhaJ)",$hjBXaA S,qpI+KhhY1@4qSbSpTLE)HH)HX6T-jHHqmJ'lR1"ca04Qa4c0)3$4$5MK(NDV!M %h)l4j)RCIHbD$a(09V"V2N4jUm!j0,a('5GjTZ8KMQi%ZqC$9!SMA5Tb[qK[Fjp URbHUf5,-jC!![[@SjT6B04qLQJNa5H9[lc0jZU3kL!QrQ0NKq@*kViZe"T(N'6C rlfEFNf92jAh#c,[XUcc"rGH[mrH4&GGNKGLeE[N'YhepVpX@eIapYZIe,HjebeN Kad5Z@miU30C9ASNEF9)UYHV+)b[Ul$dRMbJID'ec#@X,LDpda)80bl-Z8jkkLBC EXE&GCmhA4G'-d'9"11d'aK4AbXT%UK6QH(Cp[4)hfkrMjV@0b,@bJGi961rDcCS lXeTCFZQ+8LPeqEh&bIX3*50jBqrUl8X5(N&#PV'5d2C5+9QQ2(YQF1[Vl-iI41T K%I8!CkJ-6JM[,j@@hZTakR+Rp`rZrkMLi5M%rq@K(LQTXbYmrEbalHXRF+IU4ee @ZTcYa9E6ec4HGj-mebQ2HNrT[F69-4,[qHCESac1JPY&JL9[U'JJYpPhNLR"68K !2ekU*mqK32Y5YrCiYEF%U01"(lX%aMBjLp4R*pNh`(30PeNVqUmqGdeMJ92P@c! kd,GbS*@jT-95Al0QfNK(khkiq@VR0pIjY+2FM'e&iecl[[c1GCd,"mD#ffkfMC) 8qiia14*5-KdCH0SC+frl@([P,BpFP8rm(FSSViUK[JdE%D*Z'4dE1CAckk[9@0, C"dHLZPUX2a%kl@SAH)KiE[AerSCfPJXm-6DN84Q"JpSQ9'jBK[Dj#ch[&,@&LAp 8lm!Bm@TYlq#`KV60MQSCpe)M[8QX)@+VMP[B86XFjF-0p8j!iDhj!%1plia#STD MhRp+aTd0+G[H4M,q6-4ZZbr22Se8X$[8l-[($EJS0[6dR@N*mTQ&,ZeH9@khca( )&Fh1,rU-*iM1(j,2lY6PJ2X!"6TZG2q(E"AUT-1&a'E9UmHM9T4&B52Dm6pXmMR GI[8iC(KVe++jcK&4)rqjDh-kU20,l1cUU&J&S'Y,NL-pCA4+Z`+kd`k[e@4fDY9 ITCT'6b33P,D5rpVVblAb49JC)0Qc[@[,$lZfDCD3!,"d,lF$i6[3TY'FiaGhFXj -3XQ0Gfhph#C5KrYfp4Uhh"3qedCcjh-AFYaqhLL9%`q%m4Q2IYS@bTEa'BrqK4E I&CmGRr(S*br4q)bcY1f$Zf4m4T!!@BP#V3bj!PPGCT!!Gq6EQ'AYYDYAEf9)bDL ddqUZaFh9l55ZGfTCZRTVIAVefGeRVkl(YA4ef)jX-A$(b'IQ*0#r`jb%dNAZjL3 X'"eF'fahbA4D(Xh#9Me-kVBjlFf+VD`Q$[cF`Gl!ejF[F'(G*BZ83BQ2#c'2#h@ eiG(bD+IGC[bdbidpq,J3@V4ZVNU*HP30AC!!YQJKMpZQIk*PZd!rIc#!hJ0EG*2 Y8dQSV9aF+Cp66Q28RiZLlr8DKK8Xakl!ASYGL9f&ABfp$PZ(AB0GLef2hB$GL0f #hBVGKQ8VRZkEHTY!R)JJp2SpXXefX*0H[dFZCqX5[pI[NI6HP6Gmqm'hD'GDPhU [RkYQ9T[$p3X1Imhf(T*pJ4CqDpeKIK!F[PGl8l[-2KI[,#Dhh+5q30IVmj9XcI3 $@9+,N!$C!i0p$0,SBqHqkdFfDP012R856DqTE#(,8QlRAREZ[C!!Y@bTfJ2T$Z3 +*'!e06p"qI,4YQ2B!QQm&(+(@&qL8E$KhlIcAIlX[F@*"-,iGaGYLdh6jh`Crfl feVCEDbrS0,)Sk)m54KiXF9009J!-V2AA&'@NFU#Tm8'bfrCP`9"edf(hM,$4L1- @-k@fY)1K6HA+kXUCCrT%bmV9-+f&MGblf1d)URhpKe1R(S#eZH81@q*0FG+SGpH J,22,P@lb92D(Umk0aYI8hpV08qLiaCe@&M8G`5DN,Y[#1CjVp1&FmK`YYf$1L9l AMaa,"SBhEPB@(CYk&aN%%9Kl0++T,NJ5Ih[VY'q29qcdY"PXVJchca@!MN[8Jdj UAiX,aPm[lY@#`2U`jKCH2&N-bDcQ2Ef)KZH63Y22P`UDh6-@V"ebbC3V$a2EN!" 4JJpNE,f'9aT0"4N[1hZa908hKc5PS6UD3jV5m)V0)C12EQMSqldCDC*NpNZ62l) M39)TIE%[QG!2@p1CR+3$9k"Yl54Mf$E&mQadYHQddm5eVKjPYEKKRShe$KiZXPB UcYTJbPQdFDbCmi#9aT0)XfR1ml`jcqBj6q)@2)Eal*P2ZFGbERDAiYJDeleM4mF H`$Ki!12K!Bb("c!HEQ-mA-CiH!$M5Gb#"c!HRYYk%XRe8iM,[!"iZUf6Cpqe2@c $AGILZUQ@GV*)3EZT-JRDJ+ZTiRHT$,R,MYT032r5@U--6QT)mV*Hf!ae(BENlA5 T(LBp#kQfNfCaXhb4Zh4lR066S+G@)j-JrHiYD6!@JTk3!+IG@G5D5Z&5d#Tj*Zf GdBNDGE5mh8QB"VhD%(5$5mSqCm$c"KD`P*ab6HeH&LBYIj'P9Dkic6@#JC(bF"Z XPh9hGJE',q*CV6qHPRHVMHYZ[@[P[TP4UN@rYV,iT54,2#SZ)5,Q[$2HbXUAPV$ B@`Yc5pab3a*hfX+(RHd`59dLZ,SaE)a2(R*S!-0"a@k-+"KG90ZR*l$LZMlCPJM qV'ZV%rEK%Dh6HREj))Y'*0f8K*1UD(&Yi8SEQ9qB`!J5l(468BbN%E$F!JUl1S+ SJeLmYPCl'EhhcXc-KrVS[56Z*$9A(NXF,3,a!VEF5aI2R%Bm33%Q+R46F`&-GM5 L@'jahG!*bAU,5Q!RLhhDU'@p[B1$clrAaM"apHITLeP@T#',af*p)XK9#+'9Ifl ,!MAF5E+YFEB`rm660dl+P3VcbCUSJ59,KYkYPRh@1`ehlEr`9XkX64S*MKr@-Y1 X@LS,C1L2*F'paJ9@5#eTD#eUXk5dV-L[2`4X6`9Kr9E@M1@l&mZJ8li(X$*QS%6 $%Gk,VE#iMf,l((`NU(2q'VE[a&Dj!XpLq`9[r$Xp)rGRK9KmJr,he-0EP0C326R 1%bVT)U`25`i@+QIe9h`XhbdM$A3q9RJG'2bH4LbShm)hU*dV,@SA"ePK9IYHcQ' )q@4PRm#E'r"bmE(dh'RXc5ip)HTfI-0q0MLCqP*I`$GF1l'+lh-F+VQS[*&kc0H DZYV@Lch$"#-'3Bm)lRJ,F8(G8eCFr3&E!J@erLUV`C+$qM[,Vd`De*,KCr[pMi* kVA1#%fqLI64i&Ejc[4i8[)EcHGVi6AbmdrPqilIJjr"GB(Ik,!G@A-A(ficLIP* 6`Ar$0pSM48Je1FDV6b&KJeh*0D5&jKP2MBmPbPNi(jrFd9[*0E5$`i5HUXC5K+l %9leqFideB%-I%,B)S5I(UcR8j*X3"CCSa-3NeB[ihXlC6UB+#4XZ#Zii$8`+I4E IaG5(e&Y)Up8bAS)FRq%`KIH6@JSpM'qUAi3PT(I9qTMiRZ6!Zq%$5eL&&VJ$5r4 VL!p-B@PkI&,EHM&kI+a$5`hL3beP$9amV"2,%[2iZ!q,bZ160pI,b10MZA8@MXF (AK+,$qaKFAKm9"M,`H0l)3I`#KmeEAZ-`X*1&RR(pcS1LmhhS'%b[SFiX*`l2M# *"GcaIB3$li!2!'@4GRaIi-#bl2LqaB&Da[F$$Lbp$J@J2X!FI'!Sq)N2hX##k[M !!TC3adFGk0%KiZ-GHA0ma,!`1Mj'V0Q&Db,##4BrahFA"`hfiJ-EE$Y,4'r-dZI ihX@"2LamImV"$Lk*h-2"-*(!mF)!kJZI`%)!h0'qTcR!Ck!c[+XGp4CP[9h0Ar# "`CBb4B9jGP1qU0E,K52J!hGXNdj8p8mM'6lUdfUe8G@2V!QXIEb['Bm51%&[FC2 jL,'E@dA&NfifhpdFU!YmVq"JPGbS10UYjRXV"pEfaFGUZq!'2M$b0[0pR)-G2K- 94YT9L+1UHcJ1[LFi[-Kmm"+`@VkBq-mGjQ0GAeB3aXGk[#mf(eJ'4Z'MYPRY&aq e4#hMidej6hcFl4lc`32fQimhVMFIY8iX2YB2&PkNIAr&!5k'MbfU`%0m2q4`RrF j,MkNqG%LL(i%[P%(pMh926[lK(%(4pXh&M'Q#4I'(0Mh$$cXG1-@MVC[($*'%bl FF@$INpqBREhli+*ZZ2#bI*ZR"Pai+AcKEE1cepkjL"XU1'G(HTYQ'HI+jjiEkHf H(c9C0fRp@$*UXSl4cmP4NhA@`Z8dqDaZ1NVVVGSb5(kf2dVRS6*HhM@h3'YUXmC i9XP$i,e&([TrlL[bX*lKX0IbR&K4I!kBmI41ZB(6hQJX2F"TmT(KA6CSpT!!"Ki DTqR(0-2fc)3Y%jFFf*@)5)hY5S5PKddP3QfACb+!k9kqemliLNFN*RfCN!$8[#m 6XLaUMHKa8N&lejahj,f)Q$96Fcb+M0p,ABV3YMFM029Q"(L[j$EkJX'*'q,@XI( 3DQLSZJK!ac$MPQ6`90fV$FMpr&bSiaq0,02EEC[RFr`HYF2[YaP"Z8rQI2+`(6P U'qQ2jR8FZXHmVFPN0rECGf@`f)*0&CYRGE`q&Sq1f)bXirGBfVGjV(YLBGHFjLm eCEEYdrIBQ$ArVrMe2CL5r&bpkRX8X*1TZ-9[q8"9Drhm&Mp!i194fqD9(ApA'4& YCeJGVrZGD@)c[SlIJfp8V3&,2[kZqKjk*YlaHaKqcqcE0VScC2%*TV0JD0!Qhq@ qaLR3"epqdH#2%1E`2C`"$'AKq4Iq'$NEaPQhISi+TP2!cY3SlSjlR(rQBJ(hk(b 3!*rHS`1!Ihk2)T)IZBFXd["rY@qBkI&kKERVHa4HSf(UH[ipGM#)f"&`Ml@[[Qc aA4P&k0fM(BPqHSmGVl,6iV['lq%%B1lk(VZF6DEmcHHHB0`bK,KVL%VMV2r@EeI @@56J(RbM+**Rhj@4UVibIc"*E2%*jZkSB+C1!14d+b$9SDpVKTEl3p14Tfelq@F dRG`ph#DUmc%Y*PXG[p"LZZja9)[TZXG4,DEl(NHdQ1)ph!YD60Ylr%U,b6hX-eT -m4j(YCLfDra5LqQkac%YjZ"&A!4D6'F1ZkG(amF5(m@lTKIpD2kF(jT1&SKqSHR N%GmCS1N8aelfV`aZY&Rj[rRSVh&2G&'i!lSIA#IG"PZ1-5rTHU@c+0(lf[$&'BT lSQ[KHUemHI`cpcL1`('QA'$FrriHlT[ZKpf,!Bb'PfE[FHH8&lUbddAp(ia!,Y* 4dFFB3GFpMM+#VRXF4I,ZHaa"mZ)pMQP9EIIiP9D9Hj!!Cj!!XhL2SmMCGSeI)QI A2BjT93F[%Vp(3Bk4HqKT#Zdjc$hd+R,CMdZX5ifmjr1i!638IBr#i'4INFJMQYa $9R&T[mI#LDdr[SGlGUTjFY&!GmVRSTYUIZ)10-f'lCG9[M0B%1@PC('jb4BXPb0 V62mNAeJj##l)8YH6JqdZQ8l,SeRBUSG*rG!+km6+hLakRf`4GkkRdD1ZlT*&bU$ %ai@Sa`AGdV@NdaEAF,Qa"amA3MNZ10b3!+$Kmh%KY[MXp$`VRlK@#Z#%Gf%R@2M !#@E#BUNR[K(kL&kLA$-LGK9hc(d+@0JD4Uh&RUT3aS[%1&ic[d9m5Vq)1b921FH ZEH-PLVNP`iXm[6QH9Z6T`h&SNBFK3*9&R[iFVhiT6hbVHiNkLH10Vp@&5$kIi6# S9'V9e8NY[X&C0@3D6@JP[L%0laZV0icJ8k8P'j3(Rm3a*@EBdVk'DkB-[5*SmhR ,H[XUSkA5H@[bqlcZQT-ICjPLh[BRXrrbR[RhNErXZlH@5YIZprF1RM`lqp4Q3Xr %MX5HK@AKBZEMMH$jcV(h18re+IB-,$1&,X"HL"f&(8fH-CDR#MX11ail!FZFQSR B'ZcTj*PNH5l#ASaP`#*,)c""4pifm'lXD[*iGF'XYKAB99LQK6-CD41@'YK-RLf @"dD`(FXXTaZ`G+%c*BJ"3#[*XmIbh)6GLi9C#Xr81&CLY4NQ6kRP!EGX5@YX!RF UX1"+Ar,dXcc81h8f!(X+pP6X308f45jj"PZHSF)YXFa"C-)4%hpRBDGJTj0RYZ@ j&(X*PL%YFl(cX-`eBi,b![*S("!I`iHZ`#l'-U0a+IC+l&ABUH4CCRQZ`5iAAL5 "5*Vf`cNJbDXE[(MYRH!&G6!02"J[j%TZ0a,2drMa5)(JjV1#kkkrPraPj"'mN6a 5%HSA5mHVU'kE9I`$`L2UMI[GAb*4iMl[2QFbrfl$Ip[Xi92LI4iiDal2IR$l[q2 C!ffl$r([1aS[q9qQr2(h14$r6VZIi1*69j)RD(Pi3M#!HZ(p)PerqYAbrqZ"pN0 acXqHR,jKG!#&EPXD0eajU02)SV@96TV&cI*&lY,YF9*2AdJa[9Cc+(Z,`Qc,Lr& ,3rS!GjG(ACC&VDNA8ma'%U`F#Y[YPa2-k%50HVP[*NYJcFha6!QSMHYZ[@Zp($i c5K%Jec8RT$kfjE"#69[dSL6HkQVCLf@LUA&Fh-aQ@"BQVCIIZ,Cr5+T8Fq6FNI* `1iZDdBlrZ*ZH#E1`Nl8l,ck1$XmrRSkrDZL&8,q68`TmQfZmN!$%m[RfelFTfTq ITX"m#40H,''rVl!Y@MSQXU'j!M(p,,N#N!#qQBlAed*&4`C8`[cMqb*L@l6hH[C FQ52Ur)LpV9-*UpG-(i0d%Z3!RHJ11#G"(X`*cd%jd9NJpi*c'1"PD#N8,lDeb(9 4j!%m%Am3[h4m-hJ6Pi0ZSV2!(3r12PJVD"0BJ'a5G!#fRb$hh&Q`pS)lbc3$e&j X'UEMJCPbb%%dd6Q!*VSGRSR2JV-AR(R+*$#RJZ@e1Q'B"$NB*VS$KNQ3!)GK`R- `6(3@KVhJA(el'9U+`S[0&88HKK2a"h&*acI$-(%j'#Bk#m2ai1b$YF)`J38B*N8 ($2X*FXqGK@%[Z,0--c$XaDCK1"kB+BFF$"1GJf'Lff'Bq#`-Hm'CTmc#m1bHX@- `6))F$"2G!F-Nb--`i6NB*MS,`ej`VVkp$#e&iFAQLL)2`iRiJlLNijYKQ,JF$"1 GKH&iF2E"@Q'B`!)-Nk)$K[d%ZHI1`V!Ah&QQ'4MfBY-`(!r-P%-1KSR1`6$4l6" -I"D'[H$-8ak!i4jCULK-p8K6AH*88CiU#&5(*+T1NDSJ8h8,9Ge598kX+XT9Km@ P(RQT4f!U5%b(4+C1Q5N[0"@NTU,B9*5EHU5IS[M6)rpd#8"&#DJJ!Kf5J6U&S)) 8e#d'GFY"18'S+!NG&R"k**`H%DFJia`5FMUPR,bB8j!!FiU#6P(5kC&ALJ*,Mm6 5*E)8CCD#d(*)DZN8@`Tb5lIJdLfjj%5ASZab@#6TN8PkK*+#9(*),1Q85r+#58% b+BSQ4GQNd-a4E1I)0h4d0bJ8@K3kQa3+E3S&GEbSMqF9mQl&Yk$jGUUq"Gfh)$F @"FHmj0JYS49%Y%iC,5ZN0DmEPqCF,6P5T+XP342VDXR43,YD-U4i9dZ#*2(+a+F )35D*,CG-H%Hp0*!!Veb+1,&T6@(S9dYSLRqe*%K#86Dqp3RR,L33-'T*IS$GY@4 *%V0-I&FKqY3X%`ihbmEQLc6&cPS5T1KC-X&5Pc)IDfFmPB609l%aP5J,N!#T,1d SQ8U8JmT8QLaHTV*N3$1G*&Z`k8c09C[1d9+H$F@9!p,Q2+eSfT!!j`#NTZ+cZ*V +NJ(A!dQk(VJ&CP-*1V%fP5S$Z1NN4iUi$AV61CV`pd##BK&NN6L9*3[(5pU1$@, *e3('ITSX&2XjfS(B6j1$B6p*&S6p("N)ENb4VGh'2-f9fjLKT5b6TC5$hR5@9Z" 0CMN!Zhjd&R6p("R)E8T4H03@Z2A$1m(@6j5"fXB8KiUe$@BE-c5"E&0ijl0R!GE 2NB9AM`l2G"1GU5QAP$dbYZDB29CpK9$VkTXIYUBk)A1m-aPQaV9M[,e40XlRk*! !9aSeYCB859%Y%cpVTZC!,e(6mLr[bfqCm#k@e&jrU83(Lr!!l@bYa(5D6$Nf-,K m6EBNkQEUqHT-jFQ8D$T*4jfQXa@*EkCLI8"YVpFNJ@bYeLE1dPUVM8NbPCSN![N k2C!!TNMVmMAUCmP8D'1+M[TXc0A*PlTVFffYpTp5!CPke!,5("%-b(4)-b"2Mfa !QL0!3kCZMF(,dX0F[93GaHJPkDR(3hT$)P%"Qh5LGY9"ef%"HXR4)dk3!1B35-I cG1JBm5cG*A-%p-Pd60JJea'1i'IU+F9Z[F6,FK4iZP36,dP11)PRk+UH([Q%0$d #*@N1XbB5G4-R,mY4lZ5PkbUFBMr3NVL6e&aj,('-`Zc3LKCh@S$R3E%(AM#R%8m !)Ch0,2J1*q,G1K+P1(cf&3r+,Le2QHjSq1RYZaieV6GdC22jErCG@m5*PPGY&)U 2hE[V44XjHMVAXYlH`F(RRmq%0(&e%jALj5DNLB1EU!DqE@*5h0U%T'ZL-Fc5Da1 9T0+*L"4l5B5e[E62RK-"(ZlDPfpJaFQJ11fb3BEY'ZF"[QILNM`X%G(mDUfeR!K 0[CT(Xha[kJe6p-Q%T+L5#@QN45BS5B%5%@QkN`KYHc1Ip53#@SRIaM"ap9FJe-4 hL%FQ48ie-[%GFT&*NGH*6)+F3'6L1e'q-8H,HQ45C'@M4(L1YLGbG(+$4*i@HTU )cPGYZi58c("3X,!CQN8M%eR38%b5V0#4##q@5MH05Z6*P8UVpZ'(jUSSThDBq*c -BH,Ep3f6)5YX*-)l&Be%RN1&NT%m%Y%(Ud(QDVF6!F)lH3!CmM5!m%i@3)Cf%N" mRJ-3AU!!U45Y$)!-13,J4HG"c8Y4+&8[6@ZPHX'j3Zh!rN5#0ZMA#9U3!*r!)[# 6)iIlAR5K3RT3hdZ6VC!!JjJIMmc@6Kla#Fm$2Z%GH%q#(0ald3@dpp,de%F@kle JV`UF5CqkYUj&(,HQfC&Y*BDcbH-T1iPVh+PS2PXTaK6AZ)1R@XAK(1,BcY(r2[A (@Lc4h#rdMr0aRbG1hd2ZPi`V[B`P"HcpNR'Vf1kap-&QPZpiP0bNmX-FRLD1R3r mZ0$6(#UE@ASNRp*D"[DGN[IVp4AZFN'cR4k*@r)P&MqFhmbQMVa6@&XqpZAH[,G r[jRD&Z,6cDc"HbAM*[`CprZckL$cQ82Iiqhe0TAb$IiDKe(YK+e$3RGp"KpE9FT h$aZaK!Gmh2YlP-2Q1eHA0h%2[4IID"phhkFiR1RMIU)FGLAr)GTUXFc(rGd`2MD [*0rM(1l0im,R2S52E5cPZeml,mccq4l8FMKX)mQ9EeqS*hq,K'dIeM+9YLlpZ-M 0(2CCAITaBH'%9cGqA&5EC`keZ[(MpYdbAamIdhD6GSZ2I3r1emFfE9eTGiLkij6 jqRMdER`,I0aGfJV5eXG,@IBaS+dhLI[#I(eFYaiI'e-5pr@jqJMYH4)I@e6+GlH fac6e%4j66E+GTP6kcSmXe)'r(QL81SlAJ4mArZK#25AcIA#K2[bi'2H,eiFI0dL EE*Vk#&qKQVEiqe%``pC(q'HZ*X$Z$R'UDXM84rJbl49P0h9mA*KNkL0mP6E*[0& i!29UkL0mm4I`X8%RqIjk(Mp'DKm0kJ+mCpF*@aq[%TEFl12k#*pUmVLq2a$'[F4 `p`dFH[Zi4l3Tk*hfVYqIbpG[ac*m$rLiJF*eQqq*hIM),eqrlmlIljZ2iRZ2MqZ [c8C0[RklKB[[pA&pYEQRHHr!pi9re"h[)AK[hcY`81rdQ)ql6(R0H`Iq4MjUJAZ `SjTjlm"REX)((`'R(jkrhkHI`XHpL101jRiUqD3+eF60qrVmrCl8Qhl-lXe@4HC qUNGepR'lamUjq`9M[4re5(f$8qCq`6F+Ym!%q9CqFqjq`HekXdrjZ$fU$h1ri)l 2iK-HT2%*(Q0`)R,5,q$lQ[%RkRb`MhZ0DSLkNqq2`0Q5YSm,kLfqlH2HIJHBGV+ 2Up,Z(YraeiL#0j9rjH0@LdYqeqjh,cEQmH16fZ6dHmEcK'F%l"jVK5AJ!h(ic(Y (hUQY8rr*iUM4#Rq2KiAh6eUp2U6,QlK(YENV03,ZISR$&"rhT,C#IFTi*29XlK' jm6&m2c4FNHeFe8-qVYHr%N#0JarJE#prMbm+qm!Vm!Rq8HAcVEJ0(cRPZi8kL&4 ph$4aPAme(JchXMMp0['-Ic-H`#DTDqcHi-(6aVHNEVckZ&YFlNIfIY5cVBpc2SE [aclZC'fiqL&r[fq)GpUY5%qqKhFGk11qqNrc&,C#m3rkHfb$0hLVcFD%XjCrI"q I-ZifK)e6JeB(Ei&2H#[6KS9h&PGQ82G+'#*IL6BZ+S1AJcXq[jQ+$K%U+epXpkr aCNVEbI*dXMeV++qRf1i9TjVq!-6GKkq[MkZ(@bYK[RbIH"rjq[ZiNm!QTBeQ`8Z iKHB0XGhh8@2+0Q)j9CaEmiEBqjk&HbPY2-XlNIF-Rkm5,&$'ef2S&UEqBVY[)*F b[PkLYp4i'G[p1Ef0jHX$a&%d(XGfGm(2e&NqVPceq6`Ipa%`5)%cZQlJ*b90pLT ,e)Cq*p&[!KpUKNGhAbX4(cEhVYr-9VNUhrK9q,&XH4YHjq1Q28#FfE&CR3l[-$J GfhX&@mFUEE[+m`S1'Cb1lAN%A&6#4[P1S`i-6XIfDQPETHeKjCXcAkreer2f#Vp q$lQIVBrkc@b4Ue4he-dr(-+Rf0irr#*aXTbXcJIqDGb0lGd%cLREb(B''mmDr)l Y$H"-5Y[CNNq`bZ"hE1m0ZJFEfa)(cl6hH#Am4QQ,@qLmE'aVm$Zfjbkp"j[Gb[F DX0$L5[e[`Qf8YYQ&(m#A0*l&pYccF@'-LGXXc$"i8lm6V85*Ji$Im(jlMhI`4SS Dj(XGA-6JI@c2RGmLMXf#jAX6ZSc&TrSEU3QPlAQTErL$a[[BRZpG54akMRbRL0F Ch0Vr-&LSE11AQ2L*`DhpGi%T5TbD1RMPI2fK"5MHAlp6R0r(p[kPhS-YGZ8E*Yc 4r##fG`IF58PMNDm@hF6`JpMH')kUT)2"ekN6c3pLHem(Pe2DJ"IHrm3KIL$i)4L Pa#APfiM@B[L"h%0d,28QidrS$JCIpcm+*e$D(*JiH*jp[dAS@1TYaZGiAiYc`IZ *XlVA32&JJkreTm+TP1PHmm!2Lkre[`kI8kClKD89fAZ-q50mFKGJlA,UfI$Df0j 2#!2Bh&MLVJ)$,&lZ[d[Da'Gph%e`"3m[[k)D%icPQi9qjZ(PD186rBV[IYl9`m[ TUPIi(Hq"eQMIqir&rG!*LB2[fATpC!mqZ$rm$lbf1,G!R"`p3Ei9UN@,Pkq(f`@ NGj%2($&iZIpCm$)JIB!ikX2HBk5`!&iXheid#3m[2kbDrRXIYdFkVF@jkQ[*`CE $8$MZBqYl`+IaIGR(E8AImI"bJIL0h8`iV$UhpbMrAAaImA%28cm@,qY6m@2"+,l h``-p[(`[R&qCRMTEqT("brTedP$J&q#Ik+mHAMi'Pe@fi9G)R0lJCAdGV89*2k3 1@1h-eTr@@RKciQK0X2AhQ',3#H@l9RU"`F[pMdQMK0m"0E)KQBHA&G*DV)Dm5hU (aFYDD3bm!lL1CQ$`X[l0dNVJ0I+pkX[cH$Ri6`K!db$IQqIamR%d+D80RAQR""q Y[dEB)abCEr!#AXj%Te&k%rPZ%kjB[0`RIX`fd2!Ep%',Pj23m*9d@RJ'HN1*jrF 2`1(9Z)rl!,JFbHXlF0a"Z*Db4@0,a31Y(V!6M&#f#QaIEE&XiL)R`&'9l8Sd8"K QimD+hjKGM03PB*9h[he`"LAq+mGedS00A138DPMC)T,$a5[X[GmRhQ#hqjUR,C* Yh2IJR-TfcC`R,,4a6iQAQ&fE9%6GH[Hq(Crb1U[DaqE0hVfMD!E+G0D9iZ[fr5k rN6LVXki8cl$hqaal-bRMlCY9ccEZafJJbVDT'b3F2kNC(qfV#1FiC2@5Z&lqr`d !"8PMEfi0D@p38%-ZE@0`!*!4H$-*KJ#3$iB!!$q)!*!$&J#3"'PMEfj038063!# eQI#%YCR`K!!!!HB!N!B"&3#3""F@!*!)JE)4!!JF[-XKL%Vqkr)IQ(qRr&[Nla6 hDYcMFCFjq"YaTmHeLN[2GdpFcRaEjcXYAk0m21qMq8VPh6[[,ARVj-h+FeLHar* XRZHi20im2E`Aj5RJ6I0HR2Y9EqIFjhU,j8l,I9eZP$XeGilF20HCZF[QV*!!Frm F0q9-c,&GMVGcP-Pa@1aj14V(GSQp1qE4f25BR@-hL'NEFkrRVTMdQ%GLYiSj-,D qXmMT%0kG+6`p!r+B3c"H,b-CF*54$%p[fY"-D$T(Emh-i#$Rk@eK'$HR8'ZQ(!T cYqY-%A+HIQQ%K@(XR2`ZdLmbcpr'-b3U-8II58AYQIfMclTCrUa,aK5GkLJ8DZY dYLC!(J4+k5cqV!ZZX3#3!`d0$&*&384045j0B@028f0`!*!4KZB*KJ#3$Mi$!!" $(J#3!aErN!4849K8G(4iG!%!VZP",E@CpXm!!!'X!!!%5`#3!md!!!*C*YQfB3# 3"M&!%3!)(1ad#')JLFI%1Rb*Jij4$SQpPB0bK,62fc8T)G'Ij216P-l"P'KkQ)V "9!65Xe)GTD5H!!J3)B2Y3fARKLIIKp0[UTHYN!"1TZXCRhC&(mcFBpDR'HL$*'B bTRkDJEjBKM3rJcd9[(l15!U(i9GF#&#aYR)HIf+0dbT$lV2bl"JXUUC$EZHE"al 2iakBaArh`'Q21'8UXp'm)bLH6D%Jj*ZT4*peCGa0ec5-ImP4+,3"j!m%ZqTYqZ6 3G)IlBUBS06Yib'K"cmeeX!8+SJ#!)!1BM"JC-!+`$L,X(*!!!I2)5Sk5@mQapmK 4)jc-'!()lfAi9c`$q#LHiJRk%3`%F23LI'9'RJd))mKJ"$&X*HX$!2R!j-"86Di hP&T,(j5qZD143lS3BL2ThP2PI'XI5AP$NipTUQ[5Y8[4mDG"DGPqH44E5H18D(G GRYhHlZ5J4h+q)$f2NA("kX4KNAR[25[cBc!alGMBre3)MJ2G"UBhKAa3RC@#L!k YLdqqiJYGlkUJ`N*eRJ92AV,"NXVII@4++M3fa6*3F@STdf5#C'1L-A!6e&"Bmk- i@SYrMNqP%#KPLKKmkB3@RNJV6mT!k,10E!"'#$la2"5TmcHT!P5IN3-mIH58Q96 #$lKPhbr8+NLf&J("!%4ND4QlQ0dA*j@UqNA-l&mPQMPd'+DhCae+d4&89pQj2k, C$*%#4#&Sih`6FaYB&mlVIM,@B(82mSImU#!"I(BSTA4f0&K[0Bhpe#"`31l*&!Q GjeQd21Fj9$,cK*Z5-6*A$6f@S"H0)mXXp68BV-T,`8&6,QpI3ZUC1dK"1cN)G&H &59X*!0YIFKAPj&eMT@k&q-$'e8kVj0K(+E'e@DqffpAjf3A41$Y%i#mMa'&E0V( @dUZSC--2ZAjm2YUCDYGEa'#3!%!*lImdGq`IE-J"rMa-*q8HPC2dkekJ#P#K3!U 6KaS80)bpFKkS&H0D+KTFM!Makqek*cDEeIVmVlaVp`MSlqK9EiHF[1p`(jN120# 90cC%kk9BRkifTk[eqXJhZ,jECVAm"V`IMITMrqNXlklhKl[p$@hPqZ48LRmK)3Y MCQPdFfP[AfeKB`#3&JQ'!4B!&3+L!GB!N!-"!!!rL!#3"aB!N!1'!!!$([q3"!2 JYCR`K,@Cp[N!N!8"mK%!N!C#Q2r`rrJ!!-0!!!!3DBZi!!!: \ No newline at end of file diff --git a/external/cfitsio/cfortran.doc b/external/cfitsio/cfortran.doc new file mode 100644 index 0000000..f2230b0 --- /dev/null +++ b/external/cfitsio/cfortran.doc @@ -0,0 +1,2088 @@ +/* cfortran.doc 4.3 */ +/* www-zeus.desy.de/~burow OR anonymous ftp@zebra.desy.de */ +/* Burkhard Burow burow@desy.de 1990 - 1998. */ + +See Licensing information at the end of this file. + + + cfortran.h : Interfacing C or C++ and FORTRAN + +Supports: Alpha and VAX VMS, Alpha OSF, DECstation and VAX Ultrix, IBM RS/6000, + Silicon Graphics, Sun, CRAY, Apollo, HP9000, LynxOS, Convex, Absoft, + f2c, g77, NAG f90, PowerStation Fortran with Visual C++, NEC SX-4, + Portland Group. + +C and C++ are generally equivalent as far as cfortran.h is concerned. +Unless explicitly noted otherwise, mention of C implicitly includes C++. +C++ compilers tested include: + SunOS> CC +p +w # Clean compiles. + IRIX> CC # Clean compiles. + IRIX> CC -fullwarn # Still some warnings to be overcome. + GNU> g++ -Wall # Compiles are clean, other than warnings for unused + # cfortran.h static routines. + +N.B.: The best documentation on interfacing C or C++ and Fortran is in + the chapter named something like 'Interfacing C and Fortran' + to be found in the user's guide of almost every Fortran compiler. + Understanding this information for one or more Fortran compilers + greatly clarifies the aims and actions of cfortran.h. + Such a chapter generally also addresses issues orthogonal to cfortran.h, + for example the order of array indices, the index of the first element, + as well as compiling and linking issues. + + +0 Short Summary of the Syntax Required to Create the Interface +-------------------------------------------------------------- + +e.g. Prototyping a FORTRAN subroutine for C: + +/* PROTOCCALLSFSUBn is optional for C, but mandatory for C++. */ + + PROTOCCALLSFSUB2(SUB_NAME,sub_name,STRING,PINT) +#define SUB_NAME(A,B) CCALLSFSUB2(SUB_NAME,sub_name,STRING,PINT, A,B) + + ^ - - + number of arguments _____| | STRING BYTE PBYTE BYTEV(..)| + / | STRINGV DOUBLE PDOUBLE DOUBLEV(..)| + / | PSTRING FLOAT PFLOAT FLOATV(..)| + types of arguments ____ / | PNSTRING INT PINT INTV(..)| + \ | PPSTRING LOGICAL PLOGICAL LOGICALV(..)| + \ | PSTRINGV LONG PLONG LONGV(..)| + \ | ZTRINGV SHORT PSHORT SHORTV(..)| + | PZTRINGV ROUTINE PVOID SIMPLE | + - - + + +e.g. Prototyping a FORTRAN function for C: +/* PROTOCCALLSFFUNn is mandatory for both C and C++. */ +PROTOCCALLSFFUN1(INT,FUN_NAME,fun_name,STRING) +#define FUN_NAME(A) CCALLSFFUN1(FUN_NAME,fun_name,STRING, A) + +e.g. calling FUN_NAME from C: {int a; a = FUN_NAME("hello");} + + +e.g. Creating a FORTRAN-callable wrapper for + a C function returning void, with a 7 dimensional integer array argument: + [Not supported from C++.] +FCALLSCSUB1(csub_name,CSUB_NAME,csub_name,INTVVVVVVV) + + +e.g. Creating a FORTRAN-callable wrapper for other C functions: +FCALLSCFUN1(STRING,cfun_name,CFUN_NAME,cfun_name,INT) + [ ^-- BYTE, DOUBLE, FLOAT, INT, LOGICAL, LONG, SHORT, VOID + are other types returned by functions. ] + + +e.g. COMMON BLOCKs: +FORTRAN: common /fcb/ v,w,x + character *(13) v, w(4), x(3,2) +C: +typedef struct { char v[13],w[4][13],x[2][3][13]; } FCB_DEF; +#define FCB COMMON_BLOCK(FCB,fcb) +COMMON_BLOCK_DEF(FCB_DEF,FCB); +FCB_DEF FCB; /* Define, i.e. allocate memory, in exactly one *.c file. */ + +e.g. accessing FCB in C: printf("%.13s",FCB.v); + + + +I Introduction +-------------- + +cfortran.h is an easy-to-use powerful bridge between C and FORTRAN. +It provides a completely transparent, machine independent interface between +C and FORTRAN routines (= subroutines and/or functions) and global data, +i.e. structures and COMMON blocks. + +The complete cfortran.h package consists of 4 files: the documentation in +cfortran.doc, the engine cfortran.h, examples in cfortest.c and +cfortex.f/or. [cfortex.for under VMS, cfortex.f on other machines.] + +The cfortran.h package continues to be developed. The most recent version is +available via www at http://www-zeus.desy.de/~burow +or via anonymous ftp at zebra.desy.de (131.169.2.244). + +The examples may be run using one of the following sets of instructions: + +N.B. Unlike earlier versions, cfortran.h 3.0 and later versions + automatically uses the correct ANSI ## or pre-ANSI /**/ + preprocessor operator as required by the C compiler. + +N.B. As a general rule when trying to determine how to link C and Fortran, + link a trivial Fortran program using the Fortran compilers verbose option, + in order to see how the Fortran compiler drives the linker. e.g. + unix> cat f.f + END + unix> f77 -v f.f + .. lots of info. follows ... + +N.B. If using a C main(), i.e. Fortran PROGRAM is not entry of the executable, + and if the link bombs with a complaint about + a missing "MAIN" (e.g. MAIN__, MAIN_, f90_main or similar), + then Fortran has hijacked the entry point to the executable + and wishes to call the rest of the executable via "MAIN". + This can usually be satisfied by doing e.g. 'cc -Dmain=MAIN__ ...' + but often kills the command line arguments in argv and argc. + The f77 verbose option, usually -v, may point to a solution. + + +RS/6000> # Users are strongly urged to use f77 -qextname and cc -Dextname +RS/6000> # Use -Dextname=extname if extname is a symbol used in the C code. +RS/6000> xlf -c -qextname cfortex.f +RS/6000> cc -c -Dextname cfortest.c +RS/6000> xlf -o cfortest cfortest.o cfortex.o && cfortest + +DECFortran> #Only DECstations with DECFortran for Ultrix RISC Systems. +DECFortran> cc -c -DDECFortran cfortest.c +DECFortran> f77 -o cfortest cfortest.o cfortex.f && cfortest + +IRIX xxxxxx 5.2 02282015 IP20 mips +MIPS> # DECstations and Silicon Graphics using the MIPS compilers. +MIPS> cc -o cfortest cfortest.c cfortex.f -lI77 -lU77 -lF77 && cfortest +MIPS> # Can also let f77 drive linking, e.g. +MIPS> cc -c cfortest.c +MIPS> f77 -o cfortest cfortest.o cfortex.f && cfortest + +Apollo> # Some 'C compiler 68K Rev6.8' break. [See Section II o) Notes: Apollo] +Apollo> f77 -c cfortex.f && cc -o cfortest cfortest.c cfortex.o && cfortest + +VMS> define lnk$library sys$library:vaxcrtl +VMS> cc cfortest.c +VMS> fortran cfortex.for +VMS> link/exec=cfortest cfortest,cfortex +VMS> run cfortest + +OSF1 xxxxxx V3.0 347 alpha +Alpha/OSF> # Probably better to let cc drive linking, e.g. +Alpha/OSF> f77 -c cfortex.f +Alpha/OSF> cc -o cfortest cfortest.c cfortex.o -lUfor -lfor -lFutil -lots -lm +Alpha/OSF> cfortest +Alpha/OSF> # Else may need 'cc -Dmain=MAIN__' to let f77 drive linking. + +Sun> # Some old cc(1) need a little help. [See Section II o) Notes: Sun] +Sun> f77 -o cfortest cfortest.c cfortex.f -lc -lm && cfortest +Sun> # Some older f77 may require 'cc -Dmain=MAIN_'. + +CRAY> cft77 cfortex.f +CRAY> cc -c cfortest.c +CRAY> segldr -o cfortest.e cfortest.o cfortex.o +CRAY> ./cfortest.e + +NEC> cc -c -Xa cfortest.c +NEC> f77 -o cfortest cfortest.o cfortex.f && cfortest + +VAX/Ultrix/cc> # For cc on VAX Ultrix only, do the following once to cfortran.h. +VAX/Ultrix/cc> mv cfortran.h cftmp.h && grep -v "^#pragma" cfortran.h + +VAX/Ultrix/f77> # In the following, 'CC' is either 'cc' or 'gcc -ansi'. NOT'vcc' +VAX/Ultrix/f77> CC -c -Dmain=MAIN_ cfortest.c +VAX/Ultrix/f77> f77 -o cfortest cfortex.f cfortest.o && cfortest + +LynxOS> # In the following, 'CC' is either 'cc' or 'gcc -ansi'. +LynxOS> # Unfortunately cc is easily overwhelmed by cfortran.h, +LynxOS> # and won't compile some of the cfortest.c demos. +LynxOS> f2c -R cfortex.f +LynxOS> CC -Dlynx -o cfortest cfortest.c cfortex.c -lf2c && cfortest + +HP9000> # Tested with HP-UX 7.05 B 9000/380 and with A.08.07 A 9000/730 +HP9000> # CC may be either 'c89 -Aa' or 'cc -Aa' +HP9000> # Depending on the compiler version, you may need to include the +HP9000> # option '-tp,/lib/cpp' or worse, you'll have to stick to the K&R C. +HP9000> # [See Section II o) Notes: HP9000] +HP9000> # Users are strongly urged to use f77 +ppu and cc -Dextname +HP9000> # Use -Dextname=extname if extname is a symbol used in the C code. +HP9000> CC -Dextname -c cfortest.c +HP9000> f77 +ppu cfortex.f -o cfortest cfortest.o && cfortest +HP9000> # Older f77 may need +HP9000> f77 -c cfortex.f +HP9000> CC -o cfortest cfortest.c cfortex.o -lI77 -lF77 && cfortest + +HP0000> # If old-style f77 +800 compiled objects are required: +HP9000> # #define hpuxFortran800 +HP9000> cc -c -Aa -DhpuxFortran800 cfortest.c +HP9000> f77 +800 -o cfortest cfortest.o cfortex.f + +f2c> # In the following, 'CC' is any C compiler. +f2c> f2c -R cfortex.f +f2c> CC -o cfortest -Df2cFortran cfortest.c cfortex.c -lf2c && cfortest + +Portland Group $ # Presumably other C compilers also work. +Portland Group $ pgcc -DpgiFortran -c cfortest.c +Portland Group $ pgf77 -o cfortest cfortex.f cfortest.o && cfortest + +NAGf90> # cfortex.f is distributed with Fortran 77 style comments. +NAGf90> # To convert to f90 style comments do the following once to cfortex.f: +NAGf90> mv cfortex.f cf_temp.f && sed 's/^C/\!/g' cf_temp.f > cfortex.f +NAGf90> # In the following, 'CC' is any C compiler. +NAGf90> CC -c -DNAGf90Fortran cfortest.c +NAGf90> f90 -o cfortest cfortest.o cfortex.f && cfortest + +PC> # On a PC with PowerStation Fortran and Visual_C++ +PC> cl /c cftest.c +PC> fl32 cftest.obj cftex.for + +GNU> # GNU Fortran +GNU> # See Section VI caveat on using 'gcc -traditional'. +GNU> gcc -ansi -Wall -O -c -Df2cFortran cfortest.c +GNU> g77 -ff2c -o cfortest cfortest.o cfortex.f && cfortest + +AbsoftUNIX> # Absoft Fortran for all UNIX based operating systems. +AbsoftUNIX> # e.g. Linux or Next on Intel or Motorola68000. +AbsoftUNIX> # Absoft f77 -k allows Fortran routines to be safely called from C. +AbsoftUNIX> gcc -ansi -Wall -O -c -DAbsoftUNIXFortran cfortest.c +AbsoftUNIX> f77 -k -o cfortest cfortest.o cfortex.f && cfortest + +AbsoftPro> # Absoft Pro Fortran for MacOS +AbsoftPro> # Use #define AbsoftProFortran + +CLIPPER> # INTERGRAPH CLIX using CLIPPER C and Fortran compilers. +CLIPPER> # N.B. - User, not cfortran.h, is responsible for +CLIPPER> # f77initio() and f77uninitio() if required. +CLIPPER> # - LOGICAL values are not mentioned in CLIPPER doc.s, +CLIPPER> # so they may not yet be correct in cfortran.h. +CLIPPER> # - K&R mode (-knr or Ac=knr) breaks FLOAT functions +CLIPPER> # (see CLIPPER doc.s) and cfortran.h does not fix it up. +CLIPPER> # [cfortran.h ok for old sun C which made the same mistake.] +CLIPPER> acc cfortest.c -c -DCLIPPERFortran +CLIPPER> af77 cfortex.f cfortest.o -o cfortest + + +By changing the SELECTion ifdef of cfortest.c and recompiling one can try out +a few dozen different few-line examples. + + + +The benefits of using cfortran.h include: +1. Machine/OS/compiler independent mixing of C and FORTRAN. + +2. Identical (within syntax) calls across languages, e.g. +C FORTRAN + CALL HBOOK1(1,'pT spectrum of pi+',100,0.,5.,0.) +/* C*/ + HBOOK1(1,"pT spectrum of pi+",100,0.,5.,0.); + +3. Each routine need only be set up once in its lifetime. e.g. +/* Setting up a FORTRAN routine to be called by C. + ID,...,VMX are merely the names of arguments. + These tags must be unique w.r.t. each other but are otherwise arbitrary. */ +PROTOCCALLSFSUB6(HBOOK1,hbook1,INT,STRING,INT,FLOAT,FLOAT,FLOAT) +#define HBOOK1(ID,CHTITLE,NX,XMI,XMA,VMX) \ + CCALLSFSUB6(HBOOK1,hbook1,INT,STRING,INT,FLOAT,FLOAT,FLOAT, \ + ID,CHTITLE,NX,XMI,XMA,VMX) + +4. Source code is NOT required for the C routines exported to FORTRAN, nor for + the FORTRAN routines imported to C. In fact, routines are most easily + prototyped using the information in the routines' documentation. + +5. Routines, and the code calling them, can be coded naturally in the language + of choice. C routines may be coded with the natural assumption of being + called only by C code. cfortran.h does all the required work for FORTRAN + code to call C routines. Similarly it also does all the work required for C + to call FORTRAN routines. Therefore: + - C programmers need not embed FORTRAN argument passing mechanisms into + their code. + - FORTRAN code need not be converted into C code. i.e. The honed and + time-honored FORTRAN routines are called by C. + +6. cfortran.h is a single ~1700 line C include file; portable to most + remaining, if not all, platforms. + +7. STRINGS and VECTORS of STRINGS along with the usual simple arguments to + routines are supported as are functions returning STRINGS or numbers. Arrays + of pointers to strings and values of structures as C arguments, will soon be + implemented. After learning the machinery of cfortran.h, users can expand + it to create custom types of arguments. [This requires no modification to + cfortran.h, all the preprocessor directives required to implement the + custom types can be defined outside cfortran.h] + +8. cfortran.h requires each routine to be exported to be explicitly set up. + While is usually only be done once in a header file it would be best if + applications were required to do no work at all in order to cross languages. + cfortran.h's simple syntax could be a convenient back-end for a program + which would export FORTRAN or C routines directly from the source code. + + + ----- + +Example 1 - cfortran.h has been used to make the C header file hbook.h, + which then gives any C programmer, e.g. example.c, full and + completely transparent access to CERN's HBOOK library of routines. + Each HBOOK routine required about 3 lines of simple code in + hbook.h. The example also demonstrates how FORTRAN common blocks + are defined and used. + +/* hbook.h */ +#include "cfortran.h" + : +PROTOCCALLSFSUB6(HBOOK1,hbook1,INT,STRING,INT,FLOAT,FLOAT,FLOAT) +#define HBOOK1(ID,CHTITLE,NX,XMI,XMA,VMX) \ + CCALLSFSUB6(HBOOK1,hbook1,INT,STRING,INT,FLOAT,FLOAT,FLOAT, \ + ID,CHTITLE,NX,XMI,XMA,VMX) + : +/* end hbook.h */ + +/* example.c */ +#include "hbook.h" + : +typedef struct { + int lines; + int status[SIZE]; + float p[SIZE]; /* momentum */ +} FAKE_DEF; +#define FAKE COMMON_BLOCK(FAKE,fake) +COMMON_BLOCK_DEF(FAKE_DEF,FAKE); + : +main () +{ + : + HBOOK1(1,"pT spectrum of pi+",100,0.,5.,0.); +/* c.f. the call in FORTRAN: + CALL HBOOK1(1,'pT spectrum of pi+',100,0.,5.,0.) +*/ + : + FAKE.p[7]=1.0; + : +} + +N.B. i) The routine is language independent. + ii) hbook.h is machine independent. + iii) Applications using routines via cfortran.h are machine independent. + + ----- + +Example 2 - Many VMS System calls are most easily called from FORTRAN, but + cfortran.h now gives that ease in C. + +#include "cfortran.h" + +PROTOCCALLSFSUB3(LIB$SPAWN,lib$spawn,STRING,STRING,STRING) +#define LIB$SPAWN(command,input_file,output_file) \ + CCALLSFSUB3(LIB$SPAWN,lib$spawn,STRING,STRING,STRING, \ + command,input_file,output_file) + +main () +{ +LIB$SPAWN("set term/width=132","",""); +} + +Obviously the cfortran.h command above could be put into a header file along +with the description of the other system calls, but as this example shows, it's +not much hassle to set up cfortran.h for even a single call. + + ----- + +Example 3 - cfortran.h and the source cstring.c create the cstring.obj library + which gives FORTRAN access to all the functions in C's system + library described by the system's C header file string.h. + +C EXAMPLE.FOR + PROGRAM EXAMPLE + DIMENSION I(20), J(30) + : + CALL MEMCPY(I,J,7) + : + END + +/* cstring.c */ +#include /* string.h prototypes memcpy() */ +#include "cfortran.h" + + : +FCALLSCSUB3(memcpy,MEMCPY,memcpy,PVOID,PVOID,INT) + : + + +The simplicity exhibited in the above example exists for many but not all +machines. Note 4. of Section II ii) details the limitations and describes tools +which try to maintain the best possible interface when FORTRAN calls C +routines. + + ----- + + +II Using cfortran.h +------------------- + +The user is asked to look at the source files cfortest.c and cfortex.f +for clarification by example. + +o) Notes: + +o Specifying the Fortran compiler + cfortran.h generates interfaces for the default Fortran compiler. The default +can be overridden by defining, + . in the code, e.g.: #define NAGf90Fortran + OR . in the compile directive, e.g.: unix> cc -DNAGf90Fortran +one of the following before including cfortran.h: + NAGf90Fortran f2cFortran hpuxFortran apolloFortran sunFortran + IBMR2Fortran CRAYFortran mipsFortran DECFortran vmsFortran + CONVEXFortran PowerStationFortran AbsoftUNIXFortran + SXFortran pgiFortran AbsoftProFortran +This also allows crosscompilation. +If wanted, NAGf90Fortran, f2cFortran, DECFortran, AbsoftUNIXFortran, +AbsoftProFortran and pgiFortran must be requested by the user. + +o /**/ + cfortran.h (ab)uses the comment kludge /**/ when the ANSI C preprocessor +catenation operator ## doesn't exist. In at least MIPS C, this kludge is +sensitive to blanks surrounding arguments to macros. + Therefore, for applications using non-ANSI C compilers, the argtype_i, +routine_name, routine_type and common_block_name arguments to the +PROTOCCALLSFFUNn, CCALLSFSUB/FUNn, FCALLSCSUB/FUNn and COMMON_BLOCK macros +--- MUST NOT --- be followed by any white space characters such as +blanks, tabs or newlines. + +o LOGICAL + FORTRAN LOGICAL values of .TRUE. and .FALSE. do not agree with the C +representation of TRUE and FALSE on all machines. cfortran.h does the +conversion for LOGICAL and PLOGICAL arguments and for functions returning +LOGICAL. Users must convert arrays of LOGICALs from C to FORTRAN with the +C2FLOGICALV(array_name, elements_in_array); macro. Similarly, arrays of LOGICAL +values may be converted from the FORTRAN into C representation by using +F2CLOGICALV(array_name, elements_in_array); + + When C passes or returns LOGICAL values to FORTRAN, by default cfortran.h +only makes the minimal changes required to the value. [e.g. Set/Unset the +single relevant bit or do nothing for FORTRAN compilers which use 0 as FALSE +and treat all other values as TRUE.] Therefore cfortran.h will pass LOGICALs +to FORTRAN which do not have an identical representation to .TRUE. or .FALSE. +This is fine except for abuses of FORTRAN/77 in the style of: + logical l + if (l .eq. .TRUE.) ! (1) +instead of the correct: + if (l .eqv. .TRUE.) ! (2) +or: + if (l) ! (3) +For FORTRAN code which treats LOGICALs from C in the method of (1), +LOGICAL_STRICT must be defined before including cfortran.h, either in the +code, "#define LOGICAL_STRICT", or compile with "cc -DLOGICAL_STRICT". +There is no reason to use LOGICAL_STRICT for FORTRAN code which does not do (1). +At least the IBM's xlf and the Apollo's f77 do not even allow code along the +lines of (1). + + DECstations' DECFortran and MIPS FORTRAN compilers use different internal +representations for LOGICAL values. [Both compilers are usually called f77, +although when both are installed on a single machine the MIPS' one is usually +renamed. (e.g. f772.1 for version 2.10.)] cc doesn't know which FORTRAN +compiler is present, so cfortran.h assumes MIPS f77. To use cc with DECFortran +define the preprocessor constant 'DECFortran'. +e.g. i) cc -DDECFortran -c the_code.c + or ii) #define DECFortran /* in the C code or add to cfortran.h. */ + + MIPS f77 [SGI and DECstations], f2c, and f77 on VAX Ultrix treat +.eqv./.neqv. as .eq./.ne.. Therefore, for these compilers, LOGICAL_STRICT is +defined by default in cfortran.h. [The Sun and HP compilers have not been +tested, so they may also require LOGICAL_STRICT as the default.] + +o SHORT and BYTE + They are irrelevant for the CRAY where FORTRAN has no equivalent to C's short. +Similarly BYTE is irrelevant for f2c and for VAX Ultrix f77 and fort. The +author has tested SHORT and BYTE with a modified cfortest.c/cfortex.f on all +machines supported except for the HP9000 and the Sun. + + BYTE is a signed 8-bit quantity, i.e. values are -128 to 127, on all machines +except for the SGI [at least for MIPS Computer Systems 2.0.] On the SGI it is +an unsigned 8-bit quantity, i.e. values are 0 to 255, although the SGI 'FORTRAN +77 Programmers Guide' claims BYTE is signed. Perhaps MIPS 2.0 is dated, since +the DECstations using MIPS 2.10 f77 have a signed BYTE. + + To minimize the difficulties of signed and unsigned BYTE, cfortran.h creates +the type 'INTEGER_BYTE' to agree with FORTRAN's BYTE. Users may define +SIGNED_BYTE or UNSIGNED_BYTE, before including cfortran.h, to specify FORTRAN's +BYTE. If neither is defined, cfortran.h assumes SIGNED_BYTE. + +o CRAY + The type DOUBLE in cfortran.h corresponds to FORTRAN's DOUBLE PRECISION. + The type FLOAT in cfortran.h corresponds to FORTRAN's REAL. + +On a classic CRAY [i.e. all models except for the t3e]: +( 64 bit) C float == C double == Fortran REAL +(128 bit) C long double == Fortran DOUBLE PRECISION +Therefore when moving a mixed C and FORTRAN app. to/from a classic CRAY, +either the C code will have to change, +or the FORTRAN code and cfortran.h declarations will have to change. +DOUBLE_PRECISION is a cfortran.h macro which provides the former option, +i.e. the C code is automatically changed. +DOUBLE_PRECISION is 'long double' on classic CRAY and 'double' elsewhere. +DOUBLE_PRECISION thus corresponds to FORTRAN's DOUBLE PRECISION +on all machines, including classic CRAY. + +On a classic CRAY with the fortran compiler flag '-dp': +Fortran DOUBLE PRECISION thus is also the faster 64bit type. +(This switch is often used since the application is usually satisfied by + 64 bit precision and the application needs the speed.) +DOUBLE_PRECISION is thus not required in this case, +since the classic CRAY behaves like all other machines. +If DOUBLE_PRECISION is used nonetheless, then on the classic CRAY +the default cfortran.h behavior must be overridden, +for example by the C compiler option '-DDOUBLE_PRECISION=double'. + +On a CRAY t3e: +(32 bit) C float == Fortran Unavailable +(64 bit) C double == C long double == Fortran REAL == Fortran DOUBLE PRECISION +Notes: +- (32 bit) is available as Fortran REAL*4 and + (64 bit) is available as Fortran REAL*8. + Since cfortran.h is all about more portability, not about less portability, + the use of the nonstandard REAL*4 and REAL*8 is strongly discouraged. +- Fortran DOUBLE PRECISION is folded to REAL with the following warning: + 'DOUBLE PRECISION is not supported on this platform. REAL will be used.' + Similarly, Fortran REAL*16 is mapped to REAL*8 with a warning. +This behavior differs from that of other machines, including the classic CRAY. +FORTRAN_REAL is thus introduced for the t3e, +just as DOUBLE_PRECISION is introduced for the classic CRAY. +FORTRAN_REAL is 'double' on t3e and 'float' elsewhere. +FORTRAN_REAL thus corresponds to FORTRAN's REAL on all machines, including t3e. + + +o f2c + f2c, by default promotes REAL functions to double. cfortran.h does not (yet) +support this, so the f2c -R option must be used to turn this promotion off. + +o f2c +[Thanks to Dario Autiero for pointing out the following.] +f2c has a strange feature in that either one or two underscores are appended +to a Fortran name of a routine or common block, +depending on whether or not the original name contains an underscore. + + S.I. Feldman et al., "A fortran to C converter", + Computing Science Technical Report No. 149. + + page 2, chapter 2: INTERLANGUAGE conventions + ........... + To avoid conflict with the names of library routines and with names that + f2c generates, + Fortran names may have one or two underscores appended. Fortran names are + forced to lower case (unless the -U option described in Appendix B is in + effect); external names, i.e. the names of fortran procedures and common + blocks, have a single underscore appended if they do not contain any + underscore and have a pair of underscores appended if they do contain + underscores. Thus fortran subroutines names ABC, A_B_C and A_B_C_ result + in C functions named abc_, a_b_c__ and a_b_c___. + ........... + +cfortran.h is unable to change the naming convention on a name by name basis. +Fortran routine and common block names which do not contain an underscore +are unaffected by this feature. +Names which do contain an underscore may use the following work-around: + +/* First 2 lines are a completely standard cfortran.h interface + to the Fortran routine E_ASY . */ + PROTOCCALLSFSUB2(E_ASY,e_asy, PINT, INT) +#define E_ASY(A,B) CCALLSFSUB2(E_ASY,e_asy, PINT, INT, A, B) +#ifdef f2cFortran +#define e_asy_ e_asy__ +#endif +/* Last three lines are a work-around for the strange f2c naming feature. */ + +o NAG f90 + The Fortran 77 subset of Fortran 90 is supported. Extending cfortran.h to +interface C with all of Fortran 90 has not yet been examined. + The NAG f90 library hijacks the main() of any program and starts the user's +program with a call to: void f90_main(void); +While this in itself is only a minor hassle, a major problem arises because +NAG f90 provides no mechanism to access command line arguments. + At least version 'NAGWare f90 compiler Version 1.1(334)' appended _CB to +common block names instead of the usual _. To fix, add this to cfortran.h: +#ifdef old_NAG_f90_CB_COMMON +#define COMMON_BLOCK CFC_ /* for all other Fortran compilers */ +#else +#define COMMON_BLOCK(UN,LN) _(LN,_CB) +#endif + +o RS/6000 + Using "xlf -qextname ...", which appends an underscore, '_', to all FORTRAN +external references, requires "cc -Dextname ..." so that cfortran.h also +generates these underscores. +Use -Dextname=extname if extname is a symbol used in the C code. +The use of "xlf -qextname" is STRONGLY ENCOURAGED, since it allows for +transparent naming schemes when mixing C and Fortran. + +o HP9000 + Using "f77 +ppu ...", which appends an underscore, '_', to all FORTRAN +external references, requires "cc -Dextname ..." so that cfortran.h also +generates these underscores. +Use -Dextname=extname if extname is a symbol used in the C code. +The use of "f77 +ppu" is STRONGLY ENCOURAGED, since it allows for +transparent naming schemes when mixing C and Fortran. + + At least one release of the HP /lib/cpp.ansi preprocessor is broken and will +go into an infinite loop when trying to process cfortran.h with the +## catenation operator. The K&R version of cfortran.h must then be used and the +K&R preprocessor must be specified. e.g. + HP9000> cc -Aa -tp,/lib/cpp -c source.c +The same problem with a similar solution exists on the Apollo. +An irrelevant error message '0: extraneous name /usr/include' will appear for +each source file due to another HP bug, and can be safely ignored. +e.g. 'cc -v -c -Aa -tp,/lib/cpp cfortest.c' will show that the driver passes +'-I /usr/include' instead of '-I/usr/include' to /lib/cpp + +On some machines the above error causes compilation to stop; one must then use +K&R C, as with old HP compilers which don't support function prototyping. +cfortran.h has to be informed that K&R C is to being used, e.g. +HP9000> cc -D__CF__KnR -c source.c + +o AbsoftUNIXFortran +By default, cfortran.h follows the default AbsoftUNIX/ProFortran and prepends _C +to each COMMON BLOCK name. To override the cfortran.h behavior +#define COMMON_BLOCK(UN,LN) before #including cfortran.h. +[Search for COMMON_BLOCK in cfortran.h for examples.] + +o Apollo +On at least one release, 'C compiler 68K Rev6.8(168)', the default C +preprocessor, from cc -A xansi or cc -A ansi, enters an infinite loop when +using cfortran.h. This Apollo bug can be circumvented by using: + . cc -DANSI_C_preprocessor=0 to force use of /**/, instead of '##'. + AND . The pre-ANSI preprocessor, i.e. use cc -Yp,/usr/lib +The same problem with a similar solution exists on the HP. + +o Sun +Old versions of cc(1), say <~1986, may require help for cfortran.h applications: + . #pragma may not be understood, hence cfortran.h and cfortest.c may require + sun> mv cfortran.h cftmp.h && grep -v "^#pragma" cfortran.h + sun> mv cfortest.c cftmp.c && grep -v "^#pragma" cfortest.c + . Old copies of math.h may not include the following from a newer math.h. + [For an ancient math.h on a 386 or sparc, get similar from a new math.h.] + #ifdef mc68000 /* 5 lines Copyright (c) 1988 by Sun Microsystems, Inc. */ + #define FLOATFUNCTIONTYPE int + #define RETURNFLOAT(x) return (*(int *)(&(x))) + #define ASSIGNFLOAT(x,y) *(int *)(&x) = y + #endif + +o CRAY, Sun, Apollo [pre 6.8 cc], VAX Ultrix and HP9000 + Only FORTRAN routines with less than 15 arguments can be prototyped for C, +since these compilers don't allow more than 31 arguments to a C macro. This can +be overcome, [see Section IV], with access to any C compiler without this +limitation, e.g. gcc, on ANY machine. + +o VAX Ultrix + vcc (1) with f77 is not supported. Although: +VAXUltrix> f77 -c cfortex.f +VAXUltrix> vcc -o cfortest cfortest.c cfortex.o -lI77 -lU77 -lF77 && cfortest +will link and run. However, the FORTRAN standard I/O is NOT merged with the +stdin and stdout of C, and instead uses the files fort.6 and fort.5. For vcc, +f77 can't drive the linking, as for gcc and cc, since vcc objects must be +linked using lk (1). f77 -v doesn't tell much, and without VAX Ultrix manuals, +the author can only wait for the info. required. + + fort (1) is not supported. Without VAX Ultrix manuals the author cannot +convince vcc/gcc/cc and fort to generate names of routines and COMMON blocks +that match at the linker, lk (1). i.e. vcc/gcc/cc prepend a single underscore +to external references, e.g. NAME becomes _NAME, while fort does not modify the +references. So ... either fort has prepend an underscore to external +references, or vcc/gcc/cc have to generate unmodified names. man 1 fort +mentions JBL, is JBL the only way? + +o VAX VMS C + The compiler 'easily' exhausts its table space and generates: +%CC-F-BUGCHECK, Compiler bug check during parser phase . + Submit an SPR with a problem description. + At line number 777 in DISK:[DIR]FILE.C;1. +where the line given, '777', includes a call across C and FORTRAN via +cfortran.h, usually with >7 arguments and/or very long argument expressions. +This SPR can be staved off, with the simple modification to cfortran.h, such +that the relevant CCALLSFSUBn (or CCALLSFFUNn or FCALLSCFUNn) is not +cascaded up to CCALLSFSUB14, and instead has its own copy of the contents of +CCALLSFSUB14. [If these instructions are not obvious after examining cfortran.h +please contact the author.] +[Thanks go to Mark Kyprianou (kyp@stsci.edu) for this solution.] + +o Mips compilers + e.g. DECstations and SGI, require applications with a C main() and calls to +GETARG(3F), i.e. FORTRAN routines returning the command line arguments, to use +two macros as shown: + : +CF_DECLARE_GETARG; /* This must be external to all routines. */ + : +main(int argc, char *argv[]) +{ + : +CF_SET_GETARG(argc,argv); /* This must precede any calls to GETARG(3F). */ + : +} +The macros are null and benign on all other systems. Sun's GETARG(3F) also +doesn't work with a generic C main() and perhaps a workaround similar to the +Mips' one exists. + +o Alpha/OSF +Using the DEC Fortran and the DEC C compilers of DEC OSF/1 [RT] V1.2 (Rev. 10), +Fortran, when called from C, has occasional trouble using a routine received as +a dummy argument. + +e.g. In the following the Fortran routine 'e' will crash when it tries to use + the C routine 'c' or the Fortran routine 'f'. + The example works on other systems. + +C FORTRAN /* C */ + integer function f() #include + f = 2 int f_(); + return int e_(int (*u)()); + end + int c(){ return 1;} + integer function e(u) int d (int (*u)()) { return u();} + integer u + external u main() + e=u() { /* Calls to d work. */ + return printf("d (c ) returns %d.\n",d (c )); + end printf("d (f_) returns %d.\n",d (f_)); + /* Calls to e_ crash. */ + printf("e_(c ) returns %d.\n",e_(c )); + printf("e_(f_) returns %d.\n",e_(f_)); + } + +Solutions to the problem are welcomed! +A kludge which allows the above example to work correctly, requires an extra +argument to be given when calling the dummy argument function. +i.e. Replacing 'e=u()' by 'e=u(1)' allows the above example to work. + + +o The FORTRAN routines are called using macro expansions, therefore the usual +caveats for expressions in arguments apply. The expressions to the routines may +be evaluated more than once, leading to lower performance and in the worst case +bizarre bugs. + +o For those who wish to use cfortran.h in large applications. [See Section IV.] +This release is intended to make it easy to get applications up and running. +This implies that applications are not as efficient as they could be: +- The current mechanism is inefficient if a single header file is used to + describe a large library of FORTRAN functions. Code for a static wrapper fn. + is generated in each piece of C source code for each FORTRAN function + specified with the CCALLSFFUNn statement, irrespective of whether or not the + function is ever called. +- Code for several static utility routines internal to cfortran.h is placed + into any source code which #includes cfortran.h. These routines should + probably be in a library. + + +i) Calling FORTRAN routines from C: + -------------------------------- + +The FORTRAN routines are defined by one of the following two instructions: + +for a SUBROUTINE: +/* PROTOCCALLSFSUBn is optional for C, but mandatory for C++. */ +PROTOCCALLSFSUBn(ROUTINE_NAME,routine_name,argtype_1,...,argtype_n) +#define Routine_name(argname_1,..,argname_n) \ +CCALLSFSUBn(ROUTINE_NAME,routine_name,argtype_1,...,argtype_n, \ + argname_1,..,argname_n) + +for a FUNCTION: +PROTOCCALLSFFUNn(routine_type,ROUTINE_NAME,routine_name,argtype_1,...,argtype_n) +#define Routine_name(argname_1,..,argname_n) \ +CCALLSFFUNn(ROUTINE_NAME,routine_name,argtype_1,...,argtype_n, \ + argname_1,..,argname_n) + +Where: +'n' = 0->14 [SUBROUTINE's ->27] (easily expanded in cfortran.h to > 14 [27]) is + the number of arguments to the routine. +Routine_name = C name of the routine (IN UPPER CASE LETTERS).[see 2.below] +ROUTINE_NAME = FORTRAN name of the routine (IN UPPER CASE LETTERS). +routine_name = FORTRAN name of the routine (IN lower case LETTERS). +routine_type = the type of argument returned by FORTRAN functions. + = BYTE, DOUBLE, FLOAT, INT, LOGICAL, LONG, SHORT, STRING, VOID. + [Instead of VOID one would usually use CCALLSFSUBn. + VOID forces a wrapper function to be used.] +argtype_i = the type of argument passed to the FORTRAN routine and must be + consistent in the definition and prototyping of the routine s.a. + = BYTE, DOUBLE, FLOAT, INT, LOGICAL, LONG, SHORT, STRING. + For vectors, i.e. 1 dim. arrays use + = BYTEV, DOUBLEV, FLOATV, INTV, LOGICALV, LONGV, SHORTV, + STRINGV, ZTRINGV. + For vectors of vectors, i.e. 2 dim. arrays use + = BYTEVV, DOUBLEVV, FLOATVV, INTVV, LOGICALVV, LONGVV, SHORTVV. + For n-dim. arrays, 1<=n<=7 [7 is the maximum in Fortran 77], + = BYTEV..nV's..V, DOUBLEV..V, FLOATV..V, INTV..V, LOGICALV..V, + LONGV..V, SHORTV..V. + N.B. Array dimensions and types are checked by the C compiler. + For routines changing the values of an argument, the keyword is + prepended by a 'P'. + = PBYTE, PDOUBLE, PFLOAT, PINT, PLOGICAL, PLONG, PSHORT, + PSTRING, PSTRINGV, PZTRINGV. + For EXTERNAL procedures passed as arguments use + = ROUTINE. + For exceptional arguments which require no massaging to fit the + argument passing mechanisms use + = PVOID. + The argument is cast and passed as (void *). + Although PVOID could be used to describe all array arguments on + most (all?) machines , it shouldn't be because the C compiler + can no longer check the type and dimension of the array. +argname_i = any valid unique C tag, but must be consistent in the definition + as shown. + +Notes: + +1. cfortran.h may be expanded to handle a more argument type. To suppport new +arguments requiring complicated massaging when passed between Fortran and C, +the user will have to understand cfortran.h and follow its code and mechanisms. + +To define types requiring little or no massaging when passed between Fortran +and C, the pseudo argument type SIMPLE may be used. +For a user defined type called 'newtype', the definitions required are: + +/* The following 7 lines are required verbatim. + 'newtype' is the name of the new user defined argument type. +*/ +#define newtype_cfV( T,A,B,F) SIMPLE_cfV(T,A,B,F) +#define newtype_cfSEP(T, B) SIMPLE_cfSEP(T,B) +#define newtype_cfINT(N,A,B,X,Y,Z) SIMPLE_cfINT(N,A,B,X,Y,Z) +#define newtype_cfSTR(N,T,A,B,C,D,E) SIMPLE_cfSTR(N,T,A,B,C,D,E) +#define newtype_cfCC( T,A,B) SIMPLE_cfCC(T,A,B) +#define newtype_cfAA( T,A,B) newtype_cfB(T,A) /* Argument B not used. */ +#define newtype_cfU( T,A) newtype_cfN(T,A) + +/* 'parameter_type(A)' is a declaration for 'A' and describes the type of the +parameter expected by the Fortran function. This type will be used in the +prototype for the function, if using ANSI C, and to declare the argument used +by the intermediate function if calling a Fortran FUNCTION. +Valid 'parameter_type(A)' include: int A + void (*A)() + double A[17] +*/ +#define newtype_cfN( T,A) parameter_type(A) /* Argument T not used. */ + +/* Before any argument of the new type is passed to the Fortran routine, it may +be massaged as given by 'massage(A)'. +*/ +#define newtype_cfB( T,A) massage(A) /* Argument T not used. */ + +An example of a simple user defined type is given cfortex.f and cfortest.c. +Two uses of SIMPLE user defined types are [don't show the 7 verbatim #defines]: + +/* Pass the address of a structure, using a type called PSTRUCT */ +#define PSTRUCT_cfN( T,A) void *A +#define PSTRUCT_cfB( T,A) (void *) &(A) + +/* Pass an integer by value, (not standard F77 ), using a type called INTVAL */ +#define INTVAL_cfN( T,A) int A +#define INTVAL_cfB( T,A) (A) + +[If using VAX VMS, surrounding the #defines with "#pragma (no)standard" allows + the %CC-I-PARAMNOTUSED messages to be avoided.] + +Upgrades to cfortran.h try to be, and have been, backwards compatible. This +compatibility cannot be offered to user defined types. SIMPLE user defined +types are less of a risk since they require so little effort in their creation. +If a user defined type is required in more than one C header file of interfaces +to libraries of Fortran routines, good programming practice, and ease of code +maintenance, suggests keeping any user defined type within a single file which +is #included as required. To date, changes to the SIMPLE macros were introduced +in versions 2.6, 3.0 and 3.2 of cfortran.h. + + +2. Routine_name is the name of the macro which the C programmer will use in +order to call a FORTRAN routine. In theory Routine_name could be any valid and +unique name, but in practice, the name of the FORTRAN routine in UPPER CASE +works everywhere and would seem to be an obvious choice. + + +3. + +cfortran.h encourages the exact specification of the type and dimension of +array parameters because it allows the C compiler to detect errors in the +arguments when calling the routine. + +cfortran.h does not strictly require the exact specification since the argument +is merely the address of the array and is passed on to the calling routine. +Any array parameter could be declared as PVOID, but this circumvents +C's compiletime ability to check the correctness of arguments and is therefore +discouraged. + +Passing the address of these arguments implies that PBYTEV, PFLOATV, ... , +PDOUBLEVV, ... don't exist in cfortran.h, since by default the routine and the +calling code share the same array, i.e. the same values at the same memory +location. + +These comments do NOT apply to arrays of (P)S/ZTRINGV. For these parameters, +cfortran.h passes a massaged copy of the array to the routine. When the routine +returns, S/ZTRINGV ignores the copy, while PS/ZTRINGV replaces the calling +code's original array with copy, which may have been modified by the called +routine. + + +4. (P)STRING(V): +- STRING - If the argument is a fixed length character array, e.g. char ar[8];, +the string is blank, ' ', padded on the right to fill out the array before +being passed to the FORTRAN routine. The useful size of the string is the same +in both languages, e.g. ar[8] is passed as character*7. If the argument is a +pointer, the string cannot be blank padded, so the length is passed as +strlen(argument). On return from the FORTRAN routine, pointer arguments are not +disturbed, but arrays have the terminating '\0' replaced to its original +position. i.e. The padding blanks are never visible to the C code. + +- PSTRING - The argument is massaged as with STRING before being passed to the +FORTRAN routine. On return, the argument has all trailing blanks removed, +regardless of whether the argument was a pointer or an array. + +- (P)STRINGV - Passes a 1- or 2-dimensional char array. e.g. char a[7],b[6][8]; +STRINGV may thus also pass a string constant, e.g. "hiho". +(P)STRINGV does NOT pass a pointer, e.g. char *, to either a 1- or a +2-dimensional array, since it cannot determine the array dimensions. +A pointer can only be passed using (P)ZTRINGV. +N.B. If a C routine receives a character array argument, e.g. char a[2][3], + such an argument is actually a pointer and my thus not be passed by + (P)STRINGV. Instead (P)ZTRINGV must be used. + +- STRINGV - The elements of the argument are copied into space malloc'd, and +each element is padded with blanks. The useful size of each element is the same +in both languages. Therefore char bb[6][8]; is equivalent to character*7 bb(6). +On return from the routine the malloc'd space is simply released. + +- PSTRINGV - Since FORTRAN has no trailing '\0', elements in an array of +strings are contiguous. Therefore each element of the C array is padded with +blanks and strip out C's trailing '\0'. After returning from the routine, the +trailing '\0' is reinserted and kill the trailing blanks in each element. + +- SUMMARY: STRING(V) arguments are blank padded during the call to the FORTRAN +routine, but remain original in the C code. (P)STRINGV arguments are blank +padded for the FORTRAN call, and after returning from FORTRAN trailing blanks +are stripped off. + + +5. (P)ZTRINGV: +- (P)ZTRINGV - is identical to (P)STRINGV, +except that the dimensions of the array of strings is explicitly specified, +which thus also allows a pointer to be passed. +(P)ZTRINGV can thus pass a 1- or 2-dimensional char array, e.g. char b[6][8], +or it can pass a pointer to such an array, e.g. char *p;. +ZTRINGV may thus also pass a string constant, e.g. "hiho". +If passing a 1-dimensional array, routine_name_ELEMS_j (see below) must be 1. +[Users of (P)ZTRINGV should examine cfortest.c for examples.]: + +- (P)ZTRINGV must thus be used instead of (P)STRINGV whenever sizeof() +can't be used to determine the dimensions of the array of string or strings. +e.g. when calling FORTRAN from C with a char * received by C as an argument. + +- There is no (P)ZTRING type, since (P)ZTRINGV can pass a 1-dimensional +array or a pointer to such an array, e.g. char a[7], *b; +If passing a 1-dimensional array, routine_name_ELEMS_j (see below) must be 1. + +- To specify the numbers of elements, +routine_name_ELEMS_j and routine_name_ELEMLEN_j must be defined as shown below +before interfacing the routine with CCALLSFSUBn, PROTOCCALLSFFUNn, etc. + +#define routine_name_ELEMS_j ZTRINGV_ARGS(k) + [..ARGS for subroutines, ..ARGF for functions.] +or +#define routine_name_ELEMS_j ZTRINGV_NUM(l) +Where: routine_name is as above. + j [1-n], is the argument being specifying. + k [1-n], the value of the k'th argument is the dynamic number + of elements for argument j. The k'th argument must be + of type BYTE, DOUBLE, FLOAT, INT, LONG or SHORT. + l the number of elements for argument j. This must be an + integer constant available at compile time. + i.e. it is static. + +- Similarly to specify the useful length, [i.e. don't count C's trailing '\0',] +of each element: +#define routine_name_ELEMLEN_j ZTRINGV_ARGS(m) + [..ARGS for subroutines, ..ARGF for functions.] +or +#define routine_name_ELEMLEN_j ZTRINGV_NUM(q) +Where: m [1-n], as for k but this is the length of each element. + q as for l but this is the length of each element. + + +6. ROUTINE +The argument is an EXTERNAL procedure. + +When C passes a routine to Fortran, the language of the function must be +specified as follows: [The case of some_*_function must be given as shown.] + +When C passes a C routine to a Fortran: + FORTRAN_ROUTINE(arg1, .... , + C_FUNCTION(SOME_C_FUNCTION,some_c_function), + ...., argn); + +and similarly when C passes a Fortran routine to Fortran: + FORTRAN_ROUTINE(arg1, .... , + FORTRAN_FUNCTION(SOME_FORT_FUNCTION,some_fort_function), + ...., argn); + +If fcallsc has been redefined; the same definition of fcallsc used when creating +the wrapper for 'some_c_function' must also be defined when C_FUNCTION is used. +See ii) 4. of this section for when and how to redefine fcallsc. + +ROUTINE was introduced with cfortran.h version 2.6. Earlier versions of +cfortran.h used PVOID to pass external procedures as arguments. Using PVOID for +this purpose is no longer recommended since it won't work 'as is' for +apolloFortran, hpuxFortran800, AbsoftUNIXFortran, AbsoftProFortran. + +7. CRAY only: +In a given piece of source code, where FFUNC is any FORTRAN routine, +FORTRAN_FUNCTION(FFUNC,ffunc) +disallows a previous +#define FFUNC(..) CCALLSFSUBn(FFUNC,ffunc,...) [ or CCALLSFFUNn] +in order to make the UPPER CASE FFUNC callable from C. +#define Ffunc(..) ... is OK though, as are obviously any other names. + + +ii) Calling C routines from FORTRAN: + -------------------------------- + +Each of the following two statements to export a C routine to FORTRAN create +FORTRAN 'wrappers', written in C, which must be compiled and linked along with +the original C routines and with the FORTRAN calling code. + +FORTRAN callable 'wrappers' may also be created for C macros. i.e. in this +section, the term 'C function' may be replaced by 'C macro'. + +for C functions returning void: +FCALLSCSUBn( Routine_name,ROUTINE_NAME,routine_name,argtype_1,...,argtype_n) + +for all other C functions: +FCALLSCFUNn(routine_type,Routine_name,ROUTINE_NAME,routine_name,argtype_1,...,argtype_n) + +Where: +'n' = 0->27 (easily expanded to > 27) stands for the number of arguments to the + routine. +Routine_name = the C name of the routine. [see 9. below] +ROUTINE_NAME = the FORTRAN name of the routine (IN UPPER CASE LETTERS). +routine_name = the FORTRAN name of the routine (IN lower case LETTERS). +routine_type = the type of argument returned by C functions. + = BYTE, DOUBLE, FLOAT, INT, LOGICAL, LONG, SHORT, STRING, VOID. + [Instead of VOID, FCALLSCSUBn is recommended.] +argtype_i = the type of argument passed to the FORTRAN routine and must be + consistent in the definition and prototyping of the routine + = BYTE, DOUBLE, FLOAT, INT, LOGICAL, LONG, SHORT, STRING. + For vectors, i.e. 1 dim. arrays use + = BYTEV, DOUBLEV, FLOATV, INTV, LOGICALV, LONGV, SHORTV, STRINGV. + For vectors of vectors, 2 dim. arrays use + = BYTEVV, DOUBLEVV, FLOATVV, INTVV, LOGICALVV, LONGVV, SHORTVV. + For n-dim. arrays use + = BYTEV..nV's..V, DOUBLEV..V, FLOATV..V, INTV..V, LOGICALV..V, + LONGV..V, SHORTV..V. + For routines changing the values of an argument, the keyword is + prepended by a 'P'. + = PBYTE, PDOUBLE, PFLOAT, PINT, PLOGICAL, PLONG, PSHORT, + PSTRING, PNSTRING, PPSTRING, PSTRINGV. + For EXTERNAL procedures passed as arguments use + = ROUTINE. + For exceptional arguments which require no massaging to fit the + argument passing mechanisms use + = PVOID. + The argument is cast and passed as (void *). + + +Notes: + +0. For Fortran calling C++ routines, C++ does NOT easily allow support for: + STRINGV. + BYTEVV, DOUBLEVV, FLOATVV, INTVV, LOGICALVV, LONGVV, SHORTVV. + BYTEV..V, DOUBLEV..V, FLOATV..V, INTV..V, LOGICALV..V, LONGV..V, SHORTV..V. +Though there are ways to get around this restriction, +the restriction is not serious since these types are unlikely to be used as +arguments for a C++ routine. + +1. FCALLSCSUB/FUNn expect that the routine to be 'wrapped' has been properly +prototyped, or at least declared. + + +2. cfortran.h may be expanded to handle a new argument type not already among +the above. + + +3. + +cfortran.h encourages the exact specification of the type and dimension of +array parameters because it allows the C compiler to detect errors in the +arguments when declaring the routine using FCALLSCSUB/FUNn, assuming the +routine to be 'wrapped' has been properly prototyped. + +cfortran.h does not strictly require the exact specification since the argument +is merely the address of the array and is passed on to the calling routine. +Any array parameter could be declared as PVOID, but this circumvents +C's compiletime ability to check the correctness of arguments and is therefore +discouraged. + +Passing the address of these arguments implies that PBYTEV, PFLOATV, ... , +PDOUBLEVV, ... don't exist in cfortran.h, since by default the routine and the +calling code share the same array, i.e. the same values at the same memory +location. + +These comments do NOT apply to arrays of (P)STRINGV. For these parameters, +cfortran.h passes a massaged copy of the array to the routine. When the routine +returns, STRINGV ignores the copy, while PSTRINGV replaces the calling +code's original array with copy, which may have been modified by the called +routine. + + +4. (P(N))STRING arguments have any trailing blanks removed before being passed +to C, the same holds true for each element in (P)STRINGV. Space is malloc'd in +all cases big enough to hold the original string (elements) as well as C's +terminating '\0'. i.e. The useful size of the string (elements) is the same in +both languages. P(N)STRING(V) => the string (elements) will be copied from the +malloc'd space back into the FORTRAN bytes. If one of the two escape mechanisms +mentioned below for PNSTRING has been used, the copying back to FORTRAN is +obviously not relevant. + + +5. (PN)STRING's, [NOT PSTRING's nor (P)STRINGV's,] behavior may be overridden +in two cases. In both cases PNSTRING and STRING behave identically. + +a) If a (PN)STRING argument's first 4 bytes are all the NUL character, +i.e. '\0\0\0\0' the NULL pointer is passed to the C routine. + +b) If the characters of a (PN)STRING argument contain at least one HEX-00, i.e. +the NUL character, i.e. C strings' terminating '\0', the address of the string +is simply passed to the C routine. i.e. The argument is treated in this case as +it would be with PPSTRING, to which we refer the reader for more detail. + +Mechanism a) overrides b). Therefore, to use this mechanism to pass the NULL +string, "", to C, the first character of the string must obviously be the NUL +character, but of the first 4 characters in the string, at least one must not +be HEX-00. + +Example: +C FORTRAN /* C */ + character*40 str #include "cfortran.h" +C Set up a NULL as : void cs(char *s) {if (s) printf("%s.\n",s);} +C i) 4 NUL characters. FCALLSCSUB1(cs,CS,cs,STRING) +C ii) NULL pointer. + character*4 NULL + NULL = CHAR(0)//CHAR(0)//CHAR(0)//CHAR(0) + + data str/'just some string'/ + +C Passing the NULL pointer to cs. + call cs(NULL) +C Passing a copy of 'str' to cs. + call cs(str) +C Passing address of 'str' to cs. Trailing blanks NOT killed. + str(40:) = NULL + call cs(str) + end + +Strings passed from Fortran to C via (PN)STRING must not have undefined +contents, otherwise undefined behavior will result, since one of the above two +escape mechanisms may occur depending on the contents of the string. + +This is not be a problem for STRING arguments, which are read-only in the C +routine and hence must have a well defined value when being passed in. + +PNSTRING arguments require special care. Even if they are write-only in the C +routine, PNSTRING's above two escape mechanisms require that the value of the +argument be well defined when being passed in from Fortran to C. Therefore, +unless one or both of PNSTRING's escape mechanisms are required, PSTRING should +be used instead of PNSTRING. +Prior to version 2.8, PSTRING did have the above two escape mechanisms, +but they were removed from PSTRING to allow strings with undefined contents to +be passed in. PNSTRING behaves like the old PSTRING. +[Thanks go to Paul Dubois (dubios@icf.llnl.gov) for pointing out that PSTRING + must allow for strings with undefined contents to be passed in.] + +Example: +C FORTRAN /* C */ + character*10 s,sn #include "cfortran.h" + void ps(char *s) {strcpy(s,"hello");} +C Can call ps with undef. s. FCALLSCSUB1(ps,PS,ps,PSTRING) + call ps(s) FCALLSCSUB1(ps,PNS,pns,PNSTRING) + print *,s,'=s' + +C Can't call pns with undef. s. +C e.g. If first 4 bytes of s were +C "\0\0\0\0", ps would try +C to copy to NULL because +C of PNSTRING mechanism. + sn = "" + call pns(sn) + print *,sn,'=sn' + + end + + +6. PPSTRING +The address of the string argument is simply passed to the C routine. Therefore +the C routine and the FORTRAN calling code share the same string at the same +memory location. If the C routine modifies the string, the string will also be +modified for the FORTRAN calling code. +The user is responsible for negociating the differences in representation of a +string in Fortran and in C, i.e. the differences are not automatically resolved +as they are for (P(N)STRING(V). +This mechanism is provided for two reasons: + - Some C routines require the string to exist at the given memory location, + after the C routine has exited. Recall that for the usual (P(N)STRING(V) + mechanism, a copy of the FORTRAN string is given to the C routine, and this + copy ceases to exist after returning to the FORTRAN calling code. + - This mechanism can save runtime CPU cycles over (P(N)STRING(V), since it + does not perform their malloc, copy and kill trailing blanks of the string + to be passed. + Only in a small minority of cases does the potential benefit of the saved + CPU cycles outweigh the programming effort required to manually resolve + the differences in representation of a string in Fortran and in C. + +For arguments passed via PPSTRING, the argument passed may also be an array of +strings. + + +7. ROUTINE +ANSI C requires that the type of the value returned by the routine be known, +For all ROUTINE arguments passed from Fortran to C, the type of ROUTINE is +specified by defining a cast as follows: + +#undef ROUTINE_j +#define ROUTINE_j (cast) +where: + j [1-n], is the argument being specifying. + (cast) is a cast matching that of the argument expected by the C + function protoytpe for which a wrapper is being defined. + +e.g. To create a Fortran wrapper for qsort(3C): +#undef ROUTINE_4 +#define ROUTINE_4 (int (*)(void *,void *)) +FCALLSCSUB4(qsort,FQSORT,fqsort,PVOID,INT,INT,ROUTINE) + +In order to maintain backward compatibility, cfortran.h defines a generic cast +for ROUTINE_1, ROUTINE_2, ..., ROUTINE_27. The user's definition is therefore +strictly required only for DEC C, which at the moment is the only compiler +which insists on the correct cast for pointers to functions. + +When using the ROUTINE argument inside some Fortran code: +- it is difficult to pass a C routine as the parameter, + since in many Fortran implementations, + Fortran has no access to the normal C namespace. + e.g. For most UNIX, + Fortran implicitly only has access to C routines ending in _. + If the calling Fortran code receives the routine as a parameter + it can of course easily pass it along. +- if a Fortran routine is passed directly as the parameter, + the called C routine must call the parameter routine + using the Fortran argument passing conventions. +- if a Fortran routine is to be passed as the parameter, + but if Fortran can be made to pass a C routine as the parameter, + then it may be best to pass a C-callable wrapper for the Fortran routine. + The called C routine is thus spared all Fortran argument passing conventions. + cfortran.h can be used to create such a C-callable wrapper + to the parameter Fortran routine. + +ONLY PowerStationFortran: +This Fortran provides no easy way to pass a Fortran routine as an argument to a +C routine. The problem arises because in Fortran the stack is cleared by the +called routine, while in C/C++ it is cleared by the caller. +The C/C++ stack clearing behavior can be changed to that of Fortran by using +stdcall__ in the function prototype. The stdcall__ cannot be applied in this +case since the called C routine expects the ROUTINE parameter to be a C routine +and does not know that it should apply stdcall__. +In principle the cfortran.h generated Fortran callable wrapper for the called C +routine should be able to massage the ROUTINE argument such that stdcall__ is +performed, but it is not yet known how this could be easily done. + + +8. THE FOLLOWING INSTRUCTIONS ARE NOT REQUIRED FOR VAX VMS + ------------ +(P)STRINGV information [NOT required for VAX VMS]: cfortran.h cannot convert +the FORTRAN vector of STRINGS to the required C vector of STRINGS without +explicitly knowing the number of elements in the vector. The application must +do one of the following for each (P)STRINGV argument in a routine before that +routine's FCALLSCFUNn/SUBn is called: + +#define routine_name_STRV_Ai NUM_ELEMS(j) + or +#define routine_name_STRV_Ai NUM_ELEM_ARG(k) + or +#define routine_name_STRV_Ai TERM_CHARS(l,m) + +where: routine_name is as above. + i [i=1->n.] specifies the argument number of a STRING VECTOR. + j would specify a fixed number of elements. + k [k=1->n. k!=i] would specify an integer argument which specifies the + number of elements. + l [char] the terminating character at the beginning of an + element, indicating to cfortran.h that the preceding + elements in the vector are the valid ones. + m [m=1-...] the number of terminating characters required to appear + at the beginning of the terminating string element. + The terminating element is NOT passed on to + the C routine. + +e.g. #define ce_STRV_A1 TERM_CHARS(' ',2) + FCALLSCSUB1(ce,CE,ce,STRINGV) + +cfortran.h will pass on all elements, in the 1st and only argument to the C +routine ce, of the STRING VECTOR until, but not including, the first string +element beginning with 2 blank, ' ', characters. + + +9. INSTRUCTIONS REQUIRED ONLY FOR FORTRAN COMPILERS WHICH GENERATE + ------------- + ROUTINE NAMES WHICH ARE UNDISTINGUISHABLE FROM C ROUTINE NAMES + i.e. VAX VMS + AbsoftUNIXFortran (AbsoftProFortran ok, since it uses Uppercase names.) + HP9000 if not using the +ppu option of f77 + IBM RS/6000 if not using the -qextname option of xlf + Call them the same_namespace compilers. + +FCALLSCSUBn(...) and FCALLSCFUNn(...), when compiled, are expanded into +'wrapper' functions, so called because they wrap around the original C +functions and interface the format of the original C functions' arguments and +return values with the format of the FORTRAN call. + +Ideally one wants to be able to call the C routine from FORTRAN using the same +name as the original C name. This is not a problem for FORTRAN compilers which +append an underscore, '_', to the names of routines, since the original C +routine has the name 'name', and the FORTRAN wrapper is called 'name_'. +Similarly, if the FORTRAN compiler generates upper case names for routines, the +original C routine 'name' can have a wrapper called 'NAME', [Assuming the C +routine name is not in upper case.] For these compilers, e.g. Mips, CRAY, IBM +RS/6000 'xlf -qextname', HP-UX 'f77 +ppu', the naming of the wrappers is done +automatically. + +For same_namespace compilers things are not as simple, but cfortran.h tries to +provide tools and guidelines to minimize the costs involved in meeting their +constraints. The following two options can provide same_namespace compilers +with distinct names for the wrapper and the original C function. + +These compilers are flagged by cfortran.h with the CF_SAME_NAMESPACE constant, +so that the change in the C name occurs only when required. + +For the remainder of the discussion, routine names generated by FORTRAN +compilers are referred to in lower case, these names should be read as upper +case for the appropriate compilers. + + +HP9000: (When f77 +ppu is not used.) +f77 has a -U option which forces uppercase external names to be generated. +Unfortunately, cc does not handle recursive macros. Hence, if one wished to use +-U for separate C and FORTRAN namespaces, one would have to adopt a different +convention of naming the macros which allow C to call FORTRAN subroutines. +(Functions are not a problem.) The macros are currently the uppercase of the +original FORTRAN name, and would have to be changed to lower case or mixed +case, or to a different name. (Lower case would of course cause conflicts on +many other machines.) Therefore, it is suggested that f77 -U not be used, and +instead that Option a) or Option b) outlined below be used. + + +VAX/VMS: +For the name used by FORTRAN in calling a C routine to be the same as that of +the C routine, the source code of the C routine is required. A preprocessor +directive can then force the C compiler to generate a different name for the C +routine. +e.g. #if defined(vms) + #define name name_ + #endif + void name() {printf("name: was called.\n");} + FCALLSCSUB0(name,NAME,name) + +In the above, the C compiler generates the original routine with the name +'name_' and a wrapper called 'NAME'. This assumes that the name of the routine, +as seen by the C programmer, is not in upper case. The VAX VMS linker is not +case sensitive, allowing cfortran.h to export the upper case name as the +wrapper, which then doesn't conflict with the routine name in C. Since the IBM, +HP and AbsoftUNIXFortran platforms have case sensitive linkers +this technique is not available to them. + +The above technique is required even if the C name is in mixed case, see +Option a) for the other compilers, but is obviously not required when +Option b) is used. + + +Option a) Mixed Case names for the C routines to be called by FORTRAN. + +If the original C routines have mixed case names, there are no name space +conflicts. + +Nevertheless for VAX/VMS, the technique outlined above must also used. + + +Option b) Modifying the names of C routines when used by FORTRAN: + +The more robust naming mechanism, which guarantees portability to all machines, +'renames' C routines when called by FORTRAN. Indeed, one must change the names +on same_namespace compilers when FORTRAN calls C routines for which the source +is unavailable. [Even when the source is available, renaming may be preferable +to Option a) for large libraries of C routines.] + +Obviously, if done for a single type of machine, it must be done for all +machines since the names of routines used in FORTRAN code cannot be easily +redefined for different machines. + +The simplest way to achieve this end is to do explicitly give the modified +FORTRAN name in the FCALLSCSUBn(...) and FCALLSCFUNn(...) declarations. e.g. + +FCALLSCSUB0(name,CFNAME,cfname) + +This allows FORTRAN to call the C routine 'name' as 'cfname'. Any name can of +course be used for a given routine when it is called from FORTRAN, although +this is discouraged due to the confusion it is sure to cause. e.g. Bizarre, +but valid and allowing C's 'call_back' routine to be called from FORTRAN as +'abcd': + +FCALLSCSUB0(call_back,ABCD,abcd) + + +cfortran.h also provides preprocessor directives for a systematic 'renaming' of +the C routines when they are called from FORTRAN. This is done by redefining +the fcallsc macro before the FCALLSCSUB/FUN/n declarations as follows: + +#undef fcallsc +#define fcallsc(UN,LN) preface_fcallsc(CF,cf,UN,LN) + +FCALLSCSUB0(hello,HELLO,hello) + +Will cause C's routine 'hello' to be known in FORTRAN as 'cfhello'. Similarly +all subsequent FCALLSCSUB/FUN/n declarations will generate wrappers to allow +FORTRAN to call C with the C routine's name prefaced by 'cf'. The following has +the same effect, with subsequent FCALLSCSUB/FUN/n's appending the modifier to +the original C routines name. + +#undef fcallsc +#define fcallsc(UN,LN) append_fcallsc(Y,y,UN,LN) + +FCALLSCSUB0(Xroutine,ROUTINE,routine) + +Hence, C's Xroutine is called from FORTRAN as: + CALL XROUTINEY() + +The original behavior of FCALLSCSUB/FUN/n, where FORTRAN routine names are left +identical to those of C, is returned using: + +#undef fcallsc +#define fcallsc(UN,LN) orig_fcallsc(UN,LN) + + +In C, when passing a C routine, i.e. its wrapper, as an argument to a FORTRAN +routine, the FORTRAN name declared is used and the correct fcallsc must be in +effect. E.g. Passing 'name' and 'routine' of the above examples to the FORTRAN +routines, FT1 and FT2, respectively: + +/* This might not be needed if fcallsc is already orig_fcallsc. */ +#undef fcallsc +#define fcallsc(UN,LN) orig_fcallsc(UN,LN) +FT1(C_FUNCTION(CFNAME,cfname)); + +#undef fcallsc +#define fcallsc(UN,LN) append_fcallsc(Y,y,UN,LN) +FT1(C_FUNCTION(XROUTINE,xroutine)); + +If the names of C routines are modified when used by FORTRAN, fcallsc would +usually be defined once in a header_file.h for the application. This definition +would then be used and be valid for the entire application and fcallsc would at +no point need to be redefined. + + +ONCE AGAIN: THE DEFINITIONS, INSTRUCTIONS, DECLARATIONS AND DIFFICULTIES +DESCRIBED HERE, NOTE 9. of II ii), +APPLY ONLY FOR VAX VMS, + IBM RS/6000 WITHOUT THE -qextname OPTION FOR xlf, OR + HP-UX WITHOUT THE +ppu OPTION FOR f77 + AbsoftUNIXFortran +AND APPLY ONLY WHEN CREATING WRAPPERS WHICH ENABLE FORTRAN TO CALL C ROUTINES. + + + +iii) Using C to manipulate FORTRAN COMMON BLOCKS: + ------------------------------------------------------- + +FORTRAN common blocks are set up with the following three constructs: + +1. +#define Common_block_name COMMON_BLOCK(COMMON_BLOCK_NAME,common_block_name) + +Common_block_name is in UPPER CASE. +COMMON_BLOCK_NAME is in UPPER CASE. +common_block_name is in lower case. +[Common_block_name actually follows the same 'rules' as Routine_name in Note 2. + of II i).] This construct exists to ensure that C code accessing the common +block is machine independent. + +2. +COMMON_BLOCK_DEF(TYPEDEF_OF_STRUCT, Common_block_name); + +where +typedef { ... } TYPEDEF_OF_STRUCT; +declares the structure which maps on to the common block. The #define of +Common_block_name must come before the use of COMMON_BLOCK_DEF. + +3. +In exactly one of the C source files, storage should be set aside for the +common block with the definition: + +TYPEDEF_OF_STRUCT Common_block_name; + +The above definition may have to be omitted on some machines for a common block +which is initialized by Fortran BLOCK DATA or is declared with a smaller size +in the C routines than in the Fortran routines. + +The rules for common blocks are not well defined when linking/loading a mixture +of C and Fortran, but the following information may help resolve problems. + +From the 2nd or ANSI ed. of K&R C, p.31, last paragraph: +i) + An external variable must be defined, exactly once, outside of any function; + this sets aside storage for it. +ii) + The variable must also be declared in each function that wants to access it; + ... + The declaration ... may be implicit from context. + +In Fortran, every routine says 'common /bar/ foo', +i.e. part ii) of the above, but there's no part i) requirement. +cc/ld on some machines don't require i) either. +Therefore, when handling Fortran, and sometimes C, +the loader/linker must automagically set aside storage for common blocks. + +Some loaders, including at least one for the CRAY, turn off the +'automagically set aside storage' capability for Fortran common blocks, +if any C object declares that common block. +Therefore, C code should define, i.e. set aside storage, +for the the common block as shown above. + +e.g. +C Fortran + common /fcb/ v,w,x + character *(13) v, w(4), x(3,2) + +/* C */ +typedef struct { char v[13],w[4][13],x[2][3][13]; } FCB_DEF; +#define Fcb COMMON_BLOCK(FCB,fcb) +COMMON_BLOCK_DEF(FCB_DEF,Fcb); +FCB_DEF Fcb; /* Definition, which sets aside storage for Fcb, */ + /* may appear in at most one C source file. */ + + +C programs can place a string (or a multidimensional array of strings) into a +FORTRAN common block using the following call: + +C2FCBSTR( CSTR, FSTR,DIMENSIONS); + +where: + +CSTR is a pointer to the first element of C's copy of the string (array). + The C code must use a duplicate of, not the original, common block string, + because the FORTRAN common block does not allocate space for C strings' + terminating '\0'. + +FSTR is a pointer to the first element of the string (array) in the common + block. + +DIMENSIONS is the number of dimensions of string array. + e.g. char a[10] has DIMENSIONS=0. + char aa[10][17] has DIMENSIONS=1. + etc... + +C2FCBSTR will copy the string (array) from CSTR to FSTR, padding with blanks, +' ', the trailing characters as required. C2FCBSTR uses DIMENSIONS and FSTR to +determine the lengths of the individual string elements and the total number of +elements in the string array. + +Note that: +- the number of string elements in CSTR and FSTR are identical. +- for arrays of strings, the useful lengths of strings in CSTR and FSTR must be + the same. i.e. CSTR elements each have 1 extra character to accommodate the + terminating '\0'. +- On most non-ANSI compilers, the DIMENSION argument cannot be prepended by any + blanks. + + +FCB2CSTR( FSTR, CSTR,DIMENSIONS) + +is the inverse of C2FCBSTR, and shares the same arguments and caveats. +FCB2CSTR copies each string element of FSTR to CSTR, minus FORTRAN strings' +trailing blanks. + + +cfortran.h USERS ARE STRONGLY URGED TO EXAMINE THE COMMON BLOCK EXAMPLES IN +cfortest.c AND cfortex.f. The use of strings in common blocks is +demonstrated, along with a suggested way for C to imitate FORTRAN EQUIVALENCE'd +variables. + + + ===> USERS OF CFORTRAN.H NEED READ NO FURTHER <=== + + +III Some Musings +---------------- + +cfortran.h is simple enough to be used by the most basic of applications, i.e. +making a single C/FORTRAN routine available to the FORTRAN/C programmers. Yet +cfortran.h is powerful enough to easily make entire C/FORTRAN libraries +available to FORTRAN/C programmers. + + +cfortran.h is the ideal tool for FORTRAN libraries which are being (re)written +in C, but are to (continue to) support FORTRAN users. It allows the routines to +be written in 'natural C', without having to consider the FORTRAN argument +passing mechanisms of any machine. It also allows C code accessing these +rewritten routines, to use the C entry point. Without cfortran.h, one risks the +perverse practice of C code calling a C function using FORTRAN argument passing +mechanisms! + + +Perhaps the philosophy and mechanisms of cfortran.h could be used and extended +to create other language bridges such as ADAFORTRAN, CPASCAL, COCCAM, etc. + + +The code generation machinery inside cfortran.h, i.e. the global structure is +quite good, being clean and workable as seen by its ability to meet the needs +and constraints of many different compilers. Though the individual instructions +of the A..., C..., T..., R... and K... tables deserve to be cleaned up. + + + +IV Getting Serious with cfortran.h +----------------------------------- + +cfortran.h is set up to be as simple as possible for the casual user. While +this ease of use will always be present, 'hooks', i.e. preprocessor directives, +are required in cfortran.h so that some of the following 'inefficiencies' can +be eliminated if they cause difficulties: + +o cfortran.h contains a few small routines for string manipulation. These +routines are declared static and are included and compiled in all source code +which uses cfortran.h. Hooks should be provided in cfortran.h to create an +object file of these routines, allowing cfortran.h to merely prototypes +these routines in the application source code. This is the only 'problem' which +afflicts both halves of cfortran.h. The remaining discussion refers to the C +calls FORTRAN half only. + +o Similar to the above routines, cfortran.h generates code for a 'wrapper' +routine for each FUNCTION exported from FORTRAN. Again cfortran.h needs +preprocessor directives to create a single object file of these routines, +and to merely prototype them in the applications. + +o Libraries often contain hundreds of routines. While the preprocessor makes +quick work of generating the required interface code from cfortran.h and the +application.h's, it may be convenient for very large stable libraries to have +final_application.h's which already contain the interface code, i.e. these +final_application.h's would not require cfortran.h. [The convenience can be +imagined for the VAX VMS CC compiler which has a fixed amount of memory for +preprocessor directives. Not requiring cfortran.h, with its hundreds of +directives, could help prevent this compiler from choking on its internal +limits quite so often.] + +With a similar goal in mind, cfortran.h defines 100's of preprocessor +directives. There is always the potential that these will clash with other tags +in the users code, so final_applications.h, which don't require cfortran.h, +also provide the solution. + +In the same vein, routines with more than 14 arguments can not be interfaced by +cfortran.h with compilers which limit C macros to 31 arguments. To resolve this +difficulty, final_application.h's can be created on a compiler without this +limitation. + +Therefore, new machinery is required to do: + +application.h + cfortran.h => final_application.h + +The following example may help clarify the means and ends: + +If the following definition of the HBOOK1 routine, the /*commented_out_part*/, +is passed through the preprocessor [perhaps #undefing and #defining preprocessor +constants if creating an application.h for compiler other than that of the +preprocessor being used, e.g. cpp -Umips -DCRAY ... ] : + +#include "cfortran.h" +PROTOCCALLSFSUB6(HBOOK1,hbook1,INT,STRING,INT,FLOAT,FLOAT,FLOAT) +/*#define HBOOK1(ID,CHTITLE,NX,XMI,XMA,VMX) \*/ + CCALLSFSUB6(HBOOK1,hbook1,INT,STRING,INT,FLOAT,FLOAT,FLOAT, \ + ID,CHTITLE,NX,XMI,XMA,VMX) + +A function prototype is produced by the PROTOCCALLSFSUB6(...). +Interface code is produced, based on the 'variables', +ID,CHTITLE,NX,XMI,XMA,VMX, which will correctly massage a HBOOK1 call. +Therefore, adding the #define line: + +'prototype code' +#define HBOOK1(ID,CHTITLE,NX,XMI,XMA,VMX) \ + 'interface code'(ID,CHTITLE,NX,XMI,XMA,VMX) + +which is placed into final_application.h. + +The only known limitation of the above method does not allow the 'variable' +names to include B1,B2,...,B9,BA,BB,... + +Obviously the machinery to automatically generate final_applications.h from +cfortran.h and applications.h needs more than just some preprocessor +directives, but a fairly simple unix shell script should be sufficient. Any +takers? + + + +V Machine Dependencies of cfortran.h +------------------------------------ + +Porting cfortran.h applications, e.g. the hbook.h and cstring.c mentioned +above, to other machines is trivial since they are machine independent. Porting +cfortran.h requires a solid knowledge of the new machines C preprocessor, and +its FORTRAN argument passing mechanisms. Logically cfortran.h exists as two +halves, a "C CALLS FORTRAN" and a "FORTRAN CALLS C" utility. In some cases it +may be perfectly reasonable to port only 'one half' of cfortran.h onto a new +system. + + +The lucky programmer porting cfortran.h to a new machine, must discover the +FORTRAN argument passing mechanisms. A safe starting point is to assume that +variables and arrays are simply passed by reference, but nothing is guaranteed. +Strings, and n-dimensional arrays of strings are a different story. It is +doubtful that any systems do it quite like VAX VMS does it, so that a UNIX or +f2c versions may provide an easier starting point. + + +cfortran.h uses and abuses the preprocessor's ## operator. Although the ## +operator does not exist in many compilers, many kludges do. cfortran.h uses +/**/ with no space allowed between the slashes, '/', and the macros or tags +to be concatenated. e.g. +#define concat(a,b) a/**/b /* works*/ +main() +{ + concat(pri,ntf)("hello"); /* e.g. */ +} +N.B. On some compilers without ##, /**/ may also not work. The author may be +able to offer alternate kludges. + + + +VI Bugs in vendors C compilers and other curiosities +---------------------------------------------------- + +1. ULTRIX xxxxxx 4.3 1 RISC + +Condolences to long suffering ultrix users! +DEC supplies a working C front end for alpha/OSF, but not for ultrix. + +From K&R ANSI C p. 231: + ultrix> cat cat.c + #define cat(x, y) x ## y + #define xcat(x,y) cat(x,y) + cat(cat(1,2),3) + xcat(xcat(1,2),3) + ultrix> cc -E cat.c + 123 <---- Should be: cat(1,2)3 + 123 <---- Correct. + ultrix> + +The problem for cfortran.h, preventing use of -std and -std1: + ultrix> cat c.c + #define cat(x, y) x ## y + #define xcat(x,y) cat(x,y) + #define AB(X) X+X + #define C(E,F,G) cat(E,F)(G) + #define X(E,F,G) xcat(E,F)(G) + C(A,B,2) + X(A,B,2) + ultrix> cc -std1 -E c.c + 2+2 + AB (2) <---- ????????????? + ultrix> + ultrix> cc -std0 -E c.c + 2+2 + AB(2) <---- ????????????? + ultrix> + +Due to further ultrix preprocessor problems, +for all definitions of definitions with arguments, +cfortran.h >= 3.0 includes the arguments and recommends the same, +even though it is not required by ANSI C. +e.g. Users are advised to do + #define fcallsc(UN,LN) orig_fcallsc(UN,LN) +instead of + #define fcallsc orig_fcallsc +since ultrix fails to properly preprocess the latter example. +CRAY used to (still does?) occasionally trip up on this problem. + + +2. ConvexOS convex C210 11.0 convex + +In a program with a C main, output to LUN=6=* from Fortran goes into +$pwd/fort.6 instead of stdout. Presumably, a magic incantation can be called +from the C main in order to properly initialize the Fortran I/O. + + +3. SunOS 5.3 Generic_101318-69 sun4m sparc + +The default data and code alignments produced by cc, gcc and f77 are compatible. +If deviating from the defaults, consistent alignment options must be used +across all objects compiled by cc and f77. [Does gcc provide such options?] + + +4. SunOS 5.3 Generic_101318-69 sun4m sparc with cc: SC3.0.1 13 Jul 1994 + or equivalently + ULTRIX 4.4 0 RISC using cc -oldc + are K&R C preprocessors that suffer from infinite loop macros, e.g. + + zedy03> cat src.c + #include "cfortran.h" + PROTOCCALLSFFUN1(INT,FREV,frev, INTV) + #define FREV(A1) CCALLSFFUN1( FREV,frev, INTV, A1) + /* To avoid the problem, deletete these ---^^^^--- spaces. */ + main() { static int a[] = {1,2}; FREV(a); return EXIT_SUCCESS; } + + zedy03> cc -c -Xs -v -DMAX_PREPRO_ARGS=31 -D__CF__KnR src.c + "src.c", line 4: FREV: actuals too long + "src.c", line 4: FREV: actuals too long + .... 3427 more lines of the same message + "src.c", line 4: FREV: actuals too long + cc : Fatal error in /usr/ccs/lib/cpp + Segmentation fault (core dumped) + + +5. Older sun C compilers + +To link to f77 objects, older sun C compilers require the math.h macros: + +#define RETURNFLOAT(x) { union {double _d; float _f; } _kluge; \ + _kluge._f = (x); return _kluge._d; } +#define ASSIGNFLOAT(x,y) { union {double _d; float _f; } _kluge; \ + _kluge._d = (y); x = _kluge._f; } + +Unfortunately, in at least some copies of the sun math.h, the semi-colon +for 'float _f;' is left out, leading to compiler warnings. + +The solution is to correct math.h, or to change cfortran.h to #define +RETURNFLOAT(x) and ASSIGNFLOAT(x,y) instead of including math.h. + + +6. gcc version 2.6.3 and probably all other versions as well + +Unlike all other C compilers supported by cfortran.h, +'gcc -traditional' promotes to double all functions returning float +as demonstrated bu the following example. + +/* m.c */ +#include +int main() { FLOAT_FUNCTION d(); float f; f = d(); printf("%f\n",f); return 0; } + +/* d.c */ +float d() { return -123.124; } + +burow[29] gcc -c -traditional d.c +burow[30] gcc -DFLOAT_FUNCTION=float m.c d.o && a.out +0.000000 +burow[31] gcc -DFLOAT_FUNCTION=double m.c d.o && a.out +-123.124001 +burow[32] + +Thus, 'gcc -traditional' is not supported by cfortran.h. +Support would require the same RETURNFLOAT, etc. macro machinery +present in old sun math.h, before sun gave up the same promotion. + + +7. CRAY + +At least some versions of the t3e and t3d C preprocessor are broken +in the fashion described below. +At least some versions of the t90 C preprocessor do not have this problem. + +On the CRAY, all Fortran names are converted to uppercase. +Generally the uppercase name is also used for the macro interface +created by cfortran.h. + +For example, in the following interface, +EASY is both the name of the macro in the original C code +and EASY is the name of the resulting function to be called. + +#define EASY(A,B) CCALLSFSUB2(EASY,easy, PINT, INTV, A, B) + +The fact that a macro called EASY() expands to a function called EASY() +is not a problem for a working C preprocessor. +From Kernighan and Ritchie, 2nd edition, p.230: + + In both kinds of macro, the replacement token sequence is repeatedly + rescanned for more identifiers. However, once a given identifier has been + replaced in a given expansion, it is not replaced if it turns up again during + rescanning; instead it is left unchanged. + +Unfortunately, some CRAY preprocessors are broken and don't obey the above rule. +A work-around is for the user to NOT use the uppercase name +of the name of the macro interface provided by cfortran.h. For example: + +#define Easy(A,B) CCALLSFSUB2(EASY,easy, PINT, INTV, A, B) + +Luckily, the above work-around is not required since the following +work-around within cfortran.h also circumvents the bug: + + /* (UN), not UN, is required in order to get around CRAY preprocessor bug.*/ + #define CFC_(UN,LN) (UN) /* Uppercase FORTRAN symbols. */ + +Aside: The Visual C++ compiler is happy with UN, but barfs on (UN), + so either (UN) causes nonstandard C/C++ or Visual C++ is broken. + + +VII History and Acknowledgements +-------------------------------- + +1.0 - Supports VAX VMS using C 3.1 and FORTRAN 5.4. Oct. '90. +1.0 - Supports Silicon Graphics w. Mips Computer 2.0 f77 and cc. Feb. '91. + [Port of C calls FORTRAN half only.] +1.1 - Supports Mips Computer System 2.0 f77 and cc. Mar. '91. + [Runs on at least: Silicon Graphics IRIX 3.3.1 + DECstations with Ultrix V4.1] +1.2 - Internals made simpler, smaller, faster, stronger. May '91. + - Mips version works on IBM RS/6000, this is now called the unix version. +1.3 - UNIX and VAX VMS versions are merged into a single cfortran.h. July '91. + - C can help manipulate (arrays of) strings in FORTRAN common blocks. + - Dimensions of string arrays arguments can be explicit. + - Supports Apollo DomainOS 10.2 (sys5.3) with f77 10.7 and cc 6.7. + +2.0 - Improved code generation machinery creates K&R or ANSI C. Aug. '91. + - Supports Sun, CRAY. f2c with vcc on VAX Ultrix. + - cfortran.h macros now require routine and COMMON block names in both + upper and lower case. No changes required to applications though. + - PROTOCCALLSFSUBn is eliminated, with no loss to cfortran.h performance. + - Improved tools and guidelines for naming C routines called by FORTRAN. +2.1 - LOGICAL correctly supported across all machines. Oct. '91. + - Improved support for DOUBLE PRECISION on the CRAY. + - HP9000 fully supported. + - VAX Ultrix cc or gcc with f77 now supported. +2.2 - SHORT, i.e. INTEGER*2, and BYTE now supported. Dec. '91. + - LOGICAL_STRICT introduced. More compact and robust internal tables. + - typeV and typeVV for type = BYTE, DOUBLE, FLOAT, INT, LOGICAL, LONG,SHORT. + - FORTRAN passing strings and NULL pointer to C routines improved. +2.3 - Extraneous arguments removed from many internal tables. May '92. + - Introduce pseudo argument type SIMPLE for user defined types. + - LynxOS using f2c supported. (Tested with LynxOS 2.0 386/AT.) +2.4 - Separation of internal C and Fortran compilation directives. Oct. '92. + - f2c and NAG f90 supported on all machines. +2.5 - Minor mod.s to source and/or doc for HP9000, f2c, and NAG f90. Nov. '92. +2.6 - Support external procedures as arguments with type ROUTINE. Dec. '92. +2.7 - Support Alpha VMS. Support HP9000 f77 +ppu Jan. '93. + - Support arrays with up to 7 dimensions. + - Minor mod. of Fortran NULL to C via (P)STRING. + - Specify the type of ROUTINE passed from Fortran to C [ANSI C requirement.] + - Macros never receive a null parameter [RS/6000 requirement.] +2.8 - PSTRING for Fortran calls C no longer provides escape to pass April'93. + NULL pointer nor to pass address of original string. + PNSTRING introduced with old PSTRING's behavior. + PPSTRING introduced to always pass original address of string. + - Support Alpha/OSF. + - Document that common blocks used in C should be declared AND defined. + +3.0 - Automagic handling of ANSI ## versus K&R /**/ preprocessor op. March'95. + - Less chance of name space collisions between cfortran.h and other codes. + - SIMPLE macros, supporting user defined types, have changed names. +3.1 - Internal macro name _INT not used. Conflicted with IRIX 5.3. May '95. + - SunOS, all versions, should work out of the box. + - ZTRINGV_ARGS|F(k) may no longer point to a PDOUBLE or PFLOAT argument. + - ConvexOS 11.0 supported. +3.2 - __hpux no longer needs to be restricted to MAX_PREPRO_ARGS=31. Oct. '95. + - PSTRING bug fixed. + - ZTRINGV_ARGS|F(k) may not point to a PBYTE,PINT,PLONG or PSHORT argument. + - (P)ZTRINGV machinery improved. Should lead to fewer compiler warnings. + (P)ZTRINGV no longer limits recursion or the nesting of routines. + - SIMPLE macros, supporting user defined types, have changed slightly. +3.3 - Supports PowerStation Fortran with Visual C++. Nov. '95. + - g77 should work using f2cFortran, though no changes made for it. + - (PROTO)CCALLSFFUN10 extended to (PROTO)CCALLSFFUN14. + - FCALLSCFUN10 and SUB10 extended to FCALLSCFUN14 and SUB14. +3.4 - C++ supported, Dec. '95. + but it required the reintroduction of PROTOCCALLSFSUBn for users. + - HP-UX f77 +800 supported. +3.5 - Absoft UNIX Fortran supported. Sept.'96. +3.6 - Minor corrections to cfortran.doc. Oct. '96. + - Fixed bug for 15th argument. [Thanks to Tom Epperly at Aspen Tech.] + - For AbsoftUNIXFortran, obey default of prepending _C to COMMON BLOCK name. + - Fortran calling C with ROUTINE argument fixed and cleaned up. +3.7 - Circumvent IBM and HP "null argument" preprocessor warning. Oct. '96 +3.8 - (P)STRINGV and (P)ZTRINGV can pass a 1- or 2-dim. char array. Feb. '97 + (P)ZTRINGV thus effectively also provides (P)ZTRING. + - (P)ZTRINGV accepts a (char *) pointer. +3.9 - Bug fixed for *VVVVV. May '97 + - f2c: Work-around for strange underscore-dependent naming feature. + - NEC SX-4 supported. + - CRAY: LOGICAL conversion uses _btol and _ltob from CRAY's fortran.h. + - CRAY: Avoid bug of some versions of the C preprocessor. + - CRAY T3E: FORTRAN_REAL introduced. + +4.0 - new/delete now used for C++. malloc/free still used for C. Jan. '98 + - FALSE no longer is defined by cfortran.h . + - Absoft Pro Fortran for MacOS supported. +4.1 - COMMA and COLON no longer are defined by cfortran.h . April'98 + - Bug fixed when 10th arg. or beyond is a string. + [Rob Lucchesi of NASA-Goddard pointed out this bug.] + - CCALLSFSUB/FUN extended from 14 to 27 arguments. + - Workaround SunOS CC 4.2 cast bug. [Thanks to Savrak SAR of CERN.] +4.2 - Portland Group needs -DpgiFortran . [Thank George Lai of NASA.] June '98 +4.3 - (PROTO)CCALLSFSUB extended from 20 to 27 arguments. July '98 + + +['Support' implies these and more recent releases of the respective + OS/compilers/linkers can be used with cfortran.h. + Earlier releases may also work.] + + +Acknowledgements: +- CERN very generously sponsored a week in 1994 for me to work on cfortran.h. +- M.L.Luvisetto (Istituto Nazionale Fisica Nucleare - Centro Nazionale + Analisi Fotogrammi, Bologna, Italy) provided all the support for the port to + the CRAY. Marisa's encouragement and enthusiasm was also much appreciated. +- J.Bunn (CERN) supported the port to PowerStation Fortran with Visual C++. +- Paul Schenk (UC Riverside, CERN PPE/OPAL) in June 1993 extended cfortran.h 2.7 + to have C++ call Fortran. This was the starting point for full C++ in 3.4. +- Glenn P.Davis of University Corp. for Atmospheric Research (UCAR) / Unidata + supported the NEC SX-4 port and helped understand the CRAY. +- Tony Goelz of Absoft Corporation ported cfortran.h to Absoft. +- Though cfortran.h has been created in my 'copious' free time, I thank + NSERC for their generous support of my grad. student and postdoc years. +- Univ.Toronto, DESY, CERN and others have provided time on their computers. + + +THIS PACKAGE, I.E. CFORTRAN.H, THIS DOCUMENT, AND THE CFORTRAN.H EXAMPLE +PROGRAMS ARE PROPERTY OF THE AUTHOR WHO RESERVES ALL RIGHTS. THIS PACKAGE AND +THE CODE IT PRODUCES MAY BE FREELY DISTRIBUTED WITHOUT FEES, SUBJECT TO THE +FOLLOWING RESTRICTIONS: +- YOU MUST ACCOMPANY ANY COPIES OR DISTRIBUTION WITH THIS (UNALTERED) NOTICE. +- YOU MAY NOT RECEIVE MONEY FOR THE DISTRIBUTION OR FOR ITS MEDIA + (E.G. TAPE, DISK, COMPUTER, PAPER.) +- YOU MAY NOT PREVENT OTHERS FROM COPYING IT FREELY. +- YOU MAY NOT DISTRIBUTE MODIFIED VERSIONS WITHOUT CLEARLY DOCUMENTING YOUR + CHANGES AND NOTIFYING THE AUTHOR. +- YOU MAY NOT MISREPRESENTED THE ORIGIN OF THIS SOFTWARE, EITHER BY EXPLICIT + CLAIM OR BY OMISSION. + +THE INTENT OF THE ABOVE TERMS IS TO ENSURE THAT THE CFORTRAN.H PACKAGE NOT BE +USED FOR PROFIT MAKING ACTIVITIES UNLESS SOME ROYALTY ARRANGEMENT IS ENTERED +INTO WITH ITS AUTHOR. + +THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER +EXPRESSED OR IMPLIED. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST +OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. THE AUTHOR IS NOT RESPONSIBLE +FOR ANY SUPPORT OR SERVICE OF THE CFORTRAN.H PACKAGE. + + Burkhard Burow + burow@desy.de + +P.S. Your comments and questions are welcomed and usually promptly answered. + +VAX VMS and Ultrix, Alpha, OSF, Silicon Graphics (SGI), DECstation, Mips RISC, +Sun, CRAY, Convex, IBM RS/6000, Apollo DomainOS, HP, LynxOS, f2c, NAG, Absoft, +NEC SX-4, PowerStation and Visual C++ are registered trademarks of their +respective owners. + +============================================================================ + +ADDITIONAL LICENSING INFORMATION (added by W D Pence on 4 October 2007) + +The author of cfortran has subsequently stated that cfortran.h may optionally +be used and distributed under the GNU Library General Public License (LGPL). +This statement was made in an email to Kevin McCarty, which is reproduced below: + +---------------------------------------- +Date: Tue, 22 Oct 2002 12:48:00 -0400 +From: Burkhard D Steinmacher-burow +To: Kevin B. McCarty +Subject: Re: CFortran licensing question + +Kevin, + +[Just noticed that I didn't send this yesterady.] + +I have no time right now to read through licenses. +IIRC, library GPL is fairly unrestrictive, so I'll choose that. So..... + +You may consider this e-mail as a notice that as an alternative to any +other cfortran licenses, +I hereby relase all versions and all parts of cfortran under the +the Library GPL license. +From among these licenses, the user is free to choose +the license or licenses under which cfortran is used. + +Contact me if you'd like to be able to choose another license. + +Burkhard + +steinmac@us.ibm.com, (914)945-3756, Fax 3684, Tieline 862 +------------------------------------------ + +/* end: cfortran.doc */ diff --git a/external/cfitsio/cfortran.h b/external/cfitsio/cfortran.h new file mode 100644 index 0000000..703a41b --- /dev/null +++ b/external/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 /* NULL [in all machines stdio.h] */ +#include /* strlen, memset, memcpy, memchr. */ +#if !( defined(VAXUltrix) || defined(sun) || (defined(apollo)&&!defined(__STDCPP__)) ) +#include /* malloc,free */ +#else +#include /* 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 +#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 +#else +#include "descrip.h" /* i.e. if crosscompiling assume user has file. */ +#endif +#endif + +#ifdef sunFortran +#if defined(sun) || defined(__sun) +#include /* 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 + * , 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) (As) { /* 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 es) { /* 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= 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/external/cfitsio/changes.txt b/external/cfitsio/changes.txt new file mode 100644 index 0000000..ebd8bb9 --- /dev/null +++ b/external/cfitsio/changes.txt @@ -0,0 +1,3844 @@ + Log of Changes Made to CFITSIO + +Version 3.31 - 18 July 2012 + + - enhanced the CFITSIO column filtering syntax to allow the comma, in addition + to the semi-colon, to be used to separate clauses, for example: + [col X,Y;Z = max(X,Y)]. This was done because users are not allowed to + enter the semi-colon character in the on-line Hera data processing + system due to computer security concerns. + + - enhanced the CFITSIO extended filename syntax to allow specifying image + compression parameters (e.g. '[compress Rice]') when opening an existing + FITS file with write access. The specified compression parameters will + be used by default if more images are appended to the existing file. + + - modified drvrfile.c to do additional file secrity checks when CFITSIO + is running within the HEASARC's Hera software system. In this case + CFITSIO will not allow FITS files to be created outside of the user's + individual Hera data directory area. + + - fixed an issue in fpack and funpack on Windows machines, caused by + the fact that the 'rename' function behaves differently on Windows + in that it does not clobber an existing file, as it does on Unix + platforms. + + - fixed bug in the way byte-swapping was being performed when writing + integer*8 null values to an image or binary table column. + + - added the missing macro definition for fffree to fitsio.h. + + - modified the low level table read and write functions in getcol*.c and + putcol*.c to remove the 32-bit limitation on the number of elements. + These routines now support reading and writing more than 2**31 elements + at one time. Thanks to Keh-Cheng Chu (Stanford U.) for the patch. + + - modified Makefile.in so that the shared libcfitsio.so is linked against + pthreads and libm. + +Version 3.30 - 11 April 2012 + + Enhancements + + - Added new routine called fits_is_reentrant which returns 1 or 0 depending on + whether or not CFITSIO was compiled with the -D_REENTRANT directive. This can + be used to determine if it is safe to use CFITSIO in multi-threaded programs. + + - Implimented much faster byte-swapping algorithms in swapproc.c based on code + provided by Julian Taylor at ESO, Garching. These routines significantly + improve the FITS image read and write speed (by more than a factor of 2 in + some cases) on little-endian machines (e.g., Linux and Microsoft Windows and + Macs running on x86 CPUs) where byte-swapping is required when reading and + writing data in FITS files. This has no effect on big-endian machines + (e.g. Motorola CPUs and some IBM systems). Even faster byte-swapping + performance can be achieved in some cases by invoking the new "--enable-sse2" + or "--enable-ssse3" configure options when building CFITSIO on machines that + have CPUs and compilers that support the SSE2 and SSSE3 machine instructions. + + - added additional support for implicit data type conversion in cases where + the floating point image has been losslessly compressed with gzip. The + pixels in these compressed images can now be read back as arrays of short, + int, and long integers as well as single and double precision floating-point. + + - modified fitsio2.h and f77_wrap.h to recognize IBM System z mainframes by + testing if __s390x__ or __s390__ is defined. + + - small change to ffgcrd in getkey.c so that it supports reading a blank + keyword (e.g., a keyword whose name simply contains 8 space chracters). + + Bug Fixes + + - fixed a bug in imcomp_decompress_tile that caused the tile-compressed image + to be uncompressed incorrectly (even though the tile-compressed image itself + was written correctly) under the following specific conditions: + - the original FITS image has a "float" datatype (R*4) + - one or more of the image tiles cannot be compressed using the standard + quantization method and instead are losslessly compressed with gzip + - the pixels in these tiles are not all equal to zero (this bug does + affect tiles where all the pixels are equal to zero) + - the program that is reading the compressed image uses CFITSIO's + "implicit datatype conversion" feature to read the "float" image + back into an array of "double" pixel values. + If all these conditions are met, then the returned pixel values in the + affected image tiles will be garbage, with values often ranging + up to 10**34. Note that this bug does not affect the fpack/funpack + programs, because funpack does not use CFITSIO's implicit datatype + conversion feature when uncompressing the image. + +Version 3.29 - 2 December 2011 + + Enhancements + + - modified Makefile.in to allow configure to override the lib and include + destination directories. + + - added (or restored actually) support for tile compression of 1-byte integer + images in imcomp_compress_tile. Support for that data type was overlooked + during recent updates to this routine. + + - modified the fits_get_token command-line parsing routine to perform more + rigorous checks to determine if the token can be interpreted as a number + or not. + + - made small modification to fpack.c to not allow the -i2f option (convert + image from integer to floating point) with the "-g -q 0" option (do lossless + gzip compression). It is more efficient to simply use the -g option alone. + + - made modifications to fitsio.h and drvrfile.c to support reading and + writing large FITS files (> 2.1 GB) when building CFITSIO using + Microsoft Visual C++ on Windows platforms. + + - added new WCS routine (ffgicsa) which returns the WCS keyword values + for a particular WCS version ('A' - 'Z'). + + Bug Fixes + + - fixed a problem with multi-threaded apps that open/close FITS files + simultaneously by putting mutex locks around the call to + fits_already_open and in fits_clear_Fptr. + + - fixed a bug when using the 'regfilter' function to select a subset of the + rows in a FITS table that have coordinates that lie within a specified + spatial region on the sky. This bug only affects the rarely used panda + (and epanda and bpanda) region shapes in which the region is defined by + the intersection of an annulus and a pie-shaped wedge. The previous code + (starting with version 3.181 of CFITSIO where support for the panda region + was first introduced) only worked correctly if the 2 angles that define + the wedge have values between -180 and +180. If not, then fewer rows than + expected may have been selected from the table. + + - fixed the extended filename parser so that when creating a histogram by + binning 2 table columns, if a keyword or column name is given as the + weighting factor, then the output histrogram image will have a floating + point datatype, not the default integer datatype as is the case when no + weigth is specified (e.g. with a filename like + "myfile.fits[bin x,y; weight_column]" + + - added fix to the code in imcompress.c to work around a problem with + dereferencing the value of a pointer, in cases where the address of + that pointer has not been defined (e.g., the nulval variable). + + - modified the byte shuffling algorithm in fits_shuffle_8bytes to work + around a strange bug in the proprietary SunStudioExpress C compiler + under OpenSolaris. + + - removed spurious messages on the CFITSIO error stack when opening a + FITS file with FTP (in drvrnet.c); + +Version 3.28 - 12 May 2011 + + - added an enhancement to the tiled-image compression method when compressing + floating-point image using the standard (lossy) quantization method. In + cases where an image tile cannot be quantized, The floating-point pixel values + will be losslessly compressed with gzip before writing them to the tile- + compressed file. Previously, the uncompressed pixel values would have + been written to the file, which obviously requires more disk space. + + - made significant internal changes to the structure of the tile compression + and uncompression routines in imcompress.c to make them more modular and + easier to maintain. + + - modified configure.in and configure to force it to build a Universal + binary on Mac OS X. + + - modified the ffiter function in putcol.c to properly clean up allocated + memory if an error occurs. + + - in quantize.c, when searching for the min and max values in a float array, + initialize the max value to -FLT_MAX instead of FLT_MIN (and similarly + for double array). + +Version 3.27 - 3 March 2011 + + Enhancements + + - added new routines fits_read_str and fits_delete_str which read or + delete, respectively, a header keyword record that contains a specified + character string. + + - added a new routine called fits_free_memory which frees the memory + that fits_read_key_longstr allocated for the long string keyword value. + + - enhanced the ffmkky routine in fitscore.c to not put a space before the + equals sign when writing long string-valued keywords using the ESO + HIERARCH keyword convension, if that extra character is needed to + fit the length of the keyword name + value string within the 80-character + FITS keyword record. + + - made small change to fits_translate_keyword to support translation of + blank keywords (where the name = 8 blank chracters) + + - modified fpack so that it uses the minimum of the 2nd, 3rd, and 5th order + MAD noise values when quantizing and compressing a floating point image. + This is more conservative than just using the 3rd order MAD value alone. + + - added new routine imcomp_copy_prime2img to imcompress.c that is used by + funpack to copy any keywords that may have been added to the primary + array of the compressed image file (a null image) back into the header of + the uncompressed image. + + - enhanced the fits_quantize_float and fits_quantize_double routines in + quantize.c to also compress the tile if it is completely filled with + null values. Previously, this type of tile would have been written + to the output compressed image without any compression. + + - enhanced imcomp_decompress_tile to support implicit datatype conversion + when reading a losslessly compressed (with gzip) real*4 image into an + array of real*8 values. + + - in imcompress.c, removed possible attempt to free memory that had not + been allocated. + + +Version 3.26 - 30 December 2010 + + Enhancements + + - defined 2 new macros in fitsio.h: + #define CFITSIO_MAJOR 3 + #define CFITSIO_MINOR 26 + These may be used within other macros to detect the CFITSIO + version number at compile time. + + - modified group.c to initialize the output URL to a null string in + fits_url2relurl. Also added more robust tests to see if 2 file + pointers point to the same file. + + - enhanced the template keyword parsing code in grparser.c to support + the 'D' exponent character in the ASCII representation of floating + point keyword values (as in TVAL = 1.23D03). Previously, the parser + would have writen this keyword with a string value (TVAL = '1.23D03'). + + - modified the low-level routines that write a keyword record to a FITS + header so that they silently replace any illegal characters (ASCII + values less than 32 or greater than 126) with an ASCII space character. + Previously, these routines would have returned with an error when + encountering these illegal characters in the keyword record (most commonly + tab, carriage return, and line feed characters). + + - made substantial internal changes to imcompress.c in preparation for + possible future support for compression methods for FITS tables analogous + to the tiled image compression method. + + - replaced all the source code in CFITSIO that was distributed under the + GNU General Public License with freely available code. In particular, + the gzip file compression and uncompression code was replaced by the + zlib compression library. Thus, beginning with this version 3.26 of CFITSIO, + other software applications may freely use CFITSIO without necessarily + incurring any GNU licensing requirement. See the License.txt file for + the CFITSIO licensing requirements. + + - added support for using cfitsio in different 'locales' which use a + comma, not a period, as the decimal point character in ASCII + representation of a floating point number (e.g., France). This + affects how floating point keyword values and floating point numbers + in ASCII tables are read and written with the 'printf' and 'strtod' + functions. + + - added a new utility routine called fits_copy_rows/ffcprw that copies + a specified range of rows from one table to another. + + - enhanced the test for illegal ASCII characters in a header (fftrec) to + print out the name of the offending character (e.g TAB or Line Feed) as + well as the Hex value of the chracter. + + - modified ffgtbc (in fitscore.c) to support nonstandard vector variable + length array columns in binary tables (e.g. with TFORMn = 2000PE(500)'). + + - modified the configure file to add "-lm" when linking CFITSIO on + Solaris machines. + + - added new routine, fits_get_inttype, to parse an integer keyword value + string and return the minimum integer datatype (TBYTE, TSHORT, TLONG, + TLONGLONG) required to store the integer value. + + - added new routine, fits_convert_hdr2str, which is similar to fits_hdr2str + 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. + + - modified the file template reading routine (ngp_line_from_file in + grparser.c) so that it ignores any carriage return characters (\r) + in the line, that might be present, e.g. if the file was created on a + Windows machine that uses \r\n as end of line characters. + + - modified the ffoptplt routine in cfileio.c to check if the PCOUNT + keyword in the template file has a non-zero value, and if so, resets + it to zero in the newly created file. + + Bug Fixes + + - fixed a bug when uncompressing floating-point images that contain Nan + values on some 64-bit platforms. + + - fixed a bug when updating the value of the CRPIXn world coordinate + system keywords when extracting a subimage from larger FITS image, using the + extended CFITSIO syntax (e.g. myimage[1:500:2, 1:500:2]). This bug only + affects casee where the pixel increment value is not equal to 1, and caused + the coordinate grid to be shifted by between 0.25 pixels (in the case of + a pixel increment of 2) and 0.5 pixels (for large pixel increment values). + + - fixed a potential string buffer overflow error in the ffmkls routine + that modifies the value and comment strings in a keyword that uses + the HEASARC long string keyword convention. + + - fixed a bug in imcompress.c that could cause programs to abort on 64-bit + machines when using gzip to tile-compress images. Changed the declaration + of clen in imcomp_compress_tile from int to size_t. + +Version 3.25 - 9 June 2010 + + - fixed bug that was introduced in version 3.13 that broke the ability + to reverse an image section along the y-axis with an image section + specifier like this: myimage.fits[*,-*]. This bug caused the output + image to be filled with zeros. + + - fixed typo in the definition of the ftgprh Fortran wrapper routine + in f77_wrap3.c. + + - modified the cfitsio.pc.in configuration file to make the lib path + a variable instead of hard coding the path. The provides more + flexibility for projects such as suse and fedora when building CFITSIO. + + - fixed bug in imcomp_compress_tile in imcompress.c which caused + null pixel values to be written incorrectly in the rare case where + the floating-point tile of pixels could not be quantized into integers. + + - modified imcompress.c to add a new specialized routine to uncompress + an input image and then write it to a output image on a tile by tile basis. + This appears to be faster than the old method of uncompressing the + whole image into memory before writing it out. It also supports + large images with more than 2**31 pixels. + + - made trivial changes to 2 statements in drvrfile.c to suppress + nuisance compiler warnings. + + - some compilers define CLOCKS_PER_SEC as a double instead of an integer, + so added an explicted integer type conversion to 2 statements in + imcompress.c that used this macro. + + - removed debugging printf statements in drvrnet.c (15 July) + +Version 3.24 - 26 January 2010 + + - modified fits_translate_keywords so that it silently ignores any + illegal ASCII characters in the value or comment fields of the input + FITS file. Otherwise, fpack would abort without compressing input + files that contained this minor violation of the FITS rules. + + - added support for Super H cpu in fitsio2.h + + - updated funpack to correctly handle the -S option, and to use a + more robust algorithm for creating temporary output files. + + - modified the imcomp_compress_tile routine to support the NOCOMPRESS + debugging option for real*4 images. + +Version 3.23 - 7 January 2010 + + - reduced the default value for the floating point image quantization + parameter (q) from 16 to 4. This parameter is used when tile compressing + floating point images. This change will increase the average compression + ratio for floating point images from about 4.6 to about 6.5 without losing + any significant information in the image. + + - enhanced the template keyword parsing routine to reject a header + template string that only contains a sequence of dashes. + + - enhanced the ASCII region file reading routine to allow tabs as well + as spaces between fields in the file. + + - got rid of bogus error message when calling fits_update_key_longstr + + - Made the error message more explicit when CFITSIO tries to write + to a GZIP compressed file. Instead of just stating "cannot write + to a READONLY file", it will say "cannot write to a GZIP compressed + file". + +Version 3.22 - 28 October 2009 + + - added an option (in imcompress.c) to losslessly compress floating + point images, rather than using the default integer scaling method. + This option is almost never useful in practice for astronomical + images (because the amount of compression is so poor), but it has + been added for test comparison purposes. + + - enhanced the dithering option when quantizing and compressing + floating point images so that a random dithering starting point + is used, so that the same dithering pattern does not get used for + every image. + + - modified the architecture setup section of fitsio2.h to support the + 64-core 8x8-architecture Tile64 platform (thanks to Ken Mighell, NOAO) + + Fixes + + - fixed a problem that was introduced in version 3.13 of CFITSIO + in cases where a program writes it own END keyword to the header + instead of letting CFITSIO do it, as is strongly recommended. In + one case this caused CFITSIO to rewrite the END keyword and any + blank fill keywords in the header many times, causing a + noticeable slow-down in the FITS file writing speed. + +Version 3.21 - 24 September 2009 + + - fixed bug in cfileio.c that caused CFITSIO to crash with a bus error + on Mac OS X if CFITSIO was compiled with multi-threaded support (with + the --enable-reentrant configure option). The Mac requires an + additional thread initialization step that is not required on Linux + machines. Even with this fix, occasional bus errors have been seen on + some Mac platforms, The bus errors are seen when running the + thread_test.c program. The bus errors are very intermittent, and occur + less than about 1% of the time, on the affected platforms. + These bus errors have not been seen on Linux platforms. + + - fixed invalid C comment delimiter ("//*" should have been "/*") + in imcompress.c. + + - Increased the CFITSIO version number string length + in fpackutil.c, to fix problem on some platforms when running + fpack -V or funpack -V. Also modified the output format of the + fpack -L command. + +Version 3.20 - 31 August 2009 + + - modified configure.in and configure so that it will build the Fortran + interface routines by default, even if no Fortran compiler is found + in the user's path. Building the interface routines may be disabled + by specifying FC="none". This was done at the request of users who + obtained CFITSIO from some other standard linux distributions, where + CFITSIO was apparently built in an environment that had no Fortran + compiler and hence did not build the Fortran wrappers. + + - modified ffchdu (close HDU) so that it calls the routine to update + the maximum length of variable length table columns in the TFORM + values in all cases where the values may have changed. Previously + it would not update the values if a value was already specified in + the TFORM value. + + - added 2 new string manipulation functions to the CFITSIO parser + (contributed by Craig Markwardt): strmid extracts a substring + from a string, and strstr searches for a substring within a string. + + - removed the code in quantize.c that treated "floating-point integer" + images as a special case (it would just do a datatype conversion from + float to int, and not otherwise quantize the pixel values). This + caused complications with the new subtractive dithering feature. + + - enhanced the code for converting floating point images to quantized + scaled integer prior to tile-compressing them, to apply a random + subtractive dithering, which improves the photometric accuracy + of the compressed images. + + - added new internal routine, iraf_delete_file, for use by fpack to + delete a pair of IRAF format header and pixel files. + + - small change in cfileio.c in the way it recognizes an IRAF format + .imh file. Instead of just requiring that the filename contain the + ".imh" string, that string must occur at the end of the file name. + + - fixed bug in the code that is used when tile-compressing real*4 FITS + images, which quantizes the floating point pixel values into + integer levels. The bug would only appear in the fairly rare + circumstance of tile compressing a floating point image that contains + null pixels (NaNs) and only when using the lossy Hcompress algorithm + (with the s parameter not equal to 1). This could cause underflow of + low valued pixels, causing them to appear as very large pixel values + (e.g., > 10**30) in the compressed image + + - changed the "if defined" blocks in fitsio.h, fitsio2.h and f77_wrap.h + to correctly set the length of long variables on sparc64 machines. + Patch contributed by Matthew Truch (U. Penn). + + - modified the HTTP file access code in drvrnet.c to support basic + HTTP authentication, where the user supplies a user name and + password. The CFITSIO filename format in this case is: + "http://username:password@hostname/..." + Thanks to Jochen Liske (ESO) for the suggestion and the code. + +Version 3.181 (BETA) - 12 May 2009 + + - modified region.c and region.h to add support for additional + types of region shapes that are supported by ds9: panda, epanda, + and bpanda. + + - fixed compiler error when using the new _REENTRANT flag, having to + do with the an attempted static definition of Fitsio_Lock in + several source files, after declaring it to be non-static in fitsio2.h. + +Version 3.18 (BETA) - 10 April 2009 + + - Made extensive changes to make CFITSIO thread safe. Previously, + all opened FITS files shared a common pool of memory to store + the most recently read or written FITS records in the files. + In a multi-threaded environment different threads could + simultaneously read or write to this common area causing + unpredictable results. This was changed so that every opened + FITS file has its own private memory area for buffering the + file. Most of the changes were in buffers.c, fitsio.h, and + fitsio2.h. Additional changes were made to cfileio.c, mainly + to put locks around small sections of code when setting up the + low-level drivers to read or write the FITS file. Also, locks + were needed around the GZIP compression and uncompression code + in compress.c., the error message stack access routine in + fitscore.c, the encode and decode routines in fits_hcompress.c + and fits_hdecompress.c, in ricecomp.c, and the table row + selection and table calculator functions. Also, removed the + 'static' declaration of the local variables in pliocomp.c + which did not appeared to be required and prevented the + routines from being thread safe. + + As a consequence of having a separate memory buffer for every + FITS file (by default, about 115 kB per file), CFITSIO may now + allocate more memory than previously when an application + program opens multiple FITS files at once. The read and write + speed may also be slightly faster, since the buffers are not + shared between files. + + - Added new families of Fortran wrapper routines to read and + write values to large tables that have more than 2**31 rows. + The arguments that define the first row and first element to + read or write must be I*8 integers, not ordinary I*4 + integers. The names of these new routines have 'LL' appended + to them, so for example, ftgcvb becomes ftgcvbll. + + Fixes + + - Corrected an obscure bug in imcompress.c that would have incorrectly + written the null values only in the rare case of writing a signed + byte array that is then tile compressed using the Hcompress or PLIO + algorithm. + +Version 3.14 - 18 March 2009 + + Enhancements + + - modified the tiled-image compression and uncompression code to + support compressing unsigned 16-bit integer images with PLIO. + FITS unsigned integer arrays are offset by -32768, but the PLIO + algorithm does not work with negative integer values. In this + case, an offset of 32768 is added to the array before compression, + and then subtracted again when reading the compressed array. + IMPORTANT NOTE: This change is not backward compatible, so + these PLIO compressed unsigned 16-bit integer images will not be + read correctly by previous versions of CFITSIO; the pixel values + will have an offset of +32768. + + - minor changes to the fpack utility to print out more complete + version information with the -V option, and format the report + produced by the -T option more compactly. + + Fixes + + - Modified imcomp_compress_image (which is called by fpack) so that + it will preserve any null values (NaNs) if the input image has + a floating point datatype (BITPIX = -32 or -64). Null values in + integer datatype images are handled correctly. + + - Modified imcomp_copy_comp2img so that it does not copy the + ZBLANK keyword, if present, from the compressed image header + when uncompressing the image. + + - Fixed typo in the Fortran wrapper macro for the ftexist function. + +Version 3.13 - 5 January 2009 + + Enhancements + + - updated the typedef of LONGLONG in fitsio.h and cfortran.h to + support the Borland compiler which uses the __int64 data type. + + - added new feature to the extended filename syntax so that when + performing a filtering operation on specified HDU, if you add + a '#' character after the name or number of the HDU, then ONLY + that HDU (and the primary array if the HDU is a table) will be + copied into the filtered version of the file in memory. Otherwise, + by default CFITSIO copies all the HDUs from the input file into + memory. + + - when specifying a section, if the specified number of dimensions + is less than the number of dimensions in the image, then CFITSIO + will use the entire dimension, as if a '*' had been specified. + Thus [1:100] is equivalent to [1:100,*] when specifying a section + of 2 dimensional image. + + - modified fits_copy_image_section to read/write the section 1 row + at a time, instead of the whole section, to reduce memory usage. + + - added new stream:// drivers for reading/writing to stdin/stdout. + This driver is somewhat fragile, but for simple FITS read and + write operations this driver streams the FITS file on stdin + or stdout without first copying the entire file in memory, as is + done when specifying the file name as "-". + + - slight modification to ffcopy to make sure that the END keyword + is correctly written before copying the data. This is required + by the new stream driver. + + - modified ffgcprll, so that when writing data to an HDU, it first + checks that the END keyword has been written to the correct place. + This is required by the new stream driver. + + Fixes + + - fixed bug in ffgcls2 when reading an ASCII string column in binary + tables in cases where the width of the column is greater than 2880 + characters and when reading more than 1 row at a time. Similar + change was made to ffpcls to fix same problem with writing to + columns wider than 2880 characters. + + - updated the source files listed in makepc.bat so that it can be + used to build CFITSIO with the Borland C++ compiler. + + - fixed overflow error in ffiblk that could cause writing to Large Files + (> 2.1 GB) to fail with an error status. + + - fixed a bug in the spatial region code (region.c) with the annulus + region. This bug only affected specialized applications which + directly use the internal region structure; it does not affect + any CFITSIO functions directly. + + - fixed memory corruption bug in region.c that was triggered if the + region file contained a large number of excluded regions. + + - got rid of a harmless error message that would appear if filtering + a FITS table with a GTI file that has zero rows. (eval_f.c) + + - modified fits_read_rgnfile so that it removes the error messages + from the error stack if it is unable to open the region file as + a FITS file. (region.c) + +Version 3.12 - 8 October 2008 + + - modified the histogramming code so that the first pixel in the binned + array is chosen as the reference pixel by default, if no other + value is previously defined. + + - modified ffitab and ffibin to allow a null pointer to the + EXTNAME string, when inserting a table with no name. + +Version 3.11 - 19 September 2008 + + - optimized the code when tile compressing real*4 images (which get + scaled to integers). This produced a modest speed increase. For + best performance, one must specify the absolute q quantization + parameter, rather than relative to the noise in the tile (which + is expensive to compute). + + - modified the FITS region file reading code to check for NaN values, + which signify the end of the array of points in a polygon region. + + - removed the test for LONGSIZE == 64 from fitsio.h, since it may + not be defined. + + - modified imcompress.c to support unconventional floating point FITS + images that also have BSCALE and BZERO keywords. The compressed + floating point images are linearly scaled twice in this case. + +Version 3.10 - 20 August 2008 + + - fixed a number of cases, mainly dealing with long input file names + (> 1024 char), where unsafe usage of strcat and strcpy could have caused + buffer overflows. These buffer overflows could cause the application + to crash, and at least theoretically, could be exploited by a + malicious user to execute arbitrary code. There are no known instances + of this type of malicious attack on CFITSIO applications, and the + likelihood of such an attack seems remote. None the less, it would + be prudent for CFITSIO users to upgrade to this new version to guard + against this possibility. + + - modified some of the routines to define input character string + parameters as "const char *" rather than just "char *" to eliminate + some compiler warnings when the calling routine passes a constant + string to the CFITSIO routine. Most of the changes were to the + keyword name argument in the many routines that read or write keywords. + + - fixed bug when tile-compressing a FITS image which caused all the + completely blank keywords in the input header to be deleted from + the output compressed image. Also added a feature to preserve any + empty FITS blocks in the header (reserved space for future keywords) + when compressing or uncompressing an image. + + - fixed small bug in the way the default tile size is set in imcompress.c. + (Fix sent in by Paul Price). + + - added support for reading FITS format region files (in addition + to the ASCII format that was previously supported). Thanks to + Keith Arnaud for modifying region.c to do this. + +Version 3.09 - 12 June 2008 + + - fixed bug in the calculator function, parse_data, that evaluates + expressions then selecting rows or modifying values in table columns. + This bug only appeared in unusual circumstances + where the calculated value has a null value (= TNULLn). The bug + could cause elements to not be flagged as having a null value, or + in rare cases could cause valid elements to be flagged as null. This + only appears to have affected 64-bit platforms (where size(long) = 8). + + - fixed typo in imcomp_decompress_tile: call to fffi2r8 should have + been to fffi4r8. + + - in the imcopy_copy_comp2img routine, moved the call to + fits_translate_keywords outside of the 'if' statement. This could + affect reading compressed images that did not have a EXTNAME keyword + in the header. + + - fixed imcomp_compress_tile in imcompress.c to properly support + writing unsigned integers, in place, to tile compressed images. + + - modified fits_read_compressed_img so that if the calling routine + specifies nullval = 0, then it will not check for null-valued + pixels in the compressed FITS image. This mimics the same + behavior when reading normal uncompressed FITS images. + +Version 3.08 - 15 April 2008 + + - fixed backwards compatibility issue when uncompressing a Rice + compressed image that was created with previous versions of + CFITSIO (this late fix was added on May 18). + + - small change to cfortran.h to add "extern" to the common block + definition. This was done for compatibility with the version + of cfortran.h that is distributed by the debian project. + + - relaxed the requirement that a string valued keyword must have a + closing quote character. If the quote is missing, CFITSIO will silently + append a quote at the end of the keyword record. This change was made + because otherwise it is very difficult to correct the keyword + because CFITSIO would exit with an error before making the fix. + + - added a new BYTEPIX compression parameter when tile-compressing + images with the Rice algorithm. + + - cached the NAXIS and NAXISn keyword values in the fitsio structure + for efficiency, to eliminate duplicates reads of these keywords. + + - added variants of the Rice compression and uncompression routines to + support short int images (in addition to the routines that support int). + + - moved the definition of LONGLONG_MIN and LONGLONG_MAX from fitsio2.h + to fitsio.h, to make it accessible to application programs. + + - make efficiency improvements to fitscore.c, to avoid needless searches + through the entire header when reading the required keywords that must + be near the beginning of the header. + + - made several improvements to getcol.c to optimize reading of compressed + and uncompressed images. + + - changed the compression level in the gzip code from 6 to 1. In most + cases this will provide nearly the same amount of compression, but is + significantly faster in some cases. + + - added new "helper routines' to imcompress.c to allow applications to + specified the "quantize level" and Hcompress scaling and smoothing + parameters + + - modified the extended filename syntax to support the "quantize level" + and Hcompress scaling and smoothing parameters. The parser in + cfileio.c was extensively modified. + + - extensive changes to quantize.c: + - replace the "nbits" parameter with "quantize level" + - the quantize level is now relative to the RMS noise in the image + - the HCOMPRESS scale factor is now relative to the RMS noise + - added routines to calculate RMS noise in image + (these changes require a change to the main file structure in fitsio.h) + + - initialize errno = 0 before the call to strtol in ffext, in case errno + has previously been set by an unrelated error condition. + + - added the corresponding long name for the ffgkyjj routine to longnam.h. + + - changed imcomp_copy_comp2img (in imcompress.c) to not require the + presence of the EXTNAME keyword in the input compressed image header. + + - modified imcompress.c to only write the UNCOMPRESSED_DATA column + in tile-compressed images if it is actually needed. This eliminates + the need to subsequently delete the column if it is not used + (which is almost always the case). + + - found that it is necessary to seek to the EOF of a file after + truncating the size of the file, to reestablish a definite + current location in the file. The required small changes to 3 + routines: file_truncate (to seek to EOF) and fftrun (to set io_pos) + and the truncation routine in drvrmem.c. + + - improved the efficiency when compressing integer images with + gzip. Previously, the image was always represented using integer*4 + pixels, which were then compressed. Now, if the range of pixel + values can be represented with integer*2 pixels or integer*1 pixels, + then that is used. This change is backward compatible with any + compressed images that used the previous method. + + - changed the default tiling pattern when using Hcompress from + large squares (200 to 600 pixels wide) to 16 rows of the image. + This generally requires less memory, compresses faster, and is more + consistent with the default row by row tiling when using the other + compression methods. + + - modified imcomp_init_table in imcompress.c to enforce a restriction + when using the Hcompress algorithm that the 1st 2 dimensions of sll + image tiles must be at least 4 pixels long. Hcompress becomes very + inefficient for smaller dimensions, and does not work at all with + 1D images. + + - fixed bug in the Hcompress compression algorithm that could affect + compression of I*4 images, using non-square compression tiles + (in the encode64 routine). + +Version 3.07 - 6 December 2007 (internal release) + + - fixed bug with the PLIO image compression routine which silently + produced a corrupted compressed image if the uncompressed image pixels + were not all in the range 0 to 2**24. (fixed in November) + + - fixed several 'for' loops in imcompress.c which were exceeding the + bounds of an array by 1. (fixed in November) + + - fixed a possible, but unlikely, memory overflow issue in iraffits.c. + + - added a clarification to the cfortran.doc file that cfortran.h + may be used and distributed under the terms of the GNU Library + General Public License. + + - fixed bug in the fits_modify_vector_len routine when modifying + the vector length of a 'X' bit column. + +Version 3.06 - 27 August 2007 + + - modified the imcopy.c utility program (to tile-compress images) + so that it writes the default EXTNAME = 'COMPRESSED_IMAGE' + keyword in the compressed images, to preserve the behavior of + earlier versions of imcopy. + + - modified the angsep function in the FITS calculator (in eval.y) + to use haversines, instead of the 'law of cosines', to provide + more precision at small angles (< 0.1 arcsec). + +Version 3.05 - July 2007 (internal release only) + + - extensive changes to imcompress.c to fully support implicit data + type conversion when reading and writing arrays of data to FITS + images, where the data type of the array is not the same as the + data type of the FITS image. This includes support for null pixels, + and data scaling via the BSCALE and BZERO keywords. + + - rewrote the fits_read_tbl_coord routine in wcssub.c, that gets the + standard set of WCS keywords appropriate to a pair of columns in a + table, to better support the full set of officially approved WCS keywords. + + - made significant changes to histo.c, which creates an image by binning + columns of a table, to better translate the WCS keywords in the table + header into the WCS keywords that are appropriate for an image HDU. + + - modified imcompress.c so that when pixels are written to a + tile-compressed image, the appropriate BSCALE and BZERO values of + that image are applied. This fixes a bug in which writing to + an unsigned integer datatype image (with BZERO = 32768) was not + done correctly. + +Version 3.04 - 3 April 2007 + + - The various table calculator routines (fits_select_rows, etc.) implicitly + assumed that the input table has not been modified immediately prior to + the call. To cover cases where the table has been modified a call to + ffrdef has been added to ffprs. IN UNUSUAL CASES THIS CHANGE COULD + CAUSE CFITSIO TO BEHAVE DIFFERENTLY THAN IN PREVIOUS VERSIONS. For + example, opening a FITS table with this column-editing virtual file + expression: + myfile.fits[3][col A==X; B = sqrt(X)] + no longer works, because the X column does not exist when the + sqrt expression is evaluated. The correct expression in this case is + myfile.fits[3][col A==X; B = sqrt(A)] + + - modified putkey.c to support USHORT_IMG when calling fits_create_img + to create a signed byte datatype image. + + - enhanced the column histogramming function to propagate any TCn_k and + TPn_k keywords in the table header to the corresponding CDi_j and PCi_j + keywords in the image header. + + - enhanced the random, randomn, and randomp functions in the lexical + parser to take a vector column name argument to specify the length + of the vector of random numbers that should be generated (provided by + Craig Markwardt, GSFC) + + - enhanced the ffmcrd routine (to modify an existing header card) to + support long string keywords so that any CONTINUE keywords associated + with the previous keyword will be deleted. + + - modified the ffgtbp routine to recognize the TDIMn keyword for + ASCII string columns in a binary table. The first dimension is + taken to be the size of a unit string. (The TFORMn = 'rAw' + syntax may also be used to specify the unit string size). + + - in fits_img_decompress, the fits_get_img_param function was called + with an invalid dimension size, which caused a fatal error on at + least 1 platform. + + - in ffopentest, set the status value before returning in case of error. + + - in the drvrnet.c file, the string terminators needed to be changed + from "\n" to "\r\n" to support the strict interpretation of the + http and ftp standard that is enforced by some newer web servers. + +Version 3.03 - 11 December 2006 + + New Routine + + - fits_write_hdu writes the current HDU to a FILE stream (e.g. stdout). + + Changes + + - modified the region parsing code to support region files where the + keyword "physical" is on a separate line preceding the region shape + token. (However, "physical" coordinates are not fully supported, and + are treated identically to "image" coordinates). + + - enhanced the iterator routines to support calculations on 64-bit + integer columns and images. Currently, the values are cast to + double precision when doing the calculations, which can cause a + loss of precision for integer values greater than about 2**52. + + - added support for accessing FITS files on the computational grid. + Giuliano Taffoni and Andrea Barisani, at INAF, University of Trieste, + Italy, implemented the necessary I/O driver routines in drvrgsiftp.c. + + - modified the tiled image compression/uncompression routines to + preserve/restore the original CHECKSUM and DATASUM keywords if they + exist. (saved as ZHECKSUM and ZDATASUM in the compressed image) + + - split fits_select_image_section into 2 routines: a higher level routine + that creates the output file and copies other HDUs from the input file + to the output file, and a lower level routine that extracts the image + section from the input image into an output image HDU. + + - Improved the error messages that get generated if one tries to + use the lexical parser to perform calculations on variable-length + array columns. + + - added "#define MACHINE NATIVE" in fitsio2.h for all machines where + BYTESWAPPED == FALSE. This may improve the file writing performance + by eliminating the need to allocate a temporary buffer in some cases. + + - modified the configure.in and configure script to fix problems with + testing if network services are available, which affects the definition + of the HAVE_NET_SERVICES flag. + + - added explicit type casting to all malloc statements, and deleted + declarations of unreferenced variables in the image compression code + to suppress compiler warnings. + + - fixed incorrect logic in fitsio2.h in the way it determined if numerical + values are byteswapped or not on MIPS and ARM architectures. + + - added __BORLANDC__ to the list of environments in fitsio.h that don't + use %lld in printf for longlong integers + + - added "#if defined(unix)" around "#include " statements in + several C source files, to make them compatible with Windows. + + +Version 3.02 - 18 Sept 2006 + + - applied the security patch to the gzip code, available at + http://security.FreeBSD.org/patches/SA-06:21/gzip.patch + The insufficient bounds checks in buffer use can cause gzip to crash, + and may permit the execution of arbitrary code. The NULL pointer + deference can cause gzip to crash. The infinite loop can cause a + Denial-of-Service situation where gzip uses all available CPU time. + + - added HCOMPRESS as one of the compression algorithm options in the + tiled image compression code. (code provided by Richard White (STScI)) + Made other improvements to preserve the exact header structure in the + compressed image file so that the compressed-and-then-uncompressed FITS + image will be as identical as possible to the original FITS image file. + + New Routines + + - the following new routines were added to support reading and writing + non-standard extension types: + fits_write_exthdr - write required keywords for a conforming extension + fits_write_ext - write data to the extension + fits_read_ext - read data from the extension + + - added new routines to compute the RMS noise in the background pixels + of an image: fits_rms_float and fits_rms_short (take an input + array of floats or shorts, respectively). + + Fixes + + - added the missing 64-bit integer case to set of "if (datatype)" + statements in the routine that returns information about a + particular column (ffgbclll). + + - fixed a parsing error in ffexts in cases where an extension number + is followed by a semi-colon and then the column and row number of an + array in a binary table. Also removed an extraneous HISTORY keyword + that was being written when specifying an input image in a table cel. + + - modified the routine that reads a table column returning a string + value (ffgcls) so that if the displayed numerical value is too + wide to fit in the specified length string, then it will return + a string of "*" characters instead of the number string. + + - small change to fitsio.h to support a particular Fortran and C + compiler combination on a SGI Altix system + + - added a test in the gunzip code to prevent seg. fault when trying + to uncompress a corrupted file (at least in some cases). + + - fixed a rarely-occurring bug in the routine that copies a table + cell into an image; had to call the ffflsh call a few lines earlier. + +Version 3.01 - (in FTOOLS 6.1 release) + + - modified fits_copy_image2cell to correctly copy all the appropriate + header keywords when copying an image into a table cell + + - in eval.y, explicitly included the code for the lgamma function + instead of assuming it is available in a system library (e.g., the + lgamma function is currently not included in MS Visual++ libraries) + + - modified the logic in fits_pixel_filter so that the default data + type of the output image will be promoted to at least BITPIX = -32 + (a single precision floating point) if the expression that is being + evaluated resolves to a floating point result. If the expression + resolves to an integer result, the output image will have the same + BITPIX as the input image. + + - in fits_copy_cell2image, added 5 more WCS keywords to the list of + keywords related to other columns that should be deleted in the + output image header. + + - disabled code in cfileio.c that would write HISTORY keywords to the + output file in fits_copy_image2cell and cell2image, because some tasks + would not want these extraneous HISTORY keywords. + + - added 2 new random number functions to the CFITSIO parser + RANDOMN() - produces a normal deviate (mean=0, stddev=1) + RANDOMP(X) - produces a Poisson deviate for an expected # of counts X + + - in f77_wrap.h, removed the restriction that "g77Fortran" must be + defined on 64-bit Itanium machines before assuming that + sizeof(long) = 8. It appears that "long"s are always + 8 bytes long on this machine, regardless of what compilers are used. + + - added test in fitsio.h so that LONGLONG cannot be multiply defined + + - modified longnam.h so that both "fits_write_nulrows" and + "fits_write_nullrows" get replace by the string "ffprwu". This + fixes a documentation error regarding the long name of this + routine. + + Bug fixes + + - fixed a potential null character string dereferencing error in the + the ffphtb and ffphbn routines that write the FITS table keywords. + This concerned the optional TUNITn keywords. + + - fixed a few issues in fits_copy_cell2image and fits_copy_image2cell + related to converting some WCS keyword between the image extension + form and the table cell form of the keyword. (cfileio.c) + + - fixed bug in fits_translate_keyword (fitscore.c) that, e.g., caused + 'EQUINOX' to be translated to EQUINOXA' if the pattern is 'EQUINOXa' + + - fixed 2 bugs that could affect 'tile compressed' floating point + images that contain NaN pixels (null pixels). First, the + ZBLANK keyword was not being written, and second, an integer + overflow could occur when computing the BZERO offset in the + compressed array. (quantize.c and imcompress.c) + +Version 3.006 - 20 February 2006 -(first full release of v3) + + - enhanced the 'col' extended filename syntax to support keyword name + expressions like + [col error=sqrt(rate); #TUNIT# = 'counts/s'], + in which the trailing '#' will be replaced by the column number + of the most recently referenced column. + + - fixed bug in the parse_data iterator work function that caused it + to fail to return a value of -1 in cases where only a selected + set of rows were to be processed. (affected Fv) + + - added code to fitsio.h and cfortran.h to typedef LONGLONG to + the appropriate 8-byte integer data type. Most compilers now + support the 'long long' data type, but older MS Visual C++ + compilers used '__int64' instead. + + - made several small changes based on testing by Martin Reinecke: + o in eval.y, change 'int undef' to 'long undef' + o in getcold.c and getcole.c, fixed a couple format conversion + specifiers when displaying the value of long long variables. + o in fitsio.h, modified the definition of USE_LL_SUFFIX in the + case of Athon64 machines. + o in fitsio2.h, defined BYTESWAPPED in the case of SGI machines. + o in group.c, added 'include unistd.h' to get rid of compiler warning. + +Version 3.005 - 20 December 2005 (beta) + + - cfortran.h has been enhanced to support 64-bit integer parameters + when calling C routines from Fortran. This modification was kindly + provided by Martin Reinecke (MPE, Garching). + + - Many new Fortran wrapper routines have been added to support reading + and writing 64-bit integer values in FITS files. These new routines + are documented in the updated version of the 'FITSIO User's Guide' + for Fortran programmers. + + - fixed a problem in the fits_get_keyclass routine that caused it + to not recognize the special COMMENT keywords at the beginning + of most FITS files that defines the FITS format. + + - added a new check to the ffifile routine that parses the + input extended file name, to distinguish between a FITS extension + name that begins with 'pix', and a pixel filtering operator that + begins with the 'pix' keyword. + + - small change to the WCSLIB interface routine, fits_read_wcstab, to + be more permissive in allowing the TDIMn keyword to be omitted for + degenerate coordinate array. + +Version 3.004 - 16 September 2005 (3rd public beta release) + + - a major enhancement to the CFITSIO virtual file parser was provided + by Robert Wiegand (GSFC). One can now specify filtering operations + that will be applied on the fly to the pixel values in a FITS image. + For example [pix sqrt(X)] will create a virtual FITS image where the + pixel values are the square root of the input image pixels. + + - modified region.c so that it interprets the position angles of regions + in a SAO style region file in the same way as DS9. In particular, if + the region parameters are given in WCS units, then the position angle + should be relative to the WCS coordinates of the image (increasing CCW + from West) instead of relative to the X/Y pixel coordinate system. + This only affects rotated images (e.g. with non-zero CROTA2 keyword) + with elliptical or rectangular regions. + + - cleaned up fitsio.h and fitsio2.h to make the definition of LONGLONG + and BYTESWAPPED and MACHINE more logical. + + - removed HAVE_LONGLONG everywhere since it is no longer needed (the + compiler now must have an 8-byte integer datatype to build CFITSIO). + + - added support for the 64-bit IBM AIX platform + + - modified eval.y so that the circle, ellipse, box, and near functions + can operate on vectors as well as scalars. This allows region filtering + on images that are stored in a vector cell in a binary table. + (provided by Craig Markwardt, GSFC) + + New Routines + + - added new fits_read_wcstab routine that serves as an interface to + Mark Calabretta's wcslib library for reading WCS information when + the -TAB table lookup convention is used in the FITS file. + + - added new fits_write_nullrows routine, which writes null values into + every column of a specified range of rows in a FITS table. + + - added the fits_translate_keyword and fits_translate_keywords utility + routines for converting the names of keywords when moving columns and + images around. + + - added fits_copy_cell2image and fits_copy_image2cell routines for + copying an image extension (or primary array) to or from a cell + in a binary table vector column. + + Bug fixes + + - fixed a memory leak in eval.y; was fixed by changing a call to malloc + to cmalloc instead. + + - changed the definition of several global variables at the beginning + of buffers.c to make them 'static' and thus invisible to applications + programs. + + - in fits_copy_image_cell, added a call to flush the internal buffers + before reading from the file, in case any records had been modified. + +Version 3.003 - 28 July 2005 - 2nd public beta release (used in HEASOFT) + + Enhancements + + - enhanced the string column reading routing fits_get_col_str to + support cases where the user enters a null pointer (rather than + a null string) as the nulval parameter. + + - modified the low level ffread and ffwrite routines that physically + read and write data from the FITS file so that they write the name + of the file to the CFITSIO error stack if an error occurs. + + - changed the definition of fits_open_file into a macro that will test + that the version of the fitsio.h include file that was used to + build the CFITSIO library is the same version as included when + compiling the application program. + + - made a simple modification to region.c to support regions files + of type "linear", for compatibility with ds9 and fv. + + - modified the internal ffgpr routine (and renamed it ffgprll) so + that it returns the TNULL value as a LONGLONG parameter instead + of 'long'. + + - in fits_get_col_display_width, added support for TFORM = 'k' + + - modified fitsio.h, fitsio2.h, and f77_wrap.h to add test for (_SX) + to identify NEC SX supercomputers. + + - modified eval_f.c to treat table columns of TULONG (unsigned long) + as a double. Also added support for TLONGLONG (8-byte integers) as + a double, which is only a temporary fix, since doubles only have about + 52 bits of precision. + + - changed the 'blank' parameter in the internal ffgphd function to + to type LONGLONG to support integer*8 FITS images. + + - when reading the TNULL keyword value, now use ffc2jj instead of + ffc2ii, to support integer*8 values. + + Bug fixes + + - fixed a significant bug when writing character strings to a variable + length array column of a binary table. This bug would result in some + unused space in the variable length heap, making the heap somewhat + larger than necessary. This in itself is usually a minor issue, since + the FITS files are perfectly valid, and other software should have + no problems reading back the characters strings. In some cases, however, + this problem could cause the program that is writing the table + to exit with a status = 108 disk read error. + + - modified the standalone imcopy.c utility program to fix a memory allocation + bug when running on 64-bit platforms where sizeof(long) = 8 bytes. + + - added an immediate 'return' statement to ffgtcl if the input status >0, + to prevent a segfault on some platforms. + +Version 3.002 - 15 April 2005 - first public beta release + + - in drvrfile.c, if it fails to open the file for some reason, then + it should reset file_outfile to a null string, to avoid errors on + a subsequent call to open a file. + + - updated fits_get_keyclass to recognize most of the WCS keywords + defined in the WCS Papers I and II. + +Version 3.001 - 15 March 2005 - released with HEASOFT 6.0 + + - numerous minor changes to the code to get rid of compiler warning + messages, mainly dealing with numerical data type casting and the + subsequent possible loss of precision in the result. + +Version 3.000 - 1 March 2005 (internal beta release) + + Enhancements: + + - Made major changes to many of the CFITSIO routines to more generally + support Large Files (> 2.1 GB). These changes are intended to + be 100% backward compatible with software that used the previous + versions of CFITSIO. The datatype of many of the integer parameters + in the CFITSIO functions has been changed from 'long' to 'LONGLONG', + which is typedef'ed to be equivalent to an 8-byte integer datatype on + each platform. With these changes, CFITSIO supports the following: + - integer FITS keywords with absolute values > 2**31 + - FITS files with total sizes > 2**31 bytes + - FITS tables in which the number of rows, the row width, or + the size of the heap is > 2**31 bytes + - FITS images with dimensions > 2**31 bytes (support is still + somewhat limited, with full support to be added later). + + - added another lexical parser function (thanks to Craig Markwardt, + GSFC): angsep computes the angular separation between 2 positions + on the celestial sphere. + + - modified the image subset extraction code (e.g., when specifying + an image subregion when opening the file, such as + 'myimage.fits[21:40, 81:90]') so that in addition to + updating the values of the primary WCS keywords CRPIXk, CDELTi, and + CDj_i in the extracted/binned image, it also looks for and updates + any secondary WCS keywords (e.g., 'CRPIX1P'). + + - made cosmetic change to group.c, so that when a group table is + copied, any extra columns will be appended after the last existing + column, instead of being inserted before the last column. + + - modified the routines that read tile compressed images to support + NULL as the input value for the 'anynul' parameter (meaning the + calling program does not want the value of 'anynul' returned to it). + + - when constructing or parsing a year/month/day character string, + (e.g, when writing the DATE keyword) the routines now rigorously + verify that the input day value is valid for the given month + (including leap years). + + - added some checks in cfileio.c to detect if some vital parameters + that are stored in memory have been corrupted. This can occur if + a user's program writes to areas of memory that it did not allocate. + + - added the wcsutil_alternate.c source code file which contains + non-working stubs for the 2 Classic AIPS world coordinate + conversion routines that are distributed under the GNU General + Public License. Users who are unwilling or unable to distribute + their software under the General Public License may use this + alternate source file which has no GPL restrictions, instead + of wcsutil.c. This will have no effect on programs that use + CFITSIO as long as they do not call the fits_pix_to_world/ffwldp + or fits_world_to_pix/ffxypx routines. + + Bug Fixes + + - in ffdtdm (which parses the TDIMn keyword value), the check for + consistency between the length of the array defined by TDIMn and + the size of the TFORMn repeat value, is now not performed for variable + length array columns (which always have repeat = 1). + + - fixed byteswapping problem when writing null values to non-standard + long integer FITS images with BITPIX = 64 and FITS table columns with + TFORMn = 'K'. + + - fixed buffer overflow problem in fits_parse_template/ffgthd that + occurred only if the input template keyword value string was much + longer than can fit in an 80-char header record. + +Version 2.510 - 2 December 2004 + + New Routines: + + - added fits_open_diskfile and fits_create_diskfile routines that simply + open or create a FITS file with a specified name. CFITSIO does not + try to parse the name using the extended filename syntax. + + - 2 new C functions, CFITS2Unit and CUnit2FITS, were added to convert + between the C fitsfile pointer value and the Fortran unit number. + These functions may be useful in mixed language C and Fortran programs. + + Enhancements: + + - added the ability to recognize and open a compressed FITS file + (compressed with gzip or unix compress) on the stdin standard input + stream. + + - Craig Markwardt (GSFC) provided 2 more lexical parser functions: + accum(x) and seqdiff(x) that compute the cumulative sum and the + sequential difference of the values of x. + + - modified putcole.c and putcold.c so that when writing arrays of + pixels to the FITS image or column that contain null values, and + there are also numerical overflows when converting some of the + non-null values to the FITS values, CFITSIO will now ignore the + overflow error until after all the data have been written. Previously, + in some circumstances CFITSIO would have simply stopped writing any + data after the first overflow error. + + - modified fitsio2.h to try to eliminate compiler warning messages + on some platforms about the use of 'long long' constants when + defining the value of LONGLONG_MAX (whether to use L or LL + suffix). + + - modified region.c to support 'physical' regions in addition to + 'image', 'fk4', etc. + + - modified ffiurl (input filename parsing routine) to increase the + maximum allowed extension number that can be specified from 9999 + to 99999 (e.g. 'myfile.fits+99999') + + Bug Fixes: + + - added check to fits_create_template to force it to start with + the primary array in the template file, in case an extension + number was specified as part of the template FITS file name. + +Version 2.500 - 28 & 30 July 2004 + + New Routine: + + - fits_file_exists tests whether the specified input file, or a + compressed version of the file, exists on disk. + + Enhancements: + + - modified the way CFITSIO reads and writes data in COMPLEX ('C') and + DBLCOMPLEX 'M' columns. Now, in all cases, when referring to the + number of elements in the vector, or the value of the offset to a + particular element within the vector, CFITSIO considers each pair of + numbers (the imaginary and real parts) as a single element instead of + treating each single number as an element. In particular, this changes + the behavior of fits_write_col_null when writing to complex columns. + It also changes the length of the 'nullarray' vector in the + fits_read_colnull routine; it is now only 1/2 as long as before. + Each element of the nullarray is set = 1 if either the real or + imaginary parts of the corresponding complex value have a null + value.(this change was added to version 2.500 on 30 July). + + - Craig Markwardt, at GSFC, provided a number of significant enhancements + to the CFITSIO lexical parser that is used to evaluate expressions: + + - the parser now can operate on bit columns ('X') in a similar + way as for other numeric columns (e.g., 'B' or 'I' columns) + + - range checking has been implemented, so that the following + conditions return a Null value, rather than returning an error: + divide by zero, sqrt(negative), arccos(>1), arcsin(>1), + log(negative), log10(negative) + + - new vector functions: MEDIAN, AVERAGE, STDDEV, and + NVALID (returns the number of non-null values in the vector) + + - all the new functions (and SUM, MIN and MAX) ignore null values + + - modified the iterator to support variable-length array columns + + - modified configure to support AIX systems that have flock in a non- + standard location. + + - modified configure to remove the -D_FILE_OFFSET_BITS flag when running + on Mac Darwin systems. This caused conflicts with the Fortran + wrappers, and should only be needed in any case when using CFITSIO + to read/write FITS files greater than 2.1 GB in size. + + - modified fitsio2.h to support compilers that define LONG_LONG_MAX. + + - modified ffrsim (resize an existing image) so that it supports changing + the datatype to an unsigned integer image using the USHORT_IMG and + ULONG_IMG definitions. + + - modified the disk file driver (drvrfile.c) so that if an output + file is specified when opening an ordinary file (e.g. with the syntax + 'myfile.fits(outputfile.fits)' then it will make a copy of the file, + close the original file and open the copy. Previously, the + specified output file would be ignored unless the file was compressed. + + - modified f77_wrap.h and f77_wrap3.c to support the Fortran wrappers + on 64-bit AMD Opteron machines + + Bug fixes: + + - made small change to ffsrow in eval_f.c to avoid potential array + bounds overflow. + + - made small change to group.c to fix problem where an 'int' was + incorrectly being cast to a 'long'. + + - corrected a memory allocation error in the new fits_hdr2str routine + that was added in version 2.48 + + - The on-the-fly row-selection filtering would fail with a segfault + if the length of a table row (NAXIS1 value) was greater than + 500000 bytes. A small change to eval_f.c was required to fix this. + +Version 2.490 - 11 February 2004 + + Bug fixes: + + - fixed a bug that was introduced in the previous release, which caused + the CFITSIO parser to no longer move to a named extension when opening + a FITS file, e.g., when opening myfile.fit[events] CFITSIO would just + open the primary array instead of moving to the EVENTS extension. + + - new group.c file from the INTEGRAL Science Data Center. It fixes + a problem when you attach a child to a parent and they are both + is the same file, but, that parent contains groups in other files. + In certain cases the attach would not happen because it seemed that + the new child was already in the parent group. + + - fixed bug in fits_calculator_rng when performing a calculation + on a range of rows in a table, so that it does not reset the + value in all the other rows that are not in the range = 0. + + - modified fits_write_chksum so that it updates the TFORMn + keywords for any variable length vector table columns BEFORE + calculating the CHECKSUM values. Otherwise the CHECKSUM + value is invalidated when the HDU is subsequently closed. + +Version 2.480 - 28 January 2004 + + New Routines: + + - fits_get_img_equivtype - just like fits_get_img_type, except in + the case of scaled integer images, it returns the 'equivalent' + data type that is necessary to store the scaled data values. + + - fits_hdr2str copies all the header keywords in the current HDU + into a single long character string. This is a convenient method + of passing the header information to other subroutines. + The user may exclude any specified keywords from the list. + + Enhancements: + + - modified the filename parser so that it accepts extension + names that begin with digits, as in 'myfile.fits[123TEST]'. + In this case CFITSIO will try to open the extension with + EXTNAME = '123TEST' instead of trying to move to the 123rd + extension in the file. + + - the template keyword parser now preserves the comments on the + the mandatory FITS keywords if present, otherwise a standard + default comment is provided. + + - modified the ftp driver file (drvrnet.c) to overcome a timeout + or hangup problem caused by some firewall software at the user's + end (Thanks to Bruce O'Neel for this fix). + + - modified iraffits.c to incorporate Doug Mink's latest changes to + his wcstools library routines. The biggest change is that now + the actual image dimensions, rather than the physically stored + dimensions, are used when converting an IRAF file to FITS. + + Bug fixes: + + - when writing to ASCII FITS tables, the 'elemnum' parameter was + supposed to be ignored if it did not have the default value of 1. + In some cases however setting elemnum to a value other than 1 + could cause the wrong number of rows to be produced in the output + table. + + - If a cfitsio calculator expression was imported from a text file + (e.g. using the extended filename syntax 'file.fits[col @file.calc]') + and if any individual lines in that text file were greater than + 255 characters long, then a space character would be inserted + after the 255th character. This could corrupt the line if the space + was inserted within a column name or keyword name token. + +Version 2.480beta (used in the FTOOLS 5.3 release, 1 Nov 2003) + + New Routines: + + - fits_get_eqcoltype - just like fits_get_coltype, except in the + case of scaled integer columns, it returns the 'equivalent' + data type that is necessary to store the scaled data values. + + - fits_split_names - splits an input string containing a comma or + space delimited list of names (typically file names or column + names) into individual name tokens. + + Enhancements: + + - changed fhist in histo.c so that it can make histograms of ASCII + table columns as well as binary table columns (as long as they + contain numeric data). + + Bug fixes: + + - removed an erroneous reference to listhead.c in makefile.vcc, that is + used to build the cfitsio dll under Windows. This caused a 'main' + routine to be added to the library, which causes problems when linking + fortran programs to cfitsio under windows. + + - if an error occurs when opening for a 2nd time (with ffopen) a file that + is already open (e.g., the specified extension doesn't exist), and + if the file had been modified before attempting to reopen it, then + the modified buffers may not get written to disk and the internal + state of the file may become corrupted. ffclos was modified to + always set status=0 before calling ffflsh if the file has been + concurrently opened more than once. + +Version 2.470 - 18 August 2003 + + Enhancements: + + - defined 'TSBYTE' to represent the 'signed char' datatype (similar to + 'TBYTE' that represents the 'unsigned char' datatype) and added + support for this datatype to all the routines that read or write + data to a FITS image or table. This was implemented by adding 2 + new C source code files to the package: getcolsb.c and putcolsb.c. + + - Defined a new '1S' shorthand data code for a signed byte column in + a binary table. CFITSIO will write TFORMn = '1B' and + TZEROn = -128 in this case, which is the convention used to + store signed byte values in a 'B' type column. + + - in fitsio2.h, added test of whether `__x86_64__` is defined, to + support the new AMD Opteron 64-bit processor + + - modified configure to not use the -fast compiler flag on Solaris + platforms when using the proprietary Solaris cc compiler. This + flag causes compilation problems in eval_y.c (compiler just + hangs forever). + + Bug fixes: + + - In the special case of writing 0 elements to a vector table column + that contains 0 rows, ffgcpr no longer adds a blank row to the table. + + - added error checking code for cases where a ASCII string column + in a binary table is greater than 28800 characters wide, to avoid + going into an infinite loop. + + - the fits_get_col_display_width routine was incorrectly returning + width = 0 for a 'A' binary table column that did not have an + explicit vector length character. + +Version 2.460 - 20 May 2003 + + Enhancements: + + - modified the HTTP driver in drvrnet.c so that CFITSIO can read + FITS files via a proxy HTTP server. (This code was contributed by + Philippe Prugniel, Obs. de Lyon). To use this feature, the + 'http_proxy' environment variable must be defined with the + address (URL) and port number of the proxy server, i.e., + > setenv http_proxy http://heasarc.gsfc.nasa.gov:3128 + will use port 3128 on heasarc.gsfc.nasa.gov + + - suppressed some compiler warnings by casting a variable of + type 'size_t' to type 'int' in fftkey (in fitscore.c) and + iraftofits and irafrdimge (in iraffits.c). + +Version 2.450 - 30 April 2003 + + Enhancements: + + - modified the WCS keyword reading routine (ffgics) to support cases + where some of the CDi_j keywords are omitted (with an assumed + value = 0). + + - Made a change to http_open_network in drvrnet.c to add a 'Host: ' + string to the open request. This is required by newer HTTP 1.1 + servers (so-called virtual servers). + + - modified ffgcll (read logical table column) to return the illegal + character value itself if the FITS file contains a logical value that is + not equal to T, F or zero. Previously it treated this case the + same as if the FITS file value was = 0. + + - modified fits_movnam_hdu (ffmnhd) so that it will move to a tile- + compressed image (that is stored in a binary table) if the input + desired HDU type is BINARY_TBL as well as if the HDU type = IMAGE_HDU. + + Bug fixes: + + - in the routine that checks the data fill bytes (ffcdfl), the call + to ffmbyt should not ignore an EOF error when trying to read the bytes. + This is a little-used routine that is not called by any other CFITSIO + routine. + + - fits_copy_file was not reporting an error if it hit the End Of File + while copying the last extension in the input file to the output file. + + - fixed inconsistencies in the virtual file column filter parser + (ffedit_columns) to properly support expressions which create or + modify a keyword, instead of a column. Previously it was only possible + to modify keywords in a table extension (not an image), and the + keyword filtering could cause some of the table columns to not + get propagated into the virtual file. Also, spaces are now + allowed within the specified keyword comment field. + + - ffdtyp was incorrectly returning the data type of FITS keyword + values of the form '1E-09' (i.e., an exponential value without + a decimal point) as integer rather than floating point. + + - The enhancement in the previous 2.440 release to allow more files to be + opened at one time introduced a bug: if ffclos is called with + a non-zero status value, then any subsequent call to ffopen will likely + cause a segmentation fault. The fits_clear_Fptr routine was modified + to fix this. + + - rearranged the order of some computations in fits_resize_img so as + to not exceed the range of a 32-bit integer when dealing with + large images. + + - the template parser routine, 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 value. + +Version 2.440 - 8 January 2003 + + Enhancements: + + - modified the iterator function, ffiter, to operate on random + groups files. + + - decoupled the NIOBUF (= 40) parameter from the limit on the number + FITS files that can be opened, so that more files may be opened + without the overhead of having to increase the number of NIOBUF + buffers. A new NMAXFILES parameter is defined in fitsio2.h which sets + the maximum number of opened FITS files. It is set = 300 by default. + Note however, that the underlying compiler or operating system may + not allow this many files to be opened at one time. + + - updated the version of cfortran.h that is distributed with CFITSIO from + version 3.9 to version 4.4. This required changes to f77_wrap.h + and f77_wrap3.c. The original cfortran.h v4.4 file was modified + slightly to support CFITSIO and ftools (see comments in the header + of cfortran.h). + + - modified ffhist so that it copies all the non-structural keywords from + the original binary table header to the binned image header. + + - modified fits_get_keyclass so that it recognizes EXTNAME = + COMPRESSED_IMAGE as a special tile compression keyword. + + - modified Makefile.in to support the standard --prefix convention + for specifying the install target directory. + + Bug fixes: + + - in fits_decompress_img, needed to add a call to ffpscl to turn + off the BZERO and BSCALE scaling when reading the compressed image. + +Version 2.430 - 4 November 2002 + + Enhancements: + + - modified fits_create_hdu/ffcrhd so that it returns without doing + anything and does not generate an error if the current HDU is + already an empty HDU. There is no need in this case to append + a new empty HDU to the file. + + - new version of group.c (supplied by B. O'Neel at the ISDC) fixes 2 + limitations: 1 - Groups now have 256 characters rather than 160 + for the path lengths in the group tables. - ISDC SPR 1720. 2 - + Groups now can have backpointers longer than 68 chars using the long + string convention. - ISDC SPR 1738. + + - small change to f77_wrap.h and f77_wrap3.c to support the fortran + wrappers on SUN solaris 64-bit sparc systems (see also change to v2.033) + + - small change to find_column in eval_f.c to support unsigned long + columns in binary tables (with TZEROn = 2147483648.0) + + - small modification to cfortran.h to support Mac OS-X, (Darwin) + + Bug fixes: + + - When reading tile-compress images, the BSCALE and BZERO scaling + keywords were not being applied, if present. + + - Previous changes to the error message stack code caused the + tile compressed image routines to not clean up spurious error + messages properly. + + - fits_open_image was not skipping over null primary arrays. + +Version 2.420 - 19 July 2002 + + Enhancements: + + - modified the virtual filename parser to support exponential notation + when specifying the min, max or binsize in a binning specifier, as in: + myfile.fits[binr X=1:10:1.0E-01, Y=1:10:1.0E-01] + + - removed the limitation on the maximum number of HDUs in a FITS file + (limit used to be 1000 HDUs per file). Now any number of HDUs + can be written/read in a FITS file. (BUT files that have huge numbers + of HDUs can be difficult to manage and are not recommended); + + - modified grparser.c to support HIERARCH keywords, based on + code supplied by Richard Mathar (Max-Planck) + + - moved the ffflsh (fits_flush_buffer) from the private to the + public interface, since this routine may be useful for some + applications. It is much faster than ffflus. + + - small change to the definition of OFF_T in fitsio.h to support + large files on IBM AIX operating systems. + + Bug fixes: + + - fixed potential problem reading beyond array bounds in ffpkls. This + would not have affected the content of any previously generated FITS + files. + + - in the net driver code in drvrnet.c, the requested protocol string + was changed from "http/1.0" to "HTTP/1.0" to support apache 1.3.26. + + - When using the virtual file syntax to open a vector cell in a binary + table as if it were a primary array image, there was a bug + in fits_copy_image_cell which garbled the data if the vector + was more than 30000 bytes long. + + - fixed problem that caused fits_report_error to crash under Visual + C++ on Windows systems. The fix is to use the '/MD' switch + on the cl command line, or, in Visual Studio, under project + settings / C++ select use runtime library multithreaded DLL + + - modified ffpscl so it does not attempt to reset the scaling values + in the internal structure if the image is tile-compressed. + + - fixed multiple bugs in mem_rawfile_open which affected the case + where a raw binary file is read and converted on the fly into + a FITS file. + + - several small changes to group.c to suppress compiler warnings. + +Version 2.410 - 22 April 2002 (used in the FTOOLS 5.2 release) + + New Routines: + + - fits_open_data behaves similarly to fits_open_file except that it + also will move to the first HDU containing significant data if + and an explicit HDU name or number to open was not specified. + This is useful for automatically skipping over a null primary + array when opening the file. + + - fits_open_table and fits_open_image behaves similarly to + fits_open_data, except they move to the first table or image + HDU in the file, respectively. + + - fits_write_errmark and fits_clear_errmark routines can be use + to write an invisible marker to the CFITSIO error stack, and + then clear any more recent messages on the stack, back to + that mark. This preserves any older messages on the stack. + + - fits_parse_range utility routine parses a row list string + and returns integer arrays giving the min and max row in each + range. + + - fits_delete_rowrange deletes a specified list of rows or row + ranges. + + - fits_copy_file copies all or part of the HDUs in the input file + to the output file. + + - added fits_insert_card/ffikey to the publicly defined set + of routines (previously, it was a private routine). + + Enhancements: + + - changed the default numeric display format in ffgkys from 'E' format + to 'G' format, and changed the format for 'X' columns to a + string of 8 1s or 0s representing each bit value. + + - modified ffflsh so the system 'fflush' call is not made in cases + where the file was opened with 'READONLY' access. + + - modified the output filename parser so the "-.gz", and "stdout.gz" + now cause the output file to be initially created in memory, + and then compressed and written out to the stdout stream when + the file is closed. + + - modified the routines that delete rows from a table to also + update the variable length array heap, to remove any orphaned + data from the heap. + + - modified ffedit_columns so that wild card characters may be + used when specifying column names in the 'col' file filter + specifier (e.g., file.fits[col TIME; *RAW] will create a + virtual table contain only the TIME column and any other columns + whose name ends with 'RAW'). + + - modified the keyword classifier utility, fits_get_keyclass, to + support cases where the input string is just the keyword name, + not the entire 80-character card. + + - modified configure.in and configure to see if a proprietary + C compiler is available (e.g. 'cc'), and only use 'gcc' if not. + + - modified ffcpcl (copy columns from one table to another) so that + it also copies any WCS keywords related to that column. + + - included an alternate source file that can be used to replace + compress.c, which is distributed under the GNU General Public + License. The alternate file contains non-functional stubs for + the compression routines, which can be used to make a version of + CFITSIO that does not have the GPL restrictions (and is also less + functional since it cannot read or write compressed FITS files). + + - modifications to the iterator routine (ffiter) to support writing + tile compressed output images. + + - modified ffourl to support the [compress] qualifier when specifying + the optional output file name. E.g., file.fit(out.file[compress])[3] + + - modified imcomp_compress_tile to fully support implicit data type + conversion when writing to tile-compressed images. Previously, + one could not write a floating point array to an integer compressed + image. + + - increased the number of internal 2880-byte I/O buffers allocated + by CFITSIO from 25 to 40, in recognition of the larger amount + of memory available on typical machines today compared with + a few years ago. The number of buffers can be set by the user + with the NIOBUF parameter in fitsio2.h. (Setting this too large + can actually hurt performance). + + - modified the #if statements in fitsio2.h, f77_wrap.h and f77_wrap1.c + to support the new Itanium 64-bit Intel PC. + + - a couple minor modifications to fitsio.h needed to support the off_t + datatype on debian linux systems. + + - increased internal buffer sizes in ffshft and ffsrow to improve + the I/O performance. + + Bug fixes: + + - fits_get_keyclass could sometimes try to append to an unterminated + string, causing an overflow of a string array. + + - fits_create_template no longer worked because of improvements made + to other routines. Had to modify ffghdt to not try to rescan + the header keywords if the file is still empty and contains no + keywords yet. + + - ffrtnm, which returns the root filename, sometimes did not work + properly when testing if the 'filename+n' convention was used for + specifying an extension number. + + - fixed minor problem in the keyword template parsing routine, ffgthd + which in rare cases could cause an improperly terminated string to + be returned. + + - the routine to compare 2 strings, ffcmps, failed to find a match + in comparing strings like "*R" and "ERROR" where the match occurs + on the last character, but where the same matching character occurs + previously in the 2nd string. + + - the region file reading routine (ffrrgn) did not work correctly if + the region file (created by POW and perhaps other programs) had an + 'exclude' region (beginning with a '-' sign) as the first region + in the file. In this case all points outside the excluded region + should be accepted, but in fact no points were being accepted + in this case. + +Version 2.401 - 28 Jan 2002 + + - added the imcopy example program to the release (and Makefile) + + Bug fixes: + + - fixed typo in the imcompress code which affected compression + of 3D datacubes. + + - made small change to fficls (insert column) to allow colums with + TFORMn = '1PU' and '1PV' to be inserted in a binary table. The + 'U' and 'V' are codes only used within CFITSIO to represent unsigned + 16-bit and 32-bit integers; They get replaced by '1PI' and '1PJ' + respectively in the FITS table header, along with the appropriate + TZEROn keyword. + +Version 2.400 - 18 Jan 2002 + + (N.B.: Application programs must be recompiled, not just relinked + with the new CFITSIO library because of changes made to fitsio.h) + + New Routines: + + - fits_write_subset/ffpss writes a rectangular subset (or the whole + image) to a FITS image. + + - added a whole new family of routines to read and write arrays of + 'long long' integers (64-bit) to FITS images or table columns. The + new routine names all end in 'jj': ffpprjj, ffppnjj, ffp2djj, + ffp3djj, ffppssjj, ffpgpjj, ffpcljj, ffpcnjj. ffgpvjj, ffgpfjj, + ffg2djj, ffg3djj, ffgsvjj, ffgsfjj, ffggpjj, ffgcvjj, and ffgcfjj. + + - added a set of helper routines that are used in conjunction with + the new support for tiled image compression. 3 routines set the + parameters that should be used when CFITSIO compresses an image: + fits_set_compression_type + fits_set_tile_dim + fits_set_noise_bits + + 3 corresponding routines report back the current settings: + fits_get_compression_type + fits_get_tile_dim + fits_get_noise_bits + + Enhancements: + + - major enhancement was made to support writing to tile-compressed + images. In this format, the image is divided up into a rectangular + grid of tiles, and each tile of pixels is compressed individually + and stored in a row of a variable-length array column in a binary + table. CFITSIO has been able to transparently read this compressed + image format ever since version 2.1. Now all the CFITSIO image + writing routines also transparently support this format. There are + 2 ways to force CFITSIO to write compressed images: 1) call the + fits_set_compression_type routine before writing the image header + keywords, or 2), specify that the image should be compressed when + entering the name of the output FITS file, using a new extended + filename syntax. (examples: "myfile.fits[compress]" will use the + default compression parameters, and "myfile.fits[compress GZIP + 100,100] will use the GZIP compression algorithm with 100 x 100 + pixel tiles. + + - added new driver to support creating output .gz compressed fits + files. If the name of the output FITS file to be created ends with + '.gz' then CFITSIO will initially write the FITS file in memory and + then, when the FITS file is closed, CFITSIO will gzip the entire + file before writing it out to disk. + + - when over-writing vectors in a variable length array in a binary + table, if the new vector to be written is less than or equal to + the length of the previously written vector, then CFITSIO will now + reuse the existing space in the heap, rather than always appending + the new array to the end of the heap. + + - modified configure.in to support building cfitsio as a dynamic + library on Mac OS X. Use 'make shared' like on other UNIX platforms, + but a .dylib file will be created instead of .so. If installed in a + nonstandard location, add its location to the DYLD_LIBRARY_PATH + environment variable so that the library can be found at run time. + + - made various modifications to better support the 8-byte long integer + datatype on more platforms. The 'LONGLONG' datatype is typedef'ed + to equal 'long long' on most Unix platforms and MacOS, and equal + to '__int64' on Windows machines. + + - modified configure.in and makefile.in to better support cases + where the system has no Fortran compiler and thus the f77 wrapper + routines should not be compiled. + + - made small modification to eval.y and eval_y.f to get rid of warning + on some platforms about redefinition of the 'alloca'. + + Bug fixes: + + - other recent bug fixes in ffdblk (delete blocks) caused ffdhdu (delete + HDU) to fail when trying to replace the primary array with a null + primary array. + + - fixed bug that prevented inserting a new variable length column + into a table that already contained variable length data. + + - modified fits_delete_file so that it will delete the file even if + the input status value is not equal to zero. + + - in fits_resize_image, it was sometimes necessary to call ffrdef to + force the image structure to be defined. + + - modified the filename parser to support input files with names like: + "myfile.fits.gz(mem://tmp)" in which the url type is specified for + the output file but not for the input file itself. This required + modifications to ffiurl and ffrtnm. + +Version 2.301 - 7 Dec 2001 + + Enhancements: + + - modified the http file driver so that if the filename to be opened + contains a '?' character (most likely a cgi related string) then it + will not attempt to append a .gz or .Z as it would normally do. + + - added support for the '!' clobber character when specifying + the output disk file name in CFITSIO's extended filename syntax, e.g., + 'http://a.b.c.d/myfile.fits.gz(!outfile.fits)' + + - added new device driver which is used when opening a compressed FITS + file on disk by uncompressing it into memory with READWRITE + access. This happens when specifying an output filename + 'mem://'. + + - added 2 other device drivers to open http and ftp files in memory + with write access. + + - improved the error trapping and reporting in cases where program + attempts to write to a READONLY file (especially in cases where the + 'file' resides in memory, as is the case when opening an ftp or http + file. + + - modified the extended filename parser so that it is does not confuse + the bracket character '[' which is sometimes used in the root name + of files of type 'http://', as the start of an extname or row filter + expression. If the file is of type 'http://', the parser now + checks to see if the last character in the extended file name is + a ')' or ']'. If not, it does not try to parse the file name + any further. + + - improved the efficiency when writing FITS files in memory, by + initially allocating enough memory for the entire HDU when it is + created, rather than incrementally reallocing memory 2880 bytes + at a time (modified ffrhdu and mem_truncate). This change also + means that the program will fail much sooner if it cannot allocate + enough memory to hold the entire FITS HDU. + + Bug fixes: + + - There was an error in the definition of the Fortran ftphtb wrapper + routine (writes required ASCII table header keywords) that caused + it to fail on DEC OSF and other platforms where sizeof(long) = 8. + +Version 2.300 - 23 Oct 2001 + + New Routines: + + - fits_comp_img and fits_decomp_img are now fully supported and + documented. These routine compress and decompress, respective, + a FITS image using a new algorithm in which the image is first + divided into a grid of rectangular tiles, then the compressed byte + stream from each tile is stored in a row of a binary table. + CFITSIO can transparently read FITS images stored in this + compressed format. Compression ratios of 3 - 6 are typically + achieved. Large compression ratios are achieved for floating + point images by throwing away non-significant noise bits in the + pixel values. + + - fits_test_heap tests the integrity of the binary table heap and + returns statistics on the amount of unused space in the heap and + the amount of space that is pointed to by more than 1 descriptor. + + - fits_compress_heap which will reorder the arrays in the binary + table heap, recovering any unused space. + + Enhancements: + + - made substantial internal changes to the code to support FITS + files containing 64-bit integer data values. These files have + BITPIX = 64 or TFORMn = 'K'. This new feature in CFITSIO is + currently only enabled if SUPPORT_64BIT_INTEGERS is defined = 1 in + the beginning of the fitsio2.h file. By default support for + 64-bit integers is not enabled. + + - improved the ability to read and return a table column value as a + formatted string by supporting quasi-legal TDISPn values which + have a lowercase format code letter, and by completely ignoring + other unrecognizable TDISPn values. Previously, unrecognized + TDISPn values could cause zero length strings to be returned. + + - made fits_write_key_longstr more efficient when writing keywords + using the long string CONTINUE convention. It previously did not + use all the available space on each card when the string to be + written contained many single quote characters. + + - added a new "CFITSIO Quick Start Guide" which provides all the + basic information needed to write C programs using CFITSIO. + + - updated the standard COMMENT keywords that are written at the + beginning of every primary array to refer to the newly published + FITS Standard document in Astronomy and Astrophysics. + Note: because of this change, any FITS file created with this + version of CFITSIO will not be identical to the same file written + with a previous version of CFITSIO. + + - replaced the 2 routines in pliocomp.c with new versions provided by + D Tody and N Zarate. These routines compress/uncompress image pixels + using the IRAF pixel list compression algorithm. + + - modified fits_copy_hdu so that when copying a Primary Array + to an Image extension, the COMMENT cards which give the reference + to the A&A journal article about FITS are not copied. In the + inverse case the COMMENT keywords are inserted in the header. + + - modified configure and Makefile.in to add capability to build a + shared version of the CFITSIO library. Type 'make shared' or + 'make libcfitsio.so' to invoke this option. + + - disabled some uninformative error messages on the error stack: + 1) when calling ffclos (and then ffchdu) with input status > 0 + 2) when ffmahd tries to move beyond the end of file. + The returned status value remains the same as before, but the + annoying error messages no longer get written to the error stack. + + - The syntax for column filtering has been modified so that + if one only specifies a list of column names, then only those + columns will be copied into the output file. This provides a simple + way to make a copy of a table containing only a specified list of + columns. If the column specifier explicitly deletes a column, however, + than all the other columns will be copied to the filtered input + file, regardless of whether the columns were listed or not. + Similarly, if the expression specifies only a column to be modified + or created, then all the other columns in the table will be + copied. + + mytable.fit[1][col Time;Rate] - only the Time and Rate + columns will be copied to the filtered input file. + + mytable.fit[1][col -Time ] - all but the Time column are copied + to the filtered input file. + + mytable.fit[1][col Rate;-Time] - same as above. + + - changed a '#if defined' statement in f77_wrap.h and f77_wrap1.c + to support the fortran wrappers on 64-bit IBM/RS6000 systems + + - modified group.c so that when attaching one group (the child) to + another (the parent), check in each file for the existence of a + pointer to the other before adding the link. This is to prevent + multiple links from forming under all circumstances. + + - modified the filename parser to accept 'STDIN', 'stdin', + 'STDOUT' and 'stdout' in addition to '-' to mean read the + file from standard input or write to standard output. + + - Added support for reversing an axis when reading a subsection + of a compressed image using the extended filename syntax, as in + myfile.fits+1[-*, *] or myfile.fits+1[600:501,501:600] + + - When copying a compressed image to a uncompressed image, the + EXTNAME keyword is no longer copied if the value is equal to + 'COMPRESSED_IMAGE'. + + - slight change to the comment field of the DATE keyword to reflect + the fact that the Unix system date and time is not true UTC time. + + Bug fixes: + + - fits_write_key_longstr was not writing the keyword if a null + input string value was given. + + - writing data to a variable length column, if that binary table is not + the last HDU in the FITS file, might overwrite the following HDU. + Fixed this by changing the order of a couple operations in ffgcpr. + + - deleting a column from a table containing variable length columns + could cause the last few FITS blocks of the file to be reset = 0. + This bug occurred as a result of modifications to ffdblk in v2.202. + This mainly affects users of the 'compress_fits' utility + program. + + - fixed obscure problem when writing bits to a variable length 'B' + column. + + - when reading a subsection of an image, the BSCALE and BZERO pixel + scaling may not have been applied when reading image pixel values + (even though the scaling keywords were properly written in the + header). + + - fits_get_keyclass was not returning 'TYP_STRUCT_KEY' for the + END keyword. + +Version 2.204 - 26 July 2001 + + Bug fixes: + + - Re-write of fits_clean_url in group.c to solve various problems + with invalid bounds checking. + +Version 2.203 - 19 July 2001 (version in FTOOLS v5.1) + + Enhancements: + + - When a row selection or calculator expression is written in + an external file (and read by CFITSIO with the '@filename' syntax) + the file can now contain comment lines. The comment line must + begin with 2 slash characters as the first 2 characters on the + line. CFITSIO will ignore the entire line when reading the + expression. + + Bug fixes: + + - With previous versions of CFITSIO, the pixel values in a FITS + image could be read incorrectly in the following case: when + opening a subset of a FITS image (using the + 'filename.fits[Xmin:Xmax,Ymin:Ymax]' notation) on a PC linux, PC + Windows, or DEC OSF machine (but not on a SUN or Mac). This + problem only occurs when reading more than 8640 bytes of data + (2160 4-byte integers) at a time, and usually only occurs if the + reading program reads the pixel data immediately after opening the + file, without first reading any header keywords. This error would + cause strips of zero valued pixels to appear at semi-random + positions in the image, where each strip usually would be 2880 + bytes long. This problem does not affect cases where the input + subsetted image is simply copied to a new output FITS file. + + +Version 2.202 - 22 May 2001 + + Enhancements: + + - revised the logic in the routine that tests if a point is + within a region: if the first region is an excluded region, + then it implicitly assumes a prior include region covering + the entire detector. It also now supports cases where a + smaller include region is within a prior exclude region. + + - made enhancement to ffgclb (read bytes) so that it can + also read values from a logical column, returning an array + of 1s and 0s. + + - defined 2 new grouping error status values (349, 350) in + cfitsio.h and made minor changes to group.c to use these new + status values. + + - modified fits_open_file so that if it encounters an error while + trying to move to a user-specified extension (or select a subset + of the rows in an input table, or make a histogram of the + column values) it will close the input file instead of leaving + it open. + + - when using the extended filename syntax to filter the rows in + an input table, or create a histogram image from the values in + a table column, CFITSIO now writes HISTORY keywords in the + output file to document the filtering expression that was used. + + Bug fixes: + + - ffdblk (called by ffdrow) could overwrite the last FITS block(s) in + the file in some cases where one writes data to a variable length + column and then calls ffdrow to delete rows in the table. This + bug was similar to the ffiblk bug that was fixed in v2.033. + + - modified fits_write_col_null to fix a problem which under unusual + circumstances would cause a End-of-File error when trying to + read back the value in an ASCII string column, after initializing + if by writing a null value to it. + + - fixed obscure bug in the calculator function that caused an + error when trying to modify the value of a keyword in a HDU + that does not have a NAXIS2 keyword (e.g., a null primary array). + + - the iterator function (in putcol.c) had a bug when calculating + the optimum number rows to process in the case where the table + has very wide rows (>33120 bytes) and the calculator expression + involves columns from more than one FITS table. This could + cause an infinite loop in calls to the ffcalc calculator function. + + - fixed bug in ffmvec, which modifies the length of an + existing vector column in a binary table. If the vector + was reduced in length, the FITS file could sometimes be left + in a corrupted state, and in all cases the values in the remaining + vector elements of that column would be altered. + + - in drvrfile.c, replaced calls to fsetpos and fgetpos with + fseek and ftell (or fseeko and ftello) because the fpos_t + filetype used in fsetpos is incompatible with the off_t + filetype used in fseek, at least on some platforms (Linux 7.0). + (This fix was inserted into the V2.201 release on April 4). + + - added "#define fits_write_pixnull ffppxn" to longnam.h + +Version 2.201 - 15 March 2001 + + Enhancements + + - enhanced the keyword reading routines so that they will do + implicit datatype conversion from a string keyword value + to a numeric keyword value, if the string consist of a + valid number enclosed in quotes. For example, the keyword + mykey = '37.5' can be read by ffgkye. + + - modified ffiimg so that it is possible to insert a new + primary array at the beginning of the file. The original + primary array is then converted into an IMAGE extension. + + - modified ffcpdt (copy data unit) to support the case where + the data unit is being copied between 2 HDUs in the same file. + + - enhanced the fits_read_pix and fits_read_pixnull routines so + that they support the tiled image compression format that the + other image reading routines also support. + + - modified the Extended File Name syntax to also accept a + minus sign (-) as well as an exclamation point (!) as + the leading character when specifying a column or or keyword + to be deleted, as in [col -time] will delete the TIME column. + + - now completely support reading subimages, including pixel + increments in each dimension, for tile-compressed images + (where the compressed image tiles are stored in a binary + table). + + Bug fixes: + + - fixed confusion in the use of the fpos_t and off_t datatypes + in the fgetpos and fsetpos routines in drvrfile.c which caused + problems with the Windows VC++ compiler. (fpos_t is not + necessarily identical to off_t) + + - fixed a typo in the fits_get_url function in group.c which + caused problems when determining the relative URL to a compressed + FITS file. + + - included fitsio.h in the shared memory utility program, + smem.c, in order to define OFF_T. + + - fixed typo in the datatype of 'nullvalue' in ffgsvi, which caused + attempts to read subsections of a short integer tiled compressed + image to fail with a bus error. + + - fixed bug in ffdkey which sometimes worked incorrectly if one + tried to delete a nonexistent keyword beyond the end of the header. + + - fixed problem in fits_select_image_section when it writes a dummy + value to the last pixel of the section. If the image contains + scaled integer pixels, then in some cases the pixel value could end + up out of range. + + - fixed obscure bug in the ffpcn_ family of routines which gave + a floating exception when trying to write zero number of pixels to + a zero length array (why would anyone do this?) + +Version 2.200 - 26 Jan 2001 + + Enhancements + + - updated the region filtering code to support the latest region + file formats that are generated by the POW, SAOtng and ds9 + programs. Region positions may now be given in HH:MM:SS.s, + DD:MM:SS.s format, and region sizes may be given arcsec or arcmin + instead of only in pixel units. Also changed the logic so that if + multiple 'include' regions are specified in the region file, they + are ORed together, instead of ANDed, so that the filtering keeps + points that are located within any of the 'include' regions, not + just the intersection of the regions. + + - added support for reading raw binary data arrays by converting + them on the fly into virtual FITS files. + + - modified ffpmsg, which writes error messages to CFITSIO's internal + error stack, so that messages > 80 characters long will be wrapped + around into multiple 80 character messages, instead of just + being truncated at 80 characters. + + - modified the CFITSIO parser so that expression which involve + scaled integer columns get cast to double rather than int. + + - Modified the keyword template parsing routine, ffgthd, to + support the HIERARCH keyword. + + - modified ffainit and ffbinit so that they don't unnecessarily + allocate 0 bytes of memory if there are no columns (TFIELDS = 0) + in the table that is being opened. + + - modified fitsio2.h to support NetBSD on Alpha OSF platforms + (NetBSD does not define the '__unix__' symbol). + + - changed the way OFF_T is defined in fitsio.h for greater + portability. + + - changed drvrsmem.c so it is compiled only when HAVE_SHMEM_SERVICES + is defined in order to removed the conditional logic from the Makefile + + - reorganized the CFITSIO User's guide to make it + clearer and easier for new users to learn the basic routines. + + - fixed ffhdef (which reserves space for more header keywords) so + that is also updates the start position of the next HDU. This + affected the offset values returned by ffghof. + +Version 2.100 - 18 Oct 2000 + + Enhancements + + - made substantial modification to the code to support Large files, + i.e., files larger than 2**31 bytes = 2.1GB. FITS files up to + 6 terabytes in size may now be read and written on platforms + that support Large files (currently only Solaris). + + - modified ffpcom and ffphis, which write COMMENT and HISTORY + keywords, respectively, so that they now use columns 9 - 80, + instead of only columns 11 - 80. Previously, these routines + avoided using columns 9 and 10, but this is was unnecessarily + restrictive. + + - modified ffdhdu so that instead of refusing to delete the + primary array, it will replace the current primary array + with a null primary array containing the bare minimum of + required keywords and no data. + + New Routines + + - fits_read_pix, fits_read_pixnull, fits_read_subset, and fits_write_pix + routines were added to enable reading and writing of Large images, + with more than 2.1e9 pixels. These new routines are now recommended + as the basic routines for reading and writing all images. + + - fits_get_hduoff returns the byte offset in the file to + the start and end of the current HDU. This routine replaces the + now obsolete fits_get_hduaddr routine; it uses 'off_t' instead of + 'long' as the datatype of the arguments and can support offsets + in files greater than 2.1GB in size. + + Bug fixes: + + - fixed bug in fits_select_image_section that caused an integer + overflow when reading very large image sections (bigger than + 8192 x 8192 4-byte pixels). + + - improved ffptbb, the low-level table writing routine, so that + it will insert additional rows in the table if the table is + not already big enough. Previously it would have just over- + written any HDUs following the table in the FITS file. + + - fixed a bug in the fits_write_col_bit/ffpclx routine which + could not write to a bit 'X' column if that was the first column + in the table to be written to. This bug would not appear if + any other datatype column was written to first. + + - non-sensible (but still formally legal) binary table TFORM values + such as '8A15', or '1A8' or 'A8' would confuse CFITSIO and cause it + to return a 308 error. When parsing the TFORMn = 'rAw' value, + the ffbnfm routine has been modified to ignore the 'w' value in cases + where w > r. + + - fixed bug in the blsearch routine in iraffits.c which sometimes + caused an out-of-bounds string pointer to be returned when searching + for blank space in the header just before the 'END' keyword. + + - fixed minor problem in ffgtcr in group.c, which sometimes failed + while trying to move to the end of file before appending a + grouping table. + + - on Solaris, with Sun CC 5.0, one must check for '__unix' rather + than '__unix__' or 'unix' as it's symbol. Needed to modify this + in drvrfile.c in 3 places. + + - in ffextn, the FITS file would be left open if the named + extension doesn't exist, thus preventing the file from being + opened again later with write access. + + - fixed bug in ffiimg that would cause attempts to insert a new + image extension following a table extension, and in front of any + other type of extension, to fail. + +Version 2.037 - 6 July 2000 + + Enhancements + + - added support in the extended filename syntax for flipping + an image along any axis either by specifying a starting + section pixel number greater than the ending pixel number, + or by using '-*' to flip the whole axis. Examples: + "myfile.fits[1:100, 50:10]" or "myfile.fits[-*,*]". + + - when reading a section of an image with the extended filename + syntax (e.g. image.fits[1:100:2, 1:100:2), any CDi_j WCS keywords + will be updated if necessary to transfer the world coordinate + system from the imput image to the output image section. + + - on UNIX platforms, added support for filenames that begin + with "~/" or "~user/". The "~" symbol will get expanded + into a string that gives the user's home directory. + + - changed the filename parser to support disk file names that + begin with a minus sign. Previously, the leading minus sign would + cause CFITSIO to try to read/write the file from/to stdin/stdout. + + - modified the general fits_update_key routine, which writes + or updates a keyword value, to use the 'G' display format + instead of the 'E' format for floating point keyword values. + This will eliminate trailing zeros from appearing in the value. + + - added support for the "-CAR" celestial coordinate projection + in the ffwldp and ffxypx routines. The "-CAR" projection is + the default simplest possible linear projection. + + - added new fits_create_memfile/ffimem routine to create a new + fits file at a designated memory location. + + - ported f77_wrap.h and f77_wrap1.c so that the Fortran interface + wrappers work correctly on 64-bit SGI operating systems. In this + environment, C 'long's are 8-bytes long, but Fortran 'integers' + are still only 4-bytes long, so the words have to be converted + by the wrappers. + + - minor modification to cfortran.h to automatically detect when it + is running on a linux platform, and then define f2cFortran in that + case. This eliminates the need to define -Df2cFortran on the + command line. + + - modified group.c to support multiple "/" characters in + the path name of the file to be opened/created. + + - minor modifications to the parser (eval.y, eval_f.c, eval_y.c) + to a) add the unary '+' operator, and b) support copying the + TDIMn keyword from the input to the output image under certain + circumstances. + + - modified the lexical parser in eval_l.y and eval_l.c to + support #NULL and #SNULL constants which act to set the + value to Null. Support was also added for the C-conditional + expression: 'boolean ? trueVal : falseVal'. + + - small modification to eval_f.c to write an error message to + the error stack if numerical overflow occurs when evaluating + an expression. + + - configure and configure.in now support the egcs g77 compiler + on Linux platforms. + + Bug fixes: + + - fixed a significant bug when using the extended filename binning + syntax to generate a 2-dimensional image from a histogram of the + values in 2 table columns. This bug would cause table events that + should have been located in the row just below the bottom row of + the image (and thus should have been excluded from the histogram) + to be instead added into the first row of the image. Similarly, + the first plane of a 3-D or 4-D data cube would include the events + that should have been excluded as falling in the previous plane of + the cube. + + - fixed minor bug when parsing an extended filename that contains + nested pairs of square brackets (e.g., '[col newcol=oldcol[9]]'). + + - fixed bug when reading unsigned integer values from a table or + image with fits_read_col_uint/ffgcvuk. This bug only occurred on + systems like Digital Unix (now Tru64 Unix) in which 'long' + integers are 8 bytes long, and only when reading more than 7200 + elements at a time. This bug would generally cause the program to + crash with a segmentation fault. + + - modified ffgcpr to update 'heapstart' as well as 'numrows' when + writing more rows beyond the end of the table. heapstart + is needed to calculate if more space needs to be inserted in the + table when inserting columns into the table. + + - modified fficls (insert column), ffmvec, ffdrow and ffdcol to + not use the value of the NAXIS2 keyword as the number of rows + in the table, and instead use the value that is stored in + an internal structure, because the keyword value may not + be up to date. + + - Fixed bug in the iterator function that affected the handling + of null values in string columns in ASCII and binary tables. + + - Reading a subsample of pixels in very large images, (e.g., + file = myfile.fits[1:10000:10,1:10000:10], could cause a + long integer overflow (value > 2**31) in the computation of the + starting byte offset in the file, and cause a return error status + = 304 (negative byte address). This was fixed by changing the + order of the arithmetic operations in calculating the value of + 'readptr' in the ffgcli, ffgclj, ffgcle, ffgcld, etc. routines. + + - In version 2.031, a fix to prevent compressed files from being + opened with write privilege was implemented incorrectly. The fix + was intended to not allow a compressed FITS file to be opened + except when a local uncompressed copy of the file is being + produced (then the copy is opened with write access), but in fact + the opposite behavior occurred: Compressed files could be opened + with write access, EXCEPT when a local copy is produced. This + has been fixed in the mem_compress_open and file_compress_open + routines. + + - in iraffits.c, a global variable called 'val' caused multiply + defined symbols warning when linking cfitsio and IRAF libraries. + This was fixed by making 'val' a local variable within the + routine. + +Version 2.036 - 1 Feb 2000 + + - added 2 new generic routines, ffgpf and ffgcf which are analogous + to ffgpv and ffgcv but return an array of null flag values instead + of setting null pixels to a reserved value. + + - minor change to eval_y.c and eval.y to "define alloca malloc" + on all platforms, not just VMS. + + - added support for the unsigned int datatype (TUINT) in the + generic ffuky routine and changed ffpky so that unsigned ints + are cast to double instead of long before being written to + the header. + + - modified ffs2c so that if a null string is given as input then + a null FITS string (2 successive single quotes) will be returned. + Previously this routine would just return a string with a single + quote, which could cause an illegal keyword record to be written. + + - The file flush operation on Windows platforms apparently + changes the internal file position pointer (!) in violation of the + C standard. Put a patch into the file_flush routine to explicitly + seek back to the original file position. + + - changed the name of imcomp_get_compressed_image_parms to + imcomp_get_compressed_image_par to not exceed the 31 character + limit on some compilers. + + - modified the filename parser (which is used when moving to a + named HDU) to support EXTNAME values which contain embedded blanks. + + - modified drvrnet.c to deal with ftp compressed files better so + that even fits files returned from cgi queries which have the wrong + mime types and/or wrong types of file names should still decompress. + + - modified ffgics to reduce the tolerance for acceptable skewness + between the axes, and added a new warning return status = + APPROX_WCS_KEY in cases where there is significant skewness + between the axes. + + - fixed bug in ffgics that affected cases where the first coordinate + axis was DEC, not RA, and the image was a mirror image of the sky. + + - fixed bug in ffhist when trying to read the default binning + factor keyword, TDBIN. + + - modified ffhist so that is correctly computes the rotation angle + in a 2-D image if the first histogram column has a CROTA type + keyword but the 2nd column does not. + + - modified ffcpcl so that it preserves the comment fields on the + TTYPE and TFORM keywords when the column is copied to a new file. + + - make small change to configure.in to support FreeBSD Linux + by setting CFLAGS = -Df2cFortran instead of -Dg77Fortran. Then + regenerated configure with autoconf 2.13 instead of 2.12. + +Version 2.035 - 7 Dec 1999 (internal release only, FTOOLS 5.0.2) + + - added new routine called fits_get_keyclass/ffgkcl that returns + the general class of the keyword, e.g., required structural + keyword, WCS keyword, Comment keyword, etc. 15 classes of + keywords have been defined in fitsio.h + + - added new routine called fits_get_img_parm/ffgipr that is similar + to ffgphd but it only return the bitpix, naxis, and naxisn values. + + - added 3 new routines that support the long string keyword + convention: fits_insert_key_longstr, fits_modify_key_longstr + fits_update_key_longstr. + + - modified ffgphd which reads image header keywords to support + the new experimental compressed image format. + + - when opening a .Z compressed file, CFITSIO tries to allocate + memory equal to 3 times the file size, which may be excessive + in some cases. This was changed so that if the allocation fails, + then CFITSIO will try again to allocate only enough memory + equal to 1 times the file size. More memory will be allocated + later if this turns out to be too small. + + - improved the error checking in the fits_insert_key routine + to check for illegal characters in the keyword. + +Version 2.034 - 23 Nov 1999 + + - enhanced support for the new 'CD' matrix world coordinate system + keywords in the ffigics routine. This routine has been enhanced + to look for the new 'CD' keywords, if present, and convert them + back to the old CDELTn and CROTAn values, which are then returned. + The routine will also swap the WCS parameters for the 2 axes if + the declination-like axis is the first WCS axis. + + - modified ffphbn in putkey.c to support the 'U' and 'V" TFORM characters + (which represent unsigned short and unsigned int columns) in variable + length array columns. (previously only supported these types in + fixed length columns). + + - added checks when reading gzipped files to detect unexpected EOF. + Previously, the 'inflate_codes' routine would just sit in an infinite + loop if the file ended unexpectedly. + + - modified fits_verify_chksum/ffvcks so that checksum keywords with + a blank value string are treated as undefined, the same as + if the keyword did not exist at all. + + - fixed ffghtb and ffghbn so that they return the extname value + in cases where there are no columns in the table. + + - fixed bug in the ffgtwcs routine (this is a little utility + routine to aid in interfacing to Doug Mink's WCS routines); + it was not correctly padding the length of string-valued keywords + in the returned string. + + - fixed bug in 'iraffits.c' that prevented Type-2 IRAF images from + being correctly byte-swapped on PCs and DEC-OSF machines. + + - fixed tiny memory leak in irafncmp in iraffits.c. Only relevant when + reading IRAF .imh files. + + - fixed a bug (introduced in version 2.027) that caused the keyword + reading routines to sometimes not find a matching keyword if the + input name template used the '*' wildcard as the last character. + (e.g., if input name = 'COMMENT*' then it would not find the + 'COMMENT' keywords. (It would have found longer keywords like + 'COMMENTX' correctly). The fix required a minor change to ffgcrd + in getkey.c + + - modified the routine (ffswap8) that does byteswapping of + double precision numbers. Some linux systems have reported floating + point exceptions because they were trying to interpret the bytes + as a double before the bytes had been swapped. + + - fixed bug in the calculation of the position of the last byte + in the string of bits to be read in ffgcxuk and ffgcxui. This + bug generally caused no harm, but could cause the routine to + exit with an invalid error message about trying to read + beyond the size of the field. + + - If a unix machine did not have '__unix__', 'unix', or '__unix' + C preprocessor symbols defined, then CFITSIO would correctly open + one FITS file, but would not correctly open subsequent files. Instead + it would think that the same file was being opened multiple times. + This problem has only been seen on an IBM/AIX machine. The fits_path2url + and fits_url2path routines in group.c were modified to fix the problem. + + - fixed bug in group.c, which affected WINDOWS platforms only, that + caused programs to go into infinite loop when trying to open + certain files. + + - the ftrsim Fortran wrapper routine to ffrsim was not defined + correctly, which caused the naxis(2) value to be passed incorrectly + on Dec OSF machines, where sizeof(long) != sizeof(int). + +Version 2.033 - 17 Sept 1999 + + - New Feature: enhanced the row selection parser so that comparisons + between values in different rows of the table are allowed, and the + string comparisons with <, >, <=, and >= are supported. + + - added new routine the returns the name of the keyword in the + input keyword record string. The name is usually the first + 8 characters of the record, except if the HIERARCH convention + is being used in which case the name may be up to 67 characters + long. + + - added new routine called fits_null_check/ffnchk that checks to + see if the current header contains any null (ASCII 0) characters. + These characters are illegal in FITS headers, but they go undetected + by the other CFITSIO routines that read the header keywords. + + - the group.c file has been replaced with a new version as supplied + by the ISDC. The changes are mainly to support 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. + + - modified ffnmhd slightly so that it will move to the first extension + in which either the EXTNAME or the HDUNAME keyword is equal to the + user-specified name. Previously, it only checked for HDUNAME if + the EXTNAME keyword did not exist. + + - made small change to drvrnet.c so that it uncompress files + which end in .Z and .gz just as for ftp files. + + - rewrote ffcphd (copy header) to handle the case where the + input and output HDU are in the same physical FITS file. + + - fixed bug in how long string keyword values (using the CONTINUE + convention) were read. If the string keyword value ended in an + '&' character, then fits_read_key_longstr, fits_modify_key_str, + and fits_delete_key would interpret the following keyword as + a continuation, regardless of whether that keyword name was + 'CONTINUE' as required by this convention. There was also a bug + in that if the string keyword value was all blanks, then + fits_modify_key_str could in certain unusual cases think + that the keyword ended in an '&' and go into an infinite loop. + + - modified ffgpv so that it calls the higher level ffgpv_ routine + rather than directly calling the lower level ffgcl_ routine. This + change is needed to eventually support reading compressed images. + + - added 3 new routines to get the image datatype, image dimensions, + and image axes length. These support the case where the image is + compressed and stored in a binary table. + + - fixed bug in ffiblk that could sometimes cause it to insert a + new block in a file somewhere in the middle of the data, instead + of at the end of the HDU. This fortunately is a rare problem, + mainly only occurring in certain cases when inserting rows in a binary + table that contains variable length array data (i.e., has a heap). + + - modified fits_write_tdim so that it double checks the TFORMn + value directly if the column repeat count stored in the internal + structure is not equal to the product of all the dimensions. + + - fixed bug that prevented ffitab or ffibin from inserting a new + table after a null primary array (can't read NAXIS2 keyword). + Required a small change to ffrdef. + + - modified testprog.c so that it will continue to run even if + it cannot open or process the template file testprog.tpt. + + - modified the logic in lines 1182-1185 of grparser.c so that + it returns the correct status value in case of an error. + + - added test in fitsio2.h to see if __sparcv9 is defined; this + identifies a machine running Solaris 7 in 64-bit mode where + long integers are 64 bits long. + +Version 2.032 - 25 May 1999 + + - the distribution .tar file was changed so that all the files + will be untarred into a subdirectory by default instead of + into the current directory. + + - modified ffclos so that it always frees the space allocated by + the fptr pointer, even when another fptr points to the same file. + + - plugged a potential (but rare in practice) memory leak in ffpinit + + - fixed bug in all the ffp3d_ and ffg3d_ routines in cases where + the data cube that has been allocated in memory has more planes + than the data cube in the FITS file. + + - modified drvrsmem.c so that it allocates a small shared + memory segment only if CFITSIO tries to read or write a + FITS file in shared memory. Previously it always allocated + the segment whether it was needed or not. Also, this small + segment is removed if 0 shared memory segments remain in + the system. + + - put "static" in front of 7 DECLARE macros in compress.c + because these global variables were causing conflicts with other + applications programs that had variables with the same names. + + - modified ffasfm to return datatype = TDOUBLE instead of TFLOAT + if the ASCII table column has TFORMn = 'Ew.d' with d > 6. + + - modified the column reading routines to a) print out the offending + entry if an error occurs when trying to read a numeric ASCII table + column, and b) print out the column number that had the error + (the messages are written to CFITSIOs error stack) + + - major updates to the Fortran FITSIO User's Guide to include many + new functions that have been added to CFITSIO in the past year. + + - modified fitsio2.h so that the test for __D_FLOAT etc. is only + made on Alpha VMS machines, to avoid syntax errors on some other + platforms. + + - modified ffgthd so that it recognizes a floating point value + that uses the 'd' or 'D' exponent character. + + - removed the range check in fftm2s that returned an error if + 'decimals' was less than zero. A negative value is OK and is + used to return only the date and not the time in the string. + +Version 2.031 - 31 Mar 1999 + + - moved the code that updates the NAXIS2 and PCOUNT keywords from + ffchdu into the lower lever ffrdef routine. This ensures that + other routines which call ffrdef will correctly update these 2 + keywords if required. Otherwise, for instance, calling + fits_write_checksum before closing the HDU could cause the NAXIS2 + keyword (number of rows in the table) to not be updated. + + - fixed bug (introduced in version 2.030) when writing null values + to a primary array or image extension. If trying to set more + than 1 pixel to null at a time, then typically only 1 null would + be written. Also fixed related bug when writing null values to + rows in a table that are beyond the currently defined size of the + table (the size of the table was not being expanded properly). + + - enhanced the extended filename parser to support '*' in image + section specifiers, to mean use the whole range of the axis. + myfile.fits[*,1:100] means use the whole range of the first + axis and pixels 1 to 100 in the second axis. Also supports + an increment, as in myfile.fits[*:2, *:2] to use just the + odd numbered rows and columns. + + - modified fitscore.c to set the initial max size of the header, when + first reading it, to the current size of the file, rather than to + 2 x 10**9 to avoid rare cases where CFITSIO ends up writing a huge + file to disk. + + - modified file_compress_open so that it will not allow a compressed + FITS file to be opened with write access. Otherwise, a program + could write to the temporary copy of the uncompressed file, but + the modification would be lost when the program exits. + +Version 2.030 - 24 Feb 1999 + + - fixed bug in ffpclu when trying to write a null value to a row + beyond the current size of the table (wouldn't append new rows + like it should). + + - major new feature: enhanced the routines that read ASCII string + columns in tables so that they can read any table column, including + logical and numeric valued columns. The column values are returned + as a formatted string. The format is determined by the TDISPn + keyword if present, otherwise a default format based on the + datatype of the column is used. + + - new routine: fits_get_col_display_width/ffgcdw returns the length + of the formatted strings that will be returned by the routines that + read table columns as strings. + + - major new feature: added support for specifying an 'image section' + when opening an image: e.g, myfile.fits[1:512:2,2:512:2] to + open a 256x256 pixel image consisting of the odd columns and the + even numbered rows of the input image. + + - added supporting project files and instructions for building + CFITSIO under Windows NT with the Microsoft Visual C++ compiler. + + - changed the variable 'template' to 'templt' in testprog.c since + it conflicted with a reserved word on some compilers. + + - modified group.c to conditionally include sys/stat.h only on + unix platforms + + - fixed bug in the ffiter iterator function that caused it to always + pass 'firstn' = 1 to the work function when reading from the + primary array or IMAGE extension. It worked correctly for tables. + + - fixed bug in the template header keyword parser (ffgthd) in cases + where the input template line contains a logical valued keyword + (T or F) without any following comment string. It was previously + interpreting this as a string-valued keyword. + + - modified ffrhdu that reads and opens a new HDU, so that it + ignores any leading blank characters in the XTENSION name, e.g., + XTENSION= ' BINTABLE' will not cause any errors, even though + this technically violates the FITS Standard. + + - modified ffgtbp that reads the required table keywords to make + it more lenient and not exit with an error if the THEAP keyword + in binary tables cannot be read as an integer. Now it will + simply ignore this keyword if it cannot be read. + + - added test for 'WIN32' as well as '__WIN32__' in fitsio2.h, + eval.l and eval_l.c in a preprocessor statement. + + - changed definition of strcasecmp and strncasecmp in fitsio2.h, + eval.l and eval_l.c to conform to the function prototypes under + the Alpha VMS v7.1 compiler. + + - corrected the long function names in longnam.h for the new WCS + utility functions in wcssubs.c + +Version 2.029 - 11 Feb 1999 + + - fixed bug in the way NANs and underflows were being detected on + VAX and Alpha VMS machines. + + - enhanced the filename parser to distinguish between a VMS-style + directory name (e.g. disk:[directory]myfile.fits) and a CFITSIO + filter specifier at the end of the name. + + - modified ffgthd to support the HIERARCH convention for keyword + names that are longer than 8 characters or contain characters + that would be illegal in standard FITS keyword names. + + - modified the include statements in grparser.c so that malloc.h + and memory.h are only included on the few platforms that really + need them. + + - modified the file_read routine in drvrfile.c to ignore the last + record in the FITS file it it only contains a single character that + is equal to 0, 10 or 32. Text editors sometimes append a character + like this to the end of the file, so CFITSIO will ignore it and + treat it as if it had reached the end of file. + + - minor modifications to fitsio.h to help support the ROOT environment. + + - installed new version of group.c and group.h; the main change + is to support relative paths (e.g. "../filename") in the URLs + + - modified the histogramming routines so that it looks for the + default preferred column axes in a keyword of the form + CPREF = 'Xcol, Ycol' + instead of separate keywords of the form + CPREF1 = 'Xcol' + CPREF2 = 'Ycol' + + - fixed bug so that if the binning spec is just a single integer, + as in [bin 4] then this will be interpreted as meaning to make + a 2D histogram using the preferred or default axes, with the + integer taken as the binning factor in both axes. + +Version 2.028 - 27 Jan 1999 + + - if the TNULLn keyword value was outside the range of a 'I' or 'B' + column, an overflow would occur when setting the short or char + to the TNULLn value, leading to incorrect values being flagged as + being undefined. This has been fixed so that CFITSIO will ignore + TNULLn values that are beyond the range of the column data type. + + - changed a few instances of the string {"\0"} to {'\0'} in the + file groups.c + + - installed new version of the grparser.c file from the ISDC + + - added new WCS support routines (in wcssub.c) which make it easier + to call Doug Mink's WCSlib routines for converting between plate + and sky coordinates. The CFITSIO routines themselves never + call a WCSlib routine, so CFITSIO is not dependent on WCSlib. + + - modified ffopen so that if you use the extended filename + syntax to both select rows in a table and then bin columns into + a histogram, then CFITSIO will simply construct an array listing + the good row numbers to be used when making the histogram, + instead of making a whole new temporary FITS file containing + the selected rows. + + - modified ffgphd which parses the primary array header keywords + when opening a file, to not choke on minor format errors in + optional keywords. Otherwise, this prevents CFITSIO from + even opening the file. + + - changed a few more variable declarations in compress.c from global + to static. + +Version 2.027 - 12 Jan 1999 + + - modified the usage of the output filename specifier so that it, + a) gives the name of the binned image, if specified, else, + b) gives the name of column filtered and/or row filtered table, if + specified, else + c) is the name for a local copy of the ftp or http file, else, + d) is the name for the local uncompressed version of the compressed + FITS file, else, + e) the output filename is ignored. + + - fixed minor bug in ffcmps, when comparing 2 strings while using + a '*' wild card character. + + - fixed bug in ftgthd that affected cases where the template string + started with a minus sign and contained 2 tokens (to rename a + keyword). + + - added support for the HIERARCH keyword convention for reading + and writing keywords longer than 8 characters or that contain + ASCII characters not allowed in normal FITS keywords. + + - modified the extended filename syntax to support opening images + that are contained in a single cell of a binary table with syntax: + filename.fits[extname; col_name(row_expression)] + +Version 2.026 - 23 Dec 1998 + + - modified the group parser to: + a) support CFITSIO_INCLUDE_FILES environment variable, which can + point to the location of template files, and, + b) the FITS file parameter passed to the parser no longer has to point + to an empty file. If there are already HDUs in the file, then the + parser appends new HDUs to the end of the file. + + - make a small change to the drvrnet.c file to accommodate creating + a static version of the CFITSIO library. + + - added 2 new routines to read consecutive bits as an unsigned integer + from a Bit 'X' or Byte 'B' column (ffgcxui and ffgcxuk). + + - modified the logic for determining histogram boundaries in ffhisto + to add one more bin by default, to catch values that are right on + the upper boundary of the histogram, or are in the last partial bin. + + - modified cfitsio2.h to support the new Solaris 7 64-bit mode operating + system. + + - Add utility routine, CFits2Unit, to the Fortran wrappers which searches + the gFitsFiles array for a fptr, returning its element (Fortran unit + number), or allocating a new element if one doesn't already + exists... for C calling Fortran calling CFITSIO. + + - modified configure so that it does not use the compiler optimizer + when using gcc 2.8.x on Linux + + - (re)added the fitsio.* documentation files that describe the + Fortran-callable FITSIO interface to the C routines. + + - modified the lexical parser in eval_f.c to fix bug in null detections + and bug in ffsrow when nrows = 0. + + - modified ffcalc so that it creates a TNULLn keyword if appropriate + when a new column is created. Also fixed detection of OVERFLOWs + so that it ignores null values. + + - added hyperbolic trig and rounding functions to + the lexical parser in the eval* files. + + - improved error message that gets written when the group number is + out of range when reading a 'random groups' array. + + - added description of shared memory, grouping, and template parsing + error messages to ffgerr and to the User's Guide. Moved the error + code definitions from drvsmem.h to fitsio.h. + + - modified grparser.c to compile correctly on Alpha/OSF machines + + - modified drvrnet.c to eliminate compiler warnings + + - Modified Makefile.in to include targets for building all the sample + programs that are included with CFITSIO. + +Version 2.025 - 1 Dec 1998 + + - modified ffgphd and ffgtbp so that they ignores BLANK and TNULLn keywords + that do not have a valid integer value. Also, any error while reading + the BSCALE, BZERO, TSCALn, or TZEROn keywords will be ignored. + Previously, CFITSIO would have simply refused to read an HDU that had + such an invalid keyword. + + - modified the parser in eval_f.c to accept out of order times in GTIs + + - updated cfitsio_mac.sit.hqx to fix bad target parameters for Mac's + speed test program + + - modified template parser in grparser.c to: 1) not write GRPNAME keyword + twice, and 2) assign correct value for EXTVERS keyword. + + - fixed minor bugs in group.c; mainly would only affect users of the + INTEGRAL Data Access Layer. + + - temporarily removed the prototype for ffiwcs from fitsio.h until + full WCS support is added to CFITSIO in the near future. + + - modified the HTTP driver to send a User-Agent string: + HEASARC/CFITSIO/ + + - declared local variables in compress.c as 'static' to avoid + conflicts with other libraries. + +Version 2.024 - 9 Nov 1998 + + - added new function fits_url_type which returns the driver prefix string + associated with a particular FITS file pointer. + +Version 2.023 - 1 Nov 1998 - first full release of CFITSIO 2.0 + + - slightly modified the way real keyword values are formatted, to ensure + that it includes a decimal point. E.g., '1.0E-09' instead of '1E-09' + + - added new function to support template files when creating new FITS files. + + - support the TCROTn WCS keyword in tables, when reading the WCS keywords. + + - modified the iterator to support null values in logical columns in + binary tables. + + - fixed bug in iterator to support null values in integer columns in + ASCII tables. + + - changed the values for FLOATNULLVALUE and DOUBLENULLVALUE to make them + less likely to duplicate actual values in the data. + + - fixed major bug when freeing memory in the iterator function. It caused + mysterious crashes on a few platforms, but had no effect on most others. + + - added support for reading IRAF format image (.imh files) + + - added more error checking to return an error if the size of the FITS + file exceeds the largest value of a long integer (2.1 GB on 32-bit + platforms). + + - CFITSIO now will automatically insert space for additional table rows + or add space to the data heap, if one writes beyond the current end + of the table or heap. This prevents any HDUs which might follow + the current HDU from being overwritten. It is thus no longer necessary + to explicitly call fits_insert_rows before writing new rows of data + to the FITS file. + + - CFITSIO now automatically keeps track of the number of rows that have + been written to a FITS table, and updates the NAXIS2 keyword accordingly + when the table is closed. It is no longer necessary for the application + program to updated NAXIS2. + + - When reading from a FITS table, CFITSIO will now return an error if the + application tries to read beyond the end of the table. + + - added 2 routines to get the number of rows or columns in a table. + + - improved the undocumented feature that allows a '20A' column to be + read as though it were a '20B' column by fits_read_col_byt. + + - added overflow error checking when reading keywords. Previously, the + returned value could be silently truncated to the maximum allowed value + for that data type. Now an error status is returned whenever an + overflow occurs. + + - added new set of routines dealing with hierarchical groups of files. + These were provided by Don Jennings of the INTEGRAL Science Data Center. + + - added new URL parsing routines. + + - changed the calling sequence to ffghad (get HDU address) from + ffghad(fitsfile *fptr, > long *headstart, long *dataend) to + ffghad(fitsfile *fptr, > long *headstart, long datastart, + long *dataend, int *status) + + - major modification to support opening the same FITS file more + than once. Now one can open the same file multiple times and + read and write simultaneously to different HDUs within the file. + fits_open_file automatically detects if the file is already opened. + + - added the ability to clobber/overwrite an existing file + with the same name when creating a new output file. Just + precede the output file name with '!' (an exclamation mark) + + - changed the ffpdat routine which writes the DATE keyword + to use the new 'YYYY-MM-DDThh:mm:ss' format. + + - added several new routines to create or parse the new date/time + format string. + + - changed ifdef for DECFortran in f77_wrap.h and f77_wrap1.c: + expanded to recognize Linux/Alpha + + - added new lexical parsing routines (from Peter Wilson): + eval_l.c, eval_y.c, eval_f.c, eval_defs.h, and eval_tab.h. + These are used when doing on-the-fly table row selections. + + - added new family of routines to support reading and writing + 'unsigned int' data type values in keywords, images or tables. + + - restructured all the putcol and getcol routines to provide + simpler and more robust support for machines which have + sizeof(long) = 8. Defined a new datatype INT32BIT which is + always 32 bits long (platform independent) and is used internally + in CFITSIO when reading or writing BITPIX = 32 images or 'J' + columns. This eliminated the need for specialize routines like + ffswaplong, ffunswaplong, and ffpacklong. + + - overhauled cfileio.c (and other files) to use loadable drivers for + doing data I/O to different devices. Now CFITSIO support network + access to ftp:// and http:// files, and to shared memory files. + + - removed the ffsmem routine and replaced it with ffomem. This will + only affect software that reads an existing file in core memory. + (written there by some other process). + + - modified all the ffgkn[] routines (get an array of keywords) so + that the 'nfound' parameter is = the number of keywords returned, + not the highest index value on the returned keywords. This makes + no difference if the starting index value to look for = 1. + This change is not backward compatible with previous versions + of CFITSIO, but is the way that FITSIO behaved. + + - added new error code = 1 for any application error external + to CFITSIO. Also reports "unknown error status" if the + value doesn't match a known CFITSIO error. + +Version 1.42 - 30 April 1998 (included in FTOOLS 4.1 release) + + - modified the routines which read a FITS float values into + a float array, or read FITS double values into a double array, + so that the array value is also explicitly set in addition + to setting the array of flag values, if the FITS value is a NaN. + This ensures that no NaN values get passed back to the calling + program, which can cause serious problems on some platforms (OSF). + + - added calls to ffrdef at the beginning of the insert + or delete rows or columns routines in editcol.c to make sure + that CFITSIO has correctly initialized the HDU information. + + - added new routine ffdrws to delete a list of rows in a table + + - added ffcphd to copy the header keywords from one hdu to another + + - made the anynul parameter in the ffgcl* routines optional + by first checking to see if the pointer is not null before + initializing it. + + - modified ffbinit and ffainit to ignore minor format + errors in header keywords so that cfitsio can at least + move to an extension that contains illegal keywords. + + - modified all the ffgcl* routines to simply return without + error if nelem = 0. + + - added check to ffclose to check the validity of the fitsfile + pointer before closing it. This should prevent program crashes + when someone tries to close the same file more than once. + + - replaced calls to strcmp and strncmp with macros FSTRCMP and + FSTRNCMP in a few places to improve performance when reading + header keywords (suggested by Mike Noble) + + Bug Fixes: + + - fixed typo in macro definition of error 504 in the file fitsio.h. + + - in ffopen, reserved space for 4 more characters in the input + file name in case a '.zip' suffix needs to be added. + + - small changes to ffpclx to fix problems when writing bit (X) data + columns beyond the current end of file. + + - fixed small bug in ffcrhd where a dummy pointer was not initialized + + - initialized the dummy variable in ffgcfe and ffgcfd which + was causing crashes under OSF in some cases. + + - increased the length of the allocated string ffgkls by 2 + to support the case of reading a numeric keyword as a string + which doesn't have the enclosing quote characters. + +Version 1.4 - 6 Feb 1998 + + - major restructuring of the CFITSIO User's Guide + + - added the new 'iterator' function. The fortran wrapper is + in f77_iter.c for now. + + - enhanced ffcrtb so that it writes a dummy primary array + if none currently exists before appending the table. + + - removed the ffgcl routine and replaced it with ffgcvl + + - modified ffpcnl to just take a single input null value instead + of an entire array of null value flags. + + - modified ffcmps and ffgnxk so that, for example, the string 'rate' + is not considered a match to the string 'rate2', and 'rate*' + is a match to the string 'rate'. + + - modified ffgrsz to also work with images, in which case + it returns the optimum number of pixels to process at + one time. + + - modified ffgthd to support null valued keywords + + - added a new source file 'f77_wrap.c' that includes all the + Fortran77 wrapper routines for calling CFITSIO. This will + eventually replace the Fortran FITSIO library. + + - added new routines: + ffppn - generic write primary array with null values + ffpprn - write null values to primary array + + ffuky - 'update' a keyword value, with any specified datatype. + + ffrprt - write out report of error status and error messages + ffiter - apply a user function iteratively to all the rows of a table + ffpkyc - write complex-valued keyword + ffpkym - write double complex-valued keyword + ffpkfc - write complex-valued keyword in fixed format + ffpkfm - write double complex-valued keyword in fixed format + + ffgkyc - read complex-valued keyword + ffgkym - read double complex-valued keyword + + ffmkyc - modify complex-valued keyword + ffmkym - modify double complex-valued keyword + ffmkfc - modify complex-valued keyword in fixed format + ffmkfm - modify double complex-valued keyword in fixed format + + ffukyc - update complex-valued keyword + ffukym - update double complex-valued keyword + ffukfc - update complex-valued keyword in fixed format + ffukfm - update double complex-valued keyword in fixed format + + ffikyc - insert complex-valued keyword + ffikym - insert double complex-valued keyword + ffikfc - insert complex-valued keyword in fixed format + ffikfm - insert double complex-valued keyword in fixed format + + ffpktp - write or modify keywords using ASCII template file + ffcpcl - copy a column from one table to another + ffcpky - copy an indexed keyword from one HDU to another + ffpcnl - write logical values, including nulls, to binary table + ffpcns - write string values, including nulls, to table + ffmnhd - move to HDU with given exttype, EXTNAME and EXTVERS values + ffthdu - return the total number of HDUs in the file + ffghdt - return the type of the CHDU + ffflnm - return the name of the open FITS file + ffflmd - return the mode of the file (READONLY or READWRITE) + + - modified ffmahd and ffmrhd (to move to a new extension) so that + a null pointer may be given for the returned HDUTYPE argument. + + - worked around a bug in the Mac CWpro2 compiler by changing all + the statements like "#if BYTESWAPPED == TRUE" to "if BYTESWAPPED". + + - modified ffitab (insert new ASCII table) to allow tables with + zero number of columns + + - modified Makefile.in and configure to define the -Dg77Fortran + CFLAGS variable on Linux platforms. This is needed to + compile the new f77_wrap.c file (which includes cfortran.h) + + Bug Fixes: + + - fixed small bug in ffgrz (get optimum row size) which sometimes + caused it to return slightly less than the maximum optimum size. + This bug would have done no harm to application programs. + + - fixed bug in ffpclk and ffgclk to add an 'else' case + if size of int is not equal to size of short or size of long. + + - added test to ffgkls to check if the input string is not null before + allocating memory for it. + +Version 1.32 - 21 November 1997 (internal release only) + + - fixed bug in the memory deallocation (free) statements + in the ffopen routine in the cfileio.c file. + + - modified ffgphd to tolerate minor violations of the FITS + standard in the format of the XTENSION = 'IMAGE ' + keyword when reading FITS files. Extra trailing spaces + are now allowed in the keyword value. (FITS standard + will be changed so that this is not a violation). + +Version 1.31 - 4 November 1997 (internal release only) + + Enhancements: + + - added support for directly reading compressed FITS files + by copying the algorithms from the gzip program. This + supports the Unix compress, gzip and pkzip algorithms. + + - modified ffiimg, ffitab, and ffibin (insert HDUs into + a FITS file) so that if the inserted HDU is at the end of + the FITS file, then it simply appends a new empty HDU + and writes the required keywords. This allows space + to be reserved for additional keywords in the header + if desired. + + - added the ffchfl and ffcdfl routines to check the header and + data fill values, for compatibility with the Fortran FITSIO + library. + + - added the ffgsdt routine to return the system date + for compatibility with the Fortran FITSIO library. + + - added a diagnostic error message (written to the error stack) + if the routines that read data from image or column fail. + + - modified ffgclb so that it simply copies the bytes from + an ASCII 'nA' or 'An' format column into the user's byte + array. Previously, CFITSIO would return an error when + trying to read an 'A' column with ffgclb. + + - modified ffpclb so that it simply copies the input array + of bytes to an ASCII 'nA' or 'An' format column. + Previously, CFITSIO would return an error when + trying to write to an 'A' column with ffpclb. + + Bug Fixes: + + - ffgkls was allocating one too few bytes when reading continued + string keyword values. + + - in testprog.c added code to properly free the memory that + had been allocated for string arrays. + + - corrected typographical errors in the User's Guide. + +Version 1.30 - 11 September 1997 + + - major overhaul to support reading and writing FITS files + in memory. The new routines fits_set_mem_buff and + fits_write_mem_buff have been added to initialize and + copy out the memory buffer, respectively. + + - added support for reading FITS files piped in on 'stdin' + and piped out on 'stdout'. Just specify the file name as '-' + when opening or creating the FITS file. + + - added support for 64-bit SGI IRIX machines. This required + adding routines to pack and unpack 32-bit integers into + 64-bit integers. + + - cleaned up the code that supports G_FLOAT and IEEE_FLOAT + on Alpha VMS systems. Now, the type of float is determined + at compile time, not run time. + + Bug Fixes: + + - replaced the malloc calls in the error message stack routines + with a static fixed size array. The malloc's cause more + problems than they solved, and were prone to cause memory + leaks if users don't clear the error message stack when + closing the FITS file. + + - when writing float or double keywords, test that the value + is not a special IEEE value such as a NaN. Some + compilers would write the string 'NaN' in this case into + the output value string. + + - fixed bug in ffiblk, to ignore EOF status return if it is + inserting blocks at the end of the file. + + - removed the 'l' from printf format string that is constructed + in the ffcfmt routine. This 'l' is non-standard and causes problems + with the Metrowerks compiler on a Mac. + + - the default null value in images was mistakenly being set + equal to NO_NULL = 314, rather than NULL_UNDEFINED = 1234554321 + in the ffgphd routine. + + - check status value in ffgkls to make sure the keyword exists + before allocating memory for the value string. + + - fixed the support for writing and reading unsigned long integer + keyword values in ffpky and ffgky by internally treating + the values as doubles. This required changes to ffc2r and + ffc2d as well. + + - added explicit cast to 'double' in one place in putcolb.c and + 6 places in pubcolui.c, to get rid of warning messages issued + by one compiler. + + - in ffbinit and ffainit, it is necessary to test that tfield > 0 + before trying to allocate memory with calloc. Otherwise, some + compilers return a null pointer which CFITSIO interprets to + mean the memory allocation failed. + + - had to explicitly cast the null buffer pointer to a char + pointer (cptr = (char *)buffer;) in 4 places in the buffers.c + file to satisfy a picky C++ compiler. + + - changed the test for an ALPHA VMS system to see if + '__VMS' is defined, rather than 'VMS'. The latter + is not defined by at least one C++ compiler. + + - modified ffpcls so that it can write a null string to + a variable length string column, without going into + an infinite loop. + + - fixed bug in ffgcfl that caused the 'next' variable to be + incremented twice. + + - fixed bug in ffgcpr that caused it write 2x the number of + complex elements into the descriptor when writing to + a complex or double complex variable length array column. + + - added call to ffrdef at the end of ffrsim to ensure that + the internal structures are updated to correspond to the + modified header keywords + +Version 1.25 - 7 July 1997 + + - improved the efficiency of the ffiblk routine, when inserting + more than one block into the file. + + - fixed bug in ffwend that in rare instances caused the beginning + of the following extension to be overwritten by blank fill. + + - added new routine to modify the size of an existing primary + array or image extension: fits_resize_img/ffrsim. + + - added support for null-valued keywords, e.g., keywords that + have no defined value. These keywords have an equal sign and + space in columns 9-10, but have not value string. Example: + KEYNAME = / null-valued keyword + Support for this feature required the following changes: + - modified ffpsvc to return a null value string without error + - modified ffc2[ilrd] to return error VALUE_UNDEFINED in this case + - modified ffgkn[sljed] to continue reading additional keywords + even if one or more keywords have undefined values. + - added 4 new routines: ffpkyu, ffikyu, ffmkyu, ffukyu to + write, insert, modify, or update an undefined keyword + + - a new makefile.os2 file was added, for building CFITSIO + on OS/2 systems. + + - modified ffgtkn so that if it finds an unexpected keyword + name, the returned error status = BAD_ORDER instead of + NOT_POS_INT. + + - added 2 new routines, fits_write_key_unit/ffpunt and + fits_read_key_unit/ffgunt to write/read the physical + units of a keyword value. These routines use a local + FITS convention for storing the units in square brackets + following the '/' comment field separator, as in: + VELOCITY= 12 / [km/s] orbit speed + The testprog.c program was modified to test these + new routines. + + - in the test of Alpha OSF/1 machines in fitsio2.h, + change 'defined(unix)' to 'defined(__unix__)' which + appears to be a more robust test. + + - remove test for linux environment variable from fitsio2.h + +Version 1.24 - 2 May 1997 + + - fixed bug in ffpbyt that incorrectly computed the current + location in the FITS file when writing > 10000 bytes. + + - changed the datatype of the 'nbytes' parameter in ffpbyt + from 'int' to 'long'. Made corresponding datatype change + to some internal variables in ffshft. + + - changed '(unsigned short *)' to '(short *)' in getcolui.c, and + changed '(unsigned long *)' to '(long *)' in getcoluj.c, to + work around problem with the VAX/VMS cc compiler. + +Version 1.23 - 24 April 1997 + + - modified ffcins and ffdins (in editcol.c) to simply return + without error if there are no (zero) rows in the table. + +Version 1.22 - 18 April 1997 + + - fixed bug in ffgcpr that caused it to think that all values were + undefined in ASCII tables columns that have TNULLn = ' ' + (i.e., the TNULLn keyword value is a string of blanks. + + - fixed bug in the ffgcl[bdeijk,ui,uj] family of routines + when parsing a numeric value in an ASCII table. The + returned values would have the decimal place shifted to + the left if the table field contained an explicit decimal + point followed by blanks. Example: in an F5.2 column, + the value '16. ' would be returned as 0.16. If the + trailing zeros were present, then cfitsio returned the + correct value (e.g., '16.00' returns 16.). + + - fixed another bug in the ffgcl[bdeijk,ui,uj] family of routines + that caused them to misread values in an ASCII table in rows + following an undefined value when all the values were read + at once in a single call to the routine. + +Version 1.21 - 26 March 1997 + + - added general support for reading and writing unsigned integer + keywords, images, and binary table column values. + + - fixed bug in the way the column number was used in ffgsve and + similar routines. This bug caused cfitsio to read (colnum - 1) + rather than the desired column. + + - fixed a bug in ftgkls that prevented it from reading more than one + continuation line of a long string keyword value. + + - fixed the definition of fits_write_longwarn in longnam.h + +Version 1.20 - 29 Jan 1997 + + - when creating a binary table with variable length vector columns, if the + calling routine does not specify a value for the maximum length of + the vector (e.g., TFORMn = '1PE(400)') then cfitsio will automatically + calculate the maximum value and append it to the TFORM value + when the binary table is first closed. + + - added the set of routines to do coordinate system transformations + + - added support for wildcards ('*', '?', and '#') in the input + keyword name when reading, modifying, or deleting keywords. + + - added new general keyword reading routine, ffgnxk, to return + the next keyword whose name matches a list of template names, + but does not match any names on a second template list. + + - modified ftgrec so that it simply moves to the beginning + of the header if the input keyword number = 0 + + - added check in ffdelt to make sure the input fits file pointer is + not already null + + - added check in ffcopy to make sure the output HDU does not + already contain any keywords (it must be empty). + + - modified ffgcls so that it does not test if each string column + value equals the null string value if the null string value + is longer than the width of the column. + + - fixed bug in ftgtdm that caused it to fail if the TDIMn + keyword did not exist in the FITS file + + - modified testprog.c to include tests of keyword wildcards + and the WCS coordinate transformation routines. + + - added a test for 'EMX' in fitsio2.h so that cfitsio builds + correctly on a PC running OS/2. + +Version 1.11 - 04 Dec 1996 + + - modified the testprog.c program that is included with the + distribution, so that the output FITS file is identical to + that produced by the Fortran FITSIO test program. + + - changed all instances of the 'extname' variable to 'extnm' + to avoid a conflict with the -Dextname switch in cfortran.h + on HP machines. + + - in all the routines like ffi4fi1, which convert an array + of values to integers just prior to writing them to the FITS + file, the integer value is now rounded to the nearest integer + rather than truncated. (ffi4fi1, ffi4fi2, ffi4fi4, etc) + + - changed ffgcfl (and hence ffgcl) so that the input value + of the logical array element is not changed if the corresponding + FITS value is undefined. + + - in ffgacl, the returned value of TBCOL was off by 1 (too small) + + - fixed the comment of EXTNAME keyword to read 'binary table' + instead of 'ASCII table' in the header of binary tables. + +Version 1.101 - 17 Nov 1996 + + - Made major I/O efficiency improvements by adding internal buffers + rather than directly reading or writing to disk. Access to + columns in binary tables is now 50 - 150 times faster. Access to + FITS image is also slightly faster. + + - made significant speed improvements when reading numerical data + in FITS ASCII tables by writing my own number parsing routines + rather than using the sscanf C library routine. This change + requires that the -lm argument now be included when linking + a program that calls cfitsio (under UNIX). + + - regrouped the source files into logically related sets of routines. + The Makefile now runs much faster since every single routine is + not split into a separate file. + + - now use the memcpy function, rather than a 'for' loop in several + places for added efficiency + + - redesigned the low-level binary table read and write routines + (ffpbytoff and ffgbytoff) for greater efficiency. + + - added a new error status: 103 = too many open FITS files. + + - added a 'extern "C"' statement around the function prototypes + in fitsio.h, to support use of cfitsio by C++ compilers. + + - fixed routines for writing or reading fixed-length substrings + within a binary table ASCII column, with TFORM values of + of the form 'rAw' where 'r' is the total width of the ASCII + column and 'w' is the width of a substring within the column. + + - no longer automatically rewrite the END card and following fill + values if they are already correct. + + - all the 'get keyword value and comment' routines have been changed + so that the comment is not returned if the input pointer is NULL. + + - added new routine to return the optimum number of tables rows + that should be read or written at one time for optimum efficiency. + + - modified the way numerical values in ASCII tables are parsed so + that embedded spaces in the value are ignored, and implicit + decimal points are now supported. (e.g, the string '123E 12' + in a 'E10.2' format column will be interpreted as 1.23 * 10**12). + + - modified ffpcl and ffgcl to support binary table columns of + all datatype (added logical, bit, complex, and double complex) + + - when writing numerical data to ASCII table columns, the ffpcl_ + routines now return an overflow error if a value is too large + to be expressed in the column format. + + - closed small memory leak in ffpcls. + + - initialized the 'incre' variable in ffgcpr to eliminate compiler warning. + +Version 1.04 - 17 Sept 1996 + + - added README.MacOS and cfitsio_mac.sit.hqx to the distribution + to support the Mac platforms. + + - fixed bug in ffpdfl that caused an EOF error (107) when a program + creates a new extension that is an exact multiple of 2880 bytes long, + AND the program does not write a value to the last element + in the table or image. + + - fixed bug in all the ffgsf* and ffgcv* routines which caused + core dumps when reading null values in a table. + +Version 1.03 - 20 August 1996 + + - added full support for reading and writing the C 'int' + data type. This was a problem on Alpha/OSF where short, + int, and long datatypes are 2, 4, and 8 bytes long, respectively. + + - cleaned up the code in the byte-swapping routines. + + - renamed the file 'longname.h' to 'longnam.h' to avoid conflict + with a file with the same name in another unrelated package. + +Version 1.02 - 15 August 1996 + + - ffgtbp was not correctly reading the THEAP keyword, hence would + not correctly read variable length data in binary tables if + the heap was not at the default starting location (i.e., + starting immediately after the fixed length table). + + - now force the cbuff variable in ffpcl_ and ffgcl_ to be + aligned on a double word boundary. Non-alignment can + cause program to crash on some systems. + +Version 1.01 - 12 August 1996 + + - initial public release diff --git a/external/cfitsio/checksum.c b/external/cfitsio/checksum.c new file mode 100644 index 0000000..c52b9ef --- /dev/null +++ b/external/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 +#include +#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/external/cfitsio/configure b/external/cfitsio/configure new file mode 100755 index 0000000..f804359 --- /dev/null +++ b/external/cfitsio/configure @@ -0,0 +1,6682 @@ +#! /bin/sh +# Guess values for system-dependent variables and create Makefiles. +# Generated by GNU Autoconf 2.68. +# +# +# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001, +# 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software +# Foundation, Inc. +# +# +# This configure script is free software; the Free Software Foundation +# gives unlimited permission to copy, distribute and modify it. +## -------------------- ## +## M4sh Initialization. ## +## -------------------- ## + +# Be more Bourne compatible +DUALCASE=1; export DUALCASE # for MKS sh +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi + + +as_nl=' +' +export as_nl +# Printing a long string crashes Solaris 7 /usr/bin/printf. +as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo +# Prefer a ksh shell builtin over an external printf program on Solaris, +# but without wasting forks for bash or zsh. +if test -z "$BASH_VERSION$ZSH_VERSION" \ + && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='print -r --' + as_echo_n='print -rn --' +elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='printf %s\n' + as_echo_n='printf %s' +else + if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then + as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' + as_echo_n='/usr/ucb/echo -n' + else + as_echo_body='eval expr "X$1" : "X\\(.*\\)"' + as_echo_n_body='eval + arg=$1; + case $arg in #( + *"$as_nl"*) + expr "X$arg" : "X\\(.*\\)$as_nl"; + arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; + esac; + expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" + ' + export as_echo_n_body + as_echo_n='sh -c $as_echo_n_body as_echo' + fi + export as_echo_body + as_echo='sh -c $as_echo_body as_echo' +fi + +# The user is always right. +if test "${PATH_SEPARATOR+set}" != set; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + +# IFS +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent editors from complaining about space-tab. +# (If _AS_PATH_WALK were called with IFS unset, it would disable word +# splitting by setting IFS to empty value.) +IFS=" "" $as_nl" + +# Find who we are. Look in the path if we contain no directory separator. +as_myself= +case $0 in #(( + *[\\/]* ) as_myself=$0 ;; + *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break + done +IFS=$as_save_IFS + + ;; +esac +# We did not find ourselves, most probably we were run as `sh COMMAND' +# in which case we are not to be found in the path. +if test "x$as_myself" = x; then + as_myself=$0 +fi +if test ! -f "$as_myself"; then + $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + exit 1 +fi + +# Unset variables that we do not need and which cause bugs (e.g. in +# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" +# suppresses any "Segmentation fault" message there. '((' could +# trigger a bug in pdksh 5.2.14. +for as_var in BASH_ENV ENV MAIL MAILPATH +do eval test x\${$as_var+set} = xset \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done +PS1='$ ' +PS2='> ' +PS4='+ ' + +# NLS nuisances. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# CDPATH. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +if test "x$CONFIG_SHELL" = x; then + as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which + # is contrary to our usage. Disable this feature. + alias -g '\${1+\"\$@\"}'='\"\$@\"' + setopt NO_GLOB_SUBST +else + case \`(set -o) 2>/dev/null\` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi +" + as_required="as_fn_return () { (exit \$1); } +as_fn_success () { as_fn_return 0; } +as_fn_failure () { as_fn_return 1; } +as_fn_ret_success () { return 0; } +as_fn_ret_failure () { return 1; } + +exitcode=0 +as_fn_success || { exitcode=1; echo as_fn_success failed.; } +as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } +as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } +as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } +if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then : + +else + exitcode=1; echo positional parameters were not saved. +fi +test x\$exitcode = x0 || exit 1" + as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO + as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO + eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && + test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1 +test \$(( 1 + 1 )) = 2 || exit 1" + if (eval "$as_required") 2>/dev/null; then : + as_have_required=yes +else + as_have_required=no +fi + if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then : + +else + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +as_found=false +for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + as_found=: + case $as_dir in #( + /*) + for as_base in sh bash ksh sh5; do + # Try only shells that exist, to save several forks. + as_shell=$as_dir/$as_base + if { test -f "$as_shell" || test -f "$as_shell.exe"; } && + { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then : + CONFIG_SHELL=$as_shell as_have_required=yes + if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then : + break 2 +fi +fi + done;; + esac + as_found=false +done +$as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } && + { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then : + CONFIG_SHELL=$SHELL as_have_required=yes +fi; } +IFS=$as_save_IFS + + + if test "x$CONFIG_SHELL" != x; then : + # We cannot yet assume a decent shell, so we have to provide a + # neutralization value for shells without unset; and this also + # works around shells that cannot unset nonexistent variables. + # Preserve -v and -x to the replacement shell. + BASH_ENV=/dev/null + ENV=/dev/null + (unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV + export CONFIG_SHELL + case $- in # (((( + *v*x* | *x*v* ) as_opts=-vx ;; + *v* ) as_opts=-v ;; + *x* ) as_opts=-x ;; + * ) as_opts= ;; + esac + exec "$CONFIG_SHELL" $as_opts "$as_myself" ${1+"$@"} +fi + + if test x$as_have_required = xno; then : + $as_echo "$0: This script requires a shell more modern than all" + $as_echo "$0: the shells that I found on your system." + if test x${ZSH_VERSION+set} = xset ; then + $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should" + $as_echo "$0: be upgraded to zsh 4.3.4 or later." + else + $as_echo "$0: Please tell bug-autoconf@gnu.org about your system, +$0: including any error possibly output before this +$0: message. Then install a modern shell, or manually run +$0: the script under such a shell if you do have one." + fi + exit 1 +fi +fi +fi +SHELL=${CONFIG_SHELL-/bin/sh} +export SHELL +# Unset more variables known to interfere with behavior of common tools. +CLICOLOR_FORCE= GREP_OPTIONS= +unset CLICOLOR_FORCE GREP_OPTIONS + +## --------------------- ## +## M4sh Shell Functions. ## +## --------------------- ## +# as_fn_unset VAR +# --------------- +# Portably unset VAR. +as_fn_unset () +{ + { eval $1=; unset $1;} +} +as_unset=as_fn_unset + +# as_fn_set_status STATUS +# ----------------------- +# Set $? to STATUS, without forking. +as_fn_set_status () +{ + return $1 +} # as_fn_set_status + +# as_fn_exit STATUS +# ----------------- +# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. +as_fn_exit () +{ + set +e + as_fn_set_status $1 + exit $1 +} # as_fn_exit + +# as_fn_mkdir_p +# ------------- +# Create "$as_dir" as a directory, including parents if necessary. +as_fn_mkdir_p () +{ + + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || eval $as_mkdir_p || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" + + +} # as_fn_mkdir_p +# as_fn_append VAR VALUE +# ---------------------- +# Append the text in VALUE to the end of the definition contained in VAR. Take +# advantage of any shell optimizations that allow amortized linear growth over +# repeated appends, instead of the typical quadratic growth present in naive +# implementations. +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : + eval 'as_fn_append () + { + eval $1+=\$2 + }' +else + as_fn_append () + { + eval $1=\$$1\$2 + } +fi # as_fn_append + +# as_fn_arith ARG... +# ------------------ +# Perform arithmetic evaluation on the ARGs, and store the result in the +# global $as_val. Take advantage of shells that can avoid forks. The arguments +# must be portable across $(()) and expr. +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : + eval 'as_fn_arith () + { + as_val=$(( $* )) + }' +else + as_fn_arith () + { + as_val=`expr "$@" || test $? -eq 1` + } +fi # as_fn_arith + + +# as_fn_error STATUS ERROR [LINENO LOG_FD] +# ---------------------------------------- +# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are +# provided, also output the error to LOG_FD, referencing LINENO. Then exit the +# script with STATUS, using 1 if that was 0. +as_fn_error () +{ + as_status=$1; test $as_status -eq 0 && as_status=1 + if test "$4"; then + as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + fi + $as_echo "$as_me: error: $2" >&2 + as_fn_exit $as_status +} # as_fn_error + +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then + as_basename=basename +else + as_basename=false +fi + +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname +else + as_dirname=false +fi + +as_me=`$as_basename -- "$0" || +$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ + X"$0" : 'X\(//\)$' \| \ + X"$0" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X/"$0" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits + + + as_lineno_1=$LINENO as_lineno_1a=$LINENO + as_lineno_2=$LINENO as_lineno_2a=$LINENO + eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" && + test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || { + # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-) + sed -n ' + p + /[$]LINENO/= + ' <$as_myself | + sed ' + s/[$]LINENO.*/&-/ + t lineno + b + :lineno + N + :loop + s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ + t loop + s/-\n.*// + ' >$as_me.lineno && + chmod +x "$as_me.lineno" || + { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } + + # Don't try to exec as it changes $[0], causing all sort of problems + # (the dirname of $[0] is not the place where we might find the + # original and so on. Autoconf is especially sensitive to this). + . "./$as_me.lineno" + # Exit status is that of the last command. + exit +} + +ECHO_C= ECHO_N= ECHO_T= +case `echo -n x` in #((((( +-n*) + case `echo 'xy\c'` in + *c*) ECHO_T=' ';; # ECHO_T is single tab character. + xy) ECHO_C='\c';; + *) echo `echo ksh88 bug on AIX 6.1` > /dev/null + ECHO_T=' ';; + esac;; +*) + ECHO_N='-n';; +esac + +rm -f conf$$ conf$$.exe conf$$.file +if test -d conf$$.dir; then + rm -f conf$$.dir/conf$$.file +else + rm -f conf$$.dir + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. + # In both cases, we have to default to `cp -p'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -p' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else + as_ln_s='cp -p' + fi +else + as_ln_s='cp -p' +fi +rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file +rmdir conf$$.dir 2>/dev/null + +if mkdir -p . 2>/dev/null; then + as_mkdir_p='mkdir -p "$as_dir"' +else + test -d ./-p && rmdir ./-p + as_mkdir_p=false +fi + +if test -x / >/dev/null 2>&1; then + as_test_x='test -x' +else + if ls -dL / >/dev/null 2>&1; then + as_ls_L_option=L + else + as_ls_L_option= + fi + as_test_x=' + eval sh -c '\'' + if test -d "$1"; then + test -d "$1/."; + else + case $1 in #( + -*)set "./$1";; + esac; + case `ls -ld'$as_ls_L_option' "$1" 2>/dev/null` in #(( + ???[sx]*):;;*)false;;esac;fi + '\'' sh + ' +fi +as_executable_p=$as_test_x + +# Sed expression to map a string onto a valid CPP name. +as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" + +# Sed expression to map a string onto a valid variable name. +as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" + + +test -n "$DJDIR" || exec 7<&0 &1 + +# Name of the host. +# hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status, +# so uname gets run too. +ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` + +# +# Initializations. +# +ac_default_prefix=/usr/local +ac_clean_files= +ac_config_libobj_dir=. +LIBOBJS= +cross_compiling=no +subdirs= +MFLAGS= +MAKEFLAGS= + +# Identity of this package. +PACKAGE_NAME= +PACKAGE_TARNAME= +PACKAGE_VERSION= +PACKAGE_STRING= +PACKAGE_BUGREPORT= +PACKAGE_URL= + +ac_unique_file="fitscore.c" +ac_default_prefix=`pwd` +# Factoring default headers for most tests. +ac_includes_default="\ +#include +#ifdef HAVE_SYS_TYPES_H +# include +#endif +#ifdef HAVE_SYS_STAT_H +# include +#endif +#ifdef STDC_HEADERS +# include +# include +#else +# ifdef HAVE_STDLIB_H +# include +# endif +#endif +#ifdef HAVE_STRING_H +# if !defined STDC_HEADERS && defined HAVE_MEMORY_H +# include +# endif +# include +#endif +#ifdef HAVE_STRINGS_H +# include +#endif +#ifdef HAVE_INTTYPES_H +# include +#endif +#ifdef HAVE_STDINT_H +# include +#endif +#ifdef HAVE_UNISTD_H +# include +#endif" + +ac_subst_vars='LTLIBOBJS +LIBOBJS +my_shmem +F77_WRAPPERS +SHLIB_SUFFIX +SHLIB_LD +LIBPRE +ARCH +GCCVERSION +SSE_FLAGS +EGREP +GREP +CPP +RANLIB +FC +OBJEXT +EXEEXT +ac_ct_CC +CPPFLAGS +LDFLAGS +CFLAGS +CC +uname_found +INSTALL_ROOT +target_alias +host_alias +build_alias +LIBS +ECHO_T +ECHO_N +ECHO_C +DEFS +mandir +localedir +libdir +psdir +pdfdir +dvidir +htmldir +infodir +docdir +oldincludedir +includedir +localstatedir +sharedstatedir +sysconfdir +datadir +datarootdir +libexecdir +sbindir +bindir +program_transform_name +prefix +exec_prefix +PACKAGE_URL +PACKAGE_BUGREPORT +PACKAGE_STRING +PACKAGE_VERSION +PACKAGE_TARNAME +PACKAGE_NAME +PATH_SEPARATOR +SHELL' +ac_subst_files='' +ac_user_opts=' +enable_option_checking +enable_reentrant +enable_sse2 +enable_ssse3 +enable_hera +with_gsiftp_flavour +with_gsiftp +' + ac_precious_vars='build_alias +host_alias +target_alias +CC +CFLAGS +LDFLAGS +LIBS +CPPFLAGS +CPP' + + +# Initialize some variables set by options. +ac_init_help= +ac_init_version=false +ac_unrecognized_opts= +ac_unrecognized_sep= +# The variables have the same names as the options, with +# dashes changed to underlines. +cache_file=/dev/null +exec_prefix=NONE +no_create= +no_recursion= +prefix=NONE +program_prefix=NONE +program_suffix=NONE +program_transform_name=s,x,x, +silent= +site= +srcdir= +verbose= +x_includes=NONE +x_libraries=NONE + +# Installation directory options. +# These are left unexpanded so users can "make install exec_prefix=/foo" +# and all the variables that are supposed to be based on exec_prefix +# by default will actually change. +# Use braces instead of parens because sh, perl, etc. also accept them. +# (The list follows the same order as the GNU Coding Standards.) +bindir='${exec_prefix}/bin' +sbindir='${exec_prefix}/sbin' +libexecdir='${exec_prefix}/libexec' +datarootdir='${prefix}/share' +datadir='${datarootdir}' +sysconfdir='${prefix}/etc' +sharedstatedir='${prefix}/com' +localstatedir='${prefix}/var' +includedir='${prefix}/include' +oldincludedir='/usr/include' +docdir='${datarootdir}/doc/${PACKAGE}' +infodir='${datarootdir}/info' +htmldir='${docdir}' +dvidir='${docdir}' +pdfdir='${docdir}' +psdir='${docdir}' +libdir='${exec_prefix}/lib' +localedir='${datarootdir}/locale' +mandir='${datarootdir}/man' + +ac_prev= +ac_dashdash= +for ac_option +do + # If the previous option needs an argument, assign it. + if test -n "$ac_prev"; then + eval $ac_prev=\$ac_option + ac_prev= + continue + fi + + case $ac_option in + *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; + *=) ac_optarg= ;; + *) ac_optarg=yes ;; + esac + + # Accept the important Cygnus configure options, so we can diagnose typos. + + case $ac_dashdash$ac_option in + --) + ac_dashdash=yes ;; + + -bindir | --bindir | --bindi | --bind | --bin | --bi) + ac_prev=bindir ;; + -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) + bindir=$ac_optarg ;; + + -build | --build | --buil | --bui | --bu) + ac_prev=build_alias ;; + -build=* | --build=* | --buil=* | --bui=* | --bu=*) + build_alias=$ac_optarg ;; + + -cache-file | --cache-file | --cache-fil | --cache-fi \ + | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) + ac_prev=cache_file ;; + -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ + | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) + cache_file=$ac_optarg ;; + + --config-cache | -C) + cache_file=config.cache ;; + + -datadir | --datadir | --datadi | --datad) + ac_prev=datadir ;; + -datadir=* | --datadir=* | --datadi=* | --datad=*) + datadir=$ac_optarg ;; + + -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \ + | --dataroo | --dataro | --datar) + ac_prev=datarootdir ;; + -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \ + | --dataroot=* | --dataroo=* | --dataro=* | --datar=*) + datarootdir=$ac_optarg ;; + + -disable-* | --disable-*) + ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid feature name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"enable_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval enable_$ac_useropt=no ;; + + -docdir | --docdir | --docdi | --doc | --do) + ac_prev=docdir ;; + -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*) + docdir=$ac_optarg ;; + + -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv) + ac_prev=dvidir ;; + -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*) + dvidir=$ac_optarg ;; + + -enable-* | --enable-*) + ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid feature name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"enable_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval enable_$ac_useropt=\$ac_optarg ;; + + -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ + | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ + | --exec | --exe | --ex) + ac_prev=exec_prefix ;; + -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ + | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ + | --exec=* | --exe=* | --ex=*) + exec_prefix=$ac_optarg ;; + + -gas | --gas | --ga | --g) + # Obsolete; use --with-gas. + with_gas=yes ;; + + -help | --help | --hel | --he | -h) + ac_init_help=long ;; + -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) + ac_init_help=recursive ;; + -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) + ac_init_help=short ;; + + -host | --host | --hos | --ho) + ac_prev=host_alias ;; + -host=* | --host=* | --hos=* | --ho=*) + host_alias=$ac_optarg ;; + + -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht) + ac_prev=htmldir ;; + -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \ + | --ht=*) + htmldir=$ac_optarg ;; + + -includedir | --includedir | --includedi | --included | --include \ + | --includ | --inclu | --incl | --inc) + ac_prev=includedir ;; + -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ + | --includ=* | --inclu=* | --incl=* | --inc=*) + includedir=$ac_optarg ;; + + -infodir | --infodir | --infodi | --infod | --info | --inf) + ac_prev=infodir ;; + -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) + infodir=$ac_optarg ;; + + -libdir | --libdir | --libdi | --libd) + ac_prev=libdir ;; + -libdir=* | --libdir=* | --libdi=* | --libd=*) + libdir=$ac_optarg ;; + + -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ + | --libexe | --libex | --libe) + ac_prev=libexecdir ;; + -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ + | --libexe=* | --libex=* | --libe=*) + libexecdir=$ac_optarg ;; + + -localedir | --localedir | --localedi | --localed | --locale) + ac_prev=localedir ;; + -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*) + localedir=$ac_optarg ;; + + -localstatedir | --localstatedir | --localstatedi | --localstated \ + | --localstate | --localstat | --localsta | --localst | --locals) + ac_prev=localstatedir ;; + -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ + | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*) + localstatedir=$ac_optarg ;; + + -mandir | --mandir | --mandi | --mand | --man | --ma | --m) + ac_prev=mandir ;; + -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) + mandir=$ac_optarg ;; + + -nfp | --nfp | --nf) + # Obsolete; use --without-fp. + with_fp=no ;; + + -no-create | --no-create | --no-creat | --no-crea | --no-cre \ + | --no-cr | --no-c | -n) + no_create=yes ;; + + -no-recursion | --no-recursion | --no-recursio | --no-recursi \ + | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) + no_recursion=yes ;; + + -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ + | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ + | --oldin | --oldi | --old | --ol | --o) + ac_prev=oldincludedir ;; + -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ + | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ + | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) + oldincludedir=$ac_optarg ;; + + -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) + ac_prev=prefix ;; + -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) + prefix=$ac_optarg ;; + + -program-prefix | --program-prefix | --program-prefi | --program-pref \ + | --program-pre | --program-pr | --program-p) + ac_prev=program_prefix ;; + -program-prefix=* | --program-prefix=* | --program-prefi=* \ + | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) + program_prefix=$ac_optarg ;; + + -program-suffix | --program-suffix | --program-suffi | --program-suff \ + | --program-suf | --program-su | --program-s) + ac_prev=program_suffix ;; + -program-suffix=* | --program-suffix=* | --program-suffi=* \ + | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) + program_suffix=$ac_optarg ;; + + -program-transform-name | --program-transform-name \ + | --program-transform-nam | --program-transform-na \ + | --program-transform-n | --program-transform- \ + | --program-transform | --program-transfor \ + | --program-transfo | --program-transf \ + | --program-trans | --program-tran \ + | --progr-tra | --program-tr | --program-t) + ac_prev=program_transform_name ;; + -program-transform-name=* | --program-transform-name=* \ + | --program-transform-nam=* | --program-transform-na=* \ + | --program-transform-n=* | --program-transform-=* \ + | --program-transform=* | --program-transfor=* \ + | --program-transfo=* | --program-transf=* \ + | --program-trans=* | --program-tran=* \ + | --progr-tra=* | --program-tr=* | --program-t=*) + program_transform_name=$ac_optarg ;; + + -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd) + ac_prev=pdfdir ;; + -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*) + pdfdir=$ac_optarg ;; + + -psdir | --psdir | --psdi | --psd | --ps) + ac_prev=psdir ;; + -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*) + psdir=$ac_optarg ;; + + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + silent=yes ;; + + -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) + ac_prev=sbindir ;; + -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ + | --sbi=* | --sb=*) + sbindir=$ac_optarg ;; + + -sharedstatedir | --sharedstatedir | --sharedstatedi \ + | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ + | --sharedst | --shareds | --shared | --share | --shar \ + | --sha | --sh) + ac_prev=sharedstatedir ;; + -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ + | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ + | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ + | --sha=* | --sh=*) + sharedstatedir=$ac_optarg ;; + + -site | --site | --sit) + ac_prev=site ;; + -site=* | --site=* | --sit=*) + site=$ac_optarg ;; + + -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) + ac_prev=srcdir ;; + -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) + srcdir=$ac_optarg ;; + + -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ + | --syscon | --sysco | --sysc | --sys | --sy) + ac_prev=sysconfdir ;; + -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ + | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) + sysconfdir=$ac_optarg ;; + + -target | --target | --targe | --targ | --tar | --ta | --t) + ac_prev=target_alias ;; + -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) + target_alias=$ac_optarg ;; + + -v | -verbose | --verbose | --verbos | --verbo | --verb) + verbose=yes ;; + + -version | --version | --versio | --versi | --vers | -V) + ac_init_version=: ;; + + -with-* | --with-*) + ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid package name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"with_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval with_$ac_useropt=\$ac_optarg ;; + + -without-* | --without-*) + ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid package name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"with_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval with_$ac_useropt=no ;; + + --x) + # Obsolete; use --with-x. + with_x=yes ;; + + -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ + | --x-incl | --x-inc | --x-in | --x-i) + ac_prev=x_includes ;; + -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ + | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) + x_includes=$ac_optarg ;; + + -x-libraries | --x-libraries | --x-librarie | --x-librari \ + | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) + ac_prev=x_libraries ;; + -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ + | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) + x_libraries=$ac_optarg ;; + + -*) as_fn_error $? "unrecognized option: \`$ac_option' +Try \`$0 --help' for more information" + ;; + + *=*) + ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` + # Reject names that are not valid shell variable names. + case $ac_envvar in #( + '' | [0-9]* | *[!_$as_cr_alnum]* ) + as_fn_error $? "invalid variable name: \`$ac_envvar'" ;; + esac + eval $ac_envvar=\$ac_optarg + export $ac_envvar ;; + + *) + # FIXME: should be removed in autoconf 3.0. + $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2 + expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && + $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2 + : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" + ;; + + esac +done + +if test -n "$ac_prev"; then + ac_option=--`echo $ac_prev | sed 's/_/-/g'` + as_fn_error $? "missing argument to $ac_option" +fi + +if test -n "$ac_unrecognized_opts"; then + case $enable_option_checking in + no) ;; + fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; + *) $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; + esac +fi + +# Check all directory arguments for consistency. +for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ + datadir sysconfdir sharedstatedir localstatedir includedir \ + oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ + libdir localedir mandir +do + eval ac_val=\$$ac_var + # Remove trailing slashes. + case $ac_val in + */ ) + ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'` + eval $ac_var=\$ac_val;; + esac + # Be sure to have absolute directory names. + case $ac_val in + [\\/$]* | ?:[\\/]* ) continue;; + NONE | '' ) case $ac_var in *prefix ) continue;; esac;; + esac + as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" +done + +# There might be people who depend on the old broken behavior: `$host' +# used to hold the argument of --host etc. +# FIXME: To remove some day. +build=$build_alias +host=$host_alias +target=$target_alias + +# FIXME: To remove some day. +if test "x$host_alias" != x; then + if test "x$build_alias" = x; then + cross_compiling=maybe + $as_echo "$as_me: WARNING: if you wanted to set the --build type, don't use --host. + If a cross compiler is detected then cross compile mode will be used" >&2 + elif test "x$build_alias" != "x$host_alias"; then + cross_compiling=yes + fi +fi + +ac_tool_prefix= +test -n "$host_alias" && ac_tool_prefix=$host_alias- + +test "$silent" = yes && exec 6>/dev/null + + +ac_pwd=`pwd` && test -n "$ac_pwd" && +ac_ls_di=`ls -di .` && +ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || + as_fn_error $? "working directory cannot be determined" +test "X$ac_ls_di" = "X$ac_pwd_ls_di" || + as_fn_error $? "pwd does not report name of working directory" + + +# Find the source files, if location was not specified. +if test -z "$srcdir"; then + ac_srcdir_defaulted=yes + # Try the directory containing this script, then the parent directory. + ac_confdir=`$as_dirname -- "$as_myself" || +$as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_myself" : 'X\(//\)[^/]' \| \ + X"$as_myself" : 'X\(//\)$' \| \ + X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_myself" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + srcdir=$ac_confdir + if test ! -r "$srcdir/$ac_unique_file"; then + srcdir=.. + fi +else + ac_srcdir_defaulted=no +fi +if test ! -r "$srcdir/$ac_unique_file"; then + test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." + as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" +fi +ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work" +ac_abs_confdir=`( + cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" + pwd)` +# When building in place, set srcdir=. +if test "$ac_abs_confdir" = "$ac_pwd"; then + srcdir=. +fi +# Remove unnecessary trailing slashes from srcdir. +# Double slashes in file names in object file debugging info +# mess up M-x gdb in Emacs. +case $srcdir in +*/) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;; +esac +for ac_var in $ac_precious_vars; do + eval ac_env_${ac_var}_set=\${${ac_var}+set} + eval ac_env_${ac_var}_value=\$${ac_var} + eval ac_cv_env_${ac_var}_set=\${${ac_var}+set} + eval ac_cv_env_${ac_var}_value=\$${ac_var} +done + +# +# Report the --help message. +# +if test "$ac_init_help" = "long"; then + # Omit some internal or obsolete options to make the list less imposing. + # This message is too long to be a string in the A/UX 3.1 sh. + cat <<_ACEOF +\`configure' configures this package to adapt to many kinds of systems. + +Usage: $0 [OPTION]... [VAR=VALUE]... + +To assign environment variables (e.g., CC, CFLAGS...), specify them as +VAR=VALUE. See below for descriptions of some of the useful variables. + +Defaults for the options are specified in brackets. + +Configuration: + -h, --help display this help and exit + --help=short display options specific to this package + --help=recursive display the short help of all the included packages + -V, --version display version information and exit + -q, --quiet, --silent do not print \`checking ...' messages + --cache-file=FILE cache test results in FILE [disabled] + -C, --config-cache alias for \`--cache-file=config.cache' + -n, --no-create do not create output files + --srcdir=DIR find the sources in DIR [configure dir or \`..'] + +Installation directories: + --prefix=PREFIX install architecture-independent files in PREFIX + [$ac_default_prefix] + --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX + [PREFIX] + +By default, \`make install' will install all the files in +\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify +an installation prefix other than \`$ac_default_prefix' using \`--prefix', +for instance \`--prefix=\$HOME'. + +For better control, use the options below. + +Fine tuning of the installation directories: + --bindir=DIR user executables [EPREFIX/bin] + --sbindir=DIR system admin executables [EPREFIX/sbin] + --libexecdir=DIR program executables [EPREFIX/libexec] + --sysconfdir=DIR read-only single-machine data [PREFIX/etc] + --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] + --localstatedir=DIR modifiable single-machine data [PREFIX/var] + --libdir=DIR object code libraries [EPREFIX/lib] + --includedir=DIR C header files [PREFIX/include] + --oldincludedir=DIR C header files for non-gcc [/usr/include] + --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] + --datadir=DIR read-only architecture-independent data [DATAROOTDIR] + --infodir=DIR info documentation [DATAROOTDIR/info] + --localedir=DIR locale-dependent data [DATAROOTDIR/locale] + --mandir=DIR man documentation [DATAROOTDIR/man] + --docdir=DIR documentation root [DATAROOTDIR/doc/PACKAGE] + --htmldir=DIR html documentation [DOCDIR] + --dvidir=DIR dvi documentation [DOCDIR] + --pdfdir=DIR pdf documentation [DOCDIR] + --psdir=DIR ps documentation [DOCDIR] +_ACEOF + + cat <<\_ACEOF +_ACEOF +fi + +if test -n "$ac_init_help"; then + + cat <<\_ACEOF + +Optional Features: + --disable-option-checking ignore unrecognized --enable/--with options + --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) + --enable-FEATURE[=ARG] include FEATURE [ARG=yes] + --enable-reentrant Enable reentrant multithreading + --enable-sse2 Enable use of instructions in the SSE2 extended + instruction set + --enable-ssse3 Enable use of instructions in the SSSE3 extended + instruction set + --enable-hera Build for HERA (ASD use only) + +Optional Packages: + --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] + --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) + --with-gsiftp-flavour[=PATH] + Enable Globus Toolkit gsiftp protocol support + --with-gsiftp[=PATH] Enable Globus Toolkit gsiftp protocol support + +Some influential environment variables: + CC C compiler command + CFLAGS C compiler flags + LDFLAGS linker flags, e.g. -L if you have libraries in a + nonstandard directory + LIBS libraries to pass to the linker, e.g. -l + CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I if + you have headers in a nonstandard directory + CPP C preprocessor + +Use these variables to override the choices made by `configure' or to help +it to find libraries and programs with nonstandard names/locations. + +Report bugs to the package provider. +_ACEOF +ac_status=$? +fi + +if test "$ac_init_help" = "recursive"; then + # If there are subdirs, report their specific --help. + for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue + test -d "$ac_dir" || + { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } || + continue + ac_builddir=. + +case "$ac_dir" in +.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; +*) + ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` + # A ".." for each directory in $ac_dir_suffix. + ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + case $ac_top_builddir_sub in + "") ac_top_builddir_sub=. ac_top_build_prefix= ;; + *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; + esac ;; +esac +ac_abs_top_builddir=$ac_pwd +ac_abs_builddir=$ac_pwd$ac_dir_suffix +# for backward compatibility: +ac_top_builddir=$ac_top_build_prefix + +case $srcdir in + .) # We are building in place. + ac_srcdir=. + ac_top_srcdir=$ac_top_builddir_sub + ac_abs_top_srcdir=$ac_pwd ;; + [\\/]* | ?:[\\/]* ) # Absolute name. + ac_srcdir=$srcdir$ac_dir_suffix; + ac_top_srcdir=$srcdir + ac_abs_top_srcdir=$srcdir ;; + *) # Relative name. + ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix + ac_top_srcdir=$ac_top_build_prefix$srcdir + ac_abs_top_srcdir=$ac_pwd/$srcdir ;; +esac +ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix + + cd "$ac_dir" || { ac_status=$?; continue; } + # Check for guested configure. + if test -f "$ac_srcdir/configure.gnu"; then + echo && + $SHELL "$ac_srcdir/configure.gnu" --help=recursive + elif test -f "$ac_srcdir/configure"; then + echo && + $SHELL "$ac_srcdir/configure" --help=recursive + else + $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2 + fi || ac_status=$? + cd "$ac_pwd" || { ac_status=$?; break; } + done +fi + +test -n "$ac_init_help" && exit $ac_status +if $ac_init_version; then + cat <<\_ACEOF +configure +generated by GNU Autoconf 2.68 + +Copyright (C) 2010 Free Software Foundation, Inc. +This configure script is free software; the Free Software Foundation +gives unlimited permission to copy, distribute and modify it. +_ACEOF + exit +fi + +## ------------------------ ## +## Autoconf initialization. ## +## ------------------------ ## + +# ac_fn_c_try_compile LINENO +# -------------------------- +# Try to compile conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_compile () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext + if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_compile + +# ac_fn_c_try_cpp LINENO +# ---------------------- +# Try to preprocess conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_cpp () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if { { ac_try="$ac_cpp conftest.$ac_ext" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } > conftest.i && { + test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" || + test ! -s conftest.err + }; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_cpp + +# ac_fn_c_check_header_mongrel LINENO HEADER VAR INCLUDES +# ------------------------------------------------------- +# Tests whether HEADER exists, giving a warning if it cannot be compiled using +# the include files in INCLUDES and setting the cache variable VAR +# accordingly. +ac_fn_c_check_header_mongrel () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if eval \${$3+:} false; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +else + # Is the header compilable? +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 usability" >&5 +$as_echo_n "checking $2 usability... " >&6; } +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +#include <$2> +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_header_compiler=yes +else + ac_header_compiler=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_compiler" >&5 +$as_echo "$ac_header_compiler" >&6; } + +# Is the header present? +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 presence" >&5 +$as_echo_n "checking $2 presence... " >&6; } +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <$2> +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + ac_header_preproc=yes +else + ac_header_preproc=no +fi +rm -f conftest.err conftest.i conftest.$ac_ext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_preproc" >&5 +$as_echo "$ac_header_preproc" >&6; } + +# So? What about this header? +case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in #(( + yes:no: ) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&5 +$as_echo "$as_me: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5 +$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;} + ;; + no:yes:* ) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: present but cannot be compiled" >&5 +$as_echo "$as_me: WARNING: $2: present but cannot be compiled" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: check for missing prerequisite headers?" >&5 +$as_echo "$as_me: WARNING: $2: check for missing prerequisite headers?" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: see the Autoconf documentation" >&5 +$as_echo "$as_me: WARNING: $2: see the Autoconf documentation" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&5 +$as_echo "$as_me: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5 +$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;} + ;; +esac + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + eval "$3=\$ac_header_compiler" +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_header_mongrel + +# ac_fn_c_try_run LINENO +# ---------------------- +# Try to link conftest.$ac_ext, and return whether this succeeded. Assumes +# that executables *can* be run. +ac_fn_c_try_run () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { ac_try='./conftest$ac_exeext' + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; }; then : + ac_retval=0 +else + $as_echo "$as_me: program exited with status $ac_status" >&5 + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=$ac_status +fi + rm -rf conftest.dSYM conftest_ipa8_conftest.oo + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_run + +# ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES +# ------------------------------------------------------- +# Tests whether HEADER exists and can be compiled using the include files in +# INCLUDES, setting the cache variable VAR accordingly. +ac_fn_c_check_header_compile () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +#include <$2> +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + eval "$3=yes" +else + eval "$3=no" +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_header_compile + +# ac_fn_c_try_link LINENO +# ----------------------- +# Try to link conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_link () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext conftest$ac_exeext + if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + $as_test_x conftest$ac_exeext + }; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information + # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would + # interfere with the next link command; also delete a directory that is + # left behind by Apple's compiler. We do this before executing the actions. + rm -rf conftest.dSYM conftest_ipa8_conftest.oo + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_link +cat >config.log <<_ACEOF +This file contains any messages produced by compilers while +running configure, to aid debugging if configure makes a mistake. + +It was created by $as_me, which was +generated by GNU Autoconf 2.68. Invocation command line was + + $ $0 $@ + +_ACEOF +exec 5>>config.log +{ +cat <<_ASUNAME +## --------- ## +## Platform. ## +## --------- ## + +hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` +uname -m = `(uname -m) 2>/dev/null || echo unknown` +uname -r = `(uname -r) 2>/dev/null || echo unknown` +uname -s = `(uname -s) 2>/dev/null || echo unknown` +uname -v = `(uname -v) 2>/dev/null || echo unknown` + +/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` + +/bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` +/usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown` +/bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` +/bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` + +_ASUNAME + +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + $as_echo "PATH: $as_dir" + done +IFS=$as_save_IFS + +} >&5 + +cat >&5 <<_ACEOF + + +## ----------- ## +## Core tests. ## +## ----------- ## + +_ACEOF + + +# Keep a trace of the command line. +# Strip out --no-create and --no-recursion so they do not pile up. +# Strip out --silent because we don't want to record it for future runs. +# Also quote any args containing shell meta-characters. +# Make two passes to allow for proper duplicate-argument suppression. +ac_configure_args= +ac_configure_args0= +ac_configure_args1= +ac_must_keep_next=false +for ac_pass in 1 2 +do + for ac_arg + do + case $ac_arg in + -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + continue ;; + *\'*) + ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; + esac + case $ac_pass in + 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; + 2) + as_fn_append ac_configure_args1 " '$ac_arg'" + if test $ac_must_keep_next = true; then + ac_must_keep_next=false # Got value, back to normal. + else + case $ac_arg in + *=* | --config-cache | -C | -disable-* | --disable-* \ + | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ + | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ + | -with-* | --with-* | -without-* | --without-* | --x) + case "$ac_configure_args0 " in + "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; + esac + ;; + -* ) ac_must_keep_next=true ;; + esac + fi + as_fn_append ac_configure_args " '$ac_arg'" + ;; + esac + done +done +{ ac_configure_args0=; unset ac_configure_args0;} +{ ac_configure_args1=; unset ac_configure_args1;} + +# When interrupted or exit'd, cleanup temporary files, and complete +# config.log. We remove comments because anyway the quotes in there +# would cause problems or look ugly. +# WARNING: Use '\'' to represent an apostrophe within the trap. +# WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. +trap 'exit_status=$? + # Save into config.log some information that might help in debugging. + { + echo + + $as_echo "## ---------------- ## +## Cache variables. ## +## ---------------- ##" + echo + # The following way of writing the cache mishandles newlines in values, +( + for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do + eval ac_val=\$$ac_var + case $ac_val in #( + *${as_nl}*) + case $ac_var in #( + *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + esac + case $ac_var in #( + _ | IFS | as_nl) ;; #( + BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( + *) { eval $ac_var=; unset $ac_var;} ;; + esac ;; + esac + done + (set) 2>&1 | + case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #( + *${as_nl}ac_space=\ *) + sed -n \ + "s/'\''/'\''\\\\'\'''\''/g; + s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p" + ;; #( + *) + sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" + ;; + esac | + sort +) + echo + + $as_echo "## ----------------- ## +## Output variables. ## +## ----------------- ##" + echo + for ac_var in $ac_subst_vars + do + eval ac_val=\$$ac_var + case $ac_val in + *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + esac + $as_echo "$ac_var='\''$ac_val'\''" + done | sort + echo + + if test -n "$ac_subst_files"; then + $as_echo "## ------------------- ## +## File substitutions. ## +## ------------------- ##" + echo + for ac_var in $ac_subst_files + do + eval ac_val=\$$ac_var + case $ac_val in + *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + esac + $as_echo "$ac_var='\''$ac_val'\''" + done | sort + echo + fi + + if test -s confdefs.h; then + $as_echo "## ----------- ## +## confdefs.h. ## +## ----------- ##" + echo + cat confdefs.h + echo + fi + test "$ac_signal" != 0 && + $as_echo "$as_me: caught signal $ac_signal" + $as_echo "$as_me: exit $exit_status" + } >&5 + rm -f core *.core core.conftest.* && + rm -f -r conftest* confdefs* conf$$* $ac_clean_files && + exit $exit_status +' 0 +for ac_signal in 1 2 13 15; do + trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal +done +ac_signal=0 + +# confdefs.h avoids OS command line length limits that DEFS can exceed. +rm -f -r conftest* confdefs.h + +$as_echo "/* confdefs.h */" > confdefs.h + +# Predefined preprocessor variables. + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_NAME "$PACKAGE_NAME" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_TARNAME "$PACKAGE_TARNAME" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_VERSION "$PACKAGE_VERSION" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_STRING "$PACKAGE_STRING" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_URL "$PACKAGE_URL" +_ACEOF + + +# Let the site file select an alternate cache file if it wants to. +# Prefer an explicitly selected file to automatically selected ones. +ac_site_file1=NONE +ac_site_file2=NONE +if test -n "$CONFIG_SITE"; then + # We do not want a PATH search for config.site. + case $CONFIG_SITE in #(( + -*) ac_site_file1=./$CONFIG_SITE;; + */*) ac_site_file1=$CONFIG_SITE;; + *) ac_site_file1=./$CONFIG_SITE;; + esac +elif test "x$prefix" != xNONE; then + ac_site_file1=$prefix/share/config.site + ac_site_file2=$prefix/etc/config.site +else + ac_site_file1=$ac_default_prefix/share/config.site + ac_site_file2=$ac_default_prefix/etc/config.site +fi +for ac_site_file in "$ac_site_file1" "$ac_site_file2" +do + test "x$ac_site_file" = xNONE && continue + if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 +$as_echo "$as_me: loading site script $ac_site_file" >&6;} + sed 's/^/| /' "$ac_site_file" >&5 + . "$ac_site_file" \ + || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "failed to load site script $ac_site_file +See \`config.log' for more details" "$LINENO" 5; } + fi +done + +if test -r "$cache_file"; then + # Some versions of bash will fail to source /dev/null (special files + # actually), so we avoid doing that. DJGPP emulates it as a regular file. + if test /dev/null != "$cache_file" && test -f "$cache_file"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 +$as_echo "$as_me: loading cache $cache_file" >&6;} + case $cache_file in + [\\/]* | ?:[\\/]* ) . "$cache_file";; + *) . "./$cache_file";; + esac + fi +else + { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 +$as_echo "$as_me: creating cache $cache_file" >&6;} + >$cache_file +fi + +# Check that the precious variables saved in the cache have kept the same +# value. +ac_cache_corrupted=false +for ac_var in $ac_precious_vars; do + eval ac_old_set=\$ac_cv_env_${ac_var}_set + eval ac_new_set=\$ac_env_${ac_var}_set + eval ac_old_val=\$ac_cv_env_${ac_var}_value + eval ac_new_val=\$ac_env_${ac_var}_value + case $ac_old_set,$ac_new_set in + set,) + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 +$as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} + ac_cache_corrupted=: ;; + ,set) + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5 +$as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} + ac_cache_corrupted=: ;; + ,);; + *) + if test "x$ac_old_val" != "x$ac_new_val"; then + # differences in whitespace do not lead to failure. + ac_old_val_w=`echo x $ac_old_val` + ac_new_val_w=`echo x $ac_new_val` + if test "$ac_old_val_w" != "$ac_new_val_w"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5 +$as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} + ac_cache_corrupted=: + else + { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5 +$as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;} + eval $ac_var=\$ac_old_val + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5 +$as_echo "$as_me: former value: \`$ac_old_val'" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5 +$as_echo "$as_me: current value: \`$ac_new_val'" >&2;} + fi;; + esac + # Pass precious variables to config.status. + if test "$ac_new_set" = set; then + case $ac_new_val in + *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; + *) ac_arg=$ac_var=$ac_new_val ;; + esac + case " $ac_configure_args " in + *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. + *) as_fn_append ac_configure_args " '$ac_arg'" ;; + esac + fi +done +if $ac_cache_corrupted; then + { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 +$as_echo "$as_me: error: changes in the environment can compromise the build" >&2;} + as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5 +fi +## -------------------- ## +## Main body of script. ## +## -------------------- ## + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + +#-------------------------------------------------------------------- +# Command options +#-------------------------------------------------------------------- + +# Check whether --enable-reentrant was given. +if test "${enable_reentrant+set}" = set; then : + enableval=$enable_reentrant; if test $enableval = yes; then BUILD_REENTRANT=yes; fi + +fi + + +SSE_FLAGS="" +# Check whether --enable-sse2 was given. +if test "${enable_sse2+set}" = set; then : + enableval=$enable_sse2; if test $enableval = yes; then SSE_FLAGS="-msse2"; fi + +fi + + +# Check whether --enable-ssse3 was given. +if test "${enable_ssse3+set}" = set; then : + enableval=$enable_ssse3; if test $enableval = yes; then SSE_FLAGS="$SSE_FLAGS -mssse3"; fi + +fi + + +# Define BUILD_HERA when building for HERA project to activate code in +# drvrfile.c (by way of fitsio2.h): +# Check whether --enable-hera was given. +if test "${enable_hera+set}" = set; then : + enableval=$enable_hera; if test $enableval = yes; then BUILD_HERA=yes; fi + +fi + +if test "x$BUILD_HERA" = xyes; then + $as_echo "#define BUILD_HERA 1" >>confdefs.h + +fi + + +# Check whether --with-gsiftp-flavour was given. +if test "${with_gsiftp_flavour+set}" = set; then : + withval=$with_gsiftp_flavour; if test "x$withval" != "xno"; then + + if test "x$withval" != "xyes" ; then + GSIFTP_FLAVOUR=${withval} + fi + +$as_echo "#define GSIFTP_FLAVOUR 1" >>confdefs.h + + fi + + +fi + + + +# Check whether --with-gsiftp was given. +if test "${with_gsiftp+set}" = set; then : + withval=$with_gsiftp; if test "x$withval" != "xno"; then + + if test "x$withval" != "xyes" ; then + CFLAGS="$CFLAGS -I${withval}/include/${GSIFTP_FLAVOUR}" + LDFLAGS="$LDFLAGS -L${withval}/lib -lglobus_ftp_client_${GSIFTP_FLAVOUR}" + HAVE_GSIFTP=yes + fi + +$as_echo "#define HAVE_GSIFTP 1" >>confdefs.h + + fi + + +fi + + +#-------------------------------------------------------------------- +# Check for install location prefix +#-------------------------------------------------------------------- + + + +# make will complain about duplicate targets for the install directories +# if prefix == exec_prefix +INSTALL_ROOT='${prefix}' + +test "$exec_prefix" != NONE -a "$prefix" != "$exec_prefix" \ + && INSTALL_ROOT="$INSTALL_ROOT "'${exec_prefix}' + + +#-------------------------------------------------------------------- +# Check "uname" to determine system type +#-------------------------------------------------------------------- +# Extract the first word of "uname", so it can be a program name with args. +set dummy uname; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_uname_found+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$uname_found"; then + ac_cv_prog_uname_found="$uname_found" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_uname_found="1" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + test -z "$ac_cv_prog_uname_found" && ac_cv_prog_uname_found="0" +fi +fi +uname_found=$ac_cv_prog_uname_found +if test -n "$uname_found"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $uname_found" >&5 +$as_echo "$uname_found" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +if test $uname_found -eq 0 ; then + echo "cfitsio: No uname found; setting system type to unknown." + system="unknown" +else + system=`uname -s`-`uname -r` +fi + + + +# Try first to find a proprietary C compiler, then gcc +if test "x$CC" = x; then + for ac_prog in cc +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_CC="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$CC" && break +done + +fi +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. +set dummy ${ac_tool_prefix}gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_CC="${ac_tool_prefix}gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_CC"; then + ac_ct_CC=$CC + # Extract the first word of "gcc", so it can be a program name with args. +set dummy gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_ac_ct_CC="gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +else + CC="$ac_cv_prog_CC" +fi + +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. +set dummy ${ac_tool_prefix}cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_CC="${ac_tool_prefix}cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + fi +fi +if test -z "$CC"; then + # Extract the first word of "cc", so it can be a program name with args. +set dummy cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + ac_prog_rejected=no +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then + ac_prog_rejected=yes + continue + fi + ac_cv_prog_CC="cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +if test $ac_prog_rejected = yes; then + # We found a bogon in the path, so make sure we never use it. + set dummy $ac_cv_prog_CC + shift + if test $# != 0; then + # We chose a different compiler from the bogus one. + # However, it has the same basename, so the bogon will be chosen + # first if we set CC to just the basename; use the full file name. + shift + ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@" + fi +fi +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + for ac_prog in cl.exe + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_CC="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$CC" && break + done +fi +if test -z "$CC"; then + ac_ct_CC=$CC + for ac_prog in cl.exe +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_ac_ct_CC="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$ac_ct_CC" && break +done + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +fi + +fi + + +test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "no acceptable C compiler found in \$PATH +See \`config.log' for more details" "$LINENO" 5; } + +# Provide some information about the compiler. +$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 +set X $ac_compile +ac_compiler=$2 +for ac_option in --version -v -V -qversion; do + { { ac_try="$ac_compiler $ac_option >&5" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compiler $ac_option >&5") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + sed '10a\ +... rest of stderr output deleted ... + 10q' conftest.err >conftest.er1 + cat conftest.er1 >&5 + fi + rm -f conftest.er1 conftest.err + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +done + +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +ac_clean_files_save=$ac_clean_files +ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out" +# Try to create an executable without -o first, disregard a.out. +# It will help us diagnose broken compilers, and finding out an intuition +# of exeext. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5 +$as_echo_n "checking whether the C compiler works... " >&6; } +ac_link_default=`$as_echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'` + +# The possible output files: +ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*" + +ac_rmfiles= +for ac_file in $ac_files +do + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; + * ) ac_rmfiles="$ac_rmfiles $ac_file";; + esac +done +rm -f $ac_rmfiles + +if { { ac_try="$ac_link_default" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link_default") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + # Autoconf-2.13 could set the ac_cv_exeext variable to `no'. +# So ignore a value of `no', otherwise this would lead to `EXEEXT = no' +# in a Makefile. We should not override ac_cv_exeext if it was cached, +# so that the user can short-circuit this test for compilers unknown to +# Autoconf. +for ac_file in $ac_files '' +do + test -f "$ac_file" || continue + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) + ;; + [ab].out ) + # We found the default executable, but exeext='' is most + # certainly right. + break;; + *.* ) + if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no; + then :; else + ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` + fi + # We set ac_cv_exeext here because the later test for it is not + # safe: cross compilers may not add the suffix if given an `-o' + # argument, so we may need to know it at that point already. + # Even if this section looks crufty: it has the advantage of + # actually working. + break;; + * ) + break;; + esac +done +test "$ac_cv_exeext" = no && ac_cv_exeext= + +else + ac_file='' +fi +if test -z "$ac_file"; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +$as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error 77 "C compiler cannot create executables +See \`config.log' for more details" "$LINENO" 5; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5 +$as_echo_n "checking for C compiler default output file name... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 +$as_echo "$ac_file" >&6; } +ac_exeext=$ac_cv_exeext + +rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out +ac_clean_files=$ac_clean_files_save +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 +$as_echo_n "checking for suffix of executables... " >&6; } +if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + # If both `conftest.exe' and `conftest' are `present' (well, observable) +# catch `conftest.exe'. For instance with Cygwin, `ls conftest' will +# work properly (i.e., refer to `conftest.exe'), while it won't with +# `rm'. +for ac_file in conftest.exe conftest conftest.*; do + test -f "$ac_file" || continue + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; + *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` + break;; + * ) break;; + esac +done +else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot compute suffix of executables: cannot compile and link +See \`config.log' for more details" "$LINENO" 5; } +fi +rm -f conftest conftest$ac_cv_exeext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 +$as_echo "$ac_cv_exeext" >&6; } + +rm -f conftest.$ac_ext +EXEEXT=$ac_cv_exeext +ac_exeext=$EXEEXT +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main () +{ +FILE *f = fopen ("conftest.out", "w"); + return ferror (f) || fclose (f) != 0; + + ; + return 0; +} +_ACEOF +ac_clean_files="$ac_clean_files conftest.out" +# Check that the compiler produces executables we can run. If not, either +# the compiler is broken, or we cross compile. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5 +$as_echo_n "checking whether we are cross compiling... " >&6; } +if test "$cross_compiling" != yes; then + { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } + if { ac_try='./conftest$ac_cv_exeext' + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; }; then + cross_compiling=no + else + if test "$cross_compiling" = maybe; then + cross_compiling=yes + else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot run C compiled programs. +If you meant to cross compile, use \`--host'. +See \`config.log' for more details" "$LINENO" 5; } + fi + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 +$as_echo "$cross_compiling" >&6; } + +rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out +ac_clean_files=$ac_clean_files_save +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 +$as_echo_n "checking for suffix of object files... " >&6; } +if ${ac_cv_objext+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +rm -f conftest.o conftest.obj +if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + for ac_file in conftest.o conftest.obj conftest.*; do + test -f "$ac_file" || continue; + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;; + *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'` + break;; + esac +done +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot compute suffix of object files: cannot compile +See \`config.log' for more details" "$LINENO" 5; } +fi +rm -f conftest.$ac_cv_objext conftest.$ac_ext +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 +$as_echo "$ac_cv_objext" >&6; } +OBJEXT=$ac_cv_objext +ac_objext=$OBJEXT +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5 +$as_echo_n "checking whether we are using the GNU C compiler... " >&6; } +if ${ac_cv_c_compiler_gnu+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ +#ifndef __GNUC__ + choke me +#endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_compiler_gnu=yes +else + ac_compiler_gnu=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +ac_cv_c_compiler_gnu=$ac_compiler_gnu + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 +$as_echo "$ac_cv_c_compiler_gnu" >&6; } +if test $ac_compiler_gnu = yes; then + GCC=yes +else + GCC= +fi +ac_test_CFLAGS=${CFLAGS+set} +ac_save_CFLAGS=$CFLAGS +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 +$as_echo_n "checking whether $CC accepts -g... " >&6; } +if ${ac_cv_prog_cc_g+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_save_c_werror_flag=$ac_c_werror_flag + ac_c_werror_flag=yes + ac_cv_prog_cc_g=no + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +else + CFLAGS="" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + +else + ac_c_werror_flag=$ac_save_c_werror_flag + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_c_werror_flag=$ac_save_c_werror_flag +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 +$as_echo "$ac_cv_prog_cc_g" >&6; } +if test "$ac_test_CFLAGS" = set; then + CFLAGS=$ac_save_CFLAGS +elif test $ac_cv_prog_cc_g = yes; then + if test "$GCC" = yes; then + CFLAGS="-g -O2" + else + CFLAGS="-g" + fi +else + if test "$GCC" = yes; then + CFLAGS="-O2" + else + CFLAGS= + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5 +$as_echo_n "checking for $CC option to accept ISO C89... " >&6; } +if ${ac_cv_prog_cc_c89+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_cv_prog_cc_c89=no +ac_save_CC=$CC +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +#include +#include +/* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */ +struct buf { int x; }; +FILE * (*rcsopen) (struct buf *, struct stat *, int); +static char *e (p, i) + char **p; + int i; +{ + return p[i]; +} +static char *f (char * (*g) (char **, int), char **p, ...) +{ + char *s; + va_list v; + va_start (v,p); + s = g (p, va_arg (v,int)); + va_end (v); + return s; +} + +/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has + function prototypes and stuff, but not '\xHH' hex character constants. + These don't provoke an error unfortunately, instead are silently treated + as 'x'. The following induces an error, until -std is added to get + proper ANSI mode. Curiously '\x00'!='x' always comes out true, for an + array size at least. It's necessary to write '\x00'==0 to get something + that's true only with -std. */ +int osf4_cc_array ['\x00' == 0 ? 1 : -1]; + +/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters + inside strings and character constants. */ +#define FOO(x) 'x' +int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1]; + +int test (int i, double x); +struct s1 {int (*f) (int a);}; +struct s2 {int (*f) (double a);}; +int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int); +int argc; +char **argv; +int +main () +{ +return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]; + ; + return 0; +} +_ACEOF +for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \ + -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" +do + CC="$ac_save_CC $ac_arg" + if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_c89=$ac_arg +fi +rm -f core conftest.err conftest.$ac_objext + test "x$ac_cv_prog_cc_c89" != "xno" && break +done +rm -f conftest.$ac_ext +CC=$ac_save_CC + +fi +# AC_CACHE_VAL +case "x$ac_cv_prog_cc_c89" in + x) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +$as_echo "none needed" >&6; } ;; + xno) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +$as_echo "unsupported" >&6; } ;; + *) + CC="$CC $ac_cv_prog_cc_c89" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 +$as_echo "$ac_cv_prog_cc_c89" >&6; } ;; +esac +if test "x$ac_cv_prog_cc_c89" != xno; then : + +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +if test "x$FC" = "xnone" ; then + { $as_echo "$as_me:${as_lineno-$LINENO}: cfitsio: == Fortran compiler search has been overridden" >&5 +$as_echo "$as_me: cfitsio: == Fortran compiler search has been overridden" >&6;} + { $as_echo "$as_me:${as_lineno-$LINENO}: cfitsio: == Cfitsio will be built without Fortran wrapper support" >&5 +$as_echo "$as_me: cfitsio: == Cfitsio will be built without Fortran wrapper support" >&6;} + FC= + F77_WRAPPERS= +else + for ac_prog in gfortran g95 g77 f77 ifort f95 f90 xlf cf77 gf77 af77 ncf f2c +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_FC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$FC"; then + ac_cv_prog_FC="$FC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_FC="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +FC=$ac_cv_prog_FC +if test -n "$FC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $FC" >&5 +$as_echo "$FC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$FC" && break +done +test -n "$FC" || FC="notfound" + + if test $FC = 'notfound' ; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cfitsio: == No acceptable Fortran compiler found in \$PATH" >&5 +$as_echo "$as_me: WARNING: cfitsio: == No acceptable Fortran compiler found in \$PATH" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: cfitsio: == Adding wrapper support for GNU Fortran by default" >&5 +$as_echo "$as_me: cfitsio: == Adding wrapper support for GNU Fortran by default" >&6;} + CFORTRANFLAGS="-Dg77Fortran" + F77_WRAPPERS="\${FITSIO_SRC}" + else + CFORTRANFLAGS= + F77_WRAPPERS="\${FITSIO_SRC}" + echo $ac_n "checking whether we are using GNU Fortran""... $ac_c" 1>&6 + if test `$FC --version -c < /dev/null 2> /dev/null | grep -c GNU` -gt 0 -o \ + `$FC --version -c < /dev/null 2> /dev/null | grep -ic egcs` -gt 0 + then + echo "$ac_t""yes" 1>&6 + echo $ac_n "cfitsio: == Adding wrapper support for GNU Fortran""... $ac_c" 1>&6 + CFORTRANFLAGS="-Dg77Fortran" + echo "$ac_t"" done" 1>&6 + else + echo "$ac_t""no" 1>&6 + if test $FC = 'f2c' ; then + echo $ac_n "cfitsio: == Adding wrapper support for f2c""... $ac_c" 1>&6 + CFORTRANFLAGS="-Df2cFortran" + echo "$ac_t"" done" 1>&6 + fi + fi + fi +fi + +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}ranlib", so it can be a program name with args. +set dummy ${ac_tool_prefix}ranlib; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_RANLIB+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$RANLIB"; then + ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_RANLIB="${ac_tool_prefix}ranlib" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +RANLIB=$ac_cv_prog_RANLIB +if test -n "$RANLIB"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RANLIB" >&5 +$as_echo "$RANLIB" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_RANLIB"; then + ac_ct_RANLIB=$RANLIB + # Extract the first word of "ranlib", so it can be a program name with args. +set dummy ranlib; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_RANLIB+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_RANLIB"; then + ac_cv_prog_ac_ct_RANLIB="$ac_ct_RANLIB" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_ac_ct_RANLIB="ranlib" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_RANLIB=$ac_cv_prog_ac_ct_RANLIB +if test -n "$ac_ct_RANLIB"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_RANLIB" >&5 +$as_echo "$ac_ct_RANLIB" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_RANLIB" = x; then + RANLIB=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + RANLIB=$ac_ct_RANLIB + fi +else + RANLIB="$ac_cv_prog_RANLIB" +fi + + + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5 +$as_echo_n "checking how to run the C preprocessor... " >&6; } +# On Suns, sometimes $CPP names a directory. +if test -n "$CPP" && test -d "$CPP"; then + CPP= +fi +if test -z "$CPP"; then + if ${ac_cv_prog_CPP+:} false; then : + $as_echo_n "(cached) " >&6 +else + # Double quotes because CPP needs to be expanded + for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp" + do + ac_preproc_ok=false +for ac_c_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer to if __STDC__ is defined, since + # exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include +#else +# include +#endif + Syntax error +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : + break +fi + + done + ac_cv_prog_CPP=$CPP + +fi + CPP=$ac_cv_prog_CPP +else + ac_cv_prog_CPP=$CPP +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5 +$as_echo "$CPP" >&6; } +ac_preproc_ok=false +for ac_c_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer to if __STDC__ is defined, since + # exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include +#else +# include +#endif + Syntax error +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : + +else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "C preprocessor \"$CPP\" fails sanity check +See \`config.log' for more details" "$LINENO" 5; } +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5 +$as_echo_n "checking for grep that handles long lines and -e... " >&6; } +if ${ac_cv_path_GREP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -z "$GREP"; then + ac_path_GREP_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in grep ggrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext" + { test -f "$ac_path_GREP" && $as_test_x "$ac_path_GREP"; } || continue +# Check for GNU ac_path_GREP and select it if it is found. + # Check for GNU $ac_path_GREP +case `"$ac_path_GREP" --version 2>&1` in +*GNU*) + ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo 'GREP' >> "conftest.nl" + "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_GREP_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_GREP="$ac_path_GREP" + ac_path_GREP_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_GREP_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_GREP"; then + as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + fi +else + ac_cv_path_GREP=$GREP +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5 +$as_echo "$ac_cv_path_GREP" >&6; } + GREP="$ac_cv_path_GREP" + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5 +$as_echo_n "checking for egrep... " >&6; } +if ${ac_cv_path_EGREP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if echo a | $GREP -E '(a|b)' >/dev/null 2>&1 + then ac_cv_path_EGREP="$GREP -E" + else + if test -z "$EGREP"; then + ac_path_EGREP_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in egrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext" + { test -f "$ac_path_EGREP" && $as_test_x "$ac_path_EGREP"; } || continue +# Check for GNU ac_path_EGREP and select it if it is found. + # Check for GNU $ac_path_EGREP +case `"$ac_path_EGREP" --version 2>&1` in +*GNU*) + ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo 'EGREP' >> "conftest.nl" + "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_EGREP_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_EGREP="$ac_path_EGREP" + ac_path_EGREP_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_EGREP_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_EGREP"; then + as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + fi +else + ac_cv_path_EGREP=$EGREP +fi + + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5 +$as_echo "$ac_cv_path_EGREP" >&6; } + EGREP="$ac_cv_path_EGREP" + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5 +$as_echo_n "checking for ANSI C header files... " >&6; } +if ${ac_cv_header_stdc+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +#include +#include + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_header_stdc=yes +else + ac_cv_header_stdc=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + +if test $ac_cv_header_stdc = yes; then + # SunOS 4.x string.h does not declare mem*, contrary to ANSI. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "memchr" >/dev/null 2>&1; then : + +else + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "free" >/dev/null 2>&1; then : + +else + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi. + if test "$cross_compiling" = yes; then : + : +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +#if ((' ' & 0x0FF) == 0x020) +# define ISLOWER(c) ('a' <= (c) && (c) <= 'z') +# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c)) +#else +# define ISLOWER(c) \ + (('a' <= (c) && (c) <= 'i') \ + || ('j' <= (c) && (c) <= 'r') \ + || ('s' <= (c) && (c) <= 'z')) +# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c)) +#endif + +#define XOR(e, f) (((e) && !(f)) || (!(e) && (f))) +int +main () +{ + int i; + for (i = 0; i < 256; i++) + if (XOR (islower (i), ISLOWER (i)) + || toupper (i) != TOUPPER (i)) + return 2; + return 0; +} +_ACEOF +if ac_fn_c_try_run "$LINENO"; then : + +else + ac_cv_header_stdc=no +fi +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ + conftest.$ac_objext conftest.beam conftest.$ac_ext +fi + +fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5 +$as_echo "$ac_cv_header_stdc" >&6; } +if test $ac_cv_header_stdc = yes; then + +$as_echo "#define STDC_HEADERS 1" >>confdefs.h + +fi + +# On IRIX 5.3, sys/types and inttypes.h are conflicting. +for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \ + inttypes.h stdint.h unistd.h +do : + as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` +ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default +" +if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 +_ACEOF + +fi + +done + + +for ac_header in stdlib.h string.h math.h limits.h +do : + as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` +ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default" +if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 +_ACEOF + ANSI_HEADER=yes +else + ANSI_HEADER=no +fi + +done + +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ +void d( int , double) + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + PROTO=yes +else + PROTO=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +if test $ANSI_HEADER = no -o $PROTO = no; then + echo " *********** WARNING: CFITSIO CONFIGURE FAILURE ************ " + echo "cfitsio: ANSI C environment NOT found. Aborting cfitsio configure." + if test $ANSI_HEADER = no; then + echo "cfitsio: You're missing a needed ANSI header file." + fi + if test $PROTO = no; then + echo "cfitsio: Your compiler can't do ANSI function prototypes." + fi + echo "cfitsio: You need an ANSI C compiler and all ANSI trappings" + echo "cfitsio: to build cfitsio. " + echo " ******************************************************* " + exit 0; +fi + +if test "x$SSE_FLAGS" != x; then + SAVE_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $SSE_FLAGS" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts $SSE_FLAGS" >&5 +$as_echo_n "checking whether $CC accepts $SSE_FLAGS... " >&6; } + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + c_has_option=yes +else + c_has_option=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $c_has_option" >&5 +$as_echo "$c_has_option" >&6; } + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + if test "$c_has_option" = no; then SSE_FLAGS=""; fi + CFLAGS="$SAVE_CFLAGS" +fi + + +CFLAGS="$CFLAGS" +LIBPRE="" + +case $system in + Darwin-*) + # Build for i386 & x86_64 architectures on Darwin 10.x or newer: + + case $system in + Darwin-[56789]*) + ;; + *) + + # Test to see whether the C compiler accepts the "-arch" + # flags for building "universal" binaries (Apple XCode only): + SAVE_CFLAGS="$CFLAGS" + C_UNIV_SWITCH="-arch i386 -arch x86_64" + CFLAGS="$CFLAGS $C_UNIV_SWITCH" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts $C_UNIV_SWITCH" >&5 +$as_echo_n "checking whether $CC accepts $C_UNIV_SWITCH... " >&6; } + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + c_has_option=yes +else + c_has_option=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $c_has_option" >&5 +$as_echo "$c_has_option" >&6; } + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + # Value of C_UNIV_SWITCH will be needed later for SHLIB_LD: + if test "$c_has_option" = no; then C_UNIV_SWITCH=""; fi + CFLAGS="$SAVE_CFLAGS $C_UNIV_SWITCH" + ;; + esac + # Darwin can be powerpc, i386, or x86_64 + ARCH=`uname -p` + EXT="darwin" + # For large file support (but may break Absoft compilers): + $as_echo "#define _LARGEFILE_SOURCE 1" >>confdefs.h + + $as_echo "#define _FILE_OFFSET_BITS 64" >>confdefs.h + + ;; + SunOS-4*) + ARCH="sun" + EXT="sun" + ;; + HP-UX-*) + ARCH="hp" + EXT="hpu" + if test "x$CFORTRANFLAGS" = x ; then + CFORTRANFLAGS="-Dappendus" + fi + CFLAGS="$CFLAGS -DPG_PPU" + LIBPRE="-Wl," + ;; + SunOS-5*) + ARCH="solaris" + EXT="sol" + if test "x$CFORTRANFLAGS" = x ; then + CFORTRANFLAGS="-Dsolaris" + fi + # We need libm on Solaris: + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for frexp in -lm" >&5 +$as_echo_n "checking for frexp in -lm... " >&6; } +if ${ac_cv_lib_m_frexp+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lm $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char frexp (); +int +main () +{ +return frexp (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_m_frexp=yes +else + ac_cv_lib_m_frexp=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_m_frexp" >&5 +$as_echo "$ac_cv_lib_m_frexp" >&6; } +if test "x$ac_cv_lib_m_frexp" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBM 1 +_ACEOF + + LIBS="-lm $LIBS" + +fi + + # For large file support: + $as_echo "#define _LARGEFILE_SOURCE 1" >>confdefs.h + + $as_echo "#define _FILE_OFFSET_BITS 64" >>confdefs.h + + ;; + OSF1*) + ARCH="alpha" + EXT="osf" + ;; + IRIX*) + ARCH="sgi" + EXT="sgi" + CFLAGS="$CFLAGS -DHAVE_POSIX_SIGNALS" + RANLIB="touch" + ;; + ULTRIX*) + ARCH="dec" + EXT="dec" + ;; + Linux*) + ARCH="linux" + EXT="lnx" + # For large file support: + $as_echo "#define _LARGEFILE_SOURCE 1" >>confdefs.h + + $as_echo "#define _FILE_OFFSET_BITS 64" >>confdefs.h + + ;; + FREEBSD*|FreeBSD*) + ARCH="linux" + EXT="lnx" + ;; + CYGWIN*) + ARCH="cygwin" + EXT="cygwin" + CFLAGS="$CFLAGS -DHAVE_POSIX_SIGNALS" + ;; + *) + echo "cfitsio: == Don't know what do do with $system" + ;; +esac + +CFLAGS="$CFLAGS $CFORTRANFLAGS" + +case $GCC in + yes) + GCCVERSION="`$CC -dumpversion 2>&1`" + echo "cfitsio: == Using gcc version $GCCVERSION" + + + gcc_test=`echo $GCCVERSION | grep -c '2\.[45678]'` + + if test $gcc_test -gt 0 + then + + CFLAGS=`echo $CFLAGS | sed 's:-O[^ ]* *::'` + + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: This gcc is pretty old. Disabling optimization to be safe." >&5 +$as_echo "$as_me: WARNING: This gcc is pretty old. Disabling optimization to be safe." >&2;} + fi + ;; + no) + echo "cfitsio: Old CFLAGS is $CFLAGS" + CFLAGS=`echo $CFLAGS | sed -e "s/-g/-O/"` + case $system in + SunOS-5*) + + if test `echo $CFLAGS | grep -c fast` -gt 0 + then + echo "cfitsio: Replacing -fast with -O3" + CFLAGS=`echo $CFLAGS | sed 's:-fast:-O3:'` + fi + + CFLAGS="$CFLAGS -DHAVE_ALLOCA_H -DHAVE_POSIX_SIGNALS" + ;; + *) + echo "== No special changes for $system" + ;; + esac + echo "New CFLAGS is $CFLAGS" + ;; + *) + # Don't do anything now + ;; +esac + +# Shared library section +#------------------------------------------------------------------------------- +SHLIB_LD=: +SHLIB_SUFFIX=".so" +lhea_shlib_cflags= +case $EXT in + cygwin) + SHLIB_LD="$CC -shared" + SHLIB_SUFFIX=".dll" + ;; + darwin) + + case $system in + Darwin-[56789]*) + SHLIB_LD="$CC -dynamiclib" + ;; + *) + # Build for i386 & x86_64 architectures on Darwin 10.x or newer: + SHLIB_LD="$CC -dynamiclib $C_UNIV_SWITCH" + ;; + esac + + SHLIB_SUFFIX=".dylib" + lhea_shlib_cflags="-fPIC -fno-common" + ;; + hpu) + SHLIB_LD="ld -b" + SHLIB_SUFFIX=".sl" + ;; + lnx) + SHLIB_LD=":" + ;; + osf) + SHLIB_LD="ld -shared -expect_unresolved '*'" + LD_FLAGS="-taso" + ;; + sol) + SHLIB_LD="/usr/ccs/bin/ld -G" + lhea_shlib_cflags="-KPIC" + ;; + sgi) + SHLIB_LD="ld -shared -rdata_shared" + ;; + *) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Unable to determine how to make a shared library" >&5 +$as_echo "$as_me: WARNING: Unable to determine how to make a shared library" >&2;} + ;; +esac +# Darwin uses gcc (=cc), but needs different flags (see above) +# if test "x$GCC" = xyes; then +if test "x$GCC" = xyes && test "x$EXT" != xdarwin && test "x$EXT" != xcygwin; then + SHLIB_LD="$CC -shared" + lhea_shlib_cflags='-fPIC' +fi +if test "x$lhea_shlib_cflags" != x; then + CFLAGS="$CFLAGS $lhea_shlib_cflags" +fi + + + +# ================= test for the unix ftruncate function ================ + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking \"whether ftruncate works\"" >&5 +$as_echo_n "checking \"whether ftruncate works\"... " >&6; } +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +int +main () +{ + +ftruncate(0, 0); + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + +$as_echo "#define HAVE_FTRUNCATE 1" >>confdefs.h + +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: \"yes\"" >&5 +$as_echo "\"yes\"" >&6; } + +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: \"no\"" >&5 +$as_echo "\"no\"" >&6; } +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + +# --------------------------------------------------------- +# some systems define long long for 64-bit ints +# --------------------------------------------------------- + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking \"whether long long is defined\"" >&5 +$as_echo_n "checking \"whether long long is defined\"... " >&6; } +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +int +main () +{ + +long long filler; + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + +$as_echo "#define HAVE_LONGLONG 1" >>confdefs.h + +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: \"yes\"" >&5 +$as_echo "\"yes\"" >&6; } + +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: \"no\"" >&5 +$as_echo "\"no\"" >&6; } +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + +# ==================== SHARED MEMORY DRIVER SECTION ======================= +# +# 09-Mar-98 : modified by JB/ISDC +# 3 checks added to support autoconfiguration of shared memory +# driver. First generic check is made whether shared memory is supported +# at all, then 2 more specific checks are made (architecture dependent). +# Currently tested on : sparc-solaris, intel-linux, sgi-irix, dec-alpha-osf + +# ------------------------------------------------------------------------- +# check is System V IPC is supported on this machine +# ------------------------------------------------------------------------- + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking \"whether system V style IPC services are supported\"" >&5 +$as_echo_n "checking \"whether system V style IPC services are supported\"... " >&6; } +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +#include + +int +main () +{ + +shmat(0, 0, 0); +shmdt(0); +shmget(0, 0, 0); +semget(0, 0, 0); + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + +$as_echo "#define HAVE_SHMEM_SERVICES 1" >>confdefs.h + +my_shmem=\${SOURCES_SHMEM} +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: \"yes\"" >&5 +$as_echo "\"yes\"" >&6; } + +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: \"no\"" >&5 +$as_echo "\"no\"" >&6; } +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + + + +# ------------------------------------------------------------------------- +# some systems define flock_t, for others we have to define it ourselves +# ------------------------------------------------------------------------- + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking \"do we have flock_t defined in sys/fcntl.h\"" >&5 +$as_echo_n "checking \"do we have flock_t defined in sys/fcntl.h\"... " >&6; } +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +int +main () +{ + +flock_t filler; + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + +$as_echo "#define HAVE_FLOCK_T 1" >>confdefs.h + +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: \"yes\"" >&5 +$as_echo "\"yes\"" >&6; } + +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: \"no\"" >&5 +$as_echo "\"no\"" >&6; } +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + +if test "$HAVE_FLOCK_T" != 1; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking \"do we have flock_t defined in sys/flock.h\"" >&5 +$as_echo_n "checking \"do we have flock_t defined in sys/flock.h\"... " >&6; } + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +int +main () +{ + + flock_t filler; + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + + $as_echo "#define HAVE_FLOCK_T 1" >>confdefs.h + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: \"yes\"" >&5 +$as_echo "\"yes\"" >&6; } + +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: \"no\"" >&5 +$as_echo "\"no\"" >&6; } +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi + +# ------------------------------------------------------------------------- +# there are some idiosyncrasies with semun defs (used in semxxx). Solaris +# does not define it at all +# ------------------------------------------------------------------------- + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking \"do we have union semun defined\"" >&5 +$as_echo_n "checking \"do we have union semun defined\"... " >&6; } +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +#include + +int +main () +{ + +union semun filler; + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + +$as_echo "#define HAVE_UNION_SEMUN 1" >>confdefs.h + +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: \"yes\"" >&5 +$as_echo "\"yes\"" >&6; } + +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: \"no\"" >&5 +$as_echo "\"no\"" >&6; } +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + +# ==================== END OF SHARED MEMORY DRIVER SECTION ================ +# ================= test for the unix networking functions ================ + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing gethostbyname" >&5 +$as_echo_n "checking for library containing gethostbyname... " >&6; } +if ${ac_cv_search_gethostbyname+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_func_search_save_LIBS=$LIBS +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char gethostbyname (); +int +main () +{ +return gethostbyname (); + ; + return 0; +} +_ACEOF +for ac_lib in '' nsl; do + if test -z "$ac_lib"; then + ac_res="none required" + else + ac_res=-l$ac_lib + LIBS="-l$ac_lib $ac_func_search_save_LIBS" + fi + if ac_fn_c_try_link "$LINENO"; then : + ac_cv_search_gethostbyname=$ac_res +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext + if ${ac_cv_search_gethostbyname+:} false; then : + break +fi +done +if ${ac_cv_search_gethostbyname+:} false; then : + +else + ac_cv_search_gethostbyname=no +fi +rm conftest.$ac_ext +LIBS=$ac_func_search_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_gethostbyname" >&5 +$as_echo "$ac_cv_search_gethostbyname" >&6; } +ac_res=$ac_cv_search_gethostbyname +if test "$ac_res" != no; then : + test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" + cfitsio_have_nsl=1 +else + cfitsio_have_nsl=0 +fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing connect" >&5 +$as_echo_n "checking for library containing connect... " >&6; } +if ${ac_cv_search_connect+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_func_search_save_LIBS=$LIBS +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char connect (); +int +main () +{ +return connect (); + ; + return 0; +} +_ACEOF +for ac_lib in '' socket; do + if test -z "$ac_lib"; then + ac_res="none required" + else + ac_res=-l$ac_lib + LIBS="-l$ac_lib -lnsl $ac_func_search_save_LIBS" + fi + if ac_fn_c_try_link "$LINENO"; then : + ac_cv_search_connect=$ac_res +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext + if ${ac_cv_search_connect+:} false; then : + break +fi +done +if ${ac_cv_search_connect+:} false; then : + +else + ac_cv_search_connect=no +fi +rm conftest.$ac_ext +LIBS=$ac_func_search_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_connect" >&5 +$as_echo "$ac_cv_search_connect" >&6; } +ac_res=$ac_cv_search_connect +if test "$ac_res" != no; then : + test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" + cfitsio_have_socket=1 +else + cfitsio_have_socket=0 +fi + + +if test "$cfitsio_have_nsl" = 1 -a "$cfitsio_have_socket" = 1; then + $as_echo "#define HAVE_NET_SERVICES 1" >>confdefs.h + +fi + +# ==================== END OF unix networking SECTION ================ + +# ------------------------------------------------------------------------------ +# Define _REENTRANT & add -lpthread to LIBS if reentrant multithreading enabled: +# ------------------------------------------------------------------------------ +if test "x$BUILD_REENTRANT" = xyes; then + $as_echo "#define _REENTRANT 1" >>confdefs.h + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for main in -lpthread" >&5 +$as_echo_n "checking for main in -lpthread... " >&6; } +if ${ac_cv_lib_pthread_main+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lpthread $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + +int +main () +{ +return main (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_pthread_main=yes +else + ac_cv_lib_pthread_main=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pthread_main" >&5 +$as_echo "$ac_cv_lib_pthread_main" >&6; } +if test "x$ac_cv_lib_pthread_main" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBPTHREAD 1 +_ACEOF + + LIBS="-lpthread $LIBS" + +else + as_fn_error $? "Unable to locate pthread library needed when enabling reentrant multithreading" "$LINENO" 5 +fi + +fi +# ------------------------------------------------------------------------------ + +ac_config_files="$ac_config_files Makefile" + +cat >confcache <<\_ACEOF +# This file is a shell script that caches the results of configure +# tests run on this system so they can be shared between configure +# scripts and configure runs, see configure's option --config-cache. +# It is not useful on other systems. If it contains results you don't +# want to keep, you may remove or edit it. +# +# config.status only pays attention to the cache file if you give it +# the --recheck option to rerun configure. +# +# `ac_cv_env_foo' variables (set or unset) will be overridden when +# loading this file, other *unset* `ac_cv_foo' will be assigned the +# following values. + +_ACEOF + +# The following way of writing the cache mishandles newlines in values, +# but we know of no workaround that is simple, portable, and efficient. +# So, we kill variables containing newlines. +# Ultrix sh set writes to stderr and can't be redirected directly, +# and sets the high bit in the cache file unless we assign to the vars. +( + for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do + eval ac_val=\$$ac_var + case $ac_val in #( + *${as_nl}*) + case $ac_var in #( + *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + esac + case $ac_var in #( + _ | IFS | as_nl) ;; #( + BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( + *) { eval $ac_var=; unset $ac_var;} ;; + esac ;; + esac + done + + (set) 2>&1 | + case $as_nl`(ac_space=' '; set) 2>&1` in #( + *${as_nl}ac_space=\ *) + # `set' does not quote correctly, so add quotes: double-quote + # substitution turns \\\\ into \\, and sed turns \\ into \. + sed -n \ + "s/'/'\\\\''/g; + s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" + ;; #( + *) + # `set' quotes correctly as required by POSIX, so do not add quotes. + sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" + ;; + esac | + sort +) | + sed ' + /^ac_cv_env_/b end + t clear + :clear + s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/ + t end + s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ + :end' >>confcache +if diff "$cache_file" confcache >/dev/null 2>&1; then :; else + if test -w "$cache_file"; then + if test "x$cache_file" != "x/dev/null"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 +$as_echo "$as_me: updating cache $cache_file" >&6;} + if test ! -f "$cache_file" || test -h "$cache_file"; then + cat confcache >"$cache_file" + else + case $cache_file in #( + */* | ?:*) + mv -f confcache "$cache_file"$$ && + mv -f "$cache_file"$$ "$cache_file" ;; #( + *) + mv -f confcache "$cache_file" ;; + esac + fi + fi + else + { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 +$as_echo "$as_me: not updating unwritable cache $cache_file" >&6;} + fi +fi +rm -f confcache + +test "x$prefix" = xNONE && prefix=$ac_default_prefix +# Let make expand exec_prefix. +test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' + +# Transform confdefs.h into DEFS. +# Protect against shell expansion while executing Makefile rules. +# Protect against Makefile macro expansion. +# +# If the first sed substitution is executed (which looks for macros that +# take arguments), then branch to the quote section. Otherwise, +# look for a macro that doesn't take arguments. +ac_script=' +:mline +/\\$/{ + N + s,\\\n,, + b mline +} +t clear +:clear +s/^[ ]*#[ ]*define[ ][ ]*\([^ (][^ (]*([^)]*)\)[ ]*\(.*\)/-D\1=\2/g +t quote +s/^[ ]*#[ ]*define[ ][ ]*\([^ ][^ ]*\)[ ]*\(.*\)/-D\1=\2/g +t quote +b any +:quote +s/[ `~#$^&*(){}\\|;'\''"<>?]/\\&/g +s/\[/\\&/g +s/\]/\\&/g +s/\$/$$/g +H +:any +${ + g + s/^\n// + s/\n/ /g + p +} +' +DEFS=`sed -n "$ac_script" confdefs.h` + + +ac_libobjs= +ac_ltlibobjs= +U= +for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue + # 1. Remove the extension, and $U if already installed. + ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' + ac_i=`$as_echo "$ac_i" | sed "$ac_script"` + # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR + # will be set to the directory where LIBOBJS objects are built. + as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" + as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo' +done +LIBOBJS=$ac_libobjs + +LTLIBOBJS=$ac_ltlibobjs + + + +: "${CONFIG_STATUS=./config.status}" +ac_write_fail=0 +ac_clean_files_save=$ac_clean_files +ac_clean_files="$ac_clean_files $CONFIG_STATUS" +{ $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 +$as_echo "$as_me: creating $CONFIG_STATUS" >&6;} +as_write_fail=0 +cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 +#! $SHELL +# Generated by $as_me. +# Run this file to recreate the current configuration. +# Compiler output produced by configure, useful for debugging +# configure, is in config.log if it exists. + +debug=false +ac_cs_recheck=false +ac_cs_silent=false + +SHELL=\${CONFIG_SHELL-$SHELL} +export SHELL +_ASEOF +cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 +## -------------------- ## +## M4sh Initialization. ## +## -------------------- ## + +# Be more Bourne compatible +DUALCASE=1; export DUALCASE # for MKS sh +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi + + +as_nl=' +' +export as_nl +# Printing a long string crashes Solaris 7 /usr/bin/printf. +as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo +# Prefer a ksh shell builtin over an external printf program on Solaris, +# but without wasting forks for bash or zsh. +if test -z "$BASH_VERSION$ZSH_VERSION" \ + && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='print -r --' + as_echo_n='print -rn --' +elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='printf %s\n' + as_echo_n='printf %s' +else + if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then + as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' + as_echo_n='/usr/ucb/echo -n' + else + as_echo_body='eval expr "X$1" : "X\\(.*\\)"' + as_echo_n_body='eval + arg=$1; + case $arg in #( + *"$as_nl"*) + expr "X$arg" : "X\\(.*\\)$as_nl"; + arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; + esac; + expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" + ' + export as_echo_n_body + as_echo_n='sh -c $as_echo_n_body as_echo' + fi + export as_echo_body + as_echo='sh -c $as_echo_body as_echo' +fi + +# The user is always right. +if test "${PATH_SEPARATOR+set}" != set; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + +# IFS +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent editors from complaining about space-tab. +# (If _AS_PATH_WALK were called with IFS unset, it would disable word +# splitting by setting IFS to empty value.) +IFS=" "" $as_nl" + +# Find who we are. Look in the path if we contain no directory separator. +as_myself= +case $0 in #(( + *[\\/]* ) as_myself=$0 ;; + *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break + done +IFS=$as_save_IFS + + ;; +esac +# We did not find ourselves, most probably we were run as `sh COMMAND' +# in which case we are not to be found in the path. +if test "x$as_myself" = x; then + as_myself=$0 +fi +if test ! -f "$as_myself"; then + $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + exit 1 +fi + +# Unset variables that we do not need and which cause bugs (e.g. in +# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" +# suppresses any "Segmentation fault" message there. '((' could +# trigger a bug in pdksh 5.2.14. +for as_var in BASH_ENV ENV MAIL MAILPATH +do eval test x\${$as_var+set} = xset \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done +PS1='$ ' +PS2='> ' +PS4='+ ' + +# NLS nuisances. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# CDPATH. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + + +# as_fn_error STATUS ERROR [LINENO LOG_FD] +# ---------------------------------------- +# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are +# provided, also output the error to LOG_FD, referencing LINENO. Then exit the +# script with STATUS, using 1 if that was 0. +as_fn_error () +{ + as_status=$1; test $as_status -eq 0 && as_status=1 + if test "$4"; then + as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + fi + $as_echo "$as_me: error: $2" >&2 + as_fn_exit $as_status +} # as_fn_error + + +# as_fn_set_status STATUS +# ----------------------- +# Set $? to STATUS, without forking. +as_fn_set_status () +{ + return $1 +} # as_fn_set_status + +# as_fn_exit STATUS +# ----------------- +# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. +as_fn_exit () +{ + set +e + as_fn_set_status $1 + exit $1 +} # as_fn_exit + +# as_fn_unset VAR +# --------------- +# Portably unset VAR. +as_fn_unset () +{ + { eval $1=; unset $1;} +} +as_unset=as_fn_unset +# as_fn_append VAR VALUE +# ---------------------- +# Append the text in VALUE to the end of the definition contained in VAR. Take +# advantage of any shell optimizations that allow amortized linear growth over +# repeated appends, instead of the typical quadratic growth present in naive +# implementations. +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : + eval 'as_fn_append () + { + eval $1+=\$2 + }' +else + as_fn_append () + { + eval $1=\$$1\$2 + } +fi # as_fn_append + +# as_fn_arith ARG... +# ------------------ +# Perform arithmetic evaluation on the ARGs, and store the result in the +# global $as_val. Take advantage of shells that can avoid forks. The arguments +# must be portable across $(()) and expr. +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : + eval 'as_fn_arith () + { + as_val=$(( $* )) + }' +else + as_fn_arith () + { + as_val=`expr "$@" || test $? -eq 1` + } +fi # as_fn_arith + + +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then + as_basename=basename +else + as_basename=false +fi + +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname +else + as_dirname=false +fi + +as_me=`$as_basename -- "$0" || +$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ + X"$0" : 'X\(//\)$' \| \ + X"$0" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X/"$0" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits + +ECHO_C= ECHO_N= ECHO_T= +case `echo -n x` in #((((( +-n*) + case `echo 'xy\c'` in + *c*) ECHO_T=' ';; # ECHO_T is single tab character. + xy) ECHO_C='\c';; + *) echo `echo ksh88 bug on AIX 6.1` > /dev/null + ECHO_T=' ';; + esac;; +*) + ECHO_N='-n';; +esac + +rm -f conf$$ conf$$.exe conf$$.file +if test -d conf$$.dir; then + rm -f conf$$.dir/conf$$.file +else + rm -f conf$$.dir + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. + # In both cases, we have to default to `cp -p'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -p' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else + as_ln_s='cp -p' + fi +else + as_ln_s='cp -p' +fi +rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file +rmdir conf$$.dir 2>/dev/null + + +# as_fn_mkdir_p +# ------------- +# Create "$as_dir" as a directory, including parents if necessary. +as_fn_mkdir_p () +{ + + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || eval $as_mkdir_p || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" + + +} # as_fn_mkdir_p +if mkdir -p . 2>/dev/null; then + as_mkdir_p='mkdir -p "$as_dir"' +else + test -d ./-p && rmdir ./-p + as_mkdir_p=false +fi + +if test -x / >/dev/null 2>&1; then + as_test_x='test -x' +else + if ls -dL / >/dev/null 2>&1; then + as_ls_L_option=L + else + as_ls_L_option= + fi + as_test_x=' + eval sh -c '\'' + if test -d "$1"; then + test -d "$1/."; + else + case $1 in #( + -*)set "./$1";; + esac; + case `ls -ld'$as_ls_L_option' "$1" 2>/dev/null` in #(( + ???[sx]*):;;*)false;;esac;fi + '\'' sh + ' +fi +as_executable_p=$as_test_x + +# Sed expression to map a string onto a valid CPP name. +as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" + +# Sed expression to map a string onto a valid variable name. +as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" + + +exec 6>&1 +## ----------------------------------- ## +## Main body of $CONFIG_STATUS script. ## +## ----------------------------------- ## +_ASEOF +test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1 + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# Save the log message, to keep $0 and so on meaningful, and to +# report actual input values of CONFIG_FILES etc. instead of their +# values after options handling. +ac_log=" +This file was extended by $as_me, which was +generated by GNU Autoconf 2.68. Invocation command line was + + CONFIG_FILES = $CONFIG_FILES + CONFIG_HEADERS = $CONFIG_HEADERS + CONFIG_LINKS = $CONFIG_LINKS + CONFIG_COMMANDS = $CONFIG_COMMANDS + $ $0 $@ + +on `(hostname || uname -n) 2>/dev/null | sed 1q` +" + +_ACEOF + +case $ac_config_files in *" +"*) set x $ac_config_files; shift; ac_config_files=$*;; +esac + + + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +# Files that config.status was made for. +config_files="$ac_config_files" + +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +ac_cs_usage="\ +\`$as_me' instantiates files and other configuration actions +from templates according to the current configuration. Unless the files +and actions are specified as TAGs, all are instantiated by default. + +Usage: $0 [OPTION]... [TAG]... + + -h, --help print this help, then exit + -V, --version print version number and configuration settings, then exit + --config print configuration, then exit + -q, --quiet, --silent + do not print progress messages + -d, --debug don't remove temporary files + --recheck update $as_me by reconfiguring in the same conditions + --file=FILE[:TEMPLATE] + instantiate the configuration file FILE + +Configuration files: +$config_files + +Report bugs to the package provider." + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" +ac_cs_version="\\ +config.status +configured by $0, generated by GNU Autoconf 2.68, + with options \\"\$ac_cs_config\\" + +Copyright (C) 2010 Free Software Foundation, Inc. +This config.status script is free software; the Free Software Foundation +gives unlimited permission to copy, distribute and modify it." + +ac_pwd='$ac_pwd' +srcdir='$srcdir' +test -n "\$AWK" || AWK=awk +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# The default lists apply if the user does not specify any file. +ac_need_defaults=: +while test $# != 0 +do + case $1 in + --*=?*) + ac_option=`expr "X$1" : 'X\([^=]*\)='` + ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` + ac_shift=: + ;; + --*=) + ac_option=`expr "X$1" : 'X\([^=]*\)='` + ac_optarg= + ac_shift=: + ;; + *) + ac_option=$1 + ac_optarg=$2 + ac_shift=shift + ;; + esac + + case $ac_option in + # Handling of the options. + -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) + ac_cs_recheck=: ;; + --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) + $as_echo "$ac_cs_version"; exit ;; + --config | --confi | --conf | --con | --co | --c ) + $as_echo "$ac_cs_config"; exit ;; + --debug | --debu | --deb | --de | --d | -d ) + debug=: ;; + --file | --fil | --fi | --f ) + $ac_shift + case $ac_optarg in + *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; + '') as_fn_error $? "missing file argument" ;; + esac + as_fn_append CONFIG_FILES " '$ac_optarg'" + ac_need_defaults=false;; + --he | --h | --help | --hel | -h ) + $as_echo "$ac_cs_usage"; exit ;; + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil | --si | --s) + ac_cs_silent=: ;; + + # This is an error. + -*) as_fn_error $? "unrecognized option: \`$1' +Try \`$0 --help' for more information." ;; + + *) as_fn_append ac_config_targets " $1" + ac_need_defaults=false ;; + + esac + shift +done + +ac_configure_extra_args= + +if $ac_cs_silent; then + exec 6>/dev/null + ac_configure_extra_args="$ac_configure_extra_args --silent" +fi + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +if \$ac_cs_recheck; then + set X '$SHELL' '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion + shift + \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6 + CONFIG_SHELL='$SHELL' + export CONFIG_SHELL + exec "\$@" +fi + +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +exec 5>>config.log +{ + echo + sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX +## Running $as_me. ## +_ASBOX + $as_echo "$ac_log" +} >&5 + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 + +# Handling of arguments. +for ac_config_target in $ac_config_targets +do + case $ac_config_target in + "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; + + *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; + esac +done + + +# If the user did not use the arguments to specify the items to instantiate, +# then the envvar interface is used. Set only those that are not. +# We use the long form for the default assignment because of an extremely +# bizarre bug on SunOS 4.1.3. +if $ac_need_defaults; then + test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files +fi + +# Have a temporary directory for convenience. Make it in the build tree +# simply because there is no reason against having it here, and in addition, +# creating and moving files from /tmp can sometimes cause problems. +# Hook for its removal unless debugging. +# Note that there is a small window in which the directory will not be cleaned: +# after its creation but before its name has been assigned to `$tmp'. +$debug || +{ + tmp= ac_tmp= + trap 'exit_status=$? + : "${ac_tmp:=$tmp}" + { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status +' 0 + trap 'as_fn_exit 1' 1 2 13 15 +} +# Create a (secure) tmp directory for tmp files. + +{ + tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && + test -d "$tmp" +} || +{ + tmp=./conf$$-$RANDOM + (umask 077 && mkdir "$tmp") +} || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5 +ac_tmp=$tmp + +# Set up the scripts for CONFIG_FILES section. +# No need to generate them if there are no CONFIG_FILES. +# This happens for instance with `./config.status config.h'. +if test -n "$CONFIG_FILES"; then + + +ac_cr=`echo X | tr X '\015'` +# On cygwin, bash can eat \r inside `` if the user requested igncr. +# But we know of no other shell where ac_cr would be empty at this +# point, so we can use a bashism as a fallback. +if test "x$ac_cr" = x; then + eval ac_cr=\$\'\\r\' +fi +ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' /dev/null` +if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then + ac_cs_awk_cr='\\r' +else + ac_cs_awk_cr=$ac_cr +fi + +echo 'BEGIN {' >"$ac_tmp/subs1.awk" && +_ACEOF + + +{ + echo "cat >conf$$subs.awk <<_ACEOF" && + echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' && + echo "_ACEOF" +} >conf$$subs.sh || + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 +ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'` +ac_delim='%!_!# ' +for ac_last_try in false false false false false :; do + . ./conf$$subs.sh || + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 + + ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X` + if test $ac_delim_n = $ac_delim_num; then + break + elif $ac_last_try; then + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 + else + ac_delim="$ac_delim!$ac_delim _$ac_delim!! " + fi +done +rm -f conf$$subs.sh + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK && +_ACEOF +sed -n ' +h +s/^/S["/; s/!.*/"]=/ +p +g +s/^[^!]*!// +:repl +t repl +s/'"$ac_delim"'$// +t delim +:nl +h +s/\(.\{148\}\)..*/\1/ +t more1 +s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/ +p +n +b repl +:more1 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t nl +:delim +h +s/\(.\{148\}\)..*/\1/ +t more2 +s/["\\]/\\&/g; s/^/"/; s/$/"/ +p +b +:more2 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t delim +' >$CONFIG_STATUS || ac_write_fail=1 +rm -f conf$$subs.awk +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +_ACAWK +cat >>"\$ac_tmp/subs1.awk" <<_ACAWK && + for (key in S) S_is_set[key] = 1 + FS = "" + +} +{ + line = $ 0 + nfields = split(line, field, "@") + substed = 0 + len = length(field[1]) + for (i = 2; i < nfields; i++) { + key = field[i] + keylen = length(key) + if (S_is_set[key]) { + value = S[key] + line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3) + len += length(value) + length(field[++i]) + substed = 1 + } else + len += 1 + keylen + } + + print line +} + +_ACAWK +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then + sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" +else + cat +fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \ + || as_fn_error $? "could not setup config files machinery" "$LINENO" 5 +_ACEOF + +# VPATH may cause trouble with some makes, so we remove sole $(srcdir), +# ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and +# trailing colons and then remove the whole line if VPATH becomes empty +# (actually we leave an empty line to preserve line numbers). +if test "x$srcdir" = x.; then + ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{ +h +s/// +s/^/:/ +s/[ ]*$/:/ +s/:\$(srcdir):/:/g +s/:\${srcdir}:/:/g +s/:@srcdir@:/:/g +s/^:*// +s/:*$// +x +s/\(=[ ]*\).*/\1/ +G +s/\n// +s/^[^=]*=[ ]*$// +}' +fi + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +fi # test -n "$CONFIG_FILES" + + +eval set X " :F $CONFIG_FILES " +shift +for ac_tag +do + case $ac_tag in + :[FHLC]) ac_mode=$ac_tag; continue;; + esac + case $ac_mode$ac_tag in + :[FHL]*:*);; + :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;; + :[FH]-) ac_tag=-:-;; + :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; + esac + ac_save_IFS=$IFS + IFS=: + set x $ac_tag + IFS=$ac_save_IFS + shift + ac_file=$1 + shift + + case $ac_mode in + :L) ac_source=$1;; + :[FH]) + ac_file_inputs= + for ac_f + do + case $ac_f in + -) ac_f="$ac_tmp/stdin";; + *) # Look for the file first in the build tree, then in the source tree + # (if the path is not absolute). The absolute path cannot be DOS-style, + # because $ac_f cannot contain `:'. + test -f "$ac_f" || + case $ac_f in + [\\/$]*) false;; + *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; + esac || + as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;; + esac + case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac + as_fn_append ac_file_inputs " '$ac_f'" + done + + # Let's still pretend it is `configure' which instantiates (i.e., don't + # use $as_me), people would be surprised to read: + # /* config.h. Generated by config.status. */ + configure_input='Generated from '` + $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' + `' by configure.' + if test x"$ac_file" != x-; then + configure_input="$ac_file. $configure_input" + { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 +$as_echo "$as_me: creating $ac_file" >&6;} + fi + # Neutralize special characters interpreted by sed in replacement strings. + case $configure_input in #( + *\&* | *\|* | *\\* ) + ac_sed_conf_input=`$as_echo "$configure_input" | + sed 's/[\\\\&|]/\\\\&/g'`;; #( + *) ac_sed_conf_input=$configure_input;; + esac + + case $ac_tag in + *:-:* | *:-) cat >"$ac_tmp/stdin" \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; + esac + ;; + esac + + ac_dir=`$as_dirname -- "$ac_file" || +$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$ac_file" : 'X\(//\)[^/]' \| \ + X"$ac_file" : 'X\(//\)$' \| \ + X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$ac_file" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + as_dir="$ac_dir"; as_fn_mkdir_p + ac_builddir=. + +case "$ac_dir" in +.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; +*) + ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` + # A ".." for each directory in $ac_dir_suffix. + ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + case $ac_top_builddir_sub in + "") ac_top_builddir_sub=. ac_top_build_prefix= ;; + *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; + esac ;; +esac +ac_abs_top_builddir=$ac_pwd +ac_abs_builddir=$ac_pwd$ac_dir_suffix +# for backward compatibility: +ac_top_builddir=$ac_top_build_prefix + +case $srcdir in + .) # We are building in place. + ac_srcdir=. + ac_top_srcdir=$ac_top_builddir_sub + ac_abs_top_srcdir=$ac_pwd ;; + [\\/]* | ?:[\\/]* ) # Absolute name. + ac_srcdir=$srcdir$ac_dir_suffix; + ac_top_srcdir=$srcdir + ac_abs_top_srcdir=$srcdir ;; + *) # Relative name. + ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix + ac_top_srcdir=$ac_top_build_prefix$srcdir + ac_abs_top_srcdir=$ac_pwd/$srcdir ;; +esac +ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix + + + case $ac_mode in + :F) + # + # CONFIG_FILE + # + +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# If the template does not know about datarootdir, expand it. +# FIXME: This hack should be removed a few years after 2.60. +ac_datarootdir_hack=; ac_datarootdir_seen= +ac_sed_dataroot=' +/datarootdir/ { + p + q +} +/@datadir@/p +/@docdir@/p +/@infodir@/p +/@localedir@/p +/@mandir@/p' +case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in +*datarootdir*) ac_datarootdir_seen=yes;; +*@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 +$as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 + ac_datarootdir_hack=' + s&@datadir@&$datadir&g + s&@docdir@&$docdir&g + s&@infodir@&$infodir&g + s&@localedir@&$localedir&g + s&@mandir@&$mandir&g + s&\\\${datarootdir}&$datarootdir&g' ;; +esac +_ACEOF + +# Neutralize VPATH when `$srcdir' = `.'. +# Shell code in configure.ac might set extrasub. +# FIXME: do we really want to maintain this feature? +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +ac_sed_extra="$ac_vpsub +$extrasub +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +:t +/@[a-zA-Z_][a-zA-Z_0-9]*@/!b +s|@configure_input@|$ac_sed_conf_input|;t t +s&@top_builddir@&$ac_top_builddir_sub&;t t +s&@top_build_prefix@&$ac_top_build_prefix&;t t +s&@srcdir@&$ac_srcdir&;t t +s&@abs_srcdir@&$ac_abs_srcdir&;t t +s&@top_srcdir@&$ac_top_srcdir&;t t +s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t +s&@builddir@&$ac_builddir&;t t +s&@abs_builddir@&$ac_abs_builddir&;t t +s&@abs_top_builddir@&$ac_abs_top_builddir&;t t +$ac_datarootdir_hack +" +eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \ + >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + +test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && + { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && + { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ + "$ac_tmp/out"`; test -z "$ac_out"; } && + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' +which seems to be undefined. Please make sure it is defined" >&5 +$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' +which seems to be undefined. Please make sure it is defined" >&2;} + + rm -f "$ac_tmp/stdin" + case $ac_file in + -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";; + *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";; + esac \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + ;; + + + + esac + +done # for ac_tag + + +as_fn_exit 0 +_ACEOF +ac_clean_files=$ac_clean_files_save + +test $ac_write_fail = 0 || + as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5 + + +# configure is writing to config.log, and then calls config.status. +# config.status does its own redirection, appending to config.log. +# Unfortunately, on DOS this fails, as config.log is still kept open +# by configure, so config.status won't be able to write to it; its +# output is simply discarded. So we exec the FD to /dev/null, +# effectively closing config.log, so it can be properly (re)opened and +# appended to by config.status. When coming back to configure, we +# need to make the FD available again. +if test "$no_create" != yes; then + ac_cs_success=: + ac_config_status_args= + test "$silent" = yes && + ac_config_status_args="$ac_config_status_args --quiet" + exec 5>/dev/null + $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false + exec 5>>config.log + # Use ||, not &&, to avoid exiting from the if with $? = 1, which + # would make configure fail if this is the last instruction. + $ac_cs_success || as_fn_exit 1 +fi +if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 +$as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} +fi + +ac_config_files="$ac_config_files cfitsio.pc" + +cat >confcache <<\_ACEOF +# This file is a shell script that caches the results of configure +# tests run on this system so they can be shared between configure +# scripts and configure runs, see configure's option --config-cache. +# It is not useful on other systems. If it contains results you don't +# want to keep, you may remove or edit it. +# +# config.status only pays attention to the cache file if you give it +# the --recheck option to rerun configure. +# +# `ac_cv_env_foo' variables (set or unset) will be overridden when +# loading this file, other *unset* `ac_cv_foo' will be assigned the +# following values. + +_ACEOF + +# The following way of writing the cache mishandles newlines in values, +# but we know of no workaround that is simple, portable, and efficient. +# So, we kill variables containing newlines. +# Ultrix sh set writes to stderr and can't be redirected directly, +# and sets the high bit in the cache file unless we assign to the vars. +( + for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do + eval ac_val=\$$ac_var + case $ac_val in #( + *${as_nl}*) + case $ac_var in #( + *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + esac + case $ac_var in #( + _ | IFS | as_nl) ;; #( + BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( + *) { eval $ac_var=; unset $ac_var;} ;; + esac ;; + esac + done + + (set) 2>&1 | + case $as_nl`(ac_space=' '; set) 2>&1` in #( + *${as_nl}ac_space=\ *) + # `set' does not quote correctly, so add quotes: double-quote + # substitution turns \\\\ into \\, and sed turns \\ into \. + sed -n \ + "s/'/'\\\\''/g; + s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" + ;; #( + *) + # `set' quotes correctly as required by POSIX, so do not add quotes. + sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" + ;; + esac | + sort +) | + sed ' + /^ac_cv_env_/b end + t clear + :clear + s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/ + t end + s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ + :end' >>confcache +if diff "$cache_file" confcache >/dev/null 2>&1; then :; else + if test -w "$cache_file"; then + if test "x$cache_file" != "x/dev/null"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 +$as_echo "$as_me: updating cache $cache_file" >&6;} + if test ! -f "$cache_file" || test -h "$cache_file"; then + cat confcache >"$cache_file" + else + case $cache_file in #( + */* | ?:*) + mv -f confcache "$cache_file"$$ && + mv -f "$cache_file"$$ "$cache_file" ;; #( + *) + mv -f confcache "$cache_file" ;; + esac + fi + fi + else + { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 +$as_echo "$as_me: not updating unwritable cache $cache_file" >&6;} + fi +fi +rm -f confcache + +test "x$prefix" = xNONE && prefix=$ac_default_prefix +# Let make expand exec_prefix. +test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' + +# Transform confdefs.h into DEFS. +# Protect against shell expansion while executing Makefile rules. +# Protect against Makefile macro expansion. +# +# If the first sed substitution is executed (which looks for macros that +# take arguments), then branch to the quote section. Otherwise, +# look for a macro that doesn't take arguments. +ac_script=' +:mline +/\\$/{ + N + s,\\\n,, + b mline +} +t clear +:clear +s/^[ ]*#[ ]*define[ ][ ]*\([^ (][^ (]*([^)]*)\)[ ]*\(.*\)/-D\1=\2/g +t quote +s/^[ ]*#[ ]*define[ ][ ]*\([^ ][^ ]*\)[ ]*\(.*\)/-D\1=\2/g +t quote +b any +:quote +s/[ `~#$^&*(){}\\|;'\''"<>?]/\\&/g +s/\[/\\&/g +s/\]/\\&/g +s/\$/$$/g +H +:any +${ + g + s/^\n// + s/\n/ /g + p +} +' +DEFS=`sed -n "$ac_script" confdefs.h` + + +ac_libobjs= +ac_ltlibobjs= +U= +for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue + # 1. Remove the extension, and $U if already installed. + ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' + ac_i=`$as_echo "$ac_i" | sed "$ac_script"` + # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR + # will be set to the directory where LIBOBJS objects are built. + as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" + as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo' +done +LIBOBJS=$ac_libobjs + +LTLIBOBJS=$ac_ltlibobjs + + + +: "${CONFIG_STATUS=./config.status}" +ac_write_fail=0 +ac_clean_files_save=$ac_clean_files +ac_clean_files="$ac_clean_files $CONFIG_STATUS" +{ $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 +$as_echo "$as_me: creating $CONFIG_STATUS" >&6;} +as_write_fail=0 +cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 +#! $SHELL +# Generated by $as_me. +# Run this file to recreate the current configuration. +# Compiler output produced by configure, useful for debugging +# configure, is in config.log if it exists. + +debug=false +ac_cs_recheck=false +ac_cs_silent=false + +SHELL=\${CONFIG_SHELL-$SHELL} +export SHELL +_ASEOF +cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 +## -------------------- ## +## M4sh Initialization. ## +## -------------------- ## + +# Be more Bourne compatible +DUALCASE=1; export DUALCASE # for MKS sh +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi + + +as_nl=' +' +export as_nl +# Printing a long string crashes Solaris 7 /usr/bin/printf. +as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo +# Prefer a ksh shell builtin over an external printf program on Solaris, +# but without wasting forks for bash or zsh. +if test -z "$BASH_VERSION$ZSH_VERSION" \ + && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='print -r --' + as_echo_n='print -rn --' +elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='printf %s\n' + as_echo_n='printf %s' +else + if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then + as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' + as_echo_n='/usr/ucb/echo -n' + else + as_echo_body='eval expr "X$1" : "X\\(.*\\)"' + as_echo_n_body='eval + arg=$1; + case $arg in #( + *"$as_nl"*) + expr "X$arg" : "X\\(.*\\)$as_nl"; + arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; + esac; + expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" + ' + export as_echo_n_body + as_echo_n='sh -c $as_echo_n_body as_echo' + fi + export as_echo_body + as_echo='sh -c $as_echo_body as_echo' +fi + +# The user is always right. +if test "${PATH_SEPARATOR+set}" != set; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + +# IFS +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent editors from complaining about space-tab. +# (If _AS_PATH_WALK were called with IFS unset, it would disable word +# splitting by setting IFS to empty value.) +IFS=" "" $as_nl" + +# Find who we are. Look in the path if we contain no directory separator. +as_myself= +case $0 in #(( + *[\\/]* ) as_myself=$0 ;; + *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break + done +IFS=$as_save_IFS + + ;; +esac +# We did not find ourselves, most probably we were run as `sh COMMAND' +# in which case we are not to be found in the path. +if test "x$as_myself" = x; then + as_myself=$0 +fi +if test ! -f "$as_myself"; then + $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + exit 1 +fi + +# Unset variables that we do not need and which cause bugs (e.g. in +# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" +# suppresses any "Segmentation fault" message there. '((' could +# trigger a bug in pdksh 5.2.14. +for as_var in BASH_ENV ENV MAIL MAILPATH +do eval test x\${$as_var+set} = xset \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done +PS1='$ ' +PS2='> ' +PS4='+ ' + +# NLS nuisances. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# CDPATH. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + + +# as_fn_error STATUS ERROR [LINENO LOG_FD] +# ---------------------------------------- +# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are +# provided, also output the error to LOG_FD, referencing LINENO. Then exit the +# script with STATUS, using 1 if that was 0. +as_fn_error () +{ + as_status=$1; test $as_status -eq 0 && as_status=1 + if test "$4"; then + as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + fi + $as_echo "$as_me: error: $2" >&2 + as_fn_exit $as_status +} # as_fn_error + + +# as_fn_set_status STATUS +# ----------------------- +# Set $? to STATUS, without forking. +as_fn_set_status () +{ + return $1 +} # as_fn_set_status + +# as_fn_exit STATUS +# ----------------- +# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. +as_fn_exit () +{ + set +e + as_fn_set_status $1 + exit $1 +} # as_fn_exit + +# as_fn_unset VAR +# --------------- +# Portably unset VAR. +as_fn_unset () +{ + { eval $1=; unset $1;} +} +as_unset=as_fn_unset +# as_fn_append VAR VALUE +# ---------------------- +# Append the text in VALUE to the end of the definition contained in VAR. Take +# advantage of any shell optimizations that allow amortized linear growth over +# repeated appends, instead of the typical quadratic growth present in naive +# implementations. +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : + eval 'as_fn_append () + { + eval $1+=\$2 + }' +else + as_fn_append () + { + eval $1=\$$1\$2 + } +fi # as_fn_append + +# as_fn_arith ARG... +# ------------------ +# Perform arithmetic evaluation on the ARGs, and store the result in the +# global $as_val. Take advantage of shells that can avoid forks. The arguments +# must be portable across $(()) and expr. +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : + eval 'as_fn_arith () + { + as_val=$(( $* )) + }' +else + as_fn_arith () + { + as_val=`expr "$@" || test $? -eq 1` + } +fi # as_fn_arith + + +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then + as_basename=basename +else + as_basename=false +fi + +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname +else + as_dirname=false +fi + +as_me=`$as_basename -- "$0" || +$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ + X"$0" : 'X\(//\)$' \| \ + X"$0" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X/"$0" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits + +ECHO_C= ECHO_N= ECHO_T= +case `echo -n x` in #((((( +-n*) + case `echo 'xy\c'` in + *c*) ECHO_T=' ';; # ECHO_T is single tab character. + xy) ECHO_C='\c';; + *) echo `echo ksh88 bug on AIX 6.1` > /dev/null + ECHO_T=' ';; + esac;; +*) + ECHO_N='-n';; +esac + +rm -f conf$$ conf$$.exe conf$$.file +if test -d conf$$.dir; then + rm -f conf$$.dir/conf$$.file +else + rm -f conf$$.dir + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. + # In both cases, we have to default to `cp -p'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -p' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else + as_ln_s='cp -p' + fi +else + as_ln_s='cp -p' +fi +rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file +rmdir conf$$.dir 2>/dev/null + + +# as_fn_mkdir_p +# ------------- +# Create "$as_dir" as a directory, including parents if necessary. +as_fn_mkdir_p () +{ + + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || eval $as_mkdir_p || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" + + +} # as_fn_mkdir_p +if mkdir -p . 2>/dev/null; then + as_mkdir_p='mkdir -p "$as_dir"' +else + test -d ./-p && rmdir ./-p + as_mkdir_p=false +fi + +if test -x / >/dev/null 2>&1; then + as_test_x='test -x' +else + if ls -dL / >/dev/null 2>&1; then + as_ls_L_option=L + else + as_ls_L_option= + fi + as_test_x=' + eval sh -c '\'' + if test -d "$1"; then + test -d "$1/."; + else + case $1 in #( + -*)set "./$1";; + esac; + case `ls -ld'$as_ls_L_option' "$1" 2>/dev/null` in #(( + ???[sx]*):;;*)false;;esac;fi + '\'' sh + ' +fi +as_executable_p=$as_test_x + +# Sed expression to map a string onto a valid CPP name. +as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" + +# Sed expression to map a string onto a valid variable name. +as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" + + +exec 6>&1 +## ----------------------------------- ## +## Main body of $CONFIG_STATUS script. ## +## ----------------------------------- ## +_ASEOF +test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1 + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# Save the log message, to keep $0 and so on meaningful, and to +# report actual input values of CONFIG_FILES etc. instead of their +# values after options handling. +ac_log=" +This file was extended by $as_me, which was +generated by GNU Autoconf 2.68. Invocation command line was + + CONFIG_FILES = $CONFIG_FILES + CONFIG_HEADERS = $CONFIG_HEADERS + CONFIG_LINKS = $CONFIG_LINKS + CONFIG_COMMANDS = $CONFIG_COMMANDS + $ $0 $@ + +on `(hostname || uname -n) 2>/dev/null | sed 1q` +" + +_ACEOF + +case $ac_config_files in *" +"*) set x $ac_config_files; shift; ac_config_files=$*;; +esac + + + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +# Files that config.status was made for. +config_files="$ac_config_files" + +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +ac_cs_usage="\ +\`$as_me' instantiates files and other configuration actions +from templates according to the current configuration. Unless the files +and actions are specified as TAGs, all are instantiated by default. + +Usage: $0 [OPTION]... [TAG]... + + -h, --help print this help, then exit + -V, --version print version number and configuration settings, then exit + --config print configuration, then exit + -q, --quiet, --silent + do not print progress messages + -d, --debug don't remove temporary files + --recheck update $as_me by reconfiguring in the same conditions + --file=FILE[:TEMPLATE] + instantiate the configuration file FILE + +Configuration files: +$config_files + +Report bugs to the package provider." + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" +ac_cs_version="\\ +config.status +configured by $0, generated by GNU Autoconf 2.68, + with options \\"\$ac_cs_config\\" + +Copyright (C) 2010 Free Software Foundation, Inc. +This config.status script is free software; the Free Software Foundation +gives unlimited permission to copy, distribute and modify it." + +ac_pwd='$ac_pwd' +srcdir='$srcdir' +test -n "\$AWK" || AWK=awk +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# The default lists apply if the user does not specify any file. +ac_need_defaults=: +while test $# != 0 +do + case $1 in + --*=?*) + ac_option=`expr "X$1" : 'X\([^=]*\)='` + ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` + ac_shift=: + ;; + --*=) + ac_option=`expr "X$1" : 'X\([^=]*\)='` + ac_optarg= + ac_shift=: + ;; + *) + ac_option=$1 + ac_optarg=$2 + ac_shift=shift + ;; + esac + + case $ac_option in + # Handling of the options. + -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) + ac_cs_recheck=: ;; + --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) + $as_echo "$ac_cs_version"; exit ;; + --config | --confi | --conf | --con | --co | --c ) + $as_echo "$ac_cs_config"; exit ;; + --debug | --debu | --deb | --de | --d | -d ) + debug=: ;; + --file | --fil | --fi | --f ) + $ac_shift + case $ac_optarg in + *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; + '') as_fn_error $? "missing file argument" ;; + esac + as_fn_append CONFIG_FILES " '$ac_optarg'" + ac_need_defaults=false;; + --he | --h | --help | --hel | -h ) + $as_echo "$ac_cs_usage"; exit ;; + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil | --si | --s) + ac_cs_silent=: ;; + + # This is an error. + -*) as_fn_error $? "unrecognized option: \`$1' +Try \`$0 --help' for more information." ;; + + *) as_fn_append ac_config_targets " $1" + ac_need_defaults=false ;; + + esac + shift +done + +ac_configure_extra_args= + +if $ac_cs_silent; then + exec 6>/dev/null + ac_configure_extra_args="$ac_configure_extra_args --silent" +fi + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +if \$ac_cs_recheck; then + set X '$SHELL' '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion + shift + \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6 + CONFIG_SHELL='$SHELL' + export CONFIG_SHELL + exec "\$@" +fi + +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +exec 5>>config.log +{ + echo + sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX +## Running $as_me. ## +_ASBOX + $as_echo "$ac_log" +} >&5 + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 + +# Handling of arguments. +for ac_config_target in $ac_config_targets +do + case $ac_config_target in + "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; + "cfitsio.pc") CONFIG_FILES="$CONFIG_FILES cfitsio.pc" ;; + + *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; + esac +done + + +# If the user did not use the arguments to specify the items to instantiate, +# then the envvar interface is used. Set only those that are not. +# We use the long form for the default assignment because of an extremely +# bizarre bug on SunOS 4.1.3. +if $ac_need_defaults; then + test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files +fi + +# Have a temporary directory for convenience. Make it in the build tree +# simply because there is no reason against having it here, and in addition, +# creating and moving files from /tmp can sometimes cause problems. +# Hook for its removal unless debugging. +# Note that there is a small window in which the directory will not be cleaned: +# after its creation but before its name has been assigned to `$tmp'. +$debug || +{ + tmp= ac_tmp= + trap 'exit_status=$? + : "${ac_tmp:=$tmp}" + { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status +' 0 + trap 'as_fn_exit 1' 1 2 13 15 +} +# Create a (secure) tmp directory for tmp files. + +{ + tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && + test -d "$tmp" +} || +{ + tmp=./conf$$-$RANDOM + (umask 077 && mkdir "$tmp") +} || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5 +ac_tmp=$tmp + +# Set up the scripts for CONFIG_FILES section. +# No need to generate them if there are no CONFIG_FILES. +# This happens for instance with `./config.status config.h'. +if test -n "$CONFIG_FILES"; then + + +ac_cr=`echo X | tr X '\015'` +# On cygwin, bash can eat \r inside `` if the user requested igncr. +# But we know of no other shell where ac_cr would be empty at this +# point, so we can use a bashism as a fallback. +if test "x$ac_cr" = x; then + eval ac_cr=\$\'\\r\' +fi +ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' /dev/null` +if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then + ac_cs_awk_cr='\\r' +else + ac_cs_awk_cr=$ac_cr +fi + +echo 'BEGIN {' >"$ac_tmp/subs1.awk" && +_ACEOF + + +{ + echo "cat >conf$$subs.awk <<_ACEOF" && + echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' && + echo "_ACEOF" +} >conf$$subs.sh || + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 +ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'` +ac_delim='%!_!# ' +for ac_last_try in false false false false false :; do + . ./conf$$subs.sh || + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 + + ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X` + if test $ac_delim_n = $ac_delim_num; then + break + elif $ac_last_try; then + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 + else + ac_delim="$ac_delim!$ac_delim _$ac_delim!! " + fi +done +rm -f conf$$subs.sh + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK && +_ACEOF +sed -n ' +h +s/^/S["/; s/!.*/"]=/ +p +g +s/^[^!]*!// +:repl +t repl +s/'"$ac_delim"'$// +t delim +:nl +h +s/\(.\{148\}\)..*/\1/ +t more1 +s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/ +p +n +b repl +:more1 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t nl +:delim +h +s/\(.\{148\}\)..*/\1/ +t more2 +s/["\\]/\\&/g; s/^/"/; s/$/"/ +p +b +:more2 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t delim +' >$CONFIG_STATUS || ac_write_fail=1 +rm -f conf$$subs.awk +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +_ACAWK +cat >>"\$ac_tmp/subs1.awk" <<_ACAWK && + for (key in S) S_is_set[key] = 1 + FS = "" + +} +{ + line = $ 0 + nfields = split(line, field, "@") + substed = 0 + len = length(field[1]) + for (i = 2; i < nfields; i++) { + key = field[i] + keylen = length(key) + if (S_is_set[key]) { + value = S[key] + line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3) + len += length(value) + length(field[++i]) + substed = 1 + } else + len += 1 + keylen + } + + print line +} + +_ACAWK +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then + sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" +else + cat +fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \ + || as_fn_error $? "could not setup config files machinery" "$LINENO" 5 +_ACEOF + +# VPATH may cause trouble with some makes, so we remove sole $(srcdir), +# ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and +# trailing colons and then remove the whole line if VPATH becomes empty +# (actually we leave an empty line to preserve line numbers). +if test "x$srcdir" = x.; then + ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{ +h +s/// +s/^/:/ +s/[ ]*$/:/ +s/:\$(srcdir):/:/g +s/:\${srcdir}:/:/g +s/:@srcdir@:/:/g +s/^:*// +s/:*$// +x +s/\(=[ ]*\).*/\1/ +G +s/\n// +s/^[^=]*=[ ]*$// +}' +fi + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +fi # test -n "$CONFIG_FILES" + + +eval set X " :F $CONFIG_FILES " +shift +for ac_tag +do + case $ac_tag in + :[FHLC]) ac_mode=$ac_tag; continue;; + esac + case $ac_mode$ac_tag in + :[FHL]*:*);; + :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;; + :[FH]-) ac_tag=-:-;; + :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; + esac + ac_save_IFS=$IFS + IFS=: + set x $ac_tag + IFS=$ac_save_IFS + shift + ac_file=$1 + shift + + case $ac_mode in + :L) ac_source=$1;; + :[FH]) + ac_file_inputs= + for ac_f + do + case $ac_f in + -) ac_f="$ac_tmp/stdin";; + *) # Look for the file first in the build tree, then in the source tree + # (if the path is not absolute). The absolute path cannot be DOS-style, + # because $ac_f cannot contain `:'. + test -f "$ac_f" || + case $ac_f in + [\\/$]*) false;; + *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; + esac || + as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;; + esac + case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac + as_fn_append ac_file_inputs " '$ac_f'" + done + + # Let's still pretend it is `configure' which instantiates (i.e., don't + # use $as_me), people would be surprised to read: + # /* config.h. Generated by config.status. */ + configure_input='Generated from '` + $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' + `' by configure.' + if test x"$ac_file" != x-; then + configure_input="$ac_file. $configure_input" + { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 +$as_echo "$as_me: creating $ac_file" >&6;} + fi + # Neutralize special characters interpreted by sed in replacement strings. + case $configure_input in #( + *\&* | *\|* | *\\* ) + ac_sed_conf_input=`$as_echo "$configure_input" | + sed 's/[\\\\&|]/\\\\&/g'`;; #( + *) ac_sed_conf_input=$configure_input;; + esac + + case $ac_tag in + *:-:* | *:-) cat >"$ac_tmp/stdin" \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; + esac + ;; + esac + + ac_dir=`$as_dirname -- "$ac_file" || +$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$ac_file" : 'X\(//\)[^/]' \| \ + X"$ac_file" : 'X\(//\)$' \| \ + X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$ac_file" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + as_dir="$ac_dir"; as_fn_mkdir_p + ac_builddir=. + +case "$ac_dir" in +.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; +*) + ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` + # A ".." for each directory in $ac_dir_suffix. + ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + case $ac_top_builddir_sub in + "") ac_top_builddir_sub=. ac_top_build_prefix= ;; + *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; + esac ;; +esac +ac_abs_top_builddir=$ac_pwd +ac_abs_builddir=$ac_pwd$ac_dir_suffix +# for backward compatibility: +ac_top_builddir=$ac_top_build_prefix + +case $srcdir in + .) # We are building in place. + ac_srcdir=. + ac_top_srcdir=$ac_top_builddir_sub + ac_abs_top_srcdir=$ac_pwd ;; + [\\/]* | ?:[\\/]* ) # Absolute name. + ac_srcdir=$srcdir$ac_dir_suffix; + ac_top_srcdir=$srcdir + ac_abs_top_srcdir=$srcdir ;; + *) # Relative name. + ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix + ac_top_srcdir=$ac_top_build_prefix$srcdir + ac_abs_top_srcdir=$ac_pwd/$srcdir ;; +esac +ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix + + + case $ac_mode in + :F) + # + # CONFIG_FILE + # + +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# If the template does not know about datarootdir, expand it. +# FIXME: This hack should be removed a few years after 2.60. +ac_datarootdir_hack=; ac_datarootdir_seen= +ac_sed_dataroot=' +/datarootdir/ { + p + q +} +/@datadir@/p +/@docdir@/p +/@infodir@/p +/@localedir@/p +/@mandir@/p' +case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in +*datarootdir*) ac_datarootdir_seen=yes;; +*@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 +$as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 + ac_datarootdir_hack=' + s&@datadir@&$datadir&g + s&@docdir@&$docdir&g + s&@infodir@&$infodir&g + s&@localedir@&$localedir&g + s&@mandir@&$mandir&g + s&\\\${datarootdir}&$datarootdir&g' ;; +esac +_ACEOF + +# Neutralize VPATH when `$srcdir' = `.'. +# Shell code in configure.ac might set extrasub. +# FIXME: do we really want to maintain this feature? +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +ac_sed_extra="$ac_vpsub +$extrasub +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +:t +/@[a-zA-Z_][a-zA-Z_0-9]*@/!b +s|@configure_input@|$ac_sed_conf_input|;t t +s&@top_builddir@&$ac_top_builddir_sub&;t t +s&@top_build_prefix@&$ac_top_build_prefix&;t t +s&@srcdir@&$ac_srcdir&;t t +s&@abs_srcdir@&$ac_abs_srcdir&;t t +s&@top_srcdir@&$ac_top_srcdir&;t t +s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t +s&@builddir@&$ac_builddir&;t t +s&@abs_builddir@&$ac_abs_builddir&;t t +s&@abs_top_builddir@&$ac_abs_top_builddir&;t t +$ac_datarootdir_hack +" +eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \ + >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + +test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && + { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && + { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ + "$ac_tmp/out"`; test -z "$ac_out"; } && + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' +which seems to be undefined. Please make sure it is defined" >&5 +$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' +which seems to be undefined. Please make sure it is defined" >&2;} + + rm -f "$ac_tmp/stdin" + case $ac_file in + -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";; + *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";; + esac \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + ;; + + + + esac + +done # for ac_tag + + +as_fn_exit 0 +_ACEOF +ac_clean_files=$ac_clean_files_save + +test $ac_write_fail = 0 || + as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5 + + +# configure is writing to config.log, and then calls config.status. +# config.status does its own redirection, appending to config.log. +# Unfortunately, on DOS this fails, as config.log is still kept open +# by configure, so config.status won't be able to write to it; its +# output is simply discarded. So we exec the FD to /dev/null, +# effectively closing config.log, so it can be properly (re)opened and +# appended to by config.status. When coming back to configure, we +# need to make the FD available again. +if test "$no_create" != yes; then + ac_cs_success=: + ac_config_status_args= + test "$silent" = yes && + ac_config_status_args="$ac_config_status_args --quiet" + exec 5>/dev/null + $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false + exec 5>>config.log + # Use ||, not &&, to avoid exiting from the if with $? = 1, which + # would make configure fail if this is the last instruction. + $ac_cs_success || as_fn_exit 1 +fi +if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 +$as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} +fi + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: " >&5 +$as_echo "" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Congratulations, Makefile update was successful." >&5 +$as_echo " Congratulations, Makefile update was successful." >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: You may want to run \"make\" now." >&5 +$as_echo " You may want to run \"make\" now." >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: " >&5 +$as_echo "" >&6; } + diff --git a/external/cfitsio/configure.in b/external/cfitsio/configure.in new file mode 100644 index 0000000..3954740 --- /dev/null +++ b/external/cfitsio/configure.in @@ -0,0 +1,507 @@ +# +# configure.in for cfitsio +# +# /redshift/sgi6/lheavc/ftools/cfitsio/configure.in,v 3.4 1996/07/26 20:27:53 pence Exp +# +# copied from host and modified +# + +dnl Process this file with autoconf to produce a configure script. +AC_INIT +AC_CONFIG_SRCDIR([fitscore.c]) + +#-------------------------------------------------------------------- +# Command options +#-------------------------------------------------------------------- + +AC_ARG_ENABLE( + reentrant, + [AS_HELP_STRING([--enable-reentrant],[Enable reentrant multithreading])], + [ if test $enableval = yes; then BUILD_REENTRANT=yes; fi ] +) + +SSE_FLAGS="" +AC_ARG_ENABLE( + sse2, + [AS_HELP_STRING([--enable-sse2],[Enable use of instructions in the SSE2 extended instruction set])], + [ if test $enableval = yes; then SSE_FLAGS="-msse2"; fi ] +) + +AC_ARG_ENABLE( + ssse3, + [AS_HELP_STRING([--enable-ssse3],[Enable use of instructions in the SSSE3 extended instruction set])], + [ if test $enableval = yes; then SSE_FLAGS="$SSE_FLAGS -mssse3"; fi ] +) + +# Define BUILD_HERA when building for HERA project to activate code in +# drvrfile.c (by way of fitsio2.h): +AC_ARG_ENABLE( + hera, + [AS_HELP_STRING([--enable-hera],[Build for HERA (ASD use only)])], + [ if test $enableval = yes; then BUILD_HERA=yes; fi ] +) +if test "x$BUILD_HERA" = xyes; then + AC_DEFINE(BUILD_HERA) +fi + +AC_ARG_WITH( + gsiftp-flavour, + [AS_HELP_STRING([--with-gsiftp-flavour[[=PATH]]],[Enable Globus Toolkit gsiftp protocol support])], + [ if test "x$withval" != "xno"; then + + if test "x$withval" != "xyes" ; then + GSIFTP_FLAVOUR=${withval} + fi + AC_DEFINE(GSIFTP_FLAVOUR,1,[Define Globus Toolkit architecture]) + fi + ] +) + +AC_ARG_WITH( + gsiftp, + [AS_HELP_STRING([--with-gsiftp[[=PATH]]],[Enable Globus Toolkit gsiftp protocol support])], + [ if test "x$withval" != "xno"; then + + if test "x$withval" != "xyes" ; then + CFLAGS="$CFLAGS -I${withval}/include/${GSIFTP_FLAVOUR}" + LDFLAGS="$LDFLAGS -L${withval}/lib -lglobus_ftp_client_${GSIFTP_FLAVOUR}" + HAVE_GSIFTP=yes + fi + AC_DEFINE(HAVE_GSIFTP,1,[Define if you want Globus Toolkit gsiftp protocol support]) + fi + ] +) + +#-------------------------------------------------------------------- +# Check for install location prefix +#-------------------------------------------------------------------- + +AC_PREFIX_DEFAULT(`pwd`) + +# make will complain about duplicate targets for the install directories +# if prefix == exec_prefix +AC_SUBST(INSTALL_ROOT,'${prefix}') +test "$exec_prefix" != NONE -a "$prefix" != "$exec_prefix" \ + && INSTALL_ROOT="$INSTALL_ROOT "'${exec_prefix}' + + +#-------------------------------------------------------------------- +# Check "uname" to determine system type +#-------------------------------------------------------------------- +AC_CHECK_PROG([uname_found],[uname],[1],[0]) +if test $uname_found -eq 0 ; then + echo "cfitsio: No uname found; setting system type to unknown." + system="unknown" +else + system=`uname -s`-`uname -r` +fi + + +dnl Checks for programs. + +# Try first to find a proprietary C compiler, then gcc +if test "x$CC" = x; then + AC_CHECK_PROGS(CC, cc) +fi +AC_PROG_CC + +if test "x$FC" = "xnone" ; then + AC_MSG_NOTICE(cfitsio: == Fortran compiler search has been overridden) + AC_MSG_NOTICE(cfitsio: == Cfitsio will be built without Fortran wrapper support) + FC= + F77_WRAPPERS= +else + AC_CHECK_PROGS(FC, gfortran g95 g77 f77 ifort f95 f90 xlf cf77 gf77 af77 ncf f2c, notfound) + if test $FC = 'notfound' ; then + AC_MSG_WARN(cfitsio: == No acceptable Fortran compiler found in \$PATH) + AC_MSG_NOTICE(cfitsio: == Adding wrapper support for GNU Fortran by default) + CFORTRANFLAGS="-Dg77Fortran" + F77_WRAPPERS="\${FITSIO_SRC}" + else + CFORTRANFLAGS= + F77_WRAPPERS="\${FITSIO_SRC}" + echo $ac_n "checking whether we are using GNU Fortran""... $ac_c" 1>&6 + if test `$FC --version -c < /dev/null 2> /dev/null | grep -c GNU` -gt 0 -o \ + `$FC --version -c < /dev/null 2> /dev/null | grep -ic egcs` -gt 0 + then + echo "$ac_t""yes" 1>&6 + echo $ac_n "cfitsio: == Adding wrapper support for GNU Fortran""... $ac_c" 1>&6 + CFORTRANFLAGS="-Dg77Fortran" + echo "$ac_t"" done" 1>&6 + else + echo "$ac_t""no" 1>&6 + if test $FC = 'f2c' ; then + echo $ac_n "cfitsio: == Adding wrapper support for f2c""... $ac_c" 1>&6 + CFORTRANFLAGS="-Df2cFortran" + echo "$ac_t"" done" 1>&6 + fi + fi + fi +fi + +AC_PROG_RANLIB + +dnl Checks for ANSI stdlib.h. +AC_CHECK_HEADERS(stdlib.h string.h math.h limits.h ,ANSI_HEADER=yes,ANSI_HEADER=no)dnl + +dnl Check if prototyping is allowed. +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[void d( int , double) ]])],[PROTO=yes],[PROTO=no])dnl + +if test $ANSI_HEADER = no -o $PROTO = no; then + echo " *********** WARNING: CFITSIO CONFIGURE FAILURE ************ " + echo "cfitsio: ANSI C environment NOT found. Aborting cfitsio configure." + if test $ANSI_HEADER = no; then + echo "cfitsio: You're missing a needed ANSI header file." + fi + if test $PROTO = no; then + echo "cfitsio: Your compiler can't do ANSI function prototypes." + fi + echo "cfitsio: You need an ANSI C compiler and all ANSI trappings" + echo "cfitsio: to build cfitsio. " + echo " ******************************************************* " + exit 0; +fi + +dnl Check if C compiler supports sse extended instruction flags. +if test "x$SSE_FLAGS" != x; then + SAVE_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $SSE_FLAGS" + AC_MSG_CHECKING([whether $CC accepts $SSE_FLAGS]) + AC_LANG_PUSH([C]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([])],[c_has_option=yes],[c_has_option=no]) + AC_MSG_RESULT($c_has_option) + AC_LANG_POP([]) + if test "$c_has_option" = no; then SSE_FLAGS=""; fi + CFLAGS="$SAVE_CFLAGS" +fi +AC_SUBST(SSE_FLAGS) + +CFLAGS="$CFLAGS" +LIBPRE="" + +case $system in + Darwin-*) + # Build for i386 & x86_64 architectures on Darwin 10.x or newer: + changequote(,) + case $system in + Darwin-[56789]*) + ;; + *) + changequote([,]) + # Test to see whether the C compiler accepts the "-arch" + # flags for building "universal" binaries (Apple XCode only): + SAVE_CFLAGS="$CFLAGS" + C_UNIV_SWITCH="-arch i386 -arch x86_64" + CFLAGS="$CFLAGS $C_UNIV_SWITCH" + AC_MSG_CHECKING([whether $CC accepts $C_UNIV_SWITCH]) + AC_LANG_PUSH([C]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([])],[c_has_option=yes],[c_has_option=no]) + AC_MSG_RESULT($c_has_option) + AC_LANG_POP([]) + # Value of C_UNIV_SWITCH will be needed later for SHLIB_LD: + if test "$c_has_option" = no; then C_UNIV_SWITCH=""; fi + CFLAGS="$SAVE_CFLAGS $C_UNIV_SWITCH" + ;; + esac + # Darwin can be powerpc, i386, or x86_64 + ARCH=`uname -p` + EXT="darwin" + # For large file support (but may break Absoft compilers): + AC_DEFINE(_LARGEFILE_SOURCE) + AC_DEFINE(_FILE_OFFSET_BITS,64) + ;; + SunOS-4*) + ARCH="sun" + EXT="sun" + ;; + HP-UX-*) + ARCH="hp" + EXT="hpu" + if test "x$CFORTRANFLAGS" = x ; then + CFORTRANFLAGS="-Dappendus" + fi + CFLAGS="$CFLAGS -DPG_PPU" + LIBPRE="-Wl," + ;; + SunOS-5*) + ARCH="solaris" + EXT="sol" + if test "x$CFORTRANFLAGS" = x ; then + CFORTRANFLAGS="-Dsolaris" + fi + # We need libm on Solaris: + AC_CHECK_LIB(m, frexp) + # For large file support: + AC_DEFINE(_LARGEFILE_SOURCE) + AC_DEFINE(_FILE_OFFSET_BITS,64) + ;; + OSF1*) + ARCH="alpha" + EXT="osf" + ;; + IRIX*) + ARCH="sgi" + EXT="sgi" + CFLAGS="$CFLAGS -DHAVE_POSIX_SIGNALS" + RANLIB="touch" + ;; + ULTRIX*) + ARCH="dec" + EXT="dec" + ;; + Linux*) + ARCH="linux" + EXT="lnx" + # For large file support: + AC_DEFINE(_LARGEFILE_SOURCE) + AC_DEFINE(_FILE_OFFSET_BITS,64) + ;; + FREEBSD*|FreeBSD*) + ARCH="linux" + EXT="lnx" + ;; + CYGWIN*) + ARCH="cygwin" + EXT="cygwin" + CFLAGS="$CFLAGS -DHAVE_POSIX_SIGNALS" + ;; + *) + echo "cfitsio: == Don't know what do do with $system" + ;; +esac + +CFLAGS="$CFLAGS $CFORTRANFLAGS" + +case $GCC in + yes) + GCCVERSION="`$CC -dumpversion 2>&1`" + echo "cfitsio: == Using gcc version $GCCVERSION" + AC_SUBST(GCCVERSION) + changequote(,) + gcc_test=`echo $GCCVERSION | grep -c '2\.[45678]'` + changequote([,]) + if test $gcc_test -gt 0 + then + changequote(,) + CFLAGS=`echo $CFLAGS | sed 's:-O[^ ]* *::'` + changequote([,]) + AC_MSG_WARN(This gcc is pretty old. Disabling optimization to be safe.) + fi + ;; + no) + echo "cfitsio: Old CFLAGS is $CFLAGS" + CFLAGS=`echo $CFLAGS | sed -e "s/-g/-O/"` + case $system in + SunOS-5*) + changequote(,) + if test `echo $CFLAGS | grep -c fast` -gt 0 + then + echo "cfitsio: Replacing -fast with -O3" + CFLAGS=`echo $CFLAGS | sed 's:-fast:-O3:'` + fi + changequote([,]) + CFLAGS="$CFLAGS -DHAVE_ALLOCA_H -DHAVE_POSIX_SIGNALS" + ;; + *) + echo "== No special changes for $system" + ;; + esac + echo "New CFLAGS is $CFLAGS" + ;; + *) + # Don't do anything now + ;; +esac + +# Shared library section +#------------------------------------------------------------------------------- +SHLIB_LD=: +SHLIB_SUFFIX=".so" +lhea_shlib_cflags= +case $EXT in + cygwin) + SHLIB_LD="$CC -shared" + SHLIB_SUFFIX=".dll" + ;; + darwin) + changequote(,) + case $system in + Darwin-[56789]*) + SHLIB_LD="$CC -dynamiclib" + ;; + *) + # Build for i386 & x86_64 architectures on Darwin 10.x or newer: + SHLIB_LD="$CC -dynamiclib $C_UNIV_SWITCH" + ;; + esac + changequote([,]) + SHLIB_SUFFIX=".dylib" + lhea_shlib_cflags="-fPIC -fno-common" + ;; + hpu) + SHLIB_LD="ld -b" + SHLIB_SUFFIX=".sl" + ;; + lnx) + SHLIB_LD=":" + ;; + osf) + SHLIB_LD="ld -shared -expect_unresolved '*'" + LD_FLAGS="-taso" + ;; + sol) + SHLIB_LD="/usr/ccs/bin/ld -G" + lhea_shlib_cflags="-KPIC" + ;; + sgi) + SHLIB_LD="ld -shared -rdata_shared" + ;; + *) + AC_MSG_WARN(Unable to determine how to make a shared library) + ;; +esac +# Darwin uses gcc (=cc), but needs different flags (see above) +# if test "x$GCC" = xyes; then +if test "x$GCC" = xyes && test "x$EXT" != xdarwin && test "x$EXT" != xcygwin; then + SHLIB_LD="$CC -shared" + lhea_shlib_cflags='-fPIC' +fi +if test "x$lhea_shlib_cflags" != x; then + CFLAGS="$CFLAGS $lhea_shlib_cflags" +fi + +AC_SUBST(ARCH)dnl +AC_SUBST(CFLAGS)dnl +AC_SUBST(CC)dnl +AC_SUBST(FC)dnl +AC_SUBST(LIBPRE)dnl +AC_SUBST(SHLIB_LD)dnl +AC_SUBST(SHLIB_SUFFIX)dnl +AC_SUBST(F77_WRAPPERS) + +# ================= test for the unix ftruncate function ================ + +AC_MSG_CHECKING("whether ftruncate works") +AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include +]], [[ +ftruncate(0, 0); +]])],[ +AC_DEFINE(HAVE_FTRUNCATE) +AC_MSG_RESULT("yes") +],[AC_MSG_RESULT("no") ]) + +# --------------------------------------------------------- +# some systems define long long for 64-bit ints +# --------------------------------------------------------- + +AC_MSG_CHECKING("whether long long is defined") +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include +]], [[ +long long filler; +]])],[ +AC_DEFINE(HAVE_LONGLONG) +AC_MSG_RESULT("yes") +],[AC_MSG_RESULT("no") ]) + +# ==================== SHARED MEMORY DRIVER SECTION ======================= +# +# 09-Mar-98 : modified by JB/ISDC +# 3 checks added to support autoconfiguration of shared memory +# driver. First generic check is made whether shared memory is supported +# at all, then 2 more specific checks are made (architecture dependent). +# Currently tested on : sparc-solaris, intel-linux, sgi-irix, dec-alpha-osf + +# ------------------------------------------------------------------------- +# check is System V IPC is supported on this machine +# ------------------------------------------------------------------------- + +AC_MSG_CHECKING("whether system V style IPC services are supported") +AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include +#include +#include +]], [[ +shmat(0, 0, 0); +shmdt(0); +shmget(0, 0, 0); +semget(0, 0, 0); +]])],[ +AC_DEFINE(HAVE_SHMEM_SERVICES) +my_shmem=\${SOURCES_SHMEM} +AC_MSG_RESULT("yes") +],[AC_MSG_RESULT("no") ]) + +AC_SUBST(my_shmem) + +# ------------------------------------------------------------------------- +# some systems define flock_t, for others we have to define it ourselves +# ------------------------------------------------------------------------- + +AC_MSG_CHECKING("do we have flock_t defined in sys/fcntl.h") +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include +]], [[ +flock_t filler; +]])],[ +AC_DEFINE(HAVE_FLOCK_T) +AC_MSG_RESULT("yes") +],[AC_MSG_RESULT("no") ]) + +if test "$HAVE_FLOCK_T" != 1; then + AC_MSG_CHECKING("do we have flock_t defined in sys/flock.h") + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include + ]], [[ + flock_t filler; + ]])],[ + AC_DEFINE(HAVE_FLOCK_T) + AC_MSG_RESULT("yes") + ],[AC_MSG_RESULT("no") ]) +fi + +# ------------------------------------------------------------------------- +# there are some idiosyncrasies with semun defs (used in semxxx). Solaris +# does not define it at all +# ------------------------------------------------------------------------- + +AC_MSG_CHECKING("do we have union semun defined") +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include +#include +#include +]], [[ +union semun filler; +]])],[ +AC_DEFINE(HAVE_UNION_SEMUN) +AC_MSG_RESULT("yes") +],[AC_MSG_RESULT("no") ]) + +# ==================== END OF SHARED MEMORY DRIVER SECTION ================ +# ================= test for the unix networking functions ================ + +AC_SEARCH_LIBS([gethostbyname], [nsl], cfitsio_have_nsl=1, cfitsio_have_nsl=0) +AC_SEARCH_LIBS([connect], [socket], cfitsio_have_socket=1, + cfitsio_have_socket=0, [-lnsl]) + +if test "$cfitsio_have_nsl" = 1 -a "$cfitsio_have_socket" = 1; then + AC_DEFINE(HAVE_NET_SERVICES) +fi + +# ==================== END OF unix networking SECTION ================ + +# ------------------------------------------------------------------------------ +# Define _REENTRANT & add -lpthread to LIBS if reentrant multithreading enabled: +# ------------------------------------------------------------------------------ +if test "x$BUILD_REENTRANT" = xyes; then + AC_DEFINE(_REENTRANT) + AC_CHECK_LIB([pthread],[main],[],[AC_MSG_ERROR(Unable to locate pthread library needed when enabling reentrant multithreading)]) +fi +# ------------------------------------------------------------------------------ + +AC_CONFIG_FILES([Makefile]) +AC_OUTPUT +AC_CONFIG_FILES([cfitsio.pc]) +AC_OUTPUT + + +AC_MSG_RESULT([]) +AC_MSG_RESULT([ Congratulations, Makefile update was successful.]) +AC_MSG_RESULT([ You may want to run \"make\" now.]) +AC_MSG_RESULT([]) + diff --git a/external/cfitsio/cookbook.c b/external/cfitsio/cookbook.c new file mode 100644 index 0000000..3b42ac6 --- /dev/null +++ b/external/cfitsio/cookbook.c @@ -0,0 +1,571 @@ +#include +#include +#include + +/* + Every program which uses the CFITSIO interface must include the + the fitsio.h header file. This contains the prototypes for all + the routines and defines the error status values and other symbolic + constants used in the interface. +*/ +#include "fitsio.h" + +int main( void ); +void writeimage( void ); +void writeascii( void ); +void writebintable( void ); +void copyhdu( void ); +void selectrows( void ); +void readheader( void ); +void readimage( void ); +void readtable( void ); +void printerror( int status); + +int main() +{ +/************************************************************************* + This is a simple main program that calls the following routines: + + writeimage - write a FITS primary array image + writeascii - write a FITS ASCII table extension + writebintable - write a FITS binary table extension + copyhdu - copy a header/data unit from one FITS file to another + selectrows - copy selected row from one HDU to another + readheader - read and print the header keywords in every extension + readimage - read a FITS image and compute the min and max value + readtable - read columns of data from ASCII and binary tables + +**************************************************************************/ + + writeimage(); + writeascii(); + writebintable(); + copyhdu(); + selectrows(); + readheader(); + readimage(); + readtable(); + + printf("\nAll the cfitsio cookbook routines ran successfully.\n"); + return(0); +} +/*--------------------------------------------------------------------------*/ +void writeimage( void ) + + /******************************************************/ + /* Create a FITS primary array containing a 2-D image */ + /******************************************************/ +{ + fitsfile *fptr; /* pointer to the FITS file, defined in fitsio.h */ + int status, ii, jj; + long fpixel, nelements, exposure; + unsigned short *array[200]; + + /* initialize FITS image parameters */ + char filename[] = "atestfil.fit"; /* name for new FITS file */ + int bitpix = USHORT_IMG; /* 16-bit unsigned short pixel values */ + long naxis = 2; /* 2-dimensional image */ + long naxes[2] = { 300, 200 }; /* image is 300 pixels wide by 200 rows */ + + /* allocate memory for the whole image */ + array[0] = (unsigned short *)malloc( naxes[0] * naxes[1] + * sizeof( unsigned short ) ); + + /* initialize pointers to the start of each row of the image */ + for( ii=1; ii 0) + { + nbuffer = npixels; + if (npixels > buffsize) + nbuffer = buffsize; /* read as many pixels as will fit in buffer */ + + /* Note that even though the FITS images contains unsigned integer */ + /* pixel values (or more accurately, signed integer pixels with */ + /* a bias of 32768), this routine is reading the values into a */ + /* float array. Cfitsio automatically performs the datatype */ + /* conversion in cases like this. */ + + if ( fits_read_img(fptr, TFLOAT, fpixel, nbuffer, &nullval, + buffer, &anynull, &status) ) + printerror( status ); + + for (ii = 0; ii < nbuffer; ii++) { + if ( buffer[ii] < datamin ) + datamin = buffer[ii]; + + if ( buffer[ii] > datamax ) + datamax = buffer[ii]; + } + npixels -= nbuffer; /* increment remaining number of pixels */ + fpixel += nbuffer; /* next pixel to be read in image */ + } + + printf("\nMin and max image pixels = %.0f, %.0f\n", datamin, datamax); + + if ( fits_close_file(fptr, &status) ) + printerror( status ); + + return; +} +/*--------------------------------------------------------------------------*/ +void readtable( void ) + + /************************************************************/ + /* read and print data values from an ASCII or binary table */ + /************************************************************/ +{ + fitsfile *fptr; /* pointer to the FITS file, defined in fitsio.h */ + int status, hdunum, hdutype, nfound, anynull, ii; + long frow, felem, nelem, longnull, dia[6]; + float floatnull, den[6]; + char strnull[10], *name[6], *ttype[3]; + + char filename[] = "atestfil.fit"; /* name of existing FITS file */ + + status = 0; + + if ( fits_open_file(&fptr, filename, READONLY, &status) ) + printerror( status ); + + for (ii = 0; ii < 3; ii++) /* allocate space for the column labels */ + ttype[ii] = (char *) malloc(FLEN_VALUE); /* max label length = 69 */ + + for (ii = 0; ii < 6; ii++) /* allocate space for string column value */ + name[ii] = (char *) malloc(10); + + for (hdunum = 2; hdunum <= 3; hdunum++) /*read ASCII, then binary table */ + { + /* move to the HDU */ + if ( fits_movabs_hdu(fptr, hdunum, &hdutype, &status) ) + printerror( status ); + + if (hdutype == ASCII_TBL) + printf("\nReading ASCII table in HDU %d:\n", hdunum); + else if (hdutype == BINARY_TBL) + printf("\nReading binary table in HDU %d:\n", hdunum); + else + { + printf("Error: this HDU is not an ASCII or binary table\n"); + printerror( status ); + } + + /* read the column names from the TTYPEn keywords */ + fits_read_keys_str(fptr, "TTYPE", 1, 3, ttype, &nfound, &status); + + printf(" Row %10s %10s %10s\n", ttype[0], ttype[1], ttype[2]); + + frow = 1; + felem = 1; + nelem = 6; + strcpy(strnull, " "); + longnull = 0; + floatnull = 0.; + + /* read the columns */ + fits_read_col(fptr, TSTRING, 1, frow, felem, nelem, strnull, name, + &anynull, &status); + fits_read_col(fptr, TLONG, 2, frow, felem, nelem, &longnull, dia, + &anynull, &status); + fits_read_col(fptr, TFLOAT, 3, frow, felem, nelem, &floatnull, den, + &anynull, &status); + + for (ii = 0; ii < 6; ii++) + printf("%5d %10s %10ld %10.2f\n", ii + 1, name[ii], dia[ii], den[ii]); + } + + for (ii = 0; ii < 3; ii++) /* free the memory for the column labels */ + free( ttype[ii] ); + + for (ii = 0; ii < 6; ii++) /* free the memory for the string column */ + free( name[ii] ); + + if ( fits_close_file(fptr, &status) ) + printerror( status ); + + return; +} +/*--------------------------------------------------------------------------*/ +void printerror( int status) +{ + /*****************************************************/ + /* Print out cfitsio error messages and exit program */ + /*****************************************************/ + + + if (status) + { + fits_report_error(stderr, status); /* print error report */ + + exit( status ); /* terminate the program, returning error status */ + } + return; +} diff --git a/external/cfitsio/cookbook.f b/external/cfitsio/cookbook.f new file mode 100644 index 0000000..8becfdd --- /dev/null +++ b/external/cfitsio/cookbook.f @@ -0,0 +1,772 @@ + program main + +C This is the FITSIO cookbook program that contains an annotated listing of +C various computer programs that read and write files in FITS format +C using the FITSIO subroutine interface. These examples are +C working programs which users may adapt and modify for their own +C purposes. This Cookbook serves as a companion to the FITSIO User's +C Guide that provides more complete documentation on all the +C available FITSIO subroutines. + +C Call each subroutine in turn: + + call writeimage + call writeascii + call writebintable + call copyhdu + call selectrows + call readheader + call readimage + call readtable + print * + print *,"All the fitsio cookbook routines ran successfully." + + end +C ************************************************************************* + subroutine writeimage + +C Create a FITS primary array containing a 2-D image + + integer status,unit,blocksize,bitpix,naxis,naxes(2) + integer i,j,group,fpixel,nelements,array(300,200) + character filename*80 + logical simple,extend + +C The STATUS parameter must be initialized before using FITSIO. A +C positive value of STATUS is returned whenever a serious error occurs. +C FITSIO uses an `inherited status' convention, which means that if a +C subroutine is called with a positive input value of STATUS, then the +C subroutine will exit immediately, preserving the status value. For +C simplicity, this program only checks the status value at the end of +C the program, but it is usually better practice to check the status +C value more frequently. + + status=0 + +C Name of the FITS file to be created: + filename='ATESTFILEZ.FITS' + +C Delete the file if it already exists, so we can then recreate it. +C The deletefile subroutine is listed at the end of this file. + call deletefile(filename,status) + +C Get an unused Logical Unit Number to use to open the FITS file. +C This routine is not required; programmers can choose any unused +C unit number to open the file. + call ftgiou(unit,status) + +C Create the new empty FITS file. The blocksize parameter is a +C historical artifact and the value is ignored by FITSIO. + blocksize=1 + call ftinit(unit,filename,blocksize,status) + +C Initialize parameters about the FITS image. +C BITPIX = 16 means that the image pixels will consist of 16-bit +C integers. The size of the image is given by the NAXES values. +C The EXTEND = TRUE parameter indicates that the FITS file +C may contain extensions following the primary array. + simple=.true. + bitpix=16 + naxis=2 + naxes(1)=300 + naxes(2)=200 + extend=.true. + +C Write the required header keywords to the file + call ftphpr(unit,simple,bitpix,naxis,naxes,0,1,extend,status) + +C Initialize the values in the image with a linear ramp function + do j=1,naxes(2) + do i=1,naxes(1) + array(i,j)=i - 1 +j - 1 + end do + end do + +C Write the array to the FITS file. +C The last letter of the subroutine name defines the datatype of the +C array argument; in this case the 'J' indicates that the array has an +C integer*4 datatype. ('I' = I*2, 'E' = Real*4, 'D' = Real*8). +C The 2D array is treated as a single 1-D array with NAXIS1 * NAXIS2 +C total number of pixels. GROUP is seldom used parameter that should +C almost always be set = 1. + group=1 + fpixel=1 + nelements=naxes(1)*naxes(2) + call ftpprj(unit,group,fpixel,nelements,array,status) + +C Write another optional keyword to the header +C The keyword record will look like this in the FITS file: +C +C EXPOSURE= 1500 / Total Exposure Time +C + call ftpkyj(unit,'EXPOSURE',1500,'Total Exposure Time',status) + +C The FITS file must always be closed before exiting the program. +C Any unit numbers allocated with FTGIOU must be freed with FTFIOU. + call ftclos(unit, status) + call ftfiou(unit, status) + +C Check for any errors, and if so print out error messages. +C The PRINTERROR subroutine is listed near the end of this file. + if (status .gt. 0)call printerror(status) + end +C ************************************************************************* + subroutine writeascii + +C Create an ASCII table containing 3 columns and 6 rows. For convenience, +C the ASCII table extension is appended to the FITS image file created +C previously by the WRITEIMAGE subroutine. + + integer status,unit,readwrite,blocksize,tfields,nrows,rowlen + integer nspace,tbcol(3),diameter(6), colnum,frow,felem + real density(6) + character filename*40,extname*16 + character*16 ttype(3),tform(3),tunit(3),name(6) + data ttype/'Planet','Diameter','Density'/ + data tform/'A8','I6','F4.2'/ + data tunit/' ','km','g/cm'/ + data name/'Mercury','Venus','Earth','Mars','Jupiter','Saturn'/ + data diameter/4880,12112,12742,6800,143000,121000/ + data density/5.1,5.3,5.52,3.94,1.33,0.69/ + +C The STATUS parameter must always be initialized. + status=0 + +C Name of the FITS file to append the ASCII table to: + filename='ATESTFILEZ.FITS' + +C Get an unused Logical Unit Number to use to open the FITS file. + call ftgiou(unit,status) + +C Open the FITS file with write access. +C (readwrite = 0 would open the file with readonly access). + readwrite=1 + call ftopen(unit,filename,readwrite,blocksize,status) + +C FTCRHD creates a new empty FITS extension following the current +C extension and moves to it. In this case, FITSIO was initially +C positioned on the primary array when the FITS file was first opened, so +C FTCRHD appends an empty extension and moves to it. All future FITSIO +C calls then operate on the new extension (which will be an ASCII +C table). + call ftcrhd(unit,status) + +C define parameters for the ASCII table (see the above data statements) + tfields=3 + nrows=6 + extname='PLANETS_ASCII' + +C FTGABC is a convenient subroutine for calculating the total width of +C the table and the starting position of each column in an ASCII table. +C Any number of blank spaces (including zero) may be inserted between +C each column of the table, as specified by the NSPACE parameter. + nspace=1 + call ftgabc(tfields,tform,nspace,rowlen,tbcol,status) + +C FTPHTB writes all the required header keywords which define the +C structure of the ASCII table. NROWS and TFIELDS give the number of +C rows and columns in the table, and the TTYPE, TBCOL, TFORM, and TUNIT +C arrays give the column name, starting position, format, and units, +C respectively of each column. The values of the ROWLEN and TBCOL parameters +C were previously calculated by the FTGABC routine. + call ftphtb(unit,rowlen,nrows,tfields,ttype,tbcol,tform,tunit, + & extname,status) + +C Write names to the first column, diameters to 2nd col., and density to 3rd +C FTPCLS writes the string values to the NAME column (column 1) of the +C table. The FTPCLJ and FTPCLE routines write the diameter (integer) and +C density (real) value to the 2nd and 3rd columns. The FITSIO routines +C are column oriented, so it is usually easier to read or write data in a +C table in a column by column order rather than row by row. + frow=1 + felem=1 + colnum=1 + call ftpcls(unit,colnum,frow,felem,nrows,name,status) + colnum=2 + call ftpclj(unit,colnum,frow,felem,nrows,diameter,status) + colnum=3 + call ftpcle(unit,colnum,frow,felem,nrows,density,status) + +C The FITS file must always be closed before exiting the program. +C Any unit numbers allocated with FTGIOU must be freed with FTFIOU. + call ftclos(unit, status) + call ftfiou(unit, status) + +C Check for any error, and if so print out error messages. +C The PRINTERROR subroutine is listed near the end of this file. + if (status .gt. 0)call printerror(status) + end +C ************************************************************************* + subroutine writebintable + +C This routine creates a FITS binary table, or BINTABLE, containing +C 3 columns and 6 rows. This routine is nearly identical to the +C previous WRITEASCII routine, except that the call to FTGABC is not +C needed, and FTPHBN is called rather than FTPHTB to write the +C required header keywords. + + integer status,unit,readwrite,blocksize,hdutype,tfields,nrows + integer varidat,diameter(6), colnum,frow,felem + real density(6) + character filename*40,extname*16 + character*16 ttype(3),tform(3),tunit(3),name(6) + data ttype/'Planet','Diameter','Density'/ + data tform/'8A','1J','1E'/ + data tunit/' ','km','g/cm'/ + data name/'Mercury','Venus','Earth','Mars','Jupiter','Saturn'/ + data diameter/4880,12112,12742,6800,143000,121000/ + data density/5.1,5.3,5.52,3.94,1.33,0.69/ + +C The STATUS parameter must always be initialized. + status=0 + +C Name of the FITS file to append the ASCII table to: + filename='ATESTFILEZ.FITS' + +C Get an unused Logical Unit Number to use to open the FITS file. + call ftgiou(unit,status) + +C Open the FITS file, with write access. + readwrite=1 + call ftopen(unit,filename,readwrite,blocksize,status) + +C Move to the last (2nd) HDU in the file (the ASCII table). + call ftmahd(unit,2,hdutype,status) + +C Append/create a new empty HDU onto the end of the file and move to it. + call ftcrhd(unit,status) + +C Define parameters for the binary table (see the above data statements) + tfields=3 + nrows=6 + extname='PLANETS_BINARY' + varidat=0 + +C FTPHBN writes all the required header keywords which define the +C structure of the binary table. NROWS and TFIELDS gives the number of +C rows and columns in the table, and the TTYPE, TFORM, and TUNIT arrays +C give the column name, format, and units, respectively of each column. + call ftphbn(unit,nrows,tfields,ttype,tform,tunit, + & extname,varidat,status) + +C Write names to the first column, diameters to 2nd col., and density to 3rd +C FTPCLS writes the string values to the NAME column (column 1) of the +C table. The FTPCLJ and FTPCLE routines write the diameter (integer) and +C density (real) value to the 2nd and 3rd columns. The FITSIO routines +C are column oriented, so it is usually easier to read or write data in a +C table in a column by column order rather than row by row. Note that +C the identical subroutine calls are used to write to either ASCII or +C binary FITS tables. + frow=1 + felem=1 + colnum=1 + call ftpcls(unit,colnum,frow,felem,nrows,name,status) + colnum=2 + call ftpclj(unit,colnum,frow,felem,nrows,diameter,status) + colnum=3 + call ftpcle(unit,colnum,frow,felem,nrows,density,status) + +C The FITS file must always be closed before exiting the program. +C Any unit numbers allocated with FTGIOU must be freed with FTFIOU. + call ftclos(unit, status) + call ftfiou(unit, status) + +C Check for any error, and if so print out error messages. +C The PRINTERROR subroutine is listed near the end of this file. + if (status .gt. 0)call printerror(status) + end +C ************************************************************************* + subroutine copyhdu + +C Copy the 1st and 3rd HDUs from the input file to a new FITS file + + integer status,inunit,outunit,readwrite,blocksize,morekeys,hdutype + character infilename*40,outfilename*40 + +C The STATUS parameter must always be initialized. + status=0 + +C Name of the FITS files: + infilename='ATESTFILEZ.FITS' + outfilename='BTESTFILEZ.FITS' + +C Delete the file if it already exists, so we can then recreate it +C The deletefile subroutine is listed at the end of this file. + call deletefile(outfilename,status) + +C Get unused Logical Unit Numbers to use to open the FITS files. + call ftgiou(inunit,status) + call ftgiou(outunit,status) + +C Open the input FITS file, with readonly access + readwrite=0 + call ftopen(inunit,infilename,readwrite,blocksize,status) + +C Create the new empty FITS file (value of blocksize is ignored) + blocksize=1 + call ftinit(outunit,outfilename,blocksize,status) + +C FTCOPY copies the current HDU from the input FITS file to the output +C file. The MOREKEY parameter allows one to reserve space for additional +C header keywords when the HDU is created. FITSIO will automatically +C insert more header space if required, so programmers do not have to +C reserve space ahead of time, although it is more efficient to do so if +C it is known that more keywords will be appended to the header. + morekeys=0 + call ftcopy(inunit,outunit,morekeys,status) + +C Append/create a new empty extension on the end of the output file + call ftcrhd(outunit,status) + +C Skip to the 3rd extension in the input file which in this case +C is the binary table created by the previous WRITEBINARY routine. + call ftmahd(inunit,3,hdutype,status) + +C FTCOPY now copies the binary table from the input FITS file +C to the output file. + call ftcopy(inunit,outunit,morekeys,status) + +C The FITS files must always be closed before exiting the program. +C Any unit numbers allocated with FTGIOU must be freed with FTFIOU. +C Giving -1 for the value of the first argument causes all previously +C allocated unit numbers to be released. + + call ftclos(inunit, status) + call ftclos(outunit, status) + call ftfiou(-1, status) + +C Check for any error, and if so print out error messages. +C The PRINTERROR subroutine is listed near the end of this file. + if (status .gt. 0)call printerror(status) + end +C ************************************************************************* + subroutine selectrows + +C This routine copies selected rows from an input table into a new output +C FITS table. In this example all the rows in the input table that have +C a value of the DENSITY column less that 3.0 are copied to the output +C table. This program illustrates several generally useful techniques, +C including: +C how to locate the end of a FITS file +C how to create a table when the total number of rows in the table +C is not known until the table is completed +C how to efficiently copy entire rows from one table to another. + + integer status,inunit,outunit,readwrite,blocksize,hdutype + integer nkeys,nspace,naxes(2),nfound,colnum,frow,felem + integer noutrows,irow,temp(100),i + real nullval,density(6) + character infilename*40,outfilename*40,record*80 + logical exact,anynulls + +C The STATUS parameter must always be initialized. + status=0 + +C Names of the FITS files: + infilename='ATESTFILEZ.FITS' + outfilename='BTESTFILEZ.FITS' + +C Get unused Logical Unit Numbers to use to open the FITS files. + call ftgiou(inunit,status) + call ftgiou(outunit,status) + +C The input FITS file is opened with READONLY access, and the output +C FITS file is opened with WRITE access. + readwrite=0 + call ftopen(inunit,infilename,readwrite,blocksize,status) + readwrite=1 + call ftopen(outunit,outfilename,readwrite,blocksize,status) + +C move to the 3rd HDU in the input file (a binary table in this case) + call ftmahd(inunit,3,hdutype,status) + +C This do-loop illustrates how to move to the last extension in any FITS +C file. The call to FTMRHD moves one extension at a time through the +C FITS file until an `End-of-file' status value (= 107) is returned. + do while (status .eq. 0) + call ftmrhd(outunit,1,hdutype,status) + end do + +C After locating the end of the FITS file, it is necessary to reset the +C status value to zero and also clear the internal error message stack +C in FITSIO. The previous `End-of-file' error will have produced +C an unimportant message on the error stack which can be cleared with +C the call to the FTCMSG routine (which has no arguments). + + if (status .eq. 107)then + status=0 + call ftcmsg + end if + +C Create a new empty extension in the output file. + call ftcrhd(outunit,status) + +C Find the number of keywords in the input table header. + call ftghsp(inunit,nkeys,nspace,status) + +C This do-loop of calls to FTGREC and FTPREC copies all the keywords from +C the input to the output FITS file. Notice that the specified number +C of rows in the output table, as given by the NAXIS2 keyword, will be +C incorrect. This value will be modified later after it is known how many +C rows will be in the table, so it does not matter how many rows are specified +C initially. + do i=1,nkeys + call ftgrec(inunit,i,record,status) + call ftprec(outunit,record,status) + end do + +C FTGKNJ is used to get the value of the NAXIS1 and NAXIS2 keywords, +C which define the width of the table in bytes, and the number of +C rows in the table. + call ftgknj(inunit,'NAXIS',1,2,naxes,nfound,status) + +C FTGCNO gets the column number of the `DENSITY' column; the column +C number is needed when reading the data in the column. The EXACT +C parameter determines whether or not the match to the column names +C will be case sensitive. + exact=.false. + call ftgcno(inunit,exact,'DENSITY',colnum,status) + +C FTGCVE reads all 6 rows of data in the `DENSITY' column. The number +C of rows in the table is given by NAXES(2). Any null values in the +C table will be returned with the corresponding value set to -99 +C (= the value of NULLVAL). The ANYNULLS parameter will be set to TRUE +C if any null values were found while reading the data values in the table. + frow=1 + felem=1 + nullval=-99. + call ftgcve(inunit,colnum,frow,felem,naxes(2),nullval, + & density,anynulls,status) + +C If the density is less than 3.0, copy the row to the output table. +C FTGTBB and FTPTBB are low-level routines to read and write, respectively, +C a specified number of bytes in the table, starting at the specified +C row number and beginning byte within the row. These routines do +C not do any interpretation of the bytes, and simply pass them to or +C from the FITS file without any modification. This is a faster +C way of transferring large chunks of data from one FITS file to another, +C than reading and then writing each column of data individually. +C In this case an entire row of bytes (the row length is specified +C by the naxes(1) parameter) is transferred. The datatype of the +C buffer array (TEMP in this case) is immaterial so long as it is +C declared large enough to hold the required number of bytes. + noutrows=0 + do irow=1,naxes(2) + if (density(irow) .lt. 3.0)then + noutrows=noutrows+1 + call ftgtbb(inunit,irow,1,naxes(1),temp,status) + call ftptbb(outunit,noutrows,1,naxes(1),temp,status) + end if + end do + +C Update the NAXIS2 keyword with the correct no. of rows in the output file. +C After all the rows have been written to the output table, the +C FTMKYJ routine is used to overwrite the NAXIS2 keyword value with +C the correct number of rows. Specifying `\&' for the comment string +C tells FITSIO to keep the current comment string in the keyword and +C only modify the value. Because the total number of rows in the table +C was unknown when the table was first created, any value (including 0) +C could have been used for the initial NAXIS2 keyword value. + call ftmkyj(outunit,'NAXIS2',noutrows,'&',status) + +C The FITS files must always be closed before exiting the program. +C Any unit numbers allocated with FTGIOU must be freed with FTFIOU. + call ftclos(inunit, status) + call ftclos(outunit, status) + call ftfiou(-1, status) + +C Check for any error, and if so print out error messages. +C The PRINTERROR subroutine is listed near the end of this file. + if (status .gt. 0)call printerror(status) + end +C ************************************************************************* + subroutine readheader + +C Print out all the header keywords in all extensions of a FITS file + + integer status,unit,readwrite,blocksize,nkeys,nspace,hdutype,i,j + character filename*80,record*80 + +C The STATUS parameter must always be initialized. + status=0 + +C Get an unused Logical Unit Number to use to open the FITS file. + call ftgiou(unit,status) + +C name of FITS file + filename='ATESTFILEZ.FITS' + +C open the FITS file, with read-only access. The returned BLOCKSIZE +C parameter is obsolete and should be ignored. + readwrite=0 + call ftopen(unit,filename,readwrite,blocksize,status) + + j = 0 +100 continue + j = j + 1 + + print *,'Header listing for HDU', j + +C The FTGHSP subroutine returns the number of existing keywords in the +C current header data unit (CHDU), not counting the required END keyword, + call ftghsp(unit,nkeys,nspace,status) + +C Read each 80-character keyword record, and print it out. + do i = 1, nkeys + call ftgrec(unit,i,record,status) + print *,record + end do + +C Print out an END record, and a blank line to mark the end of the header. + if (status .eq. 0)then + print *,'END' + print *,' ' + end if + +C Try moving to the next extension in the FITS file, if it exists. +C The FTMRHD subroutine attempts to move to the next HDU, as specified by +C the second parameter. This subroutine moves by a relative number of +C HDUs from the current HDU. The related FTMAHD routine may be used to +C move to an absolute HDU number in the FITS file. If the end-of-file is +C encountered when trying to move to the specified extension, then a +C status = 107 is returned. + call ftmrhd(unit,1,hdutype,status) + + if (status .eq. 0)then +C success, so jump back and print out keywords in this extension + go to 100 + + else if (status .eq. 107)then +C hit end of file, so quit + status=0 + end if + +C The FITS file must always be closed before exiting the program. +C Any unit numbers allocated with FTGIOU must be freed with FTFIOU. + call ftclos(unit, status) + call ftfiou(unit, status) + +C Check for any error, and if so print out error messages. +C The PRINTERROR subroutine is listed near the end of this file. + if (status .gt. 0)call printerror(status) + end +C ************************************************************************* + subroutine readimage + +C Read a FITS image and determine the minimum and maximum pixel value. +C Rather than reading the entire image in +C at once (which could require a very large array), the image is read +C in pieces, 100 pixels at a time. + + integer status,unit,readwrite,blocksize,naxes(2),nfound + integer group,firstpix,nbuffer,npixels,i + real datamin,datamax,nullval,buffer(100) + logical anynull + character filename*80 + +C The STATUS parameter must always be initialized. + status=0 + +C Get an unused Logical Unit Number to use to open the FITS file. + call ftgiou(unit,status) + +C Open the FITS file previously created by WRITEIMAGE + filename='ATESTFILEZ.FITS' + readwrite=0 + call ftopen(unit,filename,readwrite,blocksize,status) + +C Determine the size of the image. + call ftgknj(unit,'NAXIS',1,2,naxes,nfound,status) + +C Check that it found both NAXIS1 and NAXIS2 keywords. + if (nfound .ne. 2)then + print *,'READIMAGE failed to read the NAXISn keywords.' + return + end if + +C Initialize variables + npixels=naxes(1)*naxes(2) + group=1 + firstpix=1 + nullval=-999 + datamin=1.0E30 + datamax=-1.0E30 + + do while (npixels .gt. 0) +C read up to 100 pixels at a time + nbuffer=min(100,npixels) + + call ftgpve(unit,group,firstpix,nbuffer,nullval, + & buffer,anynull,status) + +C find the min and max values + do i=1,nbuffer + datamin=min(datamin,buffer(i)) + datamax=max(datamax,buffer(i)) + end do + +C increment pointers and loop back to read the next group of pixels + npixels=npixels-nbuffer + firstpix=firstpix+nbuffer + end do + + print * + print *,'Min and max image pixels = ',datamin,datamax + +C The FITS file must always be closed before exiting the program. +C Any unit numbers allocated with FTGIOU must be freed with FTFIOU. + call ftclos(unit, status) + call ftfiou(unit, status) + +C Check for any error, and if so print out error messages. +C The PRINTERROR subroutine is listed near the end of this file. + if (status .gt. 0)call printerror(status) + end +C ************************************************************************* + subroutine readtable + +C Read and print data values from an ASCII or binary table +C This example reads and prints out all the data in the ASCII and +C the binary tables that were previously created by WRITEASCII and +C WRITEBINTABLE. Note that the exact same FITSIO routines are +C used to read both types of tables. + + integer status,unit,readwrite,blocksize,hdutype,ntable + integer felem,nelems,nullj,diameter,nfound,irow,colnum + real nulle,density + character filename*40,nullstr*1,name*8,ttype(3)*10 + logical anynull + +C The STATUS parameter must always be initialized. + status=0 + +C Get an unused Logical Unit Number to use to open the FITS file. + call ftgiou(unit,status) + +C Open the FITS file previously created by WRITEIMAGE + filename='ATESTFILEZ.FITS' + readwrite=0 + call ftopen(unit,filename,readwrite,blocksize,status) + +C Loop twice, first reading the ASCII table, then the binary table + do ntable=2,3 + +C Move to the next extension + call ftmahd(unit,ntable,hdutype,status) + + print *,' ' + if (hdutype .eq. 1)then + print *,'Reading ASCII table in HDU ',ntable + else if (hdutype .eq. 2)then + print *,'Reading binary table in HDU ',ntable + end if + +C Read the TTYPEn keywords, which give the names of the columns + call ftgkns(unit,'TTYPE',1,3,ttype,nfound,status) + write(*,2000)ttype +2000 format(2x,"Row ",3a10) + +C Read the data, one row at a time, and print them out + felem=1 + nelems=1 + nullstr=' ' + nullj=0 + nulle=0. + do irow=1,6 +C FTGCVS reads the NAMES from the first column of the table. + colnum=1 + call ftgcvs(unit,colnum,irow,felem,nelems,nullstr,name, + & anynull,status) + +C FTGCVJ reads the DIAMETER values from the second column. + colnum=2 + call ftgcvj(unit,colnum,irow,felem,nelems,nullj,diameter, + & anynull,status) + +C FTGCVE reads the DENSITY values from the third column. + colnum=3 + call ftgcve(unit,colnum,irow,felem,nelems,nulle,density, + & anynull,status) + write(*,2001)irow,name,diameter,density +2001 format(i5,a10,i10,f10.2) + end do + end do + +C The FITS file must always be closed before exiting the program. +C Any unit numbers allocated with FTGIOU must be freed with FTFIOU. + call ftclos(unit, status) + call ftfiou(unit, status) + +C Check for any error, and if so print out error messages. +C The PRINTERROR subroutine is listed near the end of this file. + if (status .gt. 0)call printerror(status) + end +C ************************************************************************* + subroutine printerror(status) + +C This subroutine prints out the descriptive text corresponding to the +C error status value and prints out the contents of the internal +C error message stack generated by FITSIO whenever an error occurs. + + integer status + character errtext*30,errmessage*80 + +C Check if status is OK (no error); if so, simply return + if (status .le. 0)return + +C The FTGERR subroutine returns a descriptive 30-character text string that +C corresponds to the integer error status number. A complete list of all +C the error numbers can be found in the back of the FITSIO User's Guide. + call ftgerr(status,errtext) + print *,'FITSIO Error Status =',status,': ',errtext + +C FITSIO usually generates an internal stack of error messages whenever +C an error occurs. These messages provide much more information on the +C cause of the problem than can be provided by the single integer error +C status value. The FTGMSG subroutine retrieves the oldest message from +C the stack and shifts any remaining messages on the stack down one +C position. FTGMSG is called repeatedly until a blank message is +C returned, which indicates that the stack is empty. Each error message +C may be up to 80 characters in length. Another subroutine, called +C FTCMSG, is available to simply clear the whole error message stack in +C cases where one is not interested in the contents. + call ftgmsg(errmessage) + do while (errmessage .ne. ' ') + print *,errmessage + call ftgmsg(errmessage) + end do + end +C ************************************************************************* + subroutine deletefile(filename,status) + +C A simple little routine to delete a FITS file + + integer status,unit,blocksize + character*(*) filename + +C Simply return if status is greater than zero + if (status .gt. 0)return + +C Get an unused Logical Unit Number to use to open the FITS file + call ftgiou(unit,status) + +C Try to open the file, to see if it exists + call ftopen(unit,filename,1,blocksize,status) + + if (status .eq. 0)then +C file was opened; so now delete it + call ftdelt(unit,status) + else if (status .eq. 103)then +C file doesn't exist, so just reset status to zero and clear errors + status=0 + call ftcmsg + else +C there was some other error opening the file; delete the file anyway + status=0 + call ftcmsg + call ftdelt(unit,status) + end if + +C Free the unit number for later reuse + call ftfiou(unit, status) + end diff --git a/external/cfitsio/crc32.c b/external/cfitsio/crc32.c new file mode 100644 index 0000000..08843ff --- /dev/null +++ b/external/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 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 +# 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 +# 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/external/cfitsio/crc32.h b/external/cfitsio/crc32.h new file mode 100644 index 0000000..8053b61 --- /dev/null +++ b/external/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/external/cfitsio/deflate.c b/external/cfitsio/deflate.c new file mode 100644 index 0000000..1c6a00c --- /dev/null +++ b/external/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)<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/external/cfitsio/deflate.h b/external/cfitsio/deflate.h new file mode 100644 index 0000000..6ac0a1e --- /dev/null +++ b/external/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/external/cfitsio/drvrfile.c b/external/cfitsio/drvrfile.c new file mode 100644 index 0000000..0303503 --- /dev/null +++ b/external/cfitsio/drvrfile.c @@ -0,0 +1,966 @@ +/* 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 +#include +#include "fitsio2.h" + +#if defined(unix) || defined(__unix__) || defined(__unix) +#include /* needed in file_openfile */ + +#ifdef REPLACE_LINKS +#include +#include +#endif + +#endif + +#ifdef HAVE_FTRUNCATE +#if defined(unix) || defined(__unix__) || defined(__unix) +#include /* 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]; + +#if defined(BUILD_HERA) + + /* special code to verify that the path to the file to be created */ + /* is within the users data directory on Hera */ + + int status = 0, rootlen, slen; + char *cpos; + char cwd[FLEN_FILENAME], absURL[FLEN_FILENAME]; + /* note that "/heradata/users/" is actually "/.hera_mountpnt/hera_users/" */ + char rootstring[]="/.hera_mountpnt/hera_users/"; + char username[FLEN_FILENAME], userroot[FLEN_FILENAME]; + + /* Get the current working directory */ + fits_get_cwd(cwd, &status); + slen = strlen(cwd); + if (cwd[slen-1] != '/') strcat(cwd,"/"); /* make sure the CWD ends with slash */ + +/* printf("CWD = %s\n", cwd); */ + + /* check that CWD string matches the rootstring */ + rootlen = strlen(rootstring); + if (strncmp(rootstring, cwd, rootlen)) { + ffpmsg("invalid CWD: does not match Hera data directory"); +/* ffpmsg(rootstring); */ + return(FILE_NOT_CREATED); + } else { + + /* get the user name from CWD (it follows the root string) */ + strcpy(username, cwd+rootlen); + cpos=strchr(username, '/'); + if (!cpos) { + ffpmsg("invalid CWD: not equal to Hera data directory + username"); +/* ffpmsg(cwd); */ + return(FILE_NOT_CREATED); + } else { + *(cpos+1) = '\0'; /* truncate user name string */ + + /* construct full user root name */ + strcpy(userroot, rootstring); + strcat(userroot, username); + rootlen = strlen(userroot); + + /* convert the input filename to absolute path relative to the CWD */ + fits_relurl2url(cwd, filename, absURL, &status); +/* + printf("username = %s\n", username); + printf("userroot = %s\n", userroot); + printf("filename = %s\n", filename); + printf("ABS = %s\n", absURL); +*/ + /* check that CWD string matches the rootstring */ + + if (strncmp(userroot, absURL, rootlen)) { + ffpmsg("invalid filename: path not within user directory"); +/* + ffpmsg(absURL); + ffpmsg(userroot); +*/ + return(FILE_NOT_CREATED); + } + } + } + /* if we got here, then the input filename appears to be valid */ + +#endif + + *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/external/cfitsio/drvrgsiftp.c b/external/cfitsio/drvrgsiftp.c new file mode 100644 index 0000000..ab9aaed --- /dev/null +++ b/external/cfitsio/drvrgsiftp.c @@ -0,0 +1,522 @@ + +/* This file, drvrgsiftp.c contains driver routines for gsiftp files. */ +/* Andrea Barisani */ +/* Taffoni Giuliano */ +#ifdef HAVE_NET_SERVICES +#ifdef HAVE_GSIFTP + +#include +#include +#include +#include +#include +#include "fitsio2.h" + +#include + +#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, + ¶llelism); + } + + 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, + ¶llelism); + } + + 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/external/cfitsio/drvrgsiftp.h b/external/cfitsio/drvrgsiftp.h new file mode 100644 index 0000000..bd0ec0d --- /dev/null +++ b/external/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/external/cfitsio/drvrmem.c b/external/cfitsio/drvrmem.c new file mode 100644 index 0000000..4ef23b7 --- /dev/null +++ b/external/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 +#include +#include /* 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/external/cfitsio/drvrnet.c b/external/cfitsio/drvrnet.c new file mode 100644 index 0000000..f0e1a42 --- /dev/null +++ b/external/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 + + + + 301 Moved Permanently + +

Moved Permanently

+ The document has moved here.

+ + + 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 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 + + + + All binary information is transfered in network format, so use htonl and + ntohl to convert back and forth. + + :== 4 byte length, in network format, the len doesn't include the + length of + :== one of the message opcodes below, 4 bytes, network format + :== depends on opcode + + The response is of the same form with the same opcode sent. Success is + indicated by 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, 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 . Once + again the trailing null is sent. Once again a ROOTD_AUTH message is + returned + + ROOTD_OPEN - 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 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 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 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(unix) || defined(__unix__) || defined(__unix) +#include +#endif + +#include +#include +#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," 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 + + + + 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 + + + + + */ + + 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 +#include +#include +#include +#include +#include +#include + +#if defined(unix) || defined(__unix__) || defined(__unix) +#include +#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>\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>\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 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_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/external/cfitsio/drvrsmem.h b/external/cfitsio/drvrsmem.h new file mode 100644 index 0000000..52ac7d7 --- /dev/null +++ b/external/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 /* this is necessary for Solaris/Linux */ +#include +#include + +#ifdef _AIX +#include +#else +#include +#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/external/cfitsio/editcol.c b/external/cfitsio/editcol.c new file mode 100644 index 0000000..dc82f02 --- /dev/null +++ b/external/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 +#include +#include +#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/external/cfitsio/edithdu.c b/external/cfitsio/edithdu.c new file mode 100644 index 0000000..385bbe9 --- /dev/null +++ b/external/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 +#include +#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/external/cfitsio/eval.l b/external/cfitsio/eval.l new file mode 100644 index 0000000..c6e3cf8 --- /dev/null +++ b/external/cfitsio/eval.l @@ -0,0 +1,545 @@ +%{ +/************************************************************************/ +/* */ +/* 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 +#include +#include +#ifdef sparc +#include +#else +#include +#endif +#include "eval_defs.h" + +ParseData gParse; /* Global structure holding all parser information */ + +/***** Internal functions *****/ + + int yyGetVariable( char *varName, YYSTYPE *varVal ); + +static int find_variable( char *varName ); +static int expr_read( char *buf, int nbytes ); + +/***** Definitions *****/ + +#define YY_NO_UNPUT /* Don't include YYUNPUT function */ +#define YY_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 YY_INPUT +#define YY_INPUT(buf,result,max_size) \ + if ( (result = expr_read( (char *) buf, max_size )) < 0 ) \ + YY_FATAL_ERROR( "read() in flex scanner failed" ); + +%} +bit ([bB][01xX]+) +oct ([oO][01234567xX]+) +hex ([hH][0123456789aAbBcCdDeEfFxX]+) +integer [0-9]+ +boolean (t|f|T|F) +real ([0-9]*"."[0-9]+)|([0-9]*"."*[0-9]+[eEdD][+-]?[0-9]+)|([0-9]*".") +constant ("#"[a-zA-Z0-9_]+)|("#""$"[^\n]*"$") +string ([\"][^\"\n]*[\"])|([\'][^\'\n]*[\']) +variable ([a-zA-Z_][a-zA-Z0-9_]*)|("$"[^$\n]*"$") +function [a-zA-Z][a-zA-Z0-9]+"(" +intcast ("(int)"|"(INT)") +fltcast ("(float)"|"(FLOAT)"|"(double)"|"(DOUBLE)") +power ("^"|"**") +not ("!"|".not."|".NOT."|"not."|"NOT.") +or ("||"|".or."|".OR."|"or."|"OR.") +and ("&&"|".and."|".AND."|"and."|"AND.") +equal ("=="|".eq."|".EQ."|"eq."|"EQ.") +not_equal ("!="|".ne."|".NE."|"ne."|"NE.") +greater (">"|".gt."|".GT."|"gt."|"GT.") +lesser ("<"|".lt."|".LT."|"lt."|"LT.") +greater_eq (">="|"=>"|".ge."|".GE."|"ge."|"GE.") +lesser_eq ("<="|"=<"|".le."|".LE."|"le."|"LE.") +nl \n + +%% + +[ \t]+ ; +{bit} { + int len; + len = strlen(yytext); + while (yytext[len] == ' ') + len--; + len = len - 1; + strncpy(yylval.str,&yytext[1],len); + yylval.str[len] = '\0'; + return( BITSTR ); + } +{oct} { + int len; + char tmpstring[256]; + char bitstring[256]; + len = strlen(yytext); + if (len >= 256) { + char errMsg[100]; + gParse.status = PARSE_SYNTAX_ERR; + strcpy (errMsg,"Bit string exceeds maximum length: '"); + strncat(errMsg, &(yytext[0]), 20); + strcat (errMsg,"...'"); + ffpmsg (errMsg); + len = 0; + } else { + while (yytext[len] == ' ') + len--; + len = len - 1; + strncpy(tmpstring,&yytext[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( yylval.str, bitstring ); + return( BITSTR ); + } +{hex} { + int len; + char tmpstring[256]; + char bitstring[256]; + len = strlen(yytext); + if (len >= 256) { + char errMsg[100]; + gParse.status = PARSE_SYNTAX_ERR; + strcpy (errMsg,"Hex string exceeds maximum length: '"); + strncat(errMsg, &(yytext[0]), 20); + strcat (errMsg,"...'"); + ffpmsg (errMsg); + len = 0; + } else { + while (yytext[len] == ' ') + len--; + len = len - 1; + strncpy(tmpstring,&yytext[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( yylval.str, bitstring ); + return( BITSTR ); + } +{integer} { + yylval.lng = atol(yytext); + return( LONG ); + } +{boolean} { + if ((yytext[0] == 't') || (yytext[0] == 'T')) + yylval.log = 1; + else + yylval.log = 0; + return( BOOLEAN ); + } +{real} { + yylval.dbl = atof(yytext); + return( DOUBLE ); + } +{constant} { + if( !strcasecmp(yytext,"#PI") ) { + yylval.dbl = (double)(4) * atan((double)(1)); + return( DOUBLE ); + } else if( !strcasecmp(yytext,"#E") ) { + yylval.dbl = exp((double)(1)); + return( DOUBLE ); + } else if( !strcasecmp(yytext,"#DEG") ) { + yylval.dbl = ((double)4)*atan((double)1)/((double)180); + return( DOUBLE ); + } else if( !strcasecmp(yytext,"#ROW") ) { + return( ROWREF ); + } else if( !strcasecmp(yytext,"#NULL") ) { + return( NULLREF ); + } else if( !strcasecmp(yytext,"#SNULL") ) { + return( SNULLREF ); + } else { + int len; + if (yytext[1] == '$') { + len = strlen(yytext) - 3; + yylval.str[0] = '#'; + strncpy(yylval.str+1,&yytext[2],len); + yylval.str[len+1] = '\0'; + yytext = yylval.str; + } + return( (*gParse.getData)(yytext, &yylval) ); + } + } +{string} { + int len; + len = strlen(yytext) - 2; + if (len >= MAX_STRLEN) { + char errMsg[100]; + gParse.status = PARSE_SYNTAX_ERR; + strcpy (errMsg,"String exceeds maximum length: '"); + strncat(errMsg, &(yytext[1]), 20); + strcat (errMsg,"...'"); + ffpmsg (errMsg); + len = 0; + } else { + strncpy(yylval.str,&yytext[1],len); + } + yylval.str[len] = '\0'; + return( STRING ); + } +{variable} { + int len,type; + + if (yytext[0] == '$') { + len = strlen(yytext) - 2; + strncpy(yylval.str,&yytext[1],len); + yylval.str[len] = '\0'; + yytext = yylval.str; + } + type = yyGetVariable(yytext, &yylval); + return( type ); + } +{function} { + char *fname; + int len=0; + fname = &yylval.str[0]; + while( (fname[len]=toupper(yytext[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 ); + } +{intcast} { return( INTCAST ); } +{fltcast} { return( FLTCAST ); } +{power} { return( POWER ); } +{not} { return( NOT ); } +{or} { return( OR ); } +{and} { return( AND ); } +{equal} { return( EQ ); } +{not_equal} { return( NE ); } +{greater} { return( GT ); } +{lesser} { return( LT ); } +{greater_eq} { return( GTE ); } +{lesser_eq} { return( LTE ); } +{nl} { return( '\n' ); } +. { return( yytext[0] ); } +%% + +int yywrap() +{ + /* MJT -- 13 June 1996 + Supplied for compatibility with + pre-2.5.1 versions of flex which + do not recognize %option noyywrap + */ + 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 ((nlng = varNum; + } + return( type ); +} + +static int find_variable(char *varName) +{ + int i; + + if( gParse.nCols ) + for( i=0; i 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/external/cfitsio/eval.y b/external/cfitsio/eval.y new file mode 100644 index 0000000..1f5fd99 --- /dev/null +++ b/external/cfitsio/eval.y @@ -0,0 +1,5837 @@ +%{ +/************************************************************************/ +/* */ +/* 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 + +#include + +#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 YYINITDEPTH 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 YYNEWBACKUP(token, value) \ + do \ + if (yychar == YYEMPTY ) \ + { yychar = (token); \ + memcpy( &yylval, &(value), sizeof(value) ); \ + yychar1 = YYTRANSLATE (yychar); \ + while (yylen--) YYPOPSTACK; \ + yystate = *yyssp; \ + goto yybackup; \ + } \ + else \ + { yyerror ("syntax error: cannot back up"); YYERROR; } \ + while (0) + +/***************************************************************/ +/* Useful macros for accessing/testing Nodes */ +/***************************************************************/ + +#define TEST(a) if( (a)<0 ) YYERROR +#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 yyerror(char *msg); + +#ifdef __cplusplus + } +#endif + +%} + +%union { + int Node; /* Index of Node */ + double dbl; /* real value */ + long lng; /* integer value */ + char log; /* logical value */ + char str[MAX_STRLEN]; /* string value */ +} + +%token BOOLEAN /* First 3 must be in order of */ +%token LONG /* increasing promotion for later use */ +%token DOUBLE +%token STRING +%token BITSTR +%token FUNCTION +%token BFUNCTION /* Bit function */ +%token IFUNCTION /* Integer function */ +%token GTIFILTER +%token REGFILTER +%token COLUMN +%token BCOLUMN +%token SCOLUMN +%token BITCOL +%token ROWREF +%token NULLREF +%token SNULLREF + +%type expr +%type bexpr +%type sexpr +%type bits +%type vector +%type bvector + +%left ',' '=' ':' '{' '}' +%right '?' +%left OR +%left AND +%left EQ NE '~' +%left GT LT LTE GTE +%left '+' '-' '%' +%left '*' '/' +%left '|' '&' +%right POWER +%left NOT +%left INTCAST FLTCAST +%left UMINUS +%left '[' + +%right ACCUM DIFF + +%% + +lines: /* nothing ; was | lines line */ + | lines line + ; + +line: '\n' {} + | expr '\n' + { if( $1<0 ) { + yyerror("Couldn't build node structure: out of memory?"); + YYERROR; } + gParse.resultNode = $1; + } + | bexpr '\n' + { if( $1<0 ) { + yyerror("Couldn't build node structure: out of memory?"); + YYERROR; } + gParse.resultNode = $1; + } + | sexpr '\n' + { if( $1<0 ) { + yyerror("Couldn't build node structure: out of memory?"); + YYERROR; } + gParse.resultNode = $1; + } + | bits '\n' + { if( $1<0 ) { + yyerror("Couldn't build node structure: out of memory?"); + YYERROR; } + gParse.resultNode = $1; + } + | error '\n' { yyerrok; } + ; + +bvector: '{' bexpr + { $$ = New_Vector( $2 ); TEST($$); } + | bvector ',' bexpr + { + if( gParse.Nodes[$1].nSubNodes >= MAXSUBS ) { + $1 = Close_Vec( $1 ); TEST($1); + $$ = New_Vector( $1 ); TEST($$); + } else { + $$ = $1; + } + gParse.Nodes[$$].SubNodes[ gParse.Nodes[$$].nSubNodes++ ] + = $3; + } + ; + +vector: '{' expr + { $$ = New_Vector( $2 ); TEST($$); } + | vector ',' expr + { + if( TYPE($1) < TYPE($3) ) + TYPE($1) = TYPE($3); + if( gParse.Nodes[$1].nSubNodes >= MAXSUBS ) { + $1 = Close_Vec( $1 ); TEST($1); + $$ = New_Vector( $1 ); TEST($$); + } else { + $$ = $1; + } + gParse.Nodes[$$].SubNodes[ gParse.Nodes[$$].nSubNodes++ ] + = $3; + } + | vector ',' bexpr + { + if( gParse.Nodes[$1].nSubNodes >= MAXSUBS ) { + $1 = Close_Vec( $1 ); TEST($1); + $$ = New_Vector( $1 ); TEST($$); + } else { + $$ = $1; + } + gParse.Nodes[$$].SubNodes[ gParse.Nodes[$$].nSubNodes++ ] + = $3; + } + | bvector ',' expr + { + TYPE($1) = TYPE($3); + if( gParse.Nodes[$1].nSubNodes >= MAXSUBS ) { + $1 = Close_Vec( $1 ); TEST($1); + $$ = New_Vector( $1 ); TEST($$); + } else { + $$ = $1; + } + gParse.Nodes[$$].SubNodes[ gParse.Nodes[$$].nSubNodes++ ] + = $3; + } + ; + +expr: vector '}' + { $$ = Close_Vec( $1 ); TEST($$); } + ; + +bexpr: bvector '}' + { $$ = Close_Vec( $1 ); TEST($$); } + ; + +bits: BITSTR + { + $$ = New_Const( BITSTR, $1, strlen($1)+1 ); TEST($$); + SIZE($$) = strlen($1); } + | BITCOL + { $$ = New_Column( $1 ); TEST($$); } + | BITCOL '{' expr '}' + { + if( TYPE($3) != LONG + || OPER($3) != CONST_OP ) { + yyerror("Offset argument must be a constant integer"); + YYERROR; + } + $$ = New_Offset( $1, $3 ); TEST($$); + } + | bits '&' bits + { $$ = New_BinOp( BITSTR, $1, '&', $3 ); TEST($$); + SIZE($$) = ( SIZE($1)>SIZE($3) ? SIZE($1) : SIZE($3) ); } + | bits '|' bits + { $$ = New_BinOp( BITSTR, $1, '|', $3 ); TEST($$); + SIZE($$) = ( SIZE($1)>SIZE($3) ? SIZE($1) : SIZE($3) ); } + | bits '+' bits + { + if (SIZE($1)+SIZE($3) >= MAX_STRLEN) { + yyerror("Combined bit string size exceeds " MAX_STRLEN_S " bits"); + YYERROR; + } + $$ = New_BinOp( BITSTR, $1, '+', $3 ); TEST($$); + SIZE($$) = SIZE($1) + SIZE($3); + } + | bits '[' expr ']' + { $$ = New_Deref( $1, 1, $3, 0, 0, 0, 0 ); TEST($$); } + | bits '[' expr ',' expr ']' + { $$ = New_Deref( $1, 2, $3, $5, 0, 0, 0 ); TEST($$); } + | bits '[' expr ',' expr ',' expr ']' + { $$ = New_Deref( $1, 3, $3, $5, $7, 0, 0 ); TEST($$); } + | bits '[' expr ',' expr ',' expr ',' expr ']' + { $$ = New_Deref( $1, 4, $3, $5, $7, $9, 0 ); TEST($$); } + | bits '[' expr ',' expr ',' expr ',' expr ',' expr ']' + { $$ = New_Deref( $1, 5, $3, $5, $7, $9, $11 ); TEST($$); } + | NOT bits + { $$ = New_Unary( BITSTR, NOT, $2 ); TEST($$); } + + | '(' bits ')' + { $$ = $2; } + ; + +expr: LONG + { $$ = New_Const( LONG, &($1), sizeof(long) ); TEST($$); } + | DOUBLE + { $$ = New_Const( DOUBLE, &($1), sizeof(double) ); TEST($$); } + | COLUMN + { $$ = New_Column( $1 ); TEST($$); } + | COLUMN '{' expr '}' + { + if( TYPE($3) != LONG + || OPER($3) != CONST_OP ) { + yyerror("Offset argument must be a constant integer"); + YYERROR; + } + $$ = New_Offset( $1, $3 ); TEST($$); + } + | ROWREF + { $$ = New_Func( LONG, row_fct, 0, 0, 0, 0, 0, 0, 0, 0 ); } + | NULLREF + { $$ = New_Func( LONG, null_fct, 0, 0, 0, 0, 0, 0, 0, 0 ); } + | expr '%' expr + { PROMOTE($1,$3); $$ = New_BinOp( TYPE($1), $1, '%', $3 ); + TEST($$); } + | expr '+' expr + { PROMOTE($1,$3); $$ = New_BinOp( TYPE($1), $1, '+', $3 ); + TEST($$); } + | expr '-' expr + { PROMOTE($1,$3); $$ = New_BinOp( TYPE($1), $1, '-', $3 ); + TEST($$); } + | expr '*' expr + { PROMOTE($1,$3); $$ = New_BinOp( TYPE($1), $1, '*', $3 ); + TEST($$); } + | expr '/' expr + { PROMOTE($1,$3); $$ = New_BinOp( TYPE($1), $1, '/', $3 ); + TEST($$); } + | expr POWER expr + { PROMOTE($1,$3); $$ = New_BinOp( TYPE($1), $1, POWER, $3 ); + TEST($$); } + | '+' expr %prec UMINUS + { $$ = $2; } + | '-' expr %prec UMINUS + { $$ = New_Unary( TYPE($2), UMINUS, $2 ); TEST($$); } + | '(' expr ')' + { $$ = $2; } + | expr '*' bexpr + { $3 = New_Unary( TYPE($1), 0, $3 ); + $$ = New_BinOp( TYPE($1), $1, '*', $3 ); + TEST($$); } + | bexpr '*' expr + { $1 = New_Unary( TYPE($3), 0, $1 ); + $$ = New_BinOp( TYPE($3), $1, '*', $3 ); + TEST($$); } + | bexpr '?' expr ':' expr + { + PROMOTE($3,$5); + if( ! Test_Dims($3,$5) ) { + yyerror("Incompatible dimensions in '?:' arguments"); + YYERROR; + } + $$ = New_Func( 0, ifthenelse_fct, 3, $3, $5, $1, + 0, 0, 0, 0 ); + TEST($$); + if( SIZE($3)=SIZE($4) && Test_Dims( $2, $4 ) ) { + PROMOTE($2,$4); + $$ = New_Func( 0, defnull_fct, 2, $2, $4, 0, + 0, 0, 0, 0 ); + TEST($$); + } else { + yyerror("Dimensions of DEFNULL arguments " + "are not compatible"); + YYERROR; + } + } else if (FSTRCMP($1,"ARCTAN2(") == 0) { + if( TYPE($2) != DOUBLE ) $2 = New_Unary( DOUBLE, 0, $2 ); + if( TYPE($4) != DOUBLE ) $4 = New_Unary( DOUBLE, 0, $4 ); + if( Test_Dims( $2, $4 ) ) { + $$ = New_Func( 0, atan2_fct, 2, $2, $4, 0, 0, 0, 0, 0 ); + TEST($$); + if( SIZE($2)=SIZE($4) && Test_Dims( $2, $4 ) ) { + $$ = New_Func( 0, defnull_fct, 2, $2, $4, 0, + 0, 0, 0, 0 ); + TEST($$); + } else { + yyerror("Dimensions of DEFNULL arguments are not compatible"); + YYERROR; + } + } else { + yyerror("Boolean Function(expr,expr) not supported"); + YYERROR; + } + } + | BFUNCTION expr ',' expr ',' expr ')' + { + if( TYPE($2) != DOUBLE ) $2 = New_Unary( DOUBLE, 0, $2 ); + if( TYPE($4) != DOUBLE ) $4 = New_Unary( DOUBLE, 0, $4 ); + if( TYPE($6) != DOUBLE ) $6 = New_Unary( DOUBLE, 0, $6 ); + if( ! (Test_Dims( $2, $4 ) && Test_Dims( $4, $6 ) ) ) { + yyerror("Dimensions of NEAR arguments " + "are not compatible"); + YYERROR; + } else { + if (FSTRCMP($1,"NEAR(") == 0) { + $$ = New_Func( BOOLEAN, near_fct, 3, $2, $4, $6, + 0, 0, 0, 0 ); + } else { + yyerror("Boolean Function not supported"); + YYERROR; + } + TEST($$); + + if( SIZE($$)= MAX_STRLEN) { + yyerror("Combined string size exceeds " MAX_STRLEN_S " characters"); + YYERROR; + } + $$ = New_BinOp( STRING, $1, '+', $3 ); TEST($$); + SIZE($$) = SIZE($1) + SIZE($3); + } + | bexpr '?' sexpr ':' sexpr + { + int outSize; + if( SIZE($1)!=1 ) { + yyerror("Cannot have a vector string column"); + YYERROR; + } + /* 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($3); + if (SIZE($5) > outSize) outSize = SIZE($5); + $$ = New_FuncSize( 0, ifthenelse_fct, 3, $3, $5, $1, + 0, 0, 0, 0, outSize); + + TEST($$); + if( SIZE($3) outSize) outSize = SIZE($4); + + $$ = New_FuncSize( 0, defnull_fct, 2, $2, $4, 0, + 0, 0, 0, 0, outSize ); + TEST($$); + if( SIZE($4)>SIZE($2) ) SIZE($$) = SIZE($4); + } else { + yyerror("Function(string,string) not supported"); + YYERROR; + } + } + | FUNCTION sexpr ',' expr ',' expr ')' + { + if (FSTRCMP($1,"STRMID(") == 0) { + int len; + if( TYPE($4) != LONG || SIZE($4) != 1 || + TYPE($6) != LONG || SIZE($6) != 1) { + yyerror("When using STRMID(S,P,N), P and N must be integers (and not vector columns)"); + YYERROR; + } + if (OPER($6) == CONST_OP) { + /* Constant value: use that directly */ + len = (gParse.Nodes[$6].value.data.lng); + } else { + /* Variable value: use the maximum possible (from $2) */ + len = SIZE($2); + } + if (len <= 0 || len >= MAX_STRLEN) { + yyerror("STRMID(S,P,N), N must be 1-" MAX_STRLEN_S); + YYERROR; + } + $$ = New_FuncSize( 0, strmid_fct, 3, $2, $4,$6,0,0,0,0,len); + TEST($$); + } else { + yyerror("Function(string,expr,expr) not supported"); + YYERROR; + } + } + + ; + +%% + +/*************************************************************************/ +/* 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; ivalue.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; ivalue.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; ivalue.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(); + yyerror("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; ivalue.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; ivalue.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 ) { + yyerror("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; idxoperation==CONST_OP); + + for( idx=0; idxvalue.nelem>1 ) { + Free_Last_Node(); + yyerror("Cannot use an array as an index value"); + return(-1); + } else if( theDim[idx]->type!=LONG ) { + Free_Last_Node(); + yyerror("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; idxvalue.naxis; idx++ ) { + elem *= ( this->value.naxes[idx] = theVar->value.naxes[idx] ); + } + this->value.nelem = elem; + } else { + Free_Last_Node(); + yyerror("Must specify just one or all indices for vector"); + return(-1); + } + if( constant ) this->DoOp( this ); + } + return(n); +} + +extern int yyGetVariable( char *varName, YYSTYPE *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]; + YYSTYPE colVal; + + if( Node1==-99 ) { + type = yyGetVariable( "TIME", &colVal ); + if( type==COLUMN ) { + Node1 = New_Column( (int)colVal.lng ); + } else { + yyerror("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 ) { + yyerror("Cannot use primary array for GTI filter"); + return( -1 ); + } + } else { + yyerror("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 { + yyerror("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 ) + yyerror("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; + YYSTYPE colVal; + + if( NodeX==-99 ) { + type = yyGetVariable( "X", &colVal ); + if( type==COLUMN ) { + NodeX = New_Column( (int)colVal.lng ); + } else { + yyerror("Could not build X column for REGFILTER"); + return(-1); + } + } + if( NodeY==-99 ) { + type = yyGetVariable( "Y", &colVal ); + if( type==COLUMN ) { + NodeY = New_Column( (int)colVal.lng ); + } else { + yyerror("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 ) ) ) { + yyerror("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)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 ) { + yyerror("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 ) { + yyerror("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 ) { + yyerror("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; inSubNodes; 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; ivalue.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; ivalue.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 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_ 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)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; ivalue.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; ivalue.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; ivalue.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 yyerror("Divide by Zero"); + break; + case '/': + if( val2 ) this->value.data.lng = (val1 / val2); + else yyerror("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; ivalue.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; ivalue.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 yyerror("Divide by Zero"); + break; + case '/': + if( val2 ) this->value.data.dbl = (val1 / val2); + else yyerror("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; ivalue.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; ivalue.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 ) + yyerror("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 ) + yyerror("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 ) + yyerror("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 ) + yyerror("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 ) + yyerror("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) { + yyerror("Could not allocate temporary memory in median function"); + free( this->value.data.ptr ); + break; + } + + for (irow=0; irow 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) { + yyerror("Could not allocate temporary memory in median function"); + free( this->value.data.ptr ); + break; + } + + for (irow=0; irow 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; rowtype==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 { + yyerror("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 ] ) { + yyerror("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; rowvalue.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; rowvalue.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; rowvalue.undef[row] ) { + yyerror("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 { + yyerror("Index out of range"); + free( this->value.data.ptr ); + } + } + + } else { + + /* Reduce dimensions by 1, using a nonconstant expression */ + + for( row=0; rowvalue.undef[row] ) { + yyerror("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 ] ) { + yyerror("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; ioperation>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]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] && 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; nodenSubNodes; 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) { + yyerror("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 yyerror(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/external/cfitsio/eval_defs.h b/external/cfitsio/eval_defs.h new file mode 100644 index 0000000..09c066b --- /dev/null +++ b/external/cfitsio/eval_defs.h @@ -0,0 +1,163 @@ +#include +#include +#include +#include +#if defined(__sgi) || defined(__hpux) +#include +#endif +#ifdef sparc +#include +#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/external/cfitsio/eval_f.c b/external/cfitsio/eval_f.c new file mode 100644 index 0000000..f9bdd05 --- /dev/null +++ b/external/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 +#include +#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; elem1 ? 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; elemHDUposition != (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 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( nelements1 ? 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= 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 && ivalue.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 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; kkvalue.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; kkvalue.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; kkvalue.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; kkvalue.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; kkvalue.nelem; jj++ ) { + ((char*)Data)[ jj+kk*result->value.nelem ] = + ( result->value.data.str[jj]=='1' ); + } + } else { + for( kk=0; kkvalue.nelem; jj++ ) { + ((char*)Data)[ jj+kk*result->value.nelem ] = + ( result->value.data.strptr[kk][jj]=='1' ); + } + } + break; + case TSTRING: + if( constant ) { + for( jj=0; jjvalue.data.str ); + } + } else { + for( jj=0; jjvalue.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; jjvalue.data.str ); + } else { + for( jj=0; jjvalue.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->maxRowsiotype == 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; rowarray)[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 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 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=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( currelemoperation==CONST_OP ) { + + if( result->value.data.log ) { + *(long*)userPtr = firstrow; + return( -1 ); + } + + } else { + + for( idx=0; idxvalue.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; rowfptr, 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/external/cfitsio/eval_l.c b/external/cfitsio/eval_l.c new file mode 100644 index 0000000..7dcb5dc --- /dev/null +++ b/external/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 + + +/* cfront 1.2 defines "c_plusplus" instead of "__cplusplus" */ +#ifdef c_plusplus +#ifndef __cplusplus +#define __cplusplus +#endif +#endif + + +#ifdef __cplusplus + +#include +#include + +/* 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 +#include +#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 +#include +#include +#ifdef sparc +#include +#else +#include +#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 +#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 ((nlng = varNum; + } + return( type ); +} + +static int find_variable(char *varName) +{ + int i; + + if( gParse.nCols ) + for( i=0; i 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/external/cfitsio/eval_tab.h b/external/cfitsio/eval_tab.h new file mode 100644 index 0000000..aed4459 --- /dev/null +++ b/external/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/external/cfitsio/eval_y.c b/external/cfitsio/eval_y.c new file mode 100644 index 0000000..e18cf11 --- /dev/null +++ b/external/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 + +#include + +#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 + +#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 +#else /* not sparc */ +#if defined (MSDOS) && !defined (__TURBOC__) +#include +#else /* not MSDOS, or __TURBOC__ */ +#if defined(_AIX) +#include + #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[-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) && 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)= 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) 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; ivalue.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; ivalue.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; ivalue.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; ivalue.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; ivalue.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; idxoperation==CONST_OP); + + for( idx=0; idxvalue.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; idxvalue.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)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; inSubNodes; 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; ivalue.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; ivalue.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 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_ 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)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; ivalue.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; ivalue.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; ivalue.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; ivalue.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; ivalue.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; ivalue.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; ivalue.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 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 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; rowtype==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; rowvalue.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; rowvalue.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; rowvalue.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; rowvalue.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; ioperation>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]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] && 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; nodenSubNodes; 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/external/cfitsio/f77.inc b/external/cfitsio/f77.inc new file mode 100644 index 0000000..51e05e4 --- /dev/null +++ b/external/cfitsio/f77.inc @@ -0,0 +1,31 @@ +C Codes for FITS extension types + integer IMAGE_HDU, ASCII_TBL, BINARY_TBL + parameter ( + & IMAGE_HDU = 0, + & ASCII_TBL = 1, + & BINARY_TBL = 2 ) + +C Codes for FITS table data types + + integer TBIT,TBYTE,TLOGICAL,TSTRING,TSHORT,TINT + integer TFLOAT,TDOUBLE,TCOMPLEX,TDBLCOMPLEX + parameter ( + & TBIT = 1, + & TBYTE = 11, + & TLOGICAL = 14, + & TSTRING = 16, + & TSHORT = 21, + & TINT = 31, + & TFLOAT = 42, + & TDOUBLE = 82, + & TCOMPLEX = 83, + & TDBLCOMPLEX = 163 ) + +C Codes for iterator column types + + integer InputCol, InputOutputCol, OutputCol + parameter ( + & InputCol = 0, + & InputOutputCol = 1, + & OutputCol = 2 ) + diff --git a/external/cfitsio/f77_wrap.h b/external/cfitsio/f77_wrap.h new file mode 100644 index 0000000..d512855 --- /dev/null +++ b/external/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;idsc$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;idsc$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; i0 ) return; + for( i=50;i0 ) return; + if( unit == -1 ) { + int i; for( i=50; i=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 ) + 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/external/cfitsio/f77_wrap2.c b/external/cfitsio/f77_wrap2.c new file mode 100644 index 0000000..8b7de36 --- /dev/null +++ b/external/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; iFwork_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 +#include +#include +#include +#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<> 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=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>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<> 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=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>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; i0) ? (*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 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=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< 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 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=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)< 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<>(-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<nqy) ? nqx : nqy; + log2n = (int) (log((float) nqmax)/log(2.0)+0.5); + if (nqmax > (1<= 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>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<0) { + /* + * put out the last few bits + */ + output_nbits(outfile, bitbuffer & ((1<=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<= 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>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<0) { + /* + * put out the last few bits + */ + output_nbits(outfile, bitbuffer & ((1<=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; + + 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> 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); + 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> 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 +#include +#include +#include +#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<> 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<=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= 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= 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<> 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<=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= 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= 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= 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>1; + pt = tmp; + p1 = &a[n2*nhalf]; /* pointer to a[i] */ + for (i=nhalf; 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> 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=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=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=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=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=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=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; inqy) ? nqx : nqy; + log2n = (int) (log((float) nqmax)/log(2.0)+0.5); + if (nqmax > (1<= 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<>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<= 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<>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>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>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>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>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>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>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<>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<>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/external/cfitsio/fitscopy.c b/external/cfitsio/fitscopy.c new file mode 100644 index 0000000..67e3229 --- /dev/null +++ b/external/cfitsio/fitscopy.c @@ -0,0 +1,60 @@ +#include +#include "fitsio.h" + +int main(int argc, char *argv[]) +{ + fitsfile *infptr, *outfptr; /* FITS file pointers defined in fitsio.h */ + int status = 0; /* status must always be initialized = 0 */ + + if (argc != 3) + { + printf("Usage: fitscopy inputfile outputfile\n"); + printf("\n"); + printf("Copy an input file to an output file, optionally filtering\n"); + printf("the file in the process. This seemingly simple program can\n"); + printf("apply powerful filters which transform the input file as\n"); + printf("it is being copied. Filters may be used to extract a\n"); + printf("subimage from a larger image, select rows from a table,\n"); + printf("filter a table with a GTI time extension or a SAO region file,\n"); + printf("create or delete columns in a table, create an image by\n"); + printf("binning (histogramming) 2 table columns, and convert IRAF\n"); + printf("format *.imh or raw binary data files into FITS images.\n"); + printf("See the CFITSIO User's Guide for a complete description of\n"); + printf("the Extended File Name filtering syntax.\n"); + printf("\n"); + printf("Examples:\n"); + printf("\n"); + printf("fitscopy in.fit out.fit (simple file copy)\n"); + printf("fitscopy - - (stdin to stdout)\n"); + printf("fitscopy in.fit[11:50,21:60] out.fit (copy a subimage)\n"); + printf("fitscopy iniraf.imh out.fit (IRAF image to FITS)\n"); + printf("fitscopy in.dat[i512,512] out.fit (raw array to FITS)\n"); + printf("fitscopy in.fit[events][pi>35] out.fit (copy rows with pi>35)\n"); + printf("fitscopy in.fit[events][bin X,Y] out.fit (bin an image) \n"); + printf("fitscopy in.fit[events][col x=.9*y] out.fit (new x column)\n"); + printf("fitscopy in.fit[events][gtifilter()] out.fit (time filter)\n"); + printf("fitscopy in.fit[2][regfilter(\"pow.reg\")] out.fit (spatial filter)\n"); + printf("\n"); + printf("Note that it may be necessary to enclose the input file name\n"); + printf("in single quote characters on the Unix command line.\n"); + return(0); + } + /* Open the input file */ + if ( !fits_open_file(&infptr, argv[1], READONLY, &status) ) + { + /* Create the output file */ + if ( !fits_create_file(&outfptr, argv[2], &status) ) + { + + /* copy the previous, current, and following HDUs */ + fits_copy_file(infptr, outfptr, 1, 1, 1, &status); + + fits_close_file(outfptr, &status); + } + fits_close_file(infptr, &status); + } + + /* if error occured, print out error message */ + if (status) fits_report_error(stderr, status); + return(status); +} diff --git a/external/cfitsio/fitscore.c b/external/cfitsio/fitscore.c new file mode 100644 index 0000000..3700672 --- /dev/null +++ b/external/cfitsio/fitscore.c @@ -0,0 +1,9243 @@ +/* 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 +#include +#include +#include +#include +#include +/* stddef.h is apparently needed to define size_t with some compilers ?? */ +#include +#include +#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.31; + +/* 18 Jul 2012 + + Previous releases: + *version = 3.30 11 Apr 2012 + *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 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;iFptr)->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/external/cfitsio/fitsfile.tpt b/external/cfitsio/fitsfile.tpt new file mode 100644 index 0000000..44d4aa1 --- /dev/null +++ b/external/cfitsio/fitsfile.tpt @@ -0,0 +1,8 @@ +xtension = 'bintable' +naxis2 = 20 +TTYPE1 = 'SIZE' / comment here +TFORM1 = 1J / my first column +TTYPE2 = 'DISTANCE' +TFORM2 = 1E / my second column +mykey = 16 / this is my comment + diff --git a/external/cfitsio/fitsio.doc b/external/cfitsio/fitsio.doc new file mode 100644 index 0000000..1da75b1 --- /dev/null +++ b/external/cfitsio/fitsio.doc @@ -0,0 +1,6607 @@ + FITSIO - An Interface to FITS Format Files for Fortran Programmers + + William D Pence, HEASARC, NASA/GSFC + Version 3.0 + + +[Note: This file contains various formatting command symbols in the first +column which are used when generating the LATeX version of this document.] + +*I. Introduction + +This document describes the Fortran-callable subroutine interface that +is provided as part of the CFITSIO library (which is written in ANSI +C). This is a companion document to the CFITSIO User's Guide which +should be consulted for further information about the underlying +CFITSIO library. In the remainder of this document, the terms FITSIO +and CFITSIO are interchangeable and refer to the same library. + +FITSIO/CFITSIO is a machine-independent library of routines for reading +and writing data files in the FITS (Flexible Image Transport System) +data format. It can also read IRAF format image files and raw binary +data arrays by converting them on the fly into a virtual FITS format +file. This library was written to provide a powerful yet simple +interface for accessing FITS files which will run on most commonly used +computers and workstations. FITSIO supports all the features described +in the official NOST definition of the FITS format and can read and +write all the currently defined types of extensions, including ASCII +tables (TABLE), Binary tables (BINTABLE) and IMAGE extensions. The +FITSIO subroutines insulate the programmer from having to deal with the +complicated formatting details in the FITS file, however, it is assumed +that users have a general knowledge about the structure and usage of +FITS files. + +The CFITSIO package was initially developed by the HEASARC (High Energy +Astrophysics Science Archive Research Center) at the NASA Goddard Space +Flight Center to convert various existing and newly acquired +astronomical data sets into FITS format and to further analyze data +already in FITS format. New features continue to be added to CFITSIO +in large part due to contributions of ideas or actual code from users +of the package. The Integral Science Data Center in Switzerland, and +the XMM/ESTEC project in The Netherlands made especially significant +contributions that resulted in many of the new features that appeared +in v2.0 of CFITSIO. + +The latest version of the CFITSIO source code, documentation, and +example programs are available on the World-Wide Web or via anonymous +ftp from: +- + http://heasarc.gsfc.nasa.gov/fitsio + ftp://legacy.gsfc.nasa.gov/software/fitsio/c +- +\newpage +Any questions, bug reports, or suggested enhancements related to the CFITSIO +package should be sent to the primary author: +- + Dr. William Pence Telephone: (301) 286-4599 + HEASARC, Code 662 E-mail: William.D.Pence@nasa.gov + NASA/Goddard Space Flight Center + Greenbelt, MD 20771, USA +- +This User's Guide assumes that readers already have a general +understanding of the definition and structure of FITS format files. +Further information about FITS formats is available from the FITS Support +Office at {\tt http://fits.gsfc.nasa.gov}. In particular, the +'NOST FITS Standard' gives the authoritative definition of the FITS data +format, and the `FITS User's Guide' provides additional historical background +and practical advice on using FITS files. + +CFITSIO users may also be interested in the FTOOLS package of programs +that can be used to manipulate and analyze FITS format files. +Information about FTOOLS can be obtained on the Web or via anonymous +ftp at: +- + http://heasarc.gsfc.nasa.gov/ftools + ftp://legacy.gsfc.nasa.gov/software/ftools/release +- + +*II. Creating FITSIO/CFITSIO + +**A. Building the Library + +To use the FITSIO subroutines one must first build the CFITSIO library, +which requires a C compiler. gcc is ideal, or most other ANSI-C +compilers will also work. The CFITSIO code is contained in about 40 C +source files (*.c) and header files (*.h). On VAX/VMS systems 2 +assembly-code files (vmsieeed.mar and vmsieeer.mar) are also needed. + +The Fortran interface subroutines to the C CFITSIO routines are located +in the f77\_wrap1.c, through f77\_wrap4.c files. These are relatively simple +'wrappers' that translate the arguments in the Fortran subroutine into +the appropriate format for the corresponding C routine. This +translation is performed transparently to the user by a set of C macros +located in the cfortran.h file. Unfortunately cfortran.h does not +support every combination of C and Fortran compilers so the Fortran +interface is not supported on all platforms. (see further notes below). + +A standard combination of C and Fortran compilers will be assumed by +default, but one may also specify a particular Fortran compiler by +doing: +- + > setenv CFLAGS -DcompilerName=1 +- +(where 'compilerName' is the name of the compiler) before running +the configure command. The currently recognized compiler +names are: +- + g77Fortran + IBMR2Fortran + CLIPPERFortran + pgiFortran + NAGf90Fortran + f2cFortran + hpuxFortran + apolloFortran + sunFortran + CRAYFortran + mipsFortran + DECFortran + vmsFortran + CONVEXFortran + PowerStationFortran + AbsoftUNIXFortran + AbsoftProFortran + SXFortran +- +Alternatively, one may edit the CFLAGS line in the Makefile to add the +'-DcompilerName' flag after running the './configure' command. + +The CFITSIO library is built on Unix systems by typing: +- + > ./configure [--prefix=/target/installation/path] + [--enable-sse2] [--enable-ssse3] + > make (or 'make shared') + > make install (this step is optional) +- +at the operating system prompt. The configure command customizes the +Makefile for the particular system, then the `make' command compiles the +source files and builds the library. Type `./configure' and not simply +`configure' to ensure that the configure script in the current directory +is run and not some other system-wide configure script. The optional +'prefix' argument to configure gives the path to the directory where +the CFITSIO library and include files should be installed via the later +'make install' command. For example, +- + > ./configure --prefix=/usr1/local +- +will cause the 'make install' command to copy the CFITSIO libcfitsio file +to /usr1/local/lib and the necessary include files to /usr1/local/include +(assuming of course that the process has permission to write to these +directories). + +The optional --enable-sse2 and --enable-ssse3 flags will cause configure to +attempt to build CFITSIO using faster byte-swapping algorithms. +See the "Optimizing Programs" section of this manual for +more information about these options. + +By default, the Makefile will be configured to build the set of Fortran-callable +wrapper routines whose calling sequences are described later in this +document. + +The 'make shared' option builds a shared or dynamic version of the +CFITSIO library. When using the shared library the executable code is +not copied into your program at link time and instead the program +locates the necessary library code at run time, normally through +LD\_LIBRARY\_PATH or some other method. The advantages of using a shared +library are: +- + 1. Less disk space if you build more than 1 program + 2. Less memory if more than one copy of a program using the shared + library is running at the same time since the system is smart + enough to share copies of the shared library at run time. + 3. Possibly easier maintenance since a new version of the shared + library can be installed without relinking all the software + that uses it (as long as the subroutine names and calling + sequences remain unchanged). + 4. No run-time penalty. +- +The disadvantages are: +- + 1. More hassle at runtime. You have to either build the programs + specially or have LD_LIBRARY_PATH set right. + 2. There may be a slight start up penalty, depending on where you are + reading the shared library and the program from and if your CPU is + either really slow or really heavily loaded. +- + +On HP/UX systems, the environment variable CFLAGS should be set +to -Ae before running configure to enable "extended ANSI" features. + +It may not be possible to statically link programs that use CFITSIO on +some platforms (namely, on Solaris 2.6) due to the network drivers +(which provide FTP and HTTP access to FITS files). It is possible to +make both a dynamic and a static version of the CFITSIO library, but +network file access will not be possible using the static version. + +On VAX/VMS and ALPHA/VMS systems the make\_gfloat.com command file may +be executed to build the cfitsio.olb object library using the default +G-floating point option for double variables. The make\_dfloat.com and +make\_ieee.com files may be used instead to build the library with the +other floating point options. Note that the getcwd function that is +used in the group.c module may require that programs using CFITSIO be +linked with the ALPHA\$LIBRARY:VAXCRTL.OLB library. See the example +link line in the next section of this document. + +On Windows IBM-PC type platforms the situation is more complicated +because of the wide variety of Fortran compilers that are available and +because of the inherent complexities of calling the CFITSIO C routines +from Fortran. Two different versions of the CFITSIO dll library are +available, compiled with the Borland C++ compiler and the Microsoft +Visual C++ compiler, respectively, in the files +cfitsiodll\_2xxx\_borland.zip and cfitsiodll\_3xxx\_vcc.zip, where +'3xxx' represents the current release number. Both these dll libraries +contain a set of Fortran wrapper routines which may be compatible with +some, but probably not all, available Fortran compilers. To test if +they are compatible, compile the program testf77.f and try linking to +these dll libraries. If these libraries do not work with a particular +Fortran compiler, then there are 2 possible solutions. The first +solution would be to modify the file cfortran.h for that particular +combination of C and Fortran compilers, and then rebuild the CFITSIO +dll library. This will require, however, a some expertise in +mixed language programming. +The other solution is to use the older v5.03 Fortran-77 implementation +of FITSIO that is still available from the FITSIO web-site. This +version is no longer supported, but it does provide the basic functions +for reading and writing FITS files and should be compatible with most +Fortran compilers. + +CFITSIO has currently been tested on the following platforms: +- + OPERATING SYSTEM COMPILER + Sun OS gcc and cc (3.0.1) + Sun Solaris gcc and cc + Silicon Graphics IRIX gcc and cc + Silicon Graphics IRIX64 MIPS + Dec Alpha OSF/1 gcc and cc + DECstation Ultrix gcc + Dec Alpha OpenVMS cc + DEC VAX/VMS gcc and cc + HP-UX gcc + IBM AIX gcc + Linux gcc + MkLinux DR3 + Windows 95/98/NT Borland C++ V4.5 + Windows 95/98/NT/ME/XP Microsoft/Compaq Visual C++ v5.0, v6.0 + Windows 95/98/NT Cygwin gcc + OS/2 gcc + EMX + MacOS 7.1 or greater Metrowerks 10.+ +- +CFITSIO will probably run on most other Unix platforms. Cray +supercomputers are currently not supported. + +**B. Testing the Library + +The CFITSIO library should be tested by building and running +the testprog.c program that is included with the release. +On Unix systems type: +- + % make testprog + % testprog > testprog.lis + % diff testprog.lis testprog.out + % cmp testprog.fit testprog.std +- + On VMS systems, +(assuming cc is the name of the C compiler command), type: +- + $ cc testprog.c + $ link testprog, cfitsio/lib, alpha$library:vaxcrtl/lib + $ run testprog +- +The testprog program should produce a FITS file called `testprog.fit' +that is identical to the `testprog.std' FITS file included with this +release. The diagnostic messages (which were piped to the file +testprog.lis in the Unix example) should be identical to the listing +contained in the file testprog.out. The 'diff' and 'cmp' commands +shown above should not report any differences in the files. (There +may be some minor formatting differences, such as the presence or +absence of leading zeros, or 3 digit exponents in numbers, +which can be ignored). + +The Fortran wrappers in CFITSIO may be tested with the testf77 +program. On Unix systems the fortran compilation and link command +may be called 'f77' or 'g77', depending on the system. +- + % f77 -o testf77 testf77.f -L. -lcfitsio -lnsl -lsocket + or + % f77 -f -o testf77 testf77.f -L. -lcfitsio (under SUN O/S) + or + % f77 -o testf77 testf77.f -Wl,-L. -lcfitsio -lm -lnsl -lsocket (HP/UX) + or + % g77 -o testf77 -s testf77.f -lcfitsio -lcc_dynamic -lncurses (Mac OS-X) + + % testf77 > testf77.lis + % diff testf77.lis testf77.out + % cmp testf77.fit testf77.std +- +On machines running SUN O/S, Fortran programs must be compiled with the +'-f' option to force double precision variables to be aligned on 8-byte +boundaries to make the fortran-declared variables compatible with C. A +similar compiler option may be required on other platforms. Failing to +use this option may cause the program to crash on FITSIO routines that +read or write double precision variables. + +On Windows platforms, linking Fortran programs with a C library +often depends on the particular compilers involved. Some users have +found the following commands work when using the Intel Fortran compiler: +- +ifort /libs.dll cfitsio.lib /MD testf77.f /Gm + +or possibly, + +ifort /libs:dll cfitsio.lib /MD /fpp /extfpp:cfortran.h,fitsio.h + /iface:cvf testf77.f +- +Also note that on some systems the output listing of the testf77 +program may differ slightly from the testf77.std template if leading +zeros are not printed by default before the decimal point when using F +format. + +A few other utility programs are included with CFITSIO: +- + speed - measures the maximum throughput (in MB per second) + for writing and reading FITS files with CFITSIO + + listhead - lists all the header keywords in any FITS file + + fitscopy - copies any FITS file (especially useful in conjunction + with the CFITSIO's extended input filename syntax) + + cookbook - a sample program that performs common read and + write operations on a FITS file. + + iter_a, iter_b, iter_c - examples of the CFITSIO iterator routine +- + +The first 4 of these utility programs can be compiled and linked by typing +- + % make program_name +- + +**C. Linking Programs with FITSIO + +When linking applications software with the FITSIO library, several system libraries usually need to be specified on the link command line. On +Unix systems, the most reliable way to determine what libraries are required +is to type 'make testprog' and see what libraries the configure script has +added. The typical libraries that may need to be added are -lm (the math +library) and -lnsl and -lsocket (needed only for FTP and HTTP file access). +These latter 2 libraries are not needed on VMS and Windows platforms, +because FTP file access is not currently supported on those platforms. + +Note that when upgrading to a newer version of CFITSIO it is usually +necessary to recompile, as well as relink, the programs that use CFITSIO, +because the definitions in fitsio.h often change. + +**D. Getting Started with FITSIO + +In order to effectively use the FITSIO library as quickly as possible, +it is recommended that new users follow these steps: + +1. Read the following `FITS Primer' chapter for a brief +overview of the structure of FITS files. This is especially important +for users who have not previously dealt with the FITS table and image +extensions. + +2. Write a simple program to read or write a FITS file using the Basic +Interface routines. + +3. Refer to the cookbook.f program that is included with this release +for examples of routines that perform various common FITS file +operations. + +4. Read Chapters 4 and 5 to become familiar with the conventions and +advanced features of the FITSIO interface. + +5. Scan through the more extensive set of routines that are provided +in the `Advanced Interface'. These routines perform more specialized +functions than are provided by the Basic Interface routines. + +**E. Example Program + +The following listing shows an example of how to use the FITSIO +routines in a Fortran program. Refer to the cookbook.f program that +is included with the FITSIO distribution for examples of other +FITS programs. +- + program writeimage + +C Create a FITS primary array containing a 2-D image + + integer status,unit,blocksize,bitpix,naxis,naxes(2) + integer i,j,group,fpixel,nelements,array(300,200) + character filename*80 + logical simple,extend + + status=0 +C Name of the FITS file to be created: + filename='ATESTFILE.FITS' + +C Get an unused Logical Unit Number to use to create the FITS file + call ftgiou(unit,status) + +C create the new empty FITS file + blocksize=1 + call ftinit(unit,filename,blocksize,status) + +C initialize parameters about the FITS image (300 x 200 16-bit integers) + simple=.true. + bitpix=16 + naxis=2 + naxes(1)=300 + naxes(2)=200 + extend=.true. + +C write the required header keywords + call ftphpr(unit,simple,bitpix,naxis,naxes,0,1,extend,status) + +C initialize the values in the image with a linear ramp function + do j=1,naxes(2) + do i=1,naxes(1) + array(i,j)=i+j + end do + end do + +C write the array to the FITS file + group=1 + fpixel=1 + nelements=naxes(1)*naxes(2) + call ftpprj(unit,group,fpixel,nelements,array,status) + +C write another optional keyword to the header + call ftpkyj(unit,'EXPOSURE',1500,'Total Exposure Time',status) + +C close the file and free the unit number + call ftclos(unit, status) + call ftfiou(unit, status) + end +- + +**F. Legal Stuff + +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." + +**G. Acknowledgments + +The development of many of the powerful features in CFITSIO was made +possible through collaborations with many people or organizations from +around the world. The following, in particular, have made especially +significant contributions: + +Programmers from the Integral Science Data Center, Switzerland (namely, +Jurek Borkowski, Bruce O'Neel, and Don Jennings), designed the concept +for the plug-in I/O drivers that was introduced with CFITSIO 2.0. The +use of `drivers' greatly simplified the low-level I/O, which in turn +made other new features in CFITSIO (e.g., support for compressed FITS +files and support for IRAF format image files) much easier to +implement. Jurek Borkowski wrote the Shared Memory driver, and Bruce +O'Neel wrote the drivers for accessing FITS files over the network +using the FTP, HTTP, and ROOT protocols. + +The ISDC also provided the template parsing routines (written by Jurek +Borkowski) and the hierarchical grouping routines (written by Don +Jennings). The ISDC DAL (Data Access Layer) routines are layered on +top of CFITSIO and make extensive use of these features. + +Uwe Lammers (XMM/ESA/ESTEC, The Netherlands) designed the +high-performance lexical parsing algorithm that is used to do +on-the-fly filtering of FITS tables. This algorithm essentially +pre-compiles the user-supplied selection expression into a form that +can be rapidly evaluated for each row. Peter Wilson (RSTX, NASA/GSFC) +then wrote the parsing routines used by CFITSIO based on Lammers' +design, combined with other techniques such as the CFITSIO iterator +routine to further enhance the data processing throughput. This effort +also benefited from a much earlier lexical parsing routine that was +developed by Kent Blackburn (NASA/GSFC). More recently, Craig Markwardt +(NASA/GSFC) implemented additional functions (median, average, stddev) +and other enhancements to the lexical parser. + +The CFITSIO iterator function is loosely based on similar ideas +developed for the XMM Data Access Layer. + +Peter Wilson (RSTX, NASA/GSFC) wrote the complete set of +Fortran-callable wrappers for all the CFITSIO routines, which in turn +rely on the CFORTRAN macro developed by Burkhard Burow. + +The syntax used by CFITSIO for filtering or binning input FITS files is +based on ideas developed for the AXAF Science Center Data Model by +Jonathan McDowell, Antonella Fruscione, Aneta Siemiginowska and Bill +Joye. See http://heasarc.gsfc.nasa.gov/docs/journal/axaf7.html for +further description of the AXAF Data Model. + +The file decompression code were taken directly from the gzip (GNU zip) +program developed by Jean-loup Gailly and others. + +Doug Mink, SAO, provided the routines for converting IRAF format +images into FITS format. + +Martin Reinecke (Max Planck Institute, Garching)) provided the modifications to +cfortran.h that are necessary to support 64-bit integer values when calling +C routines from fortran programs. The cfortran.h macros were originally developed +by Burkhard Burow (CERN). + +Julian Taylor (ESO, Garching) provided the fast byte-swapping algorithms +that use the SSE2 and SSSE3 machine instructions available on x86\_64 CPUs. + +In addition, many other people have made valuable contributions to the +development of CFITSIO. These include (with apologies to others that may +have inadvertently been omitted): + +Steve Allen, Carl Akerlof, Keith Arnaud, Morten Krabbe Barfoed, Kent +Blackburn, G Bodammer, Romke Bontekoe, Lucio Chiappetti, Keith Costorf, +Robin Corbet, John Davis, Richard Fink, Ning Gan, Emily Greene, Joe +Harrington, Cheng Ho, Phil Hodge, Jim Ingham, Yoshitaka Ishisaki, Diab +Jerius, Mark Levine, Todd Karakaskian, Edward King, Scott Koch, Claire +Larkin, Rob Managan, Eric Mandel, John Mattox, Carsten Meyer, Emi +Miyata, Stefan Mochnacki, Mike Noble, Oliver Oberdorf, Clive Page, +Arvind Parmar, Jeff Pedelty, Tim Pearson, Maren Purves, Scott Randall, +Chris Rogers, Arnold Rots, Barry Schlesinger, Robin Stebbins, Andrew +Szymkowiak, Allyn Tennant, Peter Teuben, James Theiler, Doug Tody, +Shiro Ueno, Steve Walton, Archie Warnock, Alan Watson, Dan Whipple, Wim +Wimmers, Peter Young, Jianjun Xu, and Nelson Zarate. + + +*III. A FITS Primer + +This section gives a brief overview of the structure of FITS files. +Users should refer to the documentation available from the NOST, as +described in the introduction, for more detailed information on FITS +formats. + +FITS was first developed in the late 1970's as a standard data +interchange format between various astronomical observatories. Since +then FITS has become the defacto standard data format supported by most +astronomical data analysis software packages. + +A FITS file consists of one or more Header + Data Units (HDUs), where +the first HDU is called the `Primary HDU', or `Primary Array'. The +primary array contains an N-dimensional array of pixels, such as a 1-D +spectrum, a 2-D image, or a 3-D data cube. Six different primary +datatypes are supported: Unsigned 8-bit bytes, 16, 32, and 64-bit signed +integers, and 32 and 64-bit floating point reals. FITS also has a +convention for storing unsigned integers (see the later +section entitled `Unsigned Integers' for more details). The primary HDU +may also consist of only a header with a null array containing no +data pixels. + +Any number of additional HDUs may follow the primary array; these +additional HDUs are called FITS `extensions'. There are currently 3 +types of extensions defined by the FITS standard: + +\begin{itemize} +\item + Image Extension - a N-dimensional array of pixels, like in a primary array +\item + ASCII Table Extension - rows and columns of data in ASCII character format +\item + Binary Table Extension - rows and columns of data in binary representation +\end{itemize} + +In each case the HDU consists of an ASCII Header Unit followed by an optional +Data Unit. For historical reasons, each Header or Data unit must be an +exact multiple of 2880 8-bit bytes long. Any unused space is padded +with fill characters (ASCII blanks or zeros). + +Each Header Unit consists of any number of 80-character keyword records +or `card images' which have the general form: +- + KEYNAME = value / comment string + NULLKEY = / comment: This keyword has no value +- +The keyword names may be up to 8 characters long and can only contain +uppercase letters, the digits 0-9, the hyphen, and the underscore +character. The keyword name is (usually) followed by an equals sign and +a space character (= ) in columns 9 - 10 of the record, followed by the +value of the keyword which may be either an integer, a floating point +number, a character string (enclosed in single quotes), or a boolean +value (the letter T or F). A keyword may also have a null or undefined +value if there is no specified value string, as in the second example. + +The last keyword in the header is always the `END' keyword which has no +value or comment fields. There are many rules governing the exact +format of a keyword record (see the NOST FITS Standard) so it is better +to rely on standard interface software like FITSIO to correctly +construct or to parse the keyword records rather than try to deal +directly with the raw FITS formats. + +Each Header Unit begins with a series of required keywords which depend +on the type of HDU. These required keywords specify the size and +format of the following Data Unit. The header may contain other +optional keywords to describe other aspects of the data, such as the +units or scaling values. Other COMMENT or HISTORY keywords are also +frequently added to further document the data file. + +The optional Data Unit immediately follows the last 2880-byte block in +the Header Unit. Some HDUs do not have a Data Unit and only consist of +the Header Unit. + +If there is more than one HDU in the FITS file, then the Header Unit of +the next HDU immediately follows the last 2880-byte block of the +previous Data Unit (or Header Unit if there is no Data Unit). + +The main required keywords in FITS primary arrays or image extensions are: +\begin{itemize} +\item +BITPIX -- defines the datatype of the array: 8, 16, 32, 64, -32, -64 for +unsigned 8--bit byte, 16--bit signed integer, 32--bit signed integer, +64--bit signed integer, +32--bit IEEE floating point, and 64--bit IEEE double precision floating +point, respectively. +\item +NAXIS -- the number of dimensions in the array, usually 0, 1, 2, 3, or 4. +\item +NAXISn -- (n ranges from 1 to NAXIS) defines the size of each dimension. +\end{itemize} + +FITS tables start with the keyword XTENSION = `TABLE' (for ASCII +tables) or XTENSION = `BINTABLE' (for binary tables) and have the +following main keywords: +\begin{itemize} +\item +TFIELDS -- number of fields or columns in the table +\item +NAXIS2 -- number of rows in the table +\item +TTYPEn -- for each column (n ranges from 1 to TFIELDS) gives the +name of the column +\item +TFORMn -- the datatype of the column +\item +TUNITn -- the physical units of the column (optional) +\end{itemize} + +Users should refer to the FITS Support Office at {\tt http://fits.gsfc.nasa.gov} +for further information about the FITS format and related software +packages. + + + +*V. FITSIO Conventions and Guidelines + +**A. CFITSIO Size Limitations + +CFITSIO places few restrictions on the size of FITS files that it +reads or writes. There are a few limits, however, which may affect +some extreme cases: + +1. The maximum number of FITS files that may be simultaneously opened +by CFITSIO is set by NMAXFILES as defined in fitsio2.h. It is currently +set = 300 by default. CFITSIO will allocate about 80 * NMAXFILES bytes +of memory for internal use. Note that the underlying C compiler or +operating system, may have a smaller limit on the number of opened files. +The C symbolic constant FOPEN\_MAX is intended to define the maximum +number of files that may open at once (including any other text or +binary files that may be open, not just FITS files). On some systems it +has been found that gcc supports a maximum of 255 opened files. + +2. By default, CFITSIO can handle FITS files up to 2.1 GB in size (2**31 +bytes). This file size limit is often imposed by 32-bit operating +systems. More recently, as 64-bit operating systems become more common, an +industry-wide standard (at least on Unix systems) has been developed to +support larger sized files (see http://ftp.sas.com/standards/large.file/). +Starting with version 2.1 of CFITSIO, larger FITS files up to 6 terabytes +in size may be read and written on supported platforms. In order +to support these larger files, CFITSIO must be compiled with the +'-D\_LARGEFILE\_SOURCE' and `-D\_FILE\_OFFSET\_BITS=64' compiler flags. +Some platforms may also require the `-D\_LARGE\_FILES' compiler flag. + This causes the compiler to allocate 8-bytes instead of +4-bytes for the `off\_t' datatype which is used to store file offset +positions. It appears that in most cases it is not necessary to +also include these compiler flags when compiling programs that link to +the CFITSIO library. + +If CFITSIO is compiled with the -D\_LARGEFILE\_SOURCE +and -D\_FILE\_OFFSET\_BITS=64 flags on a +platform that supports large files, then it can read and write FITS +files that contain up to 2**31 2880-byte FITS records, or approximately +6 terabytes in size. It is still required that the value of the NAXISn +and PCOUNT keywords in each extension be within the range of a signed +4-byte integer (max value = 2,147,483,648). Thus, each dimension of an +image (given by the NAXISn keywords), the total width of a table +(NAXIS1 keyword), the number of rows in a table (NAXIS2 keyword), and +the total size of the variable-length array heap in binary tables +(PCOUNT keyword) must be less than this limit. + +Currently, support for large files within CFITSIO has been tested +on the Linux, Solaris, and IBM AIX operating systems. + +**B. Multiple Access to the Same FITS File + +CFITSIO supports simultaneous read and write access to multiple HDUs in +the same FITS file. Thus, one can open the same FITS file twice within +a single program and move to 2 different HDUs in the file, and then +read and write data or keywords to the 2 extensions just as if one were +accessing 2 completely separate FITS files. Since in general it is +not possible to physically open the same file twice and then expect to +be able to simultaneously (or in alternating succession) write to 2 +different locations in the file, CFITSIO recognizes when the file to be +opened (in the call to fits\_open\_file) has already been opened and +instead of actually opening the file again, just logically links the +new file to the old file. (This only applies if the file is opened +more than once within the same program, and does not prevent the same +file from being simultaneously opened by more than one program). Then +before CFITSIO reads or writes to either (logical) file, it makes sure +that any modifications made to the other file have been completely +flushed from the internal buffers to the file. Thus, in principle, one +could open a file twice, in one case pointing to the first extension +and in the other pointing to the 2nd extension and then write data to +both extensions, in any order, without danger of corrupting the file, +There may be some efficiency penalties in doing this however, since +CFITSIO has to flush all the internal buffers related to one file +before switching to the other, so it would still be prudent to +minimize the number of times one switches back and forth between doing +I/O to different HDUs in the same file. + +**C. Current Header Data Unit (CHDU) + +In general, a FITS file can contain multiple Header Data Units, also +called extensions. CFITSIO only operates within one HDU at any given +time, and the currently selected HDU is called the Current Header Data +Unit (CHDU). When a FITS file is first created or opened the CHDU is +automatically defined to be the first HDU (i.e., the primary array). +CFITSIO routines are provided to move to and open any other existing +HDU within the FITS file or to append or insert a new HDU in the FITS +file which then becomes the CHDU. + +**D. Subroutine Names + +All FITSIO subroutine names begin with the letters 'ft' to distinguish +them from other subroutines and are 5 or 6 characters long. Users should +not name their own subroutines beginning with 'ft' to avoid conflicts. +(The SPP interface routines all begin with 'fs'). Subroutines which read +or get information from the FITS file have names beginning with +'ftg...'. Subroutines which write or put information into the FITS file +have names beginning with 'ftp...'. + +**E. Subroutine Families and Datatypes + +Many of the subroutines come in families which differ only in the +datatype of the associated parameter(s) . The datatype of these +subroutines is indicated by the last letter of the subroutine name +(e.g., 'j' in 'ftpkyj') as follows: +- + x - bit + b - character*1 (unsigned byte) + i - short integer (I*2) + j - integer (I*4, 32-bit integer) + k - long long integer (I*8, 64-bit integer) + e - real exponential floating point (R*4) + f - real fixed-format floating point (R*4) + d - double precision real floating-point (R*8) + g - double precision fixed-format floating point (R*8) + c - complex reals (pairs of R*4 values) + m - double precision complex (pairs of R*8 values) + l - logical (L*4) + s - character string +- + +When dealing with the FITS byte datatype, it is important to remember +that the raw values (before any scaling by the BSCALE and BZERO, or +TSCALn and TZEROn keyword values) in byte arrays (BITPIX = 8) or byte +columns (TFORMn = 'B') are interpreted as unsigned bytes with values +ranging from 0 to 255. Some Fortran compilers support a non-standard +byte datatype such as INTEGER*1, LOGICAL*1, or BYTE, which can sometimes +be used instead of CHARACTER*1 variables. Many machines permit passing a +numeric datatype (such as INTEGER*1) to the FITSIO subroutines which are +expecting a CHARACTER*1 datatype, but this technically violates the +Fortran-77 standard and is not supported on all machines (e.g., on a VAX/VMS +machine one must use the VAX-specific \%DESCR function). + +One feature of the CFITSIO routines is that they can operate on a `X' +(bit) column in a binary table as though it were a `B' (byte) column. +For example a `11X' datatype column can be interpreted the same as a +`2B' column (i.e., 2 unsigned 8-bit bytes). In some instances, it can +be more efficient to read and write whole bytes at a time, rather than +reading or writing each individual bit. + +The double precision complex datatype is not a standard Fortran-77 +datatype. If a particular Fortran compiler does not directly support +this datatype, then one may instead pass an array of pairs of double +precision values to these subroutines. The first value in each pair +is the real part, and the second is the imaginary part. + +**F. Implicit Data Type Conversion + +The FITSIO routines that read and write numerical data can perform +implicit data type conversion. This means that the data type of the +variable or array in the program does not need to be the same as the +data type of the value in the FITS file. Data type conversion is +supported for numerical and string data types (if the string contains a +valid number enclosed in quotes) when reading a FITS header keyword +value and for numeric values when reading or writing values in the +primary array or a table column. CFITSIO returns status = +NUM\_OVERFLOW if the converted data value exceeds the range of the +output data type. Implicit data type conversion is not supported +within binary tables for string, logical, complex, or double complex +data types. + +In addition, any table column may be read as if it contained string values. +In the case of numeric columns the returned string will be formatted +using the TDISPn display format if it exists. + +**G. Data Scaling + +When reading numerical data values in the primary array or a +table column, the values will be scaled automatically by the BSCALE and +BZERO (or TSCALn and TZEROn) header keyword values if they are +present in the header. The scaled data that is returned to the reading +program will have +- + output value = (FITS value) * BSCALE + BZERO +- +(a corresponding formula using TSCALn and TZEROn is used when reading +from table columns). In the case of integer output values the floating +point scaled value is truncated to an integer (not rounded to the +nearest integer). The ftpscl and fttscl subroutines may be used to +override the scaling parameters defined in the header (e.g., to turn +off the scaling so that the program can read the raw unscaled values +from the FITS file). + +When writing numerical data to the primary array or to a table +column the data values will generally be automatically inversely scaled +by the value of the BSCALE and BZERO (or TSCALn and TZEROn) header +keyword values if they they exist in the header. These keywords must +have been written to the header before any data is written for them to +have any effect. Otherwise, one may use the ftpscl and fttscl +subroutines to define or override the scaling keywords in the header +(e.g., to turn off the scaling so that the program can write the raw +unscaled values into the FITS file). If scaling is performed, the +inverse scaled output value that is written into the FITS file will +have +- + FITS value = ((input value) - BZERO) / BSCALE +- +(a corresponding formula using TSCALn and TZEROn is used when +writing to table columns). Rounding to the nearest integer, rather +than truncation, is performed when writing integer datatypes to the +FITS file. + +**H. Error Status Values and the Error Message Stack + +The last parameter in nearly every FITSIO subroutine is the error +status value which is both an input and an output parameter. A +returned positive value for this parameter indicates an error was +detected. A listing of all the FITSIO status code values is given at +the end of this document. + +The FITSIO library uses an `inherited status' convention for the status +parameter which means that if a subroutine is called with a positive +input value of the status parameter, then the subroutine will exit +immediately without changing the value of the status parameter. Thus, +if one passes the status value returned from each FITSIO routine as +input to the next FITSIO subroutine, then whenever an error is detected +all further FITSIO processing will cease. This convention can simplify +the error checking in application programs because it is not necessary +to check the value of the status parameter after every single FITSIO +subroutine call. If a program contains a sequence of several FITSIO +calls, one can just check the status value after the last call. Since +the returned status values are generally distinctive, it should be +possible to determine which subroutine originally returned the error +status. + +FITSIO also maintains an internal stack of error messages (80-character +maximum length) which in many cases provide a more detailed explanation +of the cause of the error than is provided by the error status number +alone. It is recommended that the error message stack be printed out +whenever a program detects a FITSIO error. To do this, call the FTGMSG +routine repeatedly to get the successive messages on the stack. When the +stack is empty FTGMSG will return a blank string. Note that this is a +`First In -- First Out' stack, so the oldest error message is returned +first by ftgmsg. + +**I. Variable-Length Array Facility in Binary Tables + +FITSIO provides easy-to-use support for reading and writing data in +variable length fields of a binary table. The variable length columns +have TFORMn keyword values of the form `1Pt(len)' or `1Qt(len)' where `t' is the +datatype code (e.g., I, J, E, D, etc.) and `len' is an integer +specifying the maximum length of the vector in the table. If the value +of `len' is not specified when the table is created (e.g., if the TFORM +keyword value is simply specified as '1PE' instead of '1PE(400) ), then +FITSIO will automatically scan the table when it is closed to +determine the maximum length of the vector and will append this value +to the TFORMn value. + +The same routines which read and write data in an ordinary fixed length +binary table extension are also used for variable length fields, +however, the subroutine parameters take on a slightly different +interpretation as described below. + +All the data in a variable length field is written into an area called +the `heap' which follows the main fixed-length FITS binary table. The +size of the heap, in bytes, is specified with the PCOUNT keyword in the +FITS header. When creating a new binary table, the initial value of +PCOUNT should usually be set to zero. FITSIO will recompute the size +of the heap as the data is written and will automatically update the +PCOUNT keyword value when the table is closed. When writing variable +length data to a table, CFITSIO will automatically extend the size +of the heap area if necessary, so that any following HDUs do not +get overwritten. + +By default the heap data area starts immediately after the last row of +the fixed-length table. This default starting location may be +overridden by the THEAP keyword, but this is not recommended. +If additional rows of data are added to the table, CFITSIO will +automatically shift the the heap down to make room for the new +rows, but it is obviously be more efficient to initially +create the table with the necessary number of blank rows, so that +the heap does not needed to be constantly moved. + +When writing to a variable length field, the entire array of values for +a given row of the table must be written with a single call to FTPCLx. +The total length of the array is calculated from (NELEM+FELEM-1). One +cannot append more elements to an existing field at a later time; any +attempt to do so will simply overwrite all the data which was previously +written. Note also that the new data will be written to a new area of +the heap and the heap space used by the previous write cannot be +reclaimed. For this reason it is advised that each row of a variable +length field only be written once. An exception to this general rule +occurs when setting elements of an array as undefined. One must first +write a dummy value into the array with FTPCLx, and then call FTPCLU to +flag the desired elements as undefined. (Do not use the FTPCNx family +of routines with variable length fields). Note that the rows of a table, +whether fixed or variable length, do not have to be written +consecutively and may be written in any order. + +When writing to a variable length ASCII character field (e.g., TFORM = +'1PA') only a single character string written. FTPCLS writes the whole +length of the input string (minus any trailing blank characters), thus +the NELEM and FELEM parameters are ignored. If the input string is +completely blank then FITSIO will write one blank character to the FITS +file. Similarly, FTGCVS and FTGCFS read the entire string (truncated +to the width of the character string argument in the subroutine call) +and also ignore the NELEM and FELEM parameters. + +The FTPDES subroutine is useful in situations where multiple rows of a +variable length column have the identical array of values. One can +simply write the array once for the first row, and then use FTPDES to +write the same descriptor values into the other rows (use the FTGDES +routine to read the first descriptor value); all the rows will then +point to the same storage location thus saving disk space. + +When reading from a variable length array field one can only read as +many elements as actually exist in that row of the table; reading does +not automatically continue with the next row of the table as occurs +when reading an ordinary fixed length table field. Attempts to read +more than this will cause an error status to be returned. One can +determine the number of elements in each row of a variable column with +the FTGDES subroutine. + +**I. Support for IEEE Special Values + +The ANSI/IEEE-754 floating-point number standard defines certain +special values that are used to represent such quantities as +Not-a-Number (NaN), denormalized, underflow, overflow, and infinity. +(See the Appendix in the NOST FITS standard or the NOST FITS User's +Guide for a list of these values). The FITSIO subroutines that read +floating point data in FITS files recognize these IEEE special values +and by default interpret the overflow and infinity values as being +equivalent to a NaN, and convert the underflow and denormalized values +into zeros. In some cases programmers may want access to the raw IEEE +values, without any modification by FITSIO. This can be done by +calling the FTGPVx or FTGCVx routines while specifying 0.0 as the value +of the NULLVAL parameter. This will force FITSIO to simply pass the +IEEE values through to the application program, without any +modification. This does not work for double precision values on +VAX/VMS machines, however, where there is no easy way to bypass the +default interpretation of the IEEE special values. This is also not +supported when reading floating-point images that have been compressed +with the FITS tiled image compression convention that is discussed in +section 5.6; the pixels values in tile compressed images are +represented by scaled integers, and a reserved integer value +(not a NaN) is used to represent undefined pixels. + + +**J. When the Final Size of the FITS HDU is Unknown + +It is not required to know the total size of a FITS data array or table +before beginning to write the data to the FITS file. In the case of +the primary array or an image extension, one should initially create +the array with the size of the highest dimension (largest NAXISn +keyword) set to a dummy value, such as 1. Then after all the data have +been written and the true dimensions are known, then the NAXISn value +should be updated using the fits\_ update\_key routine before moving to +another extension or closing the FITS file. + +When writing to FITS tables, CFITSIO automatically keeps track of the +highest row number that is written to, and will increase the size of +the table if necessary. CFITSIO will also automatically insert space +in the FITS file if necessary, to ensure that the data 'heap', if it +exists, and/or any additional HDUs that follow the table do not get +overwritten as new rows are written to the table. + +As a general rule it is best to specify the initial number of rows = 0 +when the table is created, then let CFITSIO keep track of the number of +rows that are actually written. The application program should not +manually update the number of rows in the table (as given by the NAXIS2 +keyword) since CFITSIO does this automatically. If a table is +initially created with more than zero rows, then this will usually be +considered as the minimum size of the table, even if fewer rows are +actually written to the table. Thus, if a table is initially created +with NAXIS2 = 20, and CFITSIO only writes 10 rows of data before +closing the table, then NAXIS2 will remain equal to 20. If however, 30 +rows of data are written to this table, then NAXIS2 will be increased +from 20 to 30. The one exception to this automatic updating of the +NAXIS2 keyword is if the application program directly modifies the +value of NAXIS2 (up or down) itself just before closing the table. In this +case, CFITSIO does not update NAXIS2 again, since it assumes that the +application program must have had a good reason for changing the value +directly. This is not recommended, however, and is only provided for +backward compatibility with software that initially creates a table +with a large number of rows, than decreases the NAXIS2 value to the +actual smaller value just before closing the table. + +**K. Local FITS Conventions supported by FITSIO + +CFITSIO supports several local FITS conventions which are not +defined in the official NOST FITS standard and which are not +necessarily recognized or supported by other FITS software packages. +Programmers should be cautious about using these features, especially +if the FITS files that are produced are expected to be processed by +other software systems which do not use the CFITSIO interface. + +***1. Support for Long String Keyword Values. + +The length of a standard FITS string keyword is limited to 68 +characters because it must fit entirely within a single FITS header +keyword record. In some instances it is necessary to encode strings +longer than this limit, so FITSIO supports a local convention in which +the string value is continued over multiple keywords. This +continuation convention uses an ampersand character at the end of each +substring to indicate that it is continued on the next keyword, and the +continuation keywords all have the name CONTINUE without an equal sign +in column 9. The string value may be continued in this way over as many +additional CONTINUE keywords as is required. The following lines +illustrate this continuation convention which is used in the value of +the STRKEY keyword: +- +LONGSTRN= 'OGIP 1.0' / The OGIP Long String Convention may be used. +STRKEY = 'This is a very long string keyword&' / Optional Comment +CONTINUE ' value that is continued over 3 keywords in the & ' +CONTINUE 'FITS header.' / This is another optional comment. +- +It is recommended that the LONGSTRN keyword, as shown +here, always be included in any HDU that uses this longstring +convention. A subroutine called FTPLSW +has been provided in CFITSIO to write this keyword if it does not +already exist. + +This long string convention is supported by the following FITSIO +subroutines that deal with string-valued keywords: +- + ftgkys - read a string keyword + ftpkls - write (append) a string keyword + ftikls - insert a string keyword + ftmkls - modify the value of an existing string keyword + ftukls - update an existing keyword, or write a new keyword + ftdkey - delete a keyword +- +These routines will transparently read, write, or delete a long string +value in the FITS file, so programmers in general do not have to be +concerned about the details of the convention that is used to encode +the long string in the FITS header. When reading a long string, one +must ensure that the character string parameter used in these +subroutine calls has been declared long enough to hold the entire +string, otherwise the returned string value will be truncated. + +Note that the more commonly used FITSIO subroutine to write string +valued keywords (FTPKYS) does NOT support this long string convention +and only supports strings up to 68 characters in length. This has been +done deliberately to prevent programs from inadvertently writing +keywords using this non-standard convention without the explicit intent +of the programmer or user. The FTPKLS subroutine must be called +instead to write long strings. This routine can also be used to write +ordinary string values less than 68 characters in length. + +***2. Arrays of Fixed-Length Strings in Binary Tables + +CFITSIO supports 2 ways to specify that a character column in a binary +table contains an array of fixed-length strings. The first way, which +is officially supported by the FITS Standard document, uses the TDIMn keyword. +For example, if TFORMn = '60A' and TDIMn = '(12,5)' then that +column will be interpreted as containing an array of 5 strings, each 12 +characters long. + +FITSIO also supports a +local convention for the format of the TFORMn keyword value of the form +'rAw' where 'r' is an integer specifying the total width in characters +of the column, and 'w' is an integer specifying the (fixed) length of +an individual unit string within the vector. For example, TFORM1 = +'120A10' would indicate that the binary table column is 120 characters +wide and consists of 12 10-character length strings. This convention +is recognized by the FITSIO subroutines that read or write strings in +binary tables. The Binary Table definition document specifies that +other optional characters may follow the datatype code in the TFORM +keyword, so this local convention is in compliance with the +FITS standard, although other FITS readers are not required to +recognize this convention. + +The Binary Table definition document that was approved by the IAU in +1994 contains an appendix describing an alternate convention for +specifying arrays of fixed or variable length strings in a binary table +character column (with the form 'rA:SSTRw/nnn)'. This appendix was not +officially voted on by the IAU and hence is still provisional. FITSIO +does not currently support this proposal. + +***3. Keyword Units Strings + +One deficiency of the current FITS Standard is that it does not define +a specific convention for recording the physical units of a keyword +value. The TUNITn keyword can be used to specify the physical units of +the values in a table column, but there is no analogous convention for +keyword values. The comment field of the keyword is often used for +this purpose, but the units are usually not specified in a well defined +format that FITS readers can easily recognize and extract. + +To solve this deficiency, FITSIO uses a local convention in which the +keyword units are enclosed in square brackets as the first token in the +keyword comment field; more specifically, the opening square bracket +immediately follows the slash '/' comment field delimiter and a single +space character. The following examples illustrate keywords that use +this convention: + +- +EXPOSURE= 1800.0 / [s] elapsed exposure time +V_HELIO = 16.23 / [km s**(-1)] heliocentric velocity +LAMBDA = 5400. / [angstrom] central wavelength +FLUX = 4.9033487787637465E-30 / [J/cm**2/s] average flux +- + +In general, the units named in the IAU(1988) Style Guide are +recommended, with the main exception that the preferred unit for angle +is 'deg' for degrees. + +The FTPUNT and FTGUNT subroutines in FITSIO write and read, +respectively, the keyword unit strings in an existing keyword. + +***4. HIERARCH Convention for Extended Keyword Names + +CFITSIO supports the HIERARCH keyword convention which allows keyword +names that are longer then 8 characters and may contain the full range +of printable ASCII text characters. This convention +was developed at the European Southern Observatory (ESO) to support +hierarchical FITS keyword such as: +- +HIERARCH ESO INS FOCU POS = -0.00002500 / Focus position +- +Basically, this convention uses the FITS keyword 'HIERARCH' to indicate +that this convention is being used, then the actual keyword name +({\tt'ESO INS FOCU POS'} in this example) begins in column 10 and can +contain any printable ASCII text characters, including spaces. The +equals sign marks the end of the keyword name and is followed by the +usual value and comment fields just as in standard FITS keywords. +Further details of this convention are described at +http://arcdev.hq.eso.org/dicb/dicd/dic-1-1.4.html (search for +HIERARCH). + +This convention allows a much broader range of keyword names +than is allowed by the FITS Standard. Here are more examples +of such keywords: +- +HIERARCH LongKeyword = 47.5 / Keyword has > 8 characters, and mixed case +HIERARCH XTE$TEMP = 98.6 / Keyword contains the '$' character +HIERARCH Earth is a star = F / Keyword contains embedded spaces +- +CFITSIO will transparently read and write these keywords, so application +programs do not in general need to know anything about the specific +implementation details of the HIERARCH convention. In particular, +application programs do not need to specify the `HIERARCH' part of the +keyword name when reading or writing keywords (although it +may be included if desired). When writing a keyword, CFITSIO first +checks to see if the keyword name is legal as a standard FITS keyword +(no more than 8 characters long and containing only letters, digits, or +a minus sign or underscore). If so it writes it as a standard FITS +keyword, otherwise it uses the hierarch convention to write the +keyword. The maximum keyword name length is 67 characters, which +leaves only 1 space for the value field. A more practical limit is +about 40 characters, which leaves enough room for most keyword values. +CFITSIO returns an error if there is not enough room for both the +keyword name and the keyword value on the 80-character card, except for +string-valued keywords which are simply truncated so that the closing +quote character falls in column 80. In the current implementation, +CFITSIO preserves the case of the letters when writing the keyword +name, but it is case-insensitive when reading or searching for a +keyword. The current implementation allows any ASCII text character +(ASCII 32 to ASCII 126) in the keyword name except for the '=' +character. A space is also required on either side of the equal sign. + +**L. Optimizing Code for Maximum Processing Speed + +CFITSIO has been carefully designed to obtain the highest possible +speed when reading and writing FITS files. In order to achieve the +best performance, however, application programmers must be careful to +call the CFITSIO routines appropriately and in an efficient sequence; +inappropriate usage of CFITSIO routines can greatly slow down the +execution speed of a program. + +The maximum possible I/O speed of CFITSIO depends of course on the type +of computer system that it is running on. As a rough guide, the +current generation of workstations can achieve speeds of 2 -- 10 MB/s +when reading or writing FITS images and similar, or slightly slower +speeds with FITS binary tables. Reading of FITS files can occur at +even higher rates (30MB/s or more) if the FITS file is still cached in +system memory following a previous read or write operation on the same +file. To more accurately predict the best performance that is possible +on any particular system, a diagnostic program called ``speed.c'' is +included with the CFITSIO distribution which can be run to +approximately measure the maximum possible speed of writing and reading +a test FITS file. + +The following 2 sections provide some background on how CFITSIO +internally manages the data I/O and describes some strategies that may +be used to optimize the processing speed of software that uses +CFITSIO. + +***1. Background Information: How CFITSIO Manages Data I/O + +Many CFITSIO operations involve transferring only a small number of +bytes to or from the FITS file (e.g, reading a keyword, or writing a +row in a table); it would be very inefficient to physically read or +write such small blocks of data directly in the FITS file on disk, +therefore CFITSIO maintains a set of internal Input--Output (IO) +buffers in RAM memory that each contain one FITS block (2880 bytes) of +data. Whenever CFITSIO needs to access data in the FITS file, it first +transfers the FITS block containing those bytes into one of the IO +buffers in memory. The next time CFITSIO needs to access bytes in the +same block it can then go to the fast IO buffer rather than using a +much slower system disk access routine. The number of available IO +buffers is determined by the NIOBUF parameter (in fitsio2.h) and is +currently set to 40. + +Whenever CFITSIO reads or writes data it first checks to see if that +block of the FITS file is already loaded into one of the IO buffers. +If not, and if there is an empty IO buffer available, then it will load +that block into the IO buffer (when reading a FITS file) or will +initialize a new block (when writing to a FITS file). If all the IO +buffers are already full, it must decide which one to reuse (generally +the one that has been accessed least recently), and flush the contents +back to disk if it has been modified before loading the new block. + +The one major exception to the above process occurs whenever a large +contiguous set of bytes are accessed, as might occur when reading or +writing a FITS image. In this case CFITSIO bypasses the internal IO +buffers and simply reads or writes the desired bytes directly in the +disk file with a single call to a low-level file read or write +routine. The minimum threshold for the number of bytes to read or +write this way is set by the MINDIRECT parameter and is currently set +to 3 FITS blocks = 8640 bytes. This is the most efficient way to read +or write large chunks of data and can achieve IO transfer rates of +5 -- 10MB/s or greater. Note that this fast direct IO process is not +applicable when accessing columns of data in a FITS table because the +bytes are generally not contiguous since they are interleaved by the +other columns of data in the table. This explains why the speed for +accessing FITS tables is generally slower than accessing +FITS images. + +Given this background information, the general strategy for efficiently +accessing FITS files should now be apparent: when dealing with FITS +images, read or write large chunks of data at a time so that the direct +IO mechanism will be invoked; when accessing FITS headers or FITS +tables, on the other hand, once a particular FITS block has been +loading into one of the IO buffers, try to access all the needed +information in that block before it gets flushed out of the IO buffer. +It is important to avoid the situation where the same FITS block is +being read then flushed from a IO buffer multiple times. + +The following section gives more specific suggestions for optimizing +the use of CFITSIO. + +***2. Optimization Strategies + +1. Because the data in FITS files is always stored in "big-endian" byte order, +where the first byte of numeric values contains the most significant bits and the +last byte contains the least significant bits, CFITSIO must swap the order of the bytes +when reading or writing FITS files when running on little-endian machines (e.g., +Linux and Microsoft Windows operating systems running on PCs with x86 CPUs). + +On fairly new CPUs that support "SSSE3" machine instructions +(e.g., starting with Intel Core 2 CPUs in 2007, and in AMD CPUs +beginning in 2011) significantly faster 4-byte and 8-byte swapping +algorithms are available. These faster byte swapping functions are +not used by default in CFITSIO (because of the potential code +portablility issues), but users can enable them on supported +platforms by adding the appropriate compiler flags (-mssse3 with gcc +or icc on linux) when compiling the swapproc.c source file, which will +allow the compiler to generate code using the SSSE3 instruction set. +A convenient way to do this is to configure the CFITSIO library +with the following command: +- + > ./configure --enable-ssse3 +- +Note, however, that a binary executable file that is +created using these faster functions will only run on +machines that support the SSSE3 machine instructions. It will +crash on machines that do not support them. + +For faster 2-byte swaps on virtually all x86-64 CPUs (even those that +do not support SSSE3), a variant using only SSE2 instructions exists. +SSE2 is enabled by default on x86\_64 CPUs with 64-bit operating systems +(and is also automatically enabled by the --enable-ssse3 flag). +When running on x86\_64 CPUs with 32-bit operating systems, these faster +2-byte swapping algorithms are not used by default in CFITSIO, but can be +enabled explicitly with: +- +./configure --enable-sse2 +- +Preliminary testing indicates that these SSSE3 and SSE2 based +byte-swapping algorithms can boost the CFITSIO performance when +reading or writing FITS images by 20\% - 30\% or more. +It is important to note, however, that compiler optimization must be +turned on (e.g., by using the -O1 or -O2 flags in gcc) when building +programs that use these fast byte-swapping algorithms in order +to reap the full benefit of the SSSE3 and SSE2 instructions; without +optimization, the code may actually run slower than when using +more traditional byte-swapping techniques. + +2. When dealing with a FITS primary array or IMAGE extension, it is +more efficient to read or write large chunks of the image at a time +(at least 3 FITS blocks = 8640 bytes) so that the direct IO mechanism +will be used as described in the previous section. Smaller chunks of +data are read or written via the IO buffers, which is somewhat less +efficient because of the extra copy operation and additional +bookkeeping steps that are required. In principle it is more efficient +to read or write as big an array of image pixels at one time as +possible, however, if the array becomes so large that the operating +system cannot store it all in RAM, then the performance may be degraded +because of the increased swapping of virtual memory to disk. + +3. When dealing with FITS tables, the most important efficiency factor +in the software design is to read or write the data in the FITS file in +a single pass through the file. An example of poor program design +would be to read a large, 3-column table by sequentially reading the +entire first column, then going back to read the 2nd column, and +finally the 3rd column; this obviously requires 3 passes through the +file which could triple the execution time of an I/O limited program. +For small tables this is not important, but when reading multi-megabyte +sized tables these inefficiencies can become significant. The more +efficient procedure in this case is to read or write only as many rows +of the table as will fit into the available internal I/O buffers, then +access all the necessary columns of data within that range of rows. +Then after the program is completely finished with the data in those +rows it can move on to the next range of rows that will fit in the +buffers, continuing in this way until the entire file has been +processed. By using this procedure of accessing all the columns of a +table in parallel rather than sequentially, each block of the FITS file +will only be read or written once. + +The optimal number of rows to read or write at one time in a given +table depends on the width of the table row, on the number of I/O +buffers that have been allocated in FITSIO, and also on the number of +other FITS files that are open at the same time (since one I/O buffer +is always reserved for each open FITS file). Fortunately, a FITSIO +routine is available that will return the optimal number of rows for a +given table: call ftgrsz(unit, nrows, status). It is not critical to +use exactly the value of nrows returned by this routine, as long as one +does not exceed it. Using a very small value however can also lead to +poor performance because of the overhead from the larger number of +subroutine calls. + +The optimal number of rows returned by ftgrsz is valid only as long as +the application program is only reading or writing data in the +specified table. Any other calls to access data in the table header +would cause additional blocks of data to be +loaded into the I/O buffers displacing data from the original table, +and should be avoided during the critical period while the table is +being read or written. + +4. Use binary table extensions rather than ASCII table +extensions for better efficiency when dealing with tabular data. The +I/O to ASCII tables is slower because of the overhead in formatting or +parsing the ASCII data fields, and because ASCII tables are about twice +as large as binary tables with the same information content. + +5. Design software so that it reads the FITS header keywords in the +same order in which they occur in the file. When reading keywords, +FITSIO searches forward starting from the position of the last keyword +that was read. If it reaches the end of the header without finding the +keyword, it then goes back to the start of the header and continues the +search down to the position where it started. In practice, as long as +the entire FITS header can fit at one time in the available internal I/O +buffers, then the header keyword access will be very fast and it makes +little difference which order they are accessed. + +6. Avoid the use of scaling (by using the BSCALE and BZERO or TSCAL and +TZERO keywords) in FITS files since the scaling operations add to the +processing time needed to read or write the data. In some cases it may +be more efficient to temporarily turn off the scaling (using ftpscl or +fttscl) and then read or write the raw unscaled values in the FITS +file. + +7. Avoid using the 'implicit datatype conversion' capability in +FITSIO. For instance, when reading a FITS image with BITPIX = -32 +(32-bit floating point pixels), read the data into a single precision +floating point data array in the program. Forcing FITSIO to convert +the data to a different datatype can significantly slow the program. + +8. Where feasible, design FITS binary tables using vector column +elements so that the data are written as a contiguous set of bytes, +rather than as single elements in multiple rows. For example, it is +faster to access the data in a table that contains a single row +and 2 columns with TFORM keywords equal to '10000E' and '10000J', than +it is to access the same amount of data in a table with 10000 rows +which has columns with the TFORM keywords equal to '1E' and '1J'. In +the former case the 10000 floating point values in the first column are +all written in a contiguous block of the file which can be read or +written quickly, whereas in the second case each floating point value +in the first column is interleaved with the integer value in the second +column of the same row so CFITSIO has to explicitly move to the +position of each element to be read or written. + +9. Avoid the use of variable length vector columns in binary tables, +since any reading or writing of these data requires that CFITSIO first +look up or compute the starting address of each row of data in the +heap. In practice, this is probably not a significant efficiency issue. + +10. When copying data from one FITS table to another, it is faster to +transfer the raw bytes instead of reading then writing each column of +the table. The FITSIO subroutines FTGTBS and FTPTBS (for ASCII +tables), and FTGTBB and FTPTBB (for binary tables) will perform +low-level reads or writes of any contiguous range of bytes in a table +extension. These routines can be used to read or write a whole row (or +multiple rows) of a table with a single subroutine call. These +routines are fast because they bypass all the usual data scaling, error +checking and machine dependent data conversion that is normally done by +FITSIO, and they allow the program to write the data to the output file +in exactly the same byte order. For these same reasons, use of these +routines can be somewhat risky because no validation or machine +dependent conversion is performed by these routines. In general these +routines are only recommended for optimizing critical pieces of code +and should only be used by programmers who thoroughly understand the +internal byte structure of the FITS tables they are reading or +writing. + +11. Another strategy for improving the speed of writing a FITS table, +similar to the previous one, is to directly construct the entire byte +stream for a whole table row (or multiple rows) within the application +program and then write it to the FITS file with +ftptbb. This avoids all the overhead normally present +in the column-oriented CFITSIO write routines. This technique should +only be used for critical applications, because it makes the code more +difficult to understand and maintain, and it makes the code more system +dependent (e.g., do the bytes need to be swapped before writing to the +FITS file?). + +12. Finally, external factors such as the type of magnetic disk +controller (SCSI or IDE), the size of the disk cache, the average seek +speed of the disk, the amount of disk fragmentation, and the amount of +RAM available on the system can all have a significant impact on +overall I/O efficiency. For critical applications, a system +administrator should review the proposed system hardware to identify any +potential I/O bottlenecks. + + + +*VII. Basic Interface Routines + +This section defines a basic set of subroutines that can be +used to perform the most common types of read and write operations +on FITS files. New users should start with these subroutines and +then, as needed, explore the more advance routines described in +the following chapter to perform more complex or specialized operations. + +A right arrow symbol ($>$) is used to separate the input parameters from +the output parameters in the definition of each routine. This symbol +is not actually part of the calling sequence. Note that +the status parameter is both an input and an output parameter +and must be initialized = 0 prior to calling the FITSIO subroutines. + +Refer to Chapter 9 for the definition of all the parameters +used by these interface routines. + +**A. FITSIO Error Status Routines \label{FTVERS} + +>1 Return the current version number of the fitsio library. + The version number will be incremented with each new +> release of CFITSIO. +- + FTVERS( > version) +- +>2 Return the descriptive text string corresponding to a FITSIO error + status code. The 30-character length string contains a brief +> description of the cause of the error. +- + FTGERR(status, > errtext) +- +>3 Return the top (oldest) 80-character error message from the + internal FITSIO stack of error messages and shift any remaining + messages on the stack up one level. Any FITSIO error will + generate one or more messages on the stack. Call this routine + repeatedly to get each message in sequence. The error stack is empty +> when a blank string is returned. +- + FTGMSG( > errmsg) +- +>4 The FTPMRK routine puts an invisible marker on the + CFITSIO error stack. The FTCMRK routine can then be + used to delete any more recent error messages on the stack, back to + the position of the marker. This preserves any older error messages + on the stack. FTCMSG simply clears the entire error message stack. +> These routines are called without any arguments. +- + FTPMRK + FTCMRK + FTCMSG +- + +>5 Print out the error message corresponding to the input status + value and all the error messages on the FITSIO stack to the specified + file stream (stream can be either the string 'STDOUT' or 'STDERR'). +> If the input status value = 0 then this routine does nothing. +- + FTRPRT (stream, > status) +- +>6 Write an 80-character message to the FITSIO error stack. Application + programs should not normally write to the stack, but there may be +> some situations where this is desirable. +- + FTPMSG(errmsg) +- + +**B. File I/O Routines + +>1 Open an existing FITS file with readonly or readwrite access. + This routine always opens the primary array (the first HDU) of + the file, and does not move to a following extension, if one was + specified as part of the filename. Use the FTNOPN routine to + automatically move to the extension. This routine will also + open IRAF images (.imh format files) and raw binary data arrays + with READONLY access by first converting them on the fly into + virtual FITS images. See the `Extended File Name Syntax' chapter + for more details. The FTDKOPEN routine simply opens the specified + file without trying to interpret the filename using the extended +> filename syntax. +- + FTOPEN(unit,filename,rwmode, > blocksize,status) + FTDKOPEN(unit,filename,rwmode, > blocksize,status) +- +>2 Open an existing FITS file with readonly or readwrite access + and move to a following extension, if one was specified as + part of the filename. (e.g., 'filename.fits+2' or + 'filename.fits[2]' will move to the 3rd HDU in the file). + Note that this routine differs from FTOPEN in that it does not +> have the redundant blocksize argument. +- + FTNOPN(unit,filename,rwmode, > status) +- +>3 Open an existing FITS file with readonly or readwrite access + and then move to the first HDU containing significant data, if a) an HDU + name or number to open was not explicitly specified as part of the + filename, and b) if the FITS file contains a null primary array (i.e., + NAXIS = 0). In this case, it will look for the first IMAGE HDU with + NAXIS > 0, or the first table that does not contain the strings `GTI' + (Good Time Interval) or `OBSTABLE' in the EXTNAME keyword value. FTTOPN + is similar, except it will move to the first significant table HDU + (skipping over any image HDUs) in the file if a specific HDU name + or number is not specified. FTIOPN will move to the first non-null +> image HDU, skipping over any tables. +- + FTDOPN(unit,filename,rwmode, > status) + FTTOPN(unit,filename,rwmode, > status) + FTIOPN(unit,filename,rwmode, > status) +- +>4 Open and initialize a new empty FITS file. A template file may also be + specified to define the structure of the new file (see section 4.2.4). + The FTDKINIT routine simply creates the specified + file without trying to interpret the filename using the extended +> filename syntax. +- + FTINIT(unit,filename,blocksize, > status) + FTDKINIT(unit,filename,blocksize, > status) +- +>>5 Close a FITS file previously opened with ftopen or ftinit +- + FTCLOS(unit, > status) +- +>6 Move to a specified (absolute) HDU in the FITS file (nhdu = 1 for the +> FITS primary array) +- + FTMAHD(unit,nhdu, > hdutype,status) +- +>7 Create a primary array (if none already exists), or insert a + new IMAGE extension immediately following the CHDU, or + insert a new Primary Array at the beginning of the file. Any + following extensions in the file will be shifted down to make room + for the new extension. If the CHDU is the last HDU in the file + then the new image extension will simply be appended to the end of + the file. One can force a new primary array to be inserted at the + beginning of the FITS file by setting status = -9 prior + to calling the routine. In this case the existing primary array will be + converted to an IMAGE extension. The new extension (or primary + array) will become the CHDU. The FTIIMGLL routine is identical + to the FTIIMG routine except that the 4th parameter (the length + of each axis) is an array of 64-bit integers rather than an array +> of 32-bit integers. +- + FTIIMG(unit,bitpix,naxis,naxes, > status) + FTIIMGLL(unit,bitpix,naxis,naxesll, > status) +- +>8 Insert a new ASCII TABLE extension immediately following the CHDU. + Any following extensions will be shifted down to make room for + the new extension. If there are no other following extensions + then the new table extension will simply be appended to the + end of the file. The new extension will become the CHDU. The FTITABLL + routine is identical + to the FTITAB routine except that the 2nd and 3rd parameters (that give + the size of the table) are 64-bit integers rather than + 32-bit integers. Under normal circumstances, the nrows and nrowsll + paramenters should have a value of 0; CFITSIO will automatically update +> the number of rows as data is written to the table. +- + FTITAB(unit,rowlen,nrows,tfields,ttype,tbcol,tform,tunit,extname, > + status) + FTITABLL(unit,rowlenll,nrowsll,tfields,ttype,tbcol,tform,tunit,extname, > + status) +- +>9 Insert a new binary table extension immediately following the CHDU. + Any following extensions will be shifted down to make room for + the new extension. If there are no other following extensions + then the new bintable extension will simply be appended to the + end of the file. The new extension will become the CHDU. The FTIBINLL + routine is identical + to the FTIBIN routine except that the 2nd parameter (that gives + the length of the table) is a 64-bit integer rather than + a 32-bit integer. Under normal circumstances, the nrows and nrowsll + paramenters should have a value of 0; CFITSIO will automatically update +> the number of rows as data is written to the table. +- + FTIBIN(unit,nrows,tfields,ttype,tform,tunit,extname,varidat > status) + FTIBINLL(unit,nrowsll,tfields,ttype,tform,tunit,extname,varidat > status) + +- +**C. Keyword I/O Routines + +>>1 Put (append) an 80-character record into the CHU. +- + FTPREC(unit,card, > status) +- +>2 Put (append) a new keyword of the appropriate datatype into the CHU. + The E and D versions of this routine have the added feature that + if the 'decimals' parameter is negative, then the 'G' display + format rather then the 'E' format will be used when constructing + the keyword value, taking the absolute value of 'decimals' for the + precision. This will suppress trailing zeros, and will use a + fixed format rather than an exponential format, +> depending on the magnitude of the value. +- + FTPKY[JKLS](unit,keyword,keyval,comment, > status) + FTPKY[EDFG](unit,keyword,keyval,decimals,comment, > status) +- +>3 Get the nth 80-character header record from the CHU. The first keyword + in the header is at key\_no = 1; if key\_no = 0 then this subroutine + simple moves the internal pointer to the beginning of the header + so that subsequent keyword operations will start at the top of +> the header; it also returns a blank card value in this case. +- + FTGREC(unit,key_no, > card,status) +- +>4 Get a keyword value (with the appropriate datatype) and comment from +> the CHU +- + FTGKY[EDJKLS](unit,keyword, > keyval,comment,status) +- +>>5 Delete an existing keyword record. +- + FTDKEY(unit,keyword, > status) +- + +**D. Data I/O Routines + +The following routines read or write data values in the current HDU of +the FITS file. Automatic datatype conversion +will be attempted for numerical datatypes if the specified datatype is +different from the actual datatype of the FITS array or table column. + +>>1 Write elements into the primary data array or image extension. +- + FTPPR[BIJKED](unit,group,fpixel,nelements,values, > status) +- +>2 Read elements from the primary data array or image extension. + Undefined array elements will be + returned with a value = nullval, unless nullval = 0 in which case no + checks for undefined pixels will be performed. The anyf parameter is + set to true (= .true.) if any of the returned +> elements were undefined. +- + FTGPV[BIJKED](unit,group,fpixel,nelements,nullval, > values,anyf,status) +- +>3 Write elements into an ASCII or binary table column. The `felem' + parameter applies only to vector columns in binary tables and is +> ignored when writing to ASCII tables. +- + FTPCL[SLBIJKEDCM](unit,colnum,frow,felem,nelements,values, > status) +- +>4 Read elements from an ASCII or binary table column. Undefined + array elements will be returned with a value = nullval, unless nullval = 0 + (or = ' ' for ftgcvs) in which case no checking for undefined values will + be performed. The ANYF parameter is set to true if any of the returned + elements are undefined. + + Any column, regardless of it's intrinsic datatype, may be read as a + string. It should be noted however that reading a numeric column + as a string is 10 - 100 times slower than reading the same column + as a number due to the large overhead in constructing the formatted + strings. The display format of the returned strings will be + determined by the TDISPn keyword, if it exists, otherwise by the + datatype of the column. The length of the returned strings can be + determined with the ftgcdw routine. The following TDISPn display + formats are currently supported: +- + Iw.m Integer + Ow.m Octal integer + Zw.m Hexadecimal integer + Fw.d Fixed floating point + Ew.d Exponential floating point + Dw.d Exponential floating point + Gw.d General; uses Fw.d if significance not lost, else Ew.d +- + where w is the width in characters of the displayed values, m is the minimum + number of digits displayed, and d is the number of digits to the right of the +> decimal. The .m field is optional. + +- + FTGCV[SBIJKEDCM](unit,colnum,frow,felem,nelements,nullval, > + values,anyf,status) +- +>5 Get the table column number and full name of the column whose name + matches the input template string. See the `Advanced Interface Routines' +> chapter for a full description of this routine. +- + FTGCNN(unit,casesen,coltemplate, > colname,colnum,status) +- + + +*VIII Advanced Interface Subroutines + +This chapter defines all the available subroutines in the FITSIO user +interface. For completeness, the basic subroutines described in the +previous chapter are also repeated here. A right arrow symbol is used +here to separate the input parameters from the output parameters in the +definition of each subroutine. This symbol is not actually part of the +calling sequence. An alphabetical list and definition of all the +parameters is given at the end of this section. + +**A. FITS File Open and Close Subroutines: \label{FTOPEN} + +>1 Open an existing FITS file with readonly or readwrite access. The +FTDKOPEN routine simply opens the specified file without trying to +interpret the filename using the extended filename syntax. FTDOPN opens +the file and +also moves to the first HDU containing significant data, if no specific +HDU is specified as part of the filename. FTTOPN and FTIOPN are similar +except that they will move to the first table HDU or image HDU, respectively, +>if a HDU name or number is not specified as part of the filename. +- + FTOPEN(unit,filename,rwmode, > blocksize,status) + FTDKOPEN(unit,filename,rwmode, > blocksize,status) + + FTDOPN(unit,filename,rwmode, > status) + FTTOPN(unit,filename,rwmode, > status) + FTIOPN(unit,filename,rwmode, > status) +- + +>2 Open an existing FITS file with readonly or readwrite access + and move to a following extension, if one was specified as + part of the filename. (e.g., 'filename.fits+2' or + 'filename.fits[2]' will move to the 3rd HDU in the file). + Note that this routine differs from FTOPEN in that it does not +> have the redundant blocksize argument. +- + FTNOPN(unit,filename,rwmode, > status) +- +>3 Reopen a FITS file that was previously opened with + FTOPEN, FTNOPN, or FTINIT. The newunit number + may then be treated as a separate file, and one may + simultaneously read or write to 2 (or more) different extensions in + the same file. The FTOPEN and FTNOPN routines (above) automatically + detects cases where a previously opened file is being opened again, + and then internally call FTREOPEN, so programs should rarely +> need to explicitly call this routine. +- + FTREOPEN(unit, > newunit, status) +- +>4 Open and initialize a new empty FITS file. + The FTDKINIT routine simply creates the specified + file without trying to interpret the filename using the extended +> filename syntax. +- + FTINIT(unit,filename,blocksize, > status) + FTDKINIT(unit,filename,blocksize, > status) +- + +>5 Create a new FITS file, using a template file to define its + initial size and structure. The template may be another FITS HDU + or an ASCII template file. If the input template file name + is blank, then this routine behaves the same as FTINIT. + The currently supported format of the ASCII template file is described + under the fits\_parse\_template routine (in the general Utilities + section), but this may change slightly later releases of +> CFITSIO. +- + FTTPLT(unit, filename, tplfilename, > status) +- +>6 Flush internal buffers of data to the output FITS file + previously opened with ftopen or ftinit. The routine usually + never needs to be called, but doing so will ensure that + if the program subsequently aborts, then the FITS file will +> have at least been closed properly. +- + FTFLUS(unit, > status) +- +>>7 Close a FITS file previously opened with ftopen or ftinit +- + FTCLOS(unit, > status) +- +>8 Close and DELETE a FITS file previously opened with ftopen or ftinit. + This routine may be useful in cases where a FITS file is created, but +> an error occurs which prevents the complete file from being written. +- + FTDELT(unit, > status) +- +>9 Get the value of an unused I/O unit number which may then be used + as input to FTOPEN or FTINIT. This routine searches for the first + unused unit number in the range from with 99 down to 50. This + routine just keeps an internal list of the allocated unit numbers + and does not physically check that the Fortran unit is available (to be + compatible with the SPP version of FITSIO). Thus users must not + independently allocate any unit numbers in the range 50 - 99 + if this routine is also to be used in the same program. This + routine is provided for convenience only, and it is not required +> that the unit numbers used by FITSIO be allocated by this routine. +- + FTGIOU( > iounit, status) +- +>10 Free (deallocate) an I/O unit number which was previously allocated + with FTGIOU. All previously allocated unit numbers may be +> deallocated at once by calling FTFIOU with iounit = -1. +- + FTFIOU(iounit, > status) +- +>11 Return the Fortran unit number that corresponds to the C fitsfile +pointer value, or vice versa. These 2 C routines may be useful in +mixed language programs where both C and Fortran subroutines need +to access the same file. For example, if a FITS file is opened +with unit 12 by a Fortran subroutine, then a C routine within the +same program could get the fitfile pointer value to access the same file +by calling 'fptr = CUnit2FITS(12)'. These routines return a value +>of zero if an error occurs. +- + int CFITS2Unit(fitsfile *ptr); + fitsfile* CUnit2FITS(int unit); +- + +>11 Parse the input filename and return the HDU number that would be +moved to if the file were opened with FTNOPN. The returned HDU +number begins with 1 for the primary array, so for example, if the +input filename = `myfile.fits[2]' then hdunum = 3 will be returned. +FITSIO does not open the file to check if the extension actually exists +if an extension number is specified. If an extension *name* is included +in the file name specification (e.g. `myfile.fits[EVENTS]' then this +routine will have to open the FITS file and look for the position of +the named extension, then close file again. This is not possible if +the file is being read from the stdin stream, and an error will be +returned in this case. If the filename does not specify an explicit +extension (e.g. 'myfile.fits') then hdunum = -99 will be returned, +which is functionally equivalent to hdunum = 1. This routine is mainly +used for backward compatibility in the ftools software package and is +not recommended for general use. It is generally better and more +efficient to first open the FITS file with FTNOPN, then use FTGHDN to +determine which HDU in the file has been opened, rather than calling +> FTEXTN followed by a call to FTNOPN. +- + FTEXTN(filename, > nhdu, status) +- +>>12 Return the name of the opened FITS file. +- + FTFLNM(unit, > filename, status) +- +>>13 Return the I/O mode of the open FITS file (READONLY = 0, READWRITE = 1). +- + FTFLMD(unit, > iomode, status) +- +>14 Return the file type of the opened FITS file (e.g. 'file://', 'ftp://', +> etc.). +- + FTURLT(unit, > urltype, status) +- +>15 Parse the input filename or URL into its component parts: the file +type (file://, ftp://, http://, etc), the base input file name, the +name of the output file that the input file is to be copied to prior +to opening, the HDU or extension specification, the filtering +specifier, the binning specifier, and the column specifier. Blank +strings will be returned for any components that are not present +>in the input file name. +- + FTIURL(filename, > filetype, infile, outfile, extspec, filter, + binspec, colspec, status) +- +>16 Parse the input file name and return the root file name. The root +name includes the file type if specified, (e.g. 'ftp://' or 'http://') +and the full path name, to the extent that it is specified in the input +filename. It does not include the HDU name or number, or any filtering +>specifications. +- + FTRTNM(filename, > rootname, status) +- + +>16 Test if the input file or a compressed version of the file (with +a .gz, .Z, .z, or .zip extension) exists on disk. The returned value of +the 'exists' parameter will have 1 of the 4 following values: +- + 2: the file does not exist, but a compressed version does exist + 1: the disk file does exist + 0: neither the file nor a compressed version of the file exist + -1: the input file name is not a disk file (could be a ftp, http, + smem, or mem file, or a file piped in on the STDIN stream) +- +> +- + FTEXIST(filename, > exists, status); +- +**B. HDU-Level Operations \label{FTMAHD} + +When a FITS file is first opened or created, the internal buffers in +FITSIO automatically point to the first HDU in the file. The following +routines may be used to move to another HDU in the file. Note that +the HDU numbering convention used in FITSIO denotes the primary array +as the first HDU, the first extension in a FITS file is the second HDU, +and so on. + +>1 Move to a specified (absolute) HDU in the FITS file (nhdu = 1 for the +> FITS primary array) +- + FTMAHD(unit,nhdu, > hdutype,status) +- +>>2 Move to a new (existing) HDU forward or backwards relative to the CHDU +- + FTMRHD(unit,nmove, > hdutype,status) +- +>3 Move to the (first) HDU which has the specified extension type and + EXTNAME (or HDUNAME) and EXTVER keyword values. The hdutype parameter + may have + a value of IMAGE\_HDU (0), ASCII\_TBL (1), BINARY\_TBL (2), or ANY\_HDU (-1) + where ANY\_HDU means that only the extname and extver values will be + used to locate the correct extension. If the input value of + extver is 0 then the EXTVER keyword is ignored and the first HDU + with a matching EXTNAME (or HDUNAME) keyword will be found. If no + matching HDU is found in the file then the current HDU will remain + unchanged +> and a status = BAD\_HDU\_NUM (301) will be returned. +- + FTMNHD(unit, hdutype, extname, extver, > status) +- +>>4 Get the number of the current HDU in the FITS file (primary array = 1) +- + FTGHDN(unit, > nhdu) +- +>5 Return the type of the current HDU in the FITS file. The possible +> values for hdutype are IMAGE\_HDU (0), ASCII\_TBL (1), or BINARY\_TBL (2). +- + FTGHDT(unit, > hdutype, status) +- +>6 Return the total number of HDUs in the FITS file. +> The CHDU remains unchanged. +- + FTTHDU(unit, > hdunum, status) +- +>7 Create (append) a new empty HDU following the last extension that + has been previously accessed by the program. This will overwrite + any extensions in an existing FITS file if the program has not already + moved to that (or a later) extension using the FTMAHD or FTMRHD routines. + For example, if an existing FITS file contains a primary array and 5 + extensions and a program (1) opens the FITS file, (2) moves to + extension 4, (3) moves back to the primary array, and (4) then calls + FTCRHD, then the new extension will be written following the 4th +> extension, overwriting the existing 5th extension. +- + FTCRHD(unit, > status) +- + +>8 Create a primary array (if none already exists), or insert a + new IMAGE extension immediately following the CHDU, or + insert a new Primary Array at the beginning of the file. Any + following extensions in the file will be shifted down to make room + for the new extension. If the CHDU is the last HDU in the file + then the new image extension will simply be appended to the end of + the file. One can force a new primary array to be inserted at the + beginning of the FITS file by setting status = -9 prior + to calling the routine. In this case the existing primary array will be + converted to an IMAGE extension. The new extension (or primary + array) will become the CHDU. The FTIIMGLL routine is identical + to the FTIIMG routine except that the 4th parameter (the length + of each axis) is an array of 64-bit integers rather than an array +> of 32-bit integers. +- + FTIIMG(unit,bitpix,naxis,naxes, > status) + FTIIMGLL(unit,bitpix,naxis,naxesll, > status) +- +>9 Insert a new ASCII TABLE extension immediately following the CHDU. + Any following extensions will be shifted down to make room for + the new extension. If there are no other following extensions + then the new table extension will simply be appended to the + end of the file. The new extension will become the CHDU. The FTITABLL + routine is identical + to the FTITAB routine except that the 2nd and 3rd parameters (that give + the size of the table) are 64-bit integers rather than +> 32-bit integers. +- + FTITAB(unit,rowlen,nrows,tfields,ttype,tbcol,tform,tunit,extname, > + status) + FTITABLL(unit,rowlenll,nrowsll,tfields,ttype,tbcol,tform,tunit,extname, > + status) +- + +>10 Insert a new binary table extension immediately following the CHDU. + Any following extensions will be shifted down to make room for + the new extension. If there are no other following extensions + then the new bintable extension will simply be appended to the + end of the file. The new extension will become the CHDU. The FTIBINLL + routine is identical + to the FTIBIN routine except that the 2nd parameter (that gives + the length of the table) is a 64-bit integer rather than +> a 32-bit integer. +- + FTIBIN(unit,nrows,tfields,ttype,tform,tunit,extname,varidat > status) + FTIBINLL(unit,nrowsll,tfields,ttype,tform,tunit,extname,varidat > status) + +- + +>11 Resize an image by modifing the size, dimensions, and/or datatype of the + current primary array or image extension. If the new image, as specified + by the input arguments, is larger than the current existing image + in the FITS file then zero fill data will be inserted at the end + of the current image and any following extensions will be moved + further back in the file. Similarly, if the new image is + smaller than the current image then any following extensions + will be shifted up towards the beginning of the FITS file + and the image data will be truncated to the new size. + This routine rewrites the BITPIX, NAXIS, and NAXISn keywords + with the appropriate values for new image. The FTRSIMLL routine is identical + to the FTRSIM routine except that the 4th parameter (the length + of each axis) is an array of 64-bit integers rather than an array +> of 32-bit integers. +- + FTRSIM(unit,bitpix,naxis,naxes,status) + FTRSIMLL(unit,bitpix,naxis,naxesll,status) +- +>12 Delete the CHDU in the FITS file. Any following HDUs will be shifted + forward in the file, to fill in the gap created by the deleted + HDU. In the case of deleting the primary array (the first HDU in + the file) then the current primary array will be replace by a null + primary array containing the minimum set of required keywords and + no data. If there are more extensions in the file following the + one that is deleted, then the the CHDU will be redefined to point + to the following extension. If there are no following extensions + then the CHDU will be redefined to point to the previous HDU. The + output HDUTYPE parameter indicates the type of the new CHDU after +> the previous CHDU has been deleted. +- + FTDHDU(unit, > hdutype,status) +- +>13 Copy all or part of the input FITS file and append it + to the end of the output FITS file. If 'previous' (an integer parameter) is + not equal to 0, then any HDUs preceding the current HDU in the input file + will be copied to the output file. Similarly, 'current' and 'following' + determine whether the current HDU, and/or any following HDUs in the input + file will be copied to the output file. If all 3 parameters are not equal + to zero, then the entire input file will be copied. On return, the current + HDU in the input file will be unchanged, and the last copied HDU will be the +> current HDU in the output file. +- + FTCPFL(iunit, ounit, previous, current, following, > status) +- +>14 Copy the entire CHDU from the FITS file associated with IUNIT to the CHDU + of the FITS file associated with OUNIT. The output HDU must be empty and + not already contain any keywords. Space will be reserved for MOREKEYS + additional keywords in the output header if there is not already enough +> space. +- + FTCOPY(iunit,ounit,morekeys, > status) +- +>15 Copy the header (and not the data) from the CHDU associated with inunit + to the CHDU associated with outunit. If the current output HDU + is not completely empty, then the CHDU will be closed and a new + HDU will be appended to the output file. This routine will automatically + transform the necessary keywords when copying a primary array to + and image extension, or an image extension to a primary array. +> An empty output data unit will be created (all values = 0). +- + FTCPHD(inunit, outunit, > status) +- +>16 Copy just the data from the CHDU associated with IUNIT + to the CHDU associated with OUNIT. This will overwrite + any data previously in the OUNIT CHDU. This low level routine is used + by FTCOPY, but it may also be useful in certain application programs + which want to copy the data from one FITS file to another but also + want to modify the header keywords in the process. all the required + header keywords must be written to the OUNIT CHDU before calling +> this routine +- + FTCPDT(iunit,ounit, > status) +- + +**C. Define or Redefine the structure of the CHDU \label{FTRDEF} + +It should rarely be necessary to call the subroutines in this section. +FITSIO internally calls these routines whenever necessary, so any calls +to these routines by application programs will likely be redundant. + +>1 This routine forces FITSIO to scan the current header keywords that + define the structure of the HDU (such as the NAXISn, PCOUNT and GCOUNT + keywords) so that it can initialize the internal buffers that describe + the HDU structure. This routine may be used instead of the more + complicated calls to ftpdef, ftadef or ftbdef. This routine is + also very useful for reinitializing the structure of an HDU, + if the number of rows in a table, as specified by the NAXIS2 keyword, +> has been modified from its initial value. +- + FTRDEF(unit, > status) (DEPRECATED) +- +>2 Define the structure of the primary array or IMAGE extension. When + writing GROUPed FITS files that by convention set the NAXIS1 keyword + equal to 0, ftpdef must be called with naxes(1) = 1, NOT 0, otherwise + FITSIO will report an error status=308 when trying to write data + to a group. Note: it is usually simpler to call FTRDEF rather +> than this routine. +- + FTPDEF(unit,bitpix,naxis,naxes,pcount,gcount, > status) (DEPRECATED) +- +>3 Define the structure of an ASCII table (TABLE) extension. Note: it +> is usually simpler to call FTRDEF rather than this routine. +- + FTADEF(unit,rowlen,tfields,tbcol,tform,nrows > status) (DEPRECATED) +- +>4 Define the structure of a binary table (BINTABLE) extension. Note: it +> is usually simpler to call FTRDEF rather than this routine. +- + FTBDEF(unit,tfields,tform,varidat,nrows > status) (DEPRECATED) +- +>5 Define the size of the Current Data Unit, overriding the length + of the data unit as previously defined by ftpdef, ftadef, or ftbdef. + This is useful if one does not know the total size of the data unit until + after the data have been written. The size (in bytes) of an ASCII or + Binary table is given by NAXIS1 * NAXIS2. (Note that to determine the + value of NAXIS1 it is often more convenient to read the value of the + NAXIS1 keyword from the output file, rather than computing the row + length directly from all the TFORM keyword values). Note: it +> is usually simpler to call FTRDEF rather than this routine. +- + FTDDEF(unit,bytlen, > status) (DEPRECATED) +- +>6 Define the zero indexed byte offset of the 'heap' measured from + the start of the binary table data. By default the heap is assumed + to start immediately following the regular table data, i.e., at + location NAXIS1 x NAXIS2. This routine is only relevant for + binary tables which contain variable length array columns (with + TFORMn = 'Pt'). This subroutine also automatically writes + the value of theap to a keyword in the extension header. This + subroutine must be called after the required keywords have been + written (with ftphbn) and after the table structure has been defined +> (with ftbdef) but before any data is written to the table. +- + FTPTHP(unit,theap, > status) +- + +**D. FITS Header I/O Subroutines + +***1. Header Space and Position Routines \label{FTHDEF} + +>1 Reserve space in the CHU for MOREKEYS more header keywords. + This subroutine may be called to reserve space for keywords which are + to be written at a later time, after the data unit or subsequent + extensions have been written to the FITS file. If this subroutine is + not explicitly called, then the initial size of the FITS header will be + limited to the space available at the time that the first data is written + to the associated data unit. FITSIO has the ability to dynamically + add more space to the header if needed, however it is more efficient +> to preallocate the required space if the size is known in advance. +- + FTHDEF(unit,morekeys, > status) +- +>2 Return the number of existing keywords in the CHU (NOT including the + END keyword which is not considered a real keyword) and the remaining + space available to write additional keywords in the CHU. (returns + KEYSADD = -1 if the header has not yet been closed). + Note that FITSIO will attempt to dynamically add space for more +> keywords if required when appending new keywords to a header. +- + FTGHSP(iunit, > keysexist,keysadd,status) +- +>3 Return the number of keywords in the header and the current position + in the header. This returns the number of the keyword record that + will be read next (or one greater than the position of the last keyword + that was read or written). A value of 1 is returned if the pointer is +> positioned at the beginning of the header. +- + FTGHPS(iunit, > keysexist,key_no,status) +- +***2. Read or Write Standard Header Routines \label{FTPHPR} + +These subroutines provide a simple method of reading or writing most of +the keyword values that are normally required in a FITS files. These +subroutines are provided for convenience only and are not required to +be used. If preferred, users may call the lower-level subroutines +described in the previous section to individually read or write the +required keywords. Note that in most cases, the required keywords such +as NAXIS, TFIELD, TTYPEn, etc, which define the structure of the HDU +must be written to the header before any data can be written to the +image or table. + +>1 Put the primary header or IMAGE extension keywords into the CHU. +There are 2 available routines: The simpler FTPHPS routine is +equivalent to calling ftphpr with the default values of SIMPLE = true, +pcount = 0, gcount = 1, and EXTEND = true. PCOUNT, GCOUNT and EXTEND +keywords are not required in the primary header and are only written if +pcount is not equal to zero, gcount is not equal to zero or one, and if +extend is TRUE, respectively. When writing to an IMAGE extension, the +>SIMPLE and EXTEND parameters are ignored. +- + FTPHPS(unit,bitpix,naxis,naxes, > status) + + FTPHPR(unit,simple,bitpix,naxis,naxes,pcount,gcount,extend, > status) +- +>2 Get primary header or IMAGE extension keywords from the CHU. When + reading from an IMAGE extension the SIMPLE and EXTEND parameters are +> ignored. +- + FTGHPR(unit,maxdim, > simple,bitpix,naxis,naxes,pcount,gcount,extend, + status) +- +>3 Put the ASCII table header keywords into the CHU. The optional +TUNITn and EXTNAME keywords are written only if the input string +>values are not blank. +- + FTPHTB(unit,rowlen,nrows,tfields,ttype,tbcol,tform,tunit,extname, > + status) +- +>>4 Get the ASCII table header keywords from the CHU +- + FTGHTB(unit,maxdim, > rowlen,nrows,tfields,ttype,tbcol,tform,tunit, + extname,status) +- +>5 Put the binary table header keywords into the CHU. The optional + TUNITn and EXTNAME keywords are written only if the input string + values are not blank. The pcount parameter, which specifies the + size of the variable length array heap, should initially = 0; + FITSIO will automatically update the PCOUNT keyword value if any + variable length array data is written to the heap. The TFORM keyword + value for variable length vector columns should have the form 'Pt(len)' + or '1Pt(len)' where `t' is the data type code letter (A,I,J,E,D, etc.) + and `len' is an integer specifying the maximum length of the vectors + in that column (len must be greater than or equal to the longest + vector in the column). If `len' is not specified when the table is + created (e.g., the input TFORMn value is just '1Pt') then FITSIO will + scan the column when the table is first closed and will append the + maximum length to the TFORM keyword value. Note that if the table + is subsequently modified to increase the maximum length of the vectors + then the modifying program is responsible for also updating the TFORM +> keyword value. + +- + FTPHBN(unit,nrows,tfields,ttype,tform,tunit,extname,varidat, > status) +- +>>6 Get the binary table header keywords from the CHU +- + FTGHBN(unit,maxdim, > nrows,tfields,ttype,tform,tunit,extname,varidat, + status) +- +***3. Write Keyword Subroutines \label{FTPREC} + +>>1 Put (append) an 80-character record into the CHU. +- + FTPREC(unit,card, > status) +- +>2 Put (append) a COMMENT keyword into the CHU. Multiple COMMENT keywords +> will be written if the input comment string is longer than 72 characters. +- + FTPCOM(unit,comment, > status) +- +>3 Put (append) a HISTORY keyword into the CHU. Multiple HISTORY keywords +> will be written if the input history string is longer than 72 characters. +- + FTPHIS(unit,history, > status) +- +>4 Put (append) the DATE keyword into the CHU. The keyword value will contain + the current system date as a character string in 'dd/mm/yy' format. If + a DATE keyword already exists in the header, then this subroutine will +> simply update the keyword value in-place with the current date. +- + FTPDAT(unit, > status) +- +>5 Put (append) a new keyword of the appropriate datatype into the CHU. + Note that FTPKYS will only write string values up to 68 characters in + length; longer strings will be truncated. The FTPKLS routine can be + used to write longer strings, using a non-standard FITS convention. + The E and D versions of this routine have the added feature that + if the 'decimals' parameter is negative, then the 'G' display + format rather then the 'E' format will be used when constructing + the keyword value, taking the absolute value of 'decimals' for the + precision. This will suppress trailing zeros, and will use a + fixed format rather than an exponential format, +> depending on the magnitude of the value. +- + FTPKY[JKLS](unit,keyword,keyval,comment, > status) + FTPKY[EDFG](unit,keyword,keyval,decimals,comment, > status) +- +>6 Put (append) a string valued keyword into the CHU which may be longer + than 68 characters in length. This uses the Long String Keyword + convention that is described in the "Usage Guidelines and Suggestions" + section of this document. Since this uses a non-standard FITS + convention to encode the long keyword string, programs which use + this routine should also call the FTPLSW routine to add some COMMENT + keywords to warn users of the FITS file that this convention is + being used. FTPLSW also writes a keyword called LONGSTRN to record + the version of the longstring convention that has been used, in case + a new convention is adopted at some point in the future. If the + LONGSTRN keyword is already present in the header, then FTPLSW will +> simply return and will not write duplicate keywords. +- + FTPKLS(unit,keyword,keyval,comment, > status) + FTPLSW(unit, > status) +- +>7 Put (append) a new keyword with an undefined, or null, value into the CHU. +> The value string of the keyword is left blank in this case. +- + FTPKYU(unit,keyword,comment, > status) +- +>8 Put (append) a numbered sequence of keywords into the CHU. One may + append the same comment to every keyword (and eliminate the need + to have an array of identical comment strings, one for each keyword) by + including the ampersand character as the last non-blank character in the + (first) COMMENTS string parameter. This same string + will then be used for the comment field in all the keywords. (Note + that the SPP version of these routines only supports a single comment +> string). +- + FTPKN[JKLS](unit,keyroot,startno,no_keys,keyvals,comments, > status) + FTPKN[EDFG](unit,keyroot,startno,no_keys,keyvals,decimals,comments, > + status) +- +>9 Copy an indexed keyword from one HDU to another, modifying + the index number of the keyword name in the process. For example, + this routine could read the TLMIN3 keyword from the input HDU + (by giving keyroot = "TLMIN" and innum = 3) and write it to the + output HDU with the keyword name TLMIN4 (by setting outnum = 4). + If the input keyword does not exist, then this routine simply +> returns without indicating an error. +- + FTCPKY(inunit, outunit, innum, outnum, keyroot, > status) +- +>10 Put (append) a 'triple precision' keyword into the CHU in F28.16 format. + The floating point keyword value is constructed by concatenating the + input integer value with the input double precision fraction value + (which must have a value between 0.0 and 1.0). The FTGKYT routine should + be used to read this keyword value, because the other keyword reading +> subroutines will not preserve the full precision of the value. +- + FTPKYT(unit,keyword,intval,dblval,comment, > status) +- +>11 Write keywords to the CHDU that are defined in an ASCII template file. + The format of the template file is described under the ftgthd +> routine below. +- + FTPKTP(unit, filename, > status) +- +>12 Append the physical units string to an existing keyword. This + routine uses a local convention, shown in the following example, + in which the keyword units are enclosed in square brackets in the +> beginning of the keyword comment field. + +- + VELOCITY= 12.3 / [km/s] orbital speed + + FTPUNT(unit,keyword,units, > status) +- +***4. Insert Keyword Subroutines \label{FTIREC} + +>1 Insert a new keyword record into the CHU at the specified position + (i.e., immediately preceding the (keyno)th keyword in the header.) + This 'insert record' subroutine is somewhat less efficient + then the 'append record' subroutine (FTPREC) described above because +> the remaining keywords in the header have to be shifted down one slot. +- + FTIREC(unit,key_no,card, > status) +- +>2 Insert a new keyword into the CHU. The new keyword is inserted + immediately following the last keyword that has been read from the header. + The FTIKLS subroutine works the same as the FTIKYS subroutine, except + it also supports long string values greater than 68 characters in length. + These 'insert keyword' subroutines are somewhat less efficient then + the 'append keyword' subroutines described above because the remaining +> keywords in the header have to be shifted down one slot. +- + FTIKEY(unit, card, > status) + FTIKY[JKLS](unit,keyword,keyval,comment, > status) + FTIKLS(unit,keyword,keyval,comment, > status) + FTIKY[EDFG](unit,keyword,keyval,decimals,comment, > status) +- +>3 Insert a new keyword with an undefined, or null, value into the CHU. +> The value string of the keyword is left blank in this case. +- + FTIKYU(unit,keyword,comment, > status) +- +***5. Read Keyword Subroutines \label{FTGREC} + +These routines return the value of the specified keyword(s). Wild card +characters (*, ?, or \#) may be used when specifying the name of the keyword +to be read: a '?' will match any single character at that position in the +keyword name and a '*' will match any length (including zero) string of +characters. The '\#' character will match any consecutive string of +decimal digits (0 - 9). Note that when a wild card is used in the input +keyword name, the routine will only search for a match from the current +header position to the end of the header. It will not resume the search +from the top of the header back to the original header position as is done +when no wildcards are included in the keyword name. If the desired +keyword string is 8-characters long (the maximum length of a keyword +name) then a '*' may be appended as the ninth character of the input +name to force the keyword search to stop at the end of the header +(e.g., 'COMMENT *' will search for the next COMMENT keyword). The +ffgrec routine may be used to set the starting position when doing +wild card searches. + +>1 Get the nth 80-character header record from the CHU. The first keyword + in the header is at key\_no = 1; if key\_no = 0 then this subroutine + simple moves the internal pointer to the beginning of the header + so that subsequent keyword operations will start at the top of +> the header; it also returns a blank card value in this case. +- + FTGREC(unit,key_no, > card,status) +- +>2 Get the name, value (as a string), and comment of the nth keyword in CHU. + This routine also checks that the returned keyword name (KEYWORD) contains + only legal ASCII characters. Call FTGREC and FTPSVC to bypass this error +> check. +- + FTGKYN(unit,key_no, > keyword,value,comment,status) +- +>>3 Get the 80-character header record for the named keyword +- + FTGCRD(unit,keyword, > card,status) +- +>4 Get the next keyword whose name matches one of the strings in + 'inclist' but does not match any of the strings in 'exclist'. + The strings in inclist and exclist may contain wild card characters + (*, ?, and \#) as described at the beginning of this section. + This routine searches from the current header position to the + end of the header, only, and does not continue the search from + the top of the header back to the original position. The current + header position may be reset with the ftgrec routine. Note + that nexc may be set = 0 if there are no keywords to be excluded. + This routine returns status = 202 if a matching +> keyword is not found. +- + FTGNXK(unit,inclist,ninc,exclist,nexc, > card,status) +- +>5 Get the literal keyword value as a character string. Regardless + of the datatype of the keyword, this routine simply returns the + string of characters in the value field of the keyword along with +> the comment field. +- + FTGKEY(unit,keyword, > value,comment,status) +- +>6 Get a keyword value (with the appropriate datatype) and comment from +> the CHU +- + FTGKY[EDJKLS](unit,keyword, > keyval,comment,status) +- +>7 Get a sequence of numbered keyword values. These +> routines do not support wild card characters in the root name. +- + FTGKN[EDJKLS](unit,keyroot,startno,max_keys, > keyvals,nfound,status) +- +>8 Get the value of a floating point keyword, returning the integer and + fractional parts of the value in separate subroutine arguments. + This subroutine may be used to read any keyword but is especially +> useful for reading the 'triple precision' keywords written by FTPKYT. +- + FTGKYT(unit,keyword, > intval,dblval,comment,status) +- +>9 Get the physical units string in an existing keyword. This + routine uses a local convention, shown in the following example, + in which the keyword units are + enclosed in square brackets in the beginning of the keyword comment + field. A blank string is returned if no units are defined +> for the keyword. +- + VELOCITY= 12.3 / [km/s] orbital speed + + FTGUNT(unit,keyword, > units,status) +- +***6. Modify Keyword Subroutines \label{FTMREC} + +Wild card characters, as described in the Read Keyword section, above, +may be used when specifying the name of the keyword to be modified. + +>>1 Modify (overwrite) the nth 80-character header record in the CHU +- + FTMREC(unit,key_no,card, > status) +- +>2 Modify (overwrite) the 80-character header record for the named keyword + in the CHU. This can be used to overwrite the name of the keyword as +> well as its value and comment fields. +- + FTMCRD(unit,keyword,card, > status) +- +>3 Modify (overwrite) the name of an existing keyword in the CHU +> preserving the current value and comment fields. +- + FTMNAM(unit,oldkey,keyword, > status) +- +>>4 Modify (overwrite) the comment field of an existing keyword in the CHU +- + FTMCOM(unit,keyword,comment, > status) +- +>5 Modify the value and comment fields of an existing keyword in the CHU. + The FTMKLS subroutine works the same as the FTMKYS subroutine, except + it also supports long string values greater than 68 characters in length. + Optionally, one may modify only the value field and leave the comment + field unchanged by setting the input COMMENT parameter equal to + the ampersand character (\&). + The E and D versions of this routine have the added feature that + if the 'decimals' parameter is negative, then the 'G' display + format rather then the 'E' format will be used when constructing + the keyword value, taking the absolute value of 'decimals' for the + precision. This will suppress trailing zeros, and will use a + fixed format rather than an exponential format, +> depending on the magnitude of the value. +- + FTMKY[JKLS](unit,keyword,keyval,comment, > status) + FTMKLS(unit,keyword,keyval,comment, > status) + FTMKY[EDFG](unit,keyword,keyval,decimals,comment, > status) +- +>6 Modify the value of an existing keyword to be undefined, or null. + The value string of the keyword is set to blank. + Optionally, one may leave the comment field unchanged by setting the +> input COMMENT parameter equal to the ampersand character (\&). +- + FTMKYU(unit,keyword,comment, > status) +- +***7. Update Keyword Subroutines \label{FTUCRD} + +>1 Update an 80-character record in the CHU. If the specified keyword + already exists then that header record will be replaced with + the input CARD string. If it does not exist then the new record will + be added to the header. + The FTUKLS subroutine works the same as the FTUKYS subroutine, except +> it also supports long string values greater than 68 characters in length. +- + FTUCRD(unit,keyword,card, > status) +- +>2 Update the value and comment fields of a keyword in the CHU. + The specified keyword is modified if it already exists (by calling + FTMKYx) otherwise a new keyword is created by calling FTPKYx. + The E and D versions of this routine have the added feature that + if the 'decimals' parameter is negative, then the 'G' display + format rather then the 'E' format will be used when constructing + the keyword value, taking the absolute value of 'decimals' for the + precision. This will suppress trailing zeros, and will use a + fixed format rather than an exponential format, +> depending on the magnitude of the value. +- + FTUKY[JKLS](unit,keyword,keyval,comment, > status) + FTUKLS(unit,keyword,keyval,comment, > status) + FTUKY[EDFG](unit,keyword,keyval,decimals,comment, > status) +- +>3 Update the value of an existing keyword to be undefined, or null, + or insert a new undefined-value keyword if it doesn't already exist. +> The value string of the keyword is left blank in this case. +- + FTUKYU(unit,keyword,comment, > status) +- +***8. Delete Keyword Subroutines \label{FTDREC} + +>1 Delete an existing keyword record. The space previously occupied by + the keyword is reclaimed by moving all the following header records up + one row in the header. The first routine deletes a keyword at a + specified position in the header (the first keyword is at position 1), + whereas the second routine deletes a specifically named keyword. + Wild card characters, as described in the Read Keyword section, above, + may be used when specifying the name of the keyword to be deleted +> (be careful!). +- + FTDREC(unit,key_no, > status) + FTDKEY(unit,keyword, > status) +- + +**F. Data Scaling and Undefined Pixel Parameters \label{FTPSCL} + +These subroutines define or modify the internal parameters used by +FITSIO to either scale the data or to represent undefined pixels. +Generally FITSIO will scale the data according to the values of the BSCALE +and BZERO (or TSCALn and TZEROn) keywords, however these subroutines +may be used to override the keyword values. This may be useful when +one wants to read or write the raw unscaled values in the FITS file. +Similarly, FITSIO generally uses the value of the BLANK or TNULLn +keyword to signify an undefined pixel, but these routines may be used +to override this value. These subroutines do not create or modify the +corresponding header keyword values. + +>1 Reset the scaling factors in the primary array or image extension; does + not change the BSCALE and BZERO keyword values and only affects the + automatic scaling performed when the data elements are written/read + to/from the FITS file. When reading from a FITS file the returned + data value = (the value given in the FITS array) * BSCALE + BZERO. + The inverse formula is used when writing data values to the FITS + file. (NOTE: BSCALE and BZERO must be declared as Double Precision +> variables). +- + FTPSCL(unit,bscale,bzero, > status) +- +>2 Reset the scaling parameters for a table column; does not change + the TSCALn or TZEROn keyword values and only affects the automatic + scaling performed when the data elements are written/read to/from + the FITS file. When reading from a FITS file the returned data + value = (the value given in the FITS array) * TSCAL + TZERO. The + inverse formula is used when writing data values to the FITS file. + (NOTE: TSCAL and TZERO must be declared as Double Precision +> variables). +- + FTTSCL(unit,colnum,tscal,tzero, > status) +- +>3 Define the integer value to be used to signify undefined pixels in the + primary array or image extension. This is only used if BITPIX = 8, 16, + 32. or 64 This does not create or change the value of the BLANK keyword in + the header. FTPNULLL is identical to FTPNUL except that the blank +> value is a 64-bit integer instead of a 32-bit integer. +- + FTPNUL(unit,blank, > status) + FTPNULLL(unit,blankll, > status) +- +>4 Define the string to be used to signify undefined pixels in + a column in an ASCII table. This does not create or change the value +> of the TNULLn keyword. +- + FTSNUL(unit,colnum,snull > status) +- +>5 Define the value to be used to signify undefined pixels in + an integer column in a binary table (where TFORMn = 'B', 'I', 'J', or 'K'). + This does not create or change the value of the TNULLn keyword. + FTTNULLL is identical to FTTNUL except that the tnull +> value is a 64-bit integer instead of a 32-bit integer. +- + FTTNUL(unit,colnum,tnull > status) + FTTNULLL(unit,colnum,tnullll > status) +- + +**G. FITS Primary Array or IMAGE Extension I/O Subroutines \label{FTPPR} + + These subroutines put or get data values in the primary data array +(i.e., the first HDU in the FITS file) or an IMAGE extension. The +data array is represented as a single one-dimensional array of +pixels regardless of the actual dimensionality of the array, and the +FPIXEL parameter gives the position within this 1-D array of the first +pixel to read or write. Automatic data type conversion is performed +for numeric data (except for complex data types) if the data type of +the primary array (defined by the BITPIX keyword) differs from the data +type of the array in the calling subroutine. The data values are also +scaled by the BSCALE and BZERO header values as they are being written +or read from the FITS array. The ftpscl subroutine MUST be +called to define the scaling parameters when writing data to the FITS +array or to override the default scaling value given in the header when +reading the FITS array. + + Two sets of subroutines are provided to read the data array which +differ in the way undefined pixels are handled. The first set of +routines (FTGPVx) simply return an array of data elements in which +undefined pixels are set equal to a value specified by the user in the +'nullval' parameter. An additional feature of these subroutines is +that if the user sets nullval = 0, then no checks for undefined pixels +will be performed, thus increasing the speed of the program. The +second set of routines (FTGPFx) returns the data element array and, in +addition, a logical array which defines whether the corresponding data +pixel is undefined. The latter set of subroutines may be more +convenient to use in some circumstances, however, it requires an +additional array of logical values which can be unwieldy when working +with large data arrays. Also for programmer convenience, sets of +subroutines to directly read or write 2 and 3 dimensional arrays have +been provided, as well as a set of subroutines to read or write any +contiguous rectangular subset of pixels within the n-dimensional array. + +>1 Get the data type of the image (= BITPIX value). Possible returned + values are: 8, 16, 32, 64, -32, or -64 corresponding to unsigned byte, + signed 2-byte integer, signed 4-byte integer, signed 8-byte integer, + real, and double. + + The second subroutine is similar to FTGIDT, except that if the image + pixel values are scaled, with non-default values for the BZERO and + BSCALE keywords, then this routine will return the 'equivalent' + data type that is needed to store the scaled values. For example, + if BITPIX = 16 and BSCALE = 0.1 then the equivalent data type is + floating point, and -32 will be returned. There are 2 special cases: + if the image contains unsigned 2-byte integer values, with BITPIX = + 16, BSCALE = 1, and BZERO = 32768, then this routine will return + a non-standard value of 20 for the bitpix value. Similarly if the + image contains unsigned 4-byte integers, then bitpix will +> be returned with a value of 40. + +- + FTGIDT(unit, > bitpix,status) + FTGIET(unit, > bitpix,status) +- +>>2 Get the dimension (number of axes = NAXIS) of the image +- + FTGIDM(unit, > naxis,status) +- +>3 Get the size of all the dimensions of the image. The FTGISZLL +> routine returns an array of 64-bit integers instead of 32-bit integers. +- + FTGISZ(unit, maxdim, > naxes,status) + FTGISZLL(unit, maxdim, > naxesll,status) +- +>4 Get the parameters that define the type and size of the image. This + routine simply combines calls to the above 3 routines. The FTGIPRLL +> routine returns an array of 64-bit integers instead of 32-bit integers. + +- + FTGIPR(unit, maxdim, > bitpix, naxis, naxes, int *status) + FTGIPRLL(unit, maxdim, > bitpix, naxis, naxesll, int *status) +- +>>5 Put elements into the data array +- + FTPPR[BIJKED](unit,group,fpixel,nelements,values, > status) +- +>6 Put elements into the data array, substituting the appropriate FITS null + value for all elements which are equal to the value of NULLVAL. For + integer FITS arrays, the null value defined by the previous call to FTPNUL + will be substituted; for floating point FITS arrays (BITPIX = -32 + or -64) then the special IEEE NaN (Not-a-Number) value will be +> substituted. +- + FTPPN[BIJKED](unit,group,fpixel,nelements,values,nullval > status) +- +>>7 Set data array elements as undefined +- + FTPPRU(unit,group,fpixel,nelements, > status) +- +>8 Get elements from the data array. Undefined array elements will be + returned with a value = nullval, unless nullval = 0 in which case no +> checks for undefined pixels will be performed. +- + FTGPV[BIJKED](unit,group,fpixel,nelements,nullval, > values,anyf,status) +- +>9 Get elements and nullflags from data array. + Any undefined array elements will have the corresponding flagvals element +> set equal to .TRUE. +- + FTGPF[BIJKED](unit,group,fpixel,nelements, > values,flagvals,anyf,status) +- +>>10 Put values into group parameters +- + FTPGP[BIJKED](unit,group,fparm,nparm,values, > status) +- +>>11 Get values from group parameters +- + FTGGP[BIJKED](unit,group,fparm,nparm, > values,status) +- +The following 4 subroutines transfer FITS images with 2 or 3 dimensions +to or from a data array which has been declared in the calling program. +The dimensionality of the FITS image is passed by the naxis1, naxis2, +and naxis3 parameters and the declared dimensions of the program array +are passed in the dim1 and dim2 parameters. Note that the program array +does not have to have the same dimensions as the FITS array, but must +be at least as big. For example if a FITS image with NAXIS1 = NAXIS2 = 400 +is read into a program array which is dimensioned as 512 x 512 pixels, +then the image will just fill the lower left corner of the array +with pixels in the range 1 - 400 in the X an Y directions. This has +the effect of taking a contiguous set of pixel value in the FITS array +and writing them to a non-contiguous array in program memory +(i.e., there are now some blank pixels around the edge of the image +in the program array). + +>>11 Put 2-D image into the data array +- + FTP2D[BIJKED](unit,group,dim1,naxis1,naxis2,image, > status) +- +>>12 Put 3-D cube into the data array +- + FTP3D[BIJKED](unit,group,dim1,dim2,naxis1,naxis2,naxis3,cube, > status) +- +>13 Get 2-D image from the data array. Undefined + pixels in the array will be set equal to the value of 'nullval', + unless nullval=0 in which case no testing for undefined pixels will +> be performed. +- + FTG2D[BIJKED](unit,group,nullval,dim1,naxis1,naxis2, > image,anyf,status) +- +>14 Get 3-D cube from the data array. Undefined + pixels in the array will be set equal to the value of 'nullval', + unless nullval=0 in which case no testing for undefined pixels will +> be performed. +- + FTG3D[BIJKED](unit,group,nullval,dim1,dim2,naxis1,naxis2,naxis3, > + cube,anyf,status) +- + +The following subroutines transfer a rectangular subset of the pixels +in a FITS N-dimensional image to or from an array which has been +declared in the calling program. The fpixels and lpixels parameters +are integer arrays which specify the starting and ending pixels in each +dimension of the FITS image that are to be read or written. (Note that +these are the starting and ending pixels in the FITS image, not in the +declared array). The array parameter is treated simply as a large +one-dimensional array of the appropriate datatype containing the pixel +values; The pixel values in the FITS array are read/written from/to +this program array in strict sequence without any gaps; it is up to +the calling routine to correctly interpret the dimensionality of this +array. The two families of FITS reading routines (FTGSVx and FTGSFx +subroutines) also have an 'incs' parameter which defines the +data sampling interval in each dimension of the FITS array. For +example, if incs(1)=2 and incs(2)=3 when reading a 2-dimensional +FITS image, then only every other pixel in the first dimension +and every 3rd pixel in the second dimension will be returned in +the 'array' parameter. [Note: the FTGSSx family of routines which +were present in previous versions of FITSIO have been superseded +by the more general FTGSVx family of routines.] + +>>15 Put an arbitrary data subsection into the data array. +- + FTPSS[BIJKED](unit,group,naxis,naxes,fpixels,lpixels,array, > status) +- +>16 Get an arbitrary data subsection from the data array. Undefined + pixels in the array will be set equal to the value of 'nullval', + unless nullval=0 in which case no testing for undefined pixels will +> be performed. +- + FTGSV[BIJKED](unit,group,naxis,naxes,fpixels,lpixels,incs,nullval, > + array,anyf,status) +- +>17 Get an arbitrary data subsection from the data array. Any Undefined + pixels in the array will have the corresponding 'flagvals' +> element set equal to .TRUE. +- + FTGSF[BIJKED](unit,group,naxis,naxes,fpixels,lpixels,incs, > + array,flagvals,anyf,status) +- + +**H. FITS ASCII and Binary Table Data I/O Subroutines + +***1. Column Information Subroutines \label{FTGCNO} + +>1 Get the number of rows or columns in the current FITS table. + The number of rows is given by the NAXIS2 keyword and the + number of columns is given by the TFIELDS keyword in the header + of the table. The FTGNRWLL routine is identical to FTGNRW except + that the number of rows is returned as a 64-bit integer rather +> than a 32-bit integer. +- + FTGNRW(unit, > nrows, status) + FTGNRWLL(unit, > nrowsll, status) + FTGNCL(unit, > ncols, status) +- +>2 Get the table column number (and name) of the column whose name +matches an input template name. The table column names are defined by +the TTYPEn keywords in the FITS header. If a column does not have a +TTYPEn keyword, then these routines assume that the name consists of +all blank characters. These 2 subroutines perform the same function +except that FTGCNO only returns the number of the matching column whereas +FTGCNN also returns the name of the column. If CASESEN = .true. then +the column name match will be case-sensitive. + +The input column name template (COLTEMPLATE) is (1) either the exact +name of the column to be searched for, or (2) it may contain wild cards +characters (*, ?, or \#), or (3) it may contain the number of the desired +column (where the number is expressed as ASCII digits). The first 2 wild +cards behave similarly to UNIX filename matching: the '*' character matches +any sequence of characters (including zero characters) and the '?' +character matches any single character. The \# wildcard will match +any consecutive string of decimal digits (0-9). As an example, the template +strings 'AB?DE', 'AB*E', and 'AB*CDE' will all match the string +'ABCDE'. If more than one column name in the table matches the +template string, then the first match is returned and the status value +will be set to 237 as a warning that a unique match was not found. To +find the other cases that match the template, simply call the +subroutine again leaving the input status value equal to 237 and the +next matching name will then be returned. Repeat this process until a +status = 219 (column name not found) is returned. If these subroutines +fail to match the template to any of the columns in the table, they +lastly check if the template can be interpreted as a simple positive +integer (e.g., '7', or '512') and if so, they return that column +number. If no matches are found then a status = 219 error is +returned. + +Note that the FITS Standard recommends that only letters, digits, and +the underscore character be used in column names (with no embedded +>spaces in the name). Trailing blank characters are not significant. +- + FTGCNO(unit,casesen,coltemplate, > colnum,status) + FTGCNN(unit,casesen,coltemplate, > colname,colnum,status) +- +>3 Get the datatype of a column in an ASCII or binary table. This + routine returns an integer code value corresponding to the datatype + of the column. (See the FTBNFM and FTASFM subroutines in the Utilities + section of this document for a list of the code values). The vector + repeat count (which is alway 1 for ASCII table columns) is also returned. + If the specified column has an ASCII character datatype (code = 16) then + the width of a unit string in the column is also returned. Note that + this routine supports the local convention for specifying arrays of + strings within a binary table character column, using the syntax + TFORM = 'rAw' where 'r' is the total number of characters (= the width + of the column) and 'w' is the width of a unit string within the column. + Thus if the column has TFORM = '60A12' then this routine will return + datacode = 16, repeat = 60, and width = 12. (The TDIMn + keyword may also be used to specify the unit string length; The pair + of keywords TFORMn = '60A' and TDIMn = '(12,5)' would have the + same effect as TFORMn = '60A12'). + + The second routine, FTEQTY is similar except that in + the case of scaled integer columns it returns the 'equivalent' data + type that is needed to store the scaled values, and not necessarily + the physical data type of the unscaled values as stored in the FITS + table. For example if a '1I' column in a binary table has TSCALn = + 1 and TZEROn = 32768, then this column effectively contains unsigned + short integer values, and thus the returned value of typecode will + be the code for an unsigned short integer, not a signed short integer. + Similarly, if a column has TTYPEn = '1I' + and TSCALn = 0.12, then the returned typecode +> will be the code for a 'real' column. +- + FTGTCL(unit,colnum, > datacode,repeat,width,status) + FTEQTY(unit,colnum, > datacode,repeat,width,status) +- +>4 Return the display width of a column. This is the length + of the string that will be returned + when reading the column as a formatted string. The display width is + determined by the TDISPn keyword, if present, otherwise by the data +> type of the column. +- + FTGCDW(unit, colnum, > dispwidth, status) +- +>5 Get information about an existing ASCII table column. (NOTE: TSCAL and + TZERO must be declared as Double Precision variables). All the +> returned parameters are scalar quantities. +- + FTGACL(unit,colnum, > + ttype,tbcol,tunit,tform,tscal,tzero,snull,tdisp,status) +- +>6 Get information about an existing binary table column. (NOTE: TSCAL and + TZERO must be declared as Double Precision variables). DATATYPE is a + character string which returns the datatype of the column as defined + by the TFORMn keyword (e.g., 'I', 'J','E', 'D', etc.). In the case + of an ASCII character column, DATATYPE will have a value of the + form 'An' where 'n' is an integer expressing the width of the field + in characters. For example, if TFORM = '160A8' then FTGBCL will return + DATATYPE='A8' and REPEAT=20. All the returned parameters are scalar +> quantities. +- + FTGBCL(unit,colnum, > + ttype,tunit,datatype,repeat,tscal,tzero,tnull,tdisp,status) +- +>7 Put (append) a TDIMn keyword whose value has the form '(l,m,n...)' + where l, m, n... are the dimensions of a multidimensional array +> column in a binary table. +- + FTPTDM(unit,colnum,naxis,naxes, > status) +- +>8 Return the number of and size of the dimensions of a table column. + Normally this information is given by the TDIMn keyword, but if + this keyword is not present then this routine returns NAXIS = 1 +> and NAXES(1) equal to the repeat count in the TFORM keyword. +- + FTGTDM(unit,colnum,maxdim, > naxis,naxes,status) +- +>9 Decode the input TDIMn keyword string (e.g. '(100,200)') and return the + number of and size of the dimensions of a binary table column. If the input + tdimstr character string is null, then this routine returns naxis = 1 + and naxes[0] equal to the repeat count in the TFORM keyword. This routine +> is called by FTGTDM. +- + FTDTDM(unit,tdimstr,colnum,maxdim, > naxis,naxes, status) +- +>10 Return the optimal number of rows to read or write at one time for + maximum I/O efficiency. Refer to the ``Optimizing Code'' section +> in Chapter 5 for more discussion on how to use this routine. + +- + FFGRSZ(unit, > nrows,status) +- + +***2. Low-Level Table Access Subroutines \label{FTGTBS} + +The following subroutines provide low-level access to the data in ASCII +or binary tables and are mainly useful as an efficient way to copy all +or part of a table from one location to another. These routines simply +read or write the specified number of consecutive bytes in an ASCII or +binary table, without regard for column boundaries or the row length in +the table. The first two subroutines read or write consecutive bytes +in a table to or from a character string variable, while the last two +subroutines read or write consecutive bytes to or from a variable +declared as a numeric data type (e.g., INTEGER, INTEGER*2, REAL, DOUBLE +PRECISION). These routines do not perform any machine dependent data +conversion or byte swapping, except that conversion to/from ASCII +format is performed by the FTGTBS and FTPTBS routines on machines which +do not use ASCII character codes in the internal data representations +(e.g., on IBM mainframe computers). + +>1 Read a consecutive string of characters from an ASCII table + into a character variable (spanning columns and multiple rows if necessary) + This routine should not be used with binary tables because of +> complications related to passing string variables between C and Fortran. +- + FTGTBS(unit,frow,startchar,nchars, > string,status) +- +>2 Write a consecutive string of characters to an ASCII table + from a character variable (spanning columns and multiple rows if necessary) + This routine should not be used with binary tables because of +> complications related to passing string variables between C and Fortran. +- + FTPTBS(unit,frow,startchar,nchars,string, > status) +- +>3 Read a consecutive array of bytes from an ASCII or binary table + into a numeric variable (spanning columns and multiple rows if necessary). + The array parameter may be declared as any numerical datatype as long + as the array is at least 'nchars' bytes long, e.g., if nchars = 17, +> then declare the array as INTEGER*4 ARRAY(5). +- + FTGTBB(unit,frow,startchar,nchars, > array,status) +- +>4 Write a consecutive array of bytes to an ASCII or binary table + from a numeric variable (spanning columns and multiple rows if necessary) + The array parameter may be declared as any numerical datatype as long + as the array is at least 'nchars' bytes long, e.g., if nchars = 17, +> then declare the array as INTEGER*4 ARRAY(5). +- + FTPTBB(unit,frow,startchar,nchars,array, > status) +- + +***3. Edit Rows or Columns \label{FTIROW} + +>1 Insert blank rows into an existing ASCII or binary table (in the CDU). + All the rows FOLLOWING row FROW are shifted down by NROWS rows. If + FROW or FROWLL equals 0 then the blank rows are inserted at the beginning of the + table. These routines modify the NAXIS2 keyword to reflect the new + number of rows in the table. Note that it is *not* necessary to insert rows in a table before + writing data to those rows (indeed, it would be inefficient to do so). + Instead, one may simply write data to any row of the table, whether that +> row of data already exists or not. +- + FTIROW(unit,frow,nrows, > status) + FTIROWLL(unit,frowll,nrowsll, > status) +- +>2 Delete rows from an existing ASCII or binary table (in the CDU). + The NROWS (or NROWSLL) is the number of rows are deleted, starting + with row FROW (or FROWLL), and + any remaining rows in the table are shifted up to fill in the space. + These routines modify the NAXIS2 keyword to reflect the new number +> of rows in the table. +- + FTDROW(unit,frow,nrows, > status) + FTDROWLL(unit,frowll,nrowsll, > status) +- +>3 Delete a list of rows from an ASCII or binary table (in the CDU). + In the first routine, 'rowrange' is a character string listing the + rows or row ranges to delete (e.g., '2-4, 5, 8-9'). In the second + routine, 'rowlist' is an integer array of row numbers to be deleted + from the table. nrows is the number of row numbers in the list. + The first row in the table is 1 not 0. The list of row numbers +> must be sorted in ascending order. +- + FTDRRG(unit,rowrange, > status) + FTDRWS(unit,rowlist,nrows, > status) +- +>4 Insert a blank column (or columns) into an existing ASCII or binary + table (in the CDU). COLNUM specifies the column number that the (first) + new column should occupy in the table. NCOLS specifies how many + columns are to be inserted. Any existing columns from this position and + higher are moved over to allow room for the new column(s). + The index number on all the following keywords will be incremented + if necessary to reflect the new position of the column(s) in the table: + TBCOLn, TFORMn, TTYPEn, TUNITn, TNULLn, TSCALn, TZEROn, TDISPn, TDIMn, + TLMINn, TLMAXn, TDMINn, TDMAXn, TCTYPn, TCRPXn, TCRVLn, TCDLTn, TCROTn, +> and TCUNIn. +- + FTICOL(unit,colnum,ttype,tform, > status) + FTICLS(unit,colnum,ncols,ttype,tform, > status) +- +>5 Modify the vector length of a binary table column (e.g., + change a column from TFORMn = '1E' to '20E'). The vector +> length may be increased or decreased from the current value. +- + FTMVEC(unit,colnum,newveclen, > status) +- +>6 Delete a column from an existing ASCII or binary table (in the CDU). + The index number of all the keywords listed above (for FTICOL) will be + decremented if necessary to reflect the new position of the column(s) in + the table. Those index keywords that refer to the deleted column will + also be deleted. Note that the physical size of the FITS file will + not be reduced by this operation, and the empty FITS blocks if any +> at the end of the file will be padded with zeros. +- + FTDCOL(unit,colnum, > status) +- +>7 Copy a column from one HDU to another (or to the same HDU). If + createcol = TRUE, then a new column will be inserted in the output + table, at position `outcolumn', otherwise the existing output column will + be overwritten (in which case it must have a compatible datatype). +> Note that the first column in a table is at colnum = 1. +- + FTCPCL(inunit,outunit,incolnum,outcolnum,createcol, > status); +- +***4. Read and Write Column Data Routines \label{FTPCLS} + +These subroutines put or get data values in the current ASCII or Binary table +extension. Automatic data type conversion is performed for numerical data +types (B,I,J,E,D) if the data type of the column (defined by the TFORM keyword) +differs from the data type of the calling subroutine. The data values are also +scaled by the TSCALn and TZEROn header values as they are being written to +or read from the FITS array. The fttscl subroutine MUST be used to define the +scaling parameters when writing data to the table or to override the default +scaling values given in the header when reading from the table. +Note that it is *not* necessary to insert rows in a table before +writing data to those rows (indeed, it would be inefficient to do so). +Instead, one may simply write data to any row of the table, whether that +row of data already exists or not. + + In the case of binary tables with vector elements, the 'felem' +parameter defines the starting pixel within the element vector. This +parameter is ignored with ASCII tables. Similarly, in the case of +binary tables the 'nelements' parameter specifies the total number of +vector values read or written (continuing on subsequent rows if +required) and not the number of table elements. Two sets of +subroutines are provided to get the column data which differ in the way +undefined pixels are handled. The first set of routines (FTGCV) +simply return an array of data elements in which undefined pixels are +set equal to a value specified by the user in the 'nullval' parameter. +An additional feature of these subroutines is that if the user sets +nullval = 0, then no checks for undefined pixels will be performed, +thus increasing the speed of the program. The second set of routines +(FTGCF) returns the data element array and in addition a logical array +of flags which defines whether the corresponding data pixel is undefined. + + Any column, regardless of it's intrinsic datatype, may be read as a + string. It should be noted however that reading a numeric column + as a string is 10 - 100 times slower than reading the same column as + a number due to the large overhead in constructing the formatted + strings. The display format of the returned strings will be + determined by the TDISPn keyword, if it exists, otherwise by the + datatype of the column. The length of the returned strings can be + determined with the ftgcdw routine. The following TDISPn display + formats are currently supported: +- + Iw.m Integer + Ow.m Octal integer + Zw.m Hexadecimal integer + Fw.d Fixed floating point + Ew.d Exponential floating point + Dw.d Exponential floating point + Gw.d General; uses Fw.d if significance not lost, else Ew.d +- + where w is the width in characters of the displayed values, m is the minimum + number of digits displayed, and d is the number of digits to the right of the + decimal. The .m field is optional. + +>1 Put elements into an ASCII or binary table column (in the CDU). + (The SPP FSPCLS routine has an additional integer argument after + the VALUES character string which specifies the size of the 1st + dimension of this 2-D CHAR array). + + The alternate version of these routines, whose names end in 'LL' + after the datatype character, support large tables with more then + 2*31 rows. When calling these routines, the frow and felem parameters +> *must* be 64-bit integer*8 variables, instead of normal 4-byte integers. +- + FTPCL[SLBIJKEDCM](unit,colnum,frow,felem,nelements,values, > status) + FTPCL[LBIJKEDCM]LL(unit,colnum,frow,felem,nelements,values, > status) +- +>2 Put elements into an ASCII or binary table column (in the CDU) + substituting the appropriate FITS null value for any elements that + are equal to NULLVAL. For ASCII TABLE extensions, the + null value defined by the previous call to FTSNUL will be substituted; + For integer FITS columns, in a binary table the null value + defined by the previous call to FTTNUL will be substituted; + For floating point FITS columns a special IEEE NaN (Not-a-Number) + value will be substituted. + + The alternate version of these routines, whose names end in 'LL' + after the datatype character, support large tables with more then + 2*31 rows. When calling these routines, the frow and felem parameters +> *must* be 64-bit integer*8 variables, instead of normal 4-byte integers. +- + FTPCN[SBIJKED](unit,colnum,frow,felem,nelements,values,nullval > status) + FTPCN[SBIJKED]LL(unit,colnum,(I*8) frow,(I*8) felem,nelements,values, + nullval > status) +- +>3 Put bit values into a binary byte ('B') or bit ('X') table column (in the + CDU). LRAY is an array of logical values corresponding to the sequence of + bits to be written. If LRAY is true then the corresponding bit is + set to 1, otherwise the bit is set to 0. Note that in the case of + 'X' columns, FITSIO will write to all 8 bits of each byte whether + they are formally valid or not. Thus if the column is defined as + '4X', and one calls FTPCLX with fbit=1 and nbit=8, then all 8 bits + will be written into the first byte (as opposed to writing the + first 4 bits into the first row and then the next 4 bits into the + next row), even though the last 4 bits of each byte are formally +> not defined. +- + FTPCLX(unit,colnum,frow,fbit,nbit,lray, > status) +- +>>4 Set table elements in a column as undefined +- + FTPCLU(unit,colnum,frow,felem,nelements, > status) +- +>5 Get elements from an ASCII or binary table column (in the CDU). These + routines return the values of the table column array elements. Undefined + array elements will be returned with a value = nullval, unless nullval = 0 + (or = ' ' for ftgcvs) in which case no checking for undefined values will + be performed. The ANYF parameter is set to true if any of the returned + elements are undefined. (Note: the ftgcl routine simple gets an array + of logical data values without any checks for undefined values; use + the ftgcfl routine to check for undefined logical elements). + (The SPP FSGCVS routine has an additional integer argument after + the VALUES character string which specifies the size of the 1st + dimension of this 2-D CHAR array). + + The alternate version of these routines, whose names end in 'LL' + after the datatype character, support large tables with more then + 2*31 rows. When calling these routines, the frow and felem parameters +> *must* be 64-bit integer*8 variables, instead of normal 4-byte integers. +- + FTGCL(unit,colnum,frow,felem,nelements, > values,status) + FTGCV[SBIJKEDCM](unit,colnum,frow,felem,nelements,nullval, > + values,anyf,status) + FTGCV[BIJKEDCM]LL(unit,colnum,(I*8) frow, (I*8) felem, nelements, + nullval, > values,anyf,status) +- +>6 Get elements and null flags from an ASCII or binary table column (in the + CHDU). These routines return the values of the table column array elements. + Any undefined array elements will have the corresponding flagvals element + set equal to .TRUE. The ANYF parameter is set to true if any of the + returned elements are undefined. + (The SPP FSGCFS routine has an additional integer argument after + the VALUES character string which specifies the size of the 1st + dimension of this 2-D CHAR array). + + The alternate version of these routines, whose names end in 'LL' + after the datatype character, support large tables with more then + 2*31 rows. When calling these routines, the frow and felem parameters +> *must* be 64-bit integer*8 variables, instead of normal 4-byte integers. +- + FTGCF[SLBIJKEDCM](unit,colnum,frow,felem,nelements, > + values,flagvals,anyf,status) + FTGCF[BIJKED]LL(unit,colnum, (I*8) frow, (I*8) felem,nelements, > + values,flagvals,anyf,status) +- +>7 Get an arbitrary data subsection from an N-dimensional array + in a binary table vector column. Undefined pixels + in the array will be set equal to the value of 'nullval', + unless nullval=0 in which case no testing for undefined pixels will + be performed. The first and last rows in the table to be read + are specified by fpixels(naxis+1) and lpixels(naxis+1), and hence + are treated as the next higher dimension of the FITS N-dimensional + array. The INCS parameter specifies the sampling interval in +> each dimension between the data elements that will be returned. +- + FTGSV[BIJKED](unit,colnum,naxis,naxes,fpixels,lpixels,incs,nullval, > + array,anyf,status) +- +>8 Get an arbitrary data subsection from an N-dimensional array + in a binary table vector column. Any Undefined + pixels in the array will have the corresponding 'flagvals' + element set equal to .TRUE. The first and last rows in the table + to be read are specified by fpixels(naxis+1) and lpixels(naxis+1), + and hence are treated as the next higher dimension of the FITS + N-dimensional array. The INCS parameter specifies the sampling + interval in each dimension between the data elements that will be +> returned. +- + FTGSF[BIJKED](unit,colnum,naxis,naxes,fpixels,lpixels,incs, > + array,flagvals,anyf,status) +- +>9 Get bit values from a byte ('B') or bit (`X`) table column (in the + CDU). LRAY is an array of logical values corresponding to the + sequence of bits to be read. If LRAY is true then the + corresponding bit was set to 1, otherwise the bit was set to 0. + Note that in the case of 'X' columns, FITSIO will read all 8 bits + of each byte whether they are formally valid or not. Thus if the + column is defined as '4X', and one calls FTGCX with fbit=1 and + nbit=8, then all 8 bits will be read from the first byte (as + opposed to reading the first 4 bits from the first row and then the + first 4 bits from the next row), even though the last 4 bits of +> each byte are formally not defined. +- + FTGCX(unit,colnum,frow,fbit,nbit, > lray,status) +- +>10 Read any consecutive set of bits from an 'X' or 'B' column and + interpret them as an unsigned n-bit integer. NBIT must be less than + or equal to 16 when calling FTGCXI, and less than or equal to 32 when + calling FTGCXJ; there is no limit on the value of NBIT for FTGCXD, but + the returned double precision value only has 48 bits of precision on + most 32-bit word machines. The NBITS bits are interpreted as an + unsigned integer unless NBITS = 16 (in FTGCXI) or 32 (in FTGCXJ) in which + case the string of bits are interpreted as 16-bit or 32-bit 2's + complement signed integers. If NROWS is greater than 1 then the + same set of bits will be read from sequential rows in the table + starting with row FROW. Note that the numbering convention + used here for the FBIT parameter adopts 1 for the first element of the +> vector of bits; this is the Most Significant Bit of the integer value. +- + FTGCX[IJD](unit,colnum,frow,nrows,fbit,nbit, > array,status) +- +>11 Get the descriptor for a variable length column in a binary table. + The descriptor consists of 2 integer parameters: the number of elements + in the array and the starting offset relative to the start of the heap. + The first routine returns a single descriptor whereas the second routine +> returns the descriptors for a range of rows in the table. +- + FTGDES(unit,colnum,rownum, > nelements,offset,status) + FTGDESLL(unit,colnum,rownum, > nelementsll,offsetll,status) + + FFGDESS(unit,colnum,firstrow,nrows > nelements,offset, status) + FFGDESSLL(unit,colnum,firstrow,nrows > nelementsll,offsetll, status) +- +>12 Write the descriptor for a variable length column in a binary table. + These subroutines can be used in conjunction with FTGDES to enable + 2 or more arrays to point to the same storage location to save +> storage space if the arrays are identical. +- + FTPDES(unit,colnum,rownum,nelements,offset, > status) + FTPDESLL(unit,colnum,rownum,nelementsll,offsetll, > status) +- + +**I. Row Selection and Calculator Routines \label{FTFROW} + +These routines all parse and evaluate an input string containing a user +defined arithmetic expression. The first 3 routines select rows in a +FITS table, based on whether the expression evaluates to true (not +equal to zero) or false (zero). The other routines evaluate the +expression and calculate a value for each row of the table. The +allowed expression syntax is described in the row filter section in the +earlier `Extended File Name Syntax' chapter of this document. The +expression may also be written to a text file, and the name of the +file, prepended with a '@' character may be supplied for the 'expr' +parameter (e.g. '@filename.txt'). The expression in the file can +be arbitrarily complex and extend over multiple lines of the file. +Lines that begin with 2 slash characters ('//') will be ignored and +may be used to add comments to the file. + +>1 Evaluate a boolean expression over the indicated rows, returning an +> array of flags indicating which rows evaluated to TRUE/FALSE +- + FTFROW(unit,expr,firstrow, nrows, > n_good_rows, row_status, status) +- +>>2 Find the first row which satisfies the input boolean expression +- + FTFFRW(unit, expr, > rownum, status) +- +>3 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 output +table is not empty, then this routine will append the new +selected rows after the existing rows. If the +>files are the same, delete the FALSE rows (preserve the TRUE rows). +- + FTSROW(inunit, outunit, expr, > status) +- +>4 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 FTTEXP to obtain +>the dimensions of the results. +- + FTCROW(unit,datatype,expr,firstrow,nelements,nulval, > + array,anynul,status) +- +>5 Evaluate an expression and write the result either to a column (if +the expression is a function of other columns in the table) or to a +keyword (if the expression evaluates to a constant and is not a +function of other columns in the table). In the former case, the +parName parameter is the name of the column (which may or may not already +exist) into which to write the results, and parInfo contains an +optional TFORM keyword value if a new column is being created. If a +TFORM value is not specified then a default format will be used, +depending on the expression. If the expression evaluates to a constant, +then the result will be written to the keyword name given by the +parName parameter, and the parInfo parameter may be used to supply an +optional comment for the keyword. If the keyword does not already +exist, then the name of the keyword must be preceded with a '\#' character, +>otherwise the result will be written to a column with that name. + +- + FTCALC(inunit, expr, outunit, parName, parInfo, > status) +- +>6 This calculator routine is similar to the previous routine, except +that the expression is only evaluated over the specified +row ranges. nranges specifies the number of row ranges, and firstrow +>and lastrow give the starting and ending row number of each range. +- + FTCALC_RNG(inunit, expr, outunit, parName, parInfo, + nranges, firstrow, lastrow, > status) +- +>7 Evaluate the given expression and return dimension and type information +on the result. The returned dimensions correspond to a single row entry +of the requested expression, and are equivalent to the result of fits\_read\_tdim(). +Note that strings are considered to be one element regardless of string length. +>If maxdim == 0, then naxes is optional. +- + FTTEXP(unit, expr, maxdim > datatype, nelem, naxis, naxes, status) +- + + +**J. Celestial Coordinate System Subroutines \label{FTGICS} + +The FITS community has adopted a set of keyword conventions that define +the transformations needed to convert between pixel locations in an +image and the corresponding celestial coordinates on the sky, or more +generally, that define world coordinates that are to be associated with +any pixel location in an n-dimensional FITS array. CFITSIO is distributed +with a couple of self-contained World Coordinate System (WCS) routines, +however, these routines DO NOT support all the latest WCS conventions, +so it is STRONGLY RECOMMENDED that software developers use a more robust +external WCS library. Several recommended libraries are: +- + WCSLIB - supported by Mark Calabretta + WCSTools - supported by Doug Mink + AST library - developed by the U.K. Starlink project +- + +More information about the WCS keyword conventions and links to all of +these WCS libraries can be found on the FITS Support Office web site at +http://fits.gsfc.nasa.gov under the WCS link. + +The functions provided in these external WCS libraries will need access to +the WCS information contained in the FITS file headers. One convenient +way to pass this information to the external library is to use FITSIO +to copy the header keywords into one long character string, and then +pass this string to an interface routine in the external library that +will extract the necessary WCS information (e.g., see the astFitsChan +and astPutCards routines in the Starlink AST library). + +The following FITSIO routines DO NOT support the more recent WCS conventions +that have been approved as part of the FITS standard. Consequently, +the following routines ARE NOW DEPRECATED. It is STRONGLY RECOMMENDED +that software developers not use these routines, and instead use an +external WCS library, as described above. + +These routines are included mainly for backward compatibility with +existing software. They support the following standard map +projections: -SIN, -TAN, -ARC, -NCP, -GLS, -MER, and -AIT (these are the +legal values for the coordtype parameter). These routines are based +on similar functions in Classic AIPS. All the angular quantities are +given in units of degrees. + +>1 Get the values of all the standard FITS celestial coordinate system + keywords from the header of a FITS image (i.e., the primary array or + an image extension). These values may then be passed to the subroutines + that perform the coordinate transformations. If any or all of the WCS + keywords are not present, then default values will be returned. If + the first coordinate axis is the declination-like coordinate, then + this routine will swap them so that the longitudinal-like coordinate + is returned as the first axis. + + If the file uses the newer 'CDj\_i' WCS transformation matrix + keywords instead of old style 'CDELTn' and 'CROTA2' keywords, then + this routine will calculate and return the values of the equivalent + old-style keywords. Note that the conversion from the new-style + keywords to the old-style values is sometimes only an + approximation, so if the approximation is larger than an internally + defined threshold level, then CFITSIO will still return the + approximate WCS keyword values, but will also return with status = + 506, to warn the calling program that approximations have been + made. It is then up to the calling program to decide whether the + approximations are sufficiently accurate for the particular + application, or whether more precise WCS transformations must be +> performed using new-style WCS keywords directly. +- + FTGICS(unit, > xrval,yrval,xrpix,yrpix,xinc,yinc,rot,coordtype,status) +- +>2 Get the values of all the standard FITS celestial coordinate system + keywords from the header of a FITS table where the X and Y (or RA and + DEC coordinates are stored in 2 separate columns of the table. + These values may then be passed to the subroutines that perform the +> coordinate transformations. +- + FTGTCS(unit,xcol,ycol, > + xrval,yrval,xrpix,yrpix,xinc,yinc,rot,coordtype,status) +- +>3 Calculate the celestial coordinate corresponding to the input +> X and Y pixel location in the image. +- + FTWLDP(xpix,ypix,xrval,yrval,xrpix,yrpix,xinc,yinc,rot, + coordtype, > xpos,ypos,status) +- +>4 Calculate the X and Y pixel location corresponding to the input +> celestial coordinate in the image. +- + FTXYPX(xpos,ypos,xrval,yrval,xrpix,yrpix,xinc,yinc,rot, + coordtype, > xpix,ypix,status) +- + +**K. File Checksum Subroutines \label{FTPCKS} + +The following routines either compute or validate the checksums for the +CHDU. The DATASUM keyword is used to store the numerical value of the +32-bit, 1's complement checksum for the data unit alone. If there is +no data unit then the value is set to zero. The numerical value is +stored as an ASCII string of digits, enclosed in quotes, because the +value may be too large to represent as a 32-bit signed integer. The +CHECKSUM keyword is used to store the ASCII encoded COMPLEMENT of the +checksum for the entire HDU. Storing the complement, rather than the +actual checksum, forces the checksum for the whole HDU to equal zero. +If the file has been modified since the checksums were computed, then +the HDU checksum will usually not equal zero. These checksum keyword +conventions are based on a paper by Rob Seaman published in the +proceedings of the ADASS IV conference in Baltimore in November 1994 +and a later revision in June 1995. + +>1 Compute and write the DATASUM and CHECKSUM keyword values for the CHDU + into the current header. The DATASUM value is the 32-bit checksum + for the data unit, expressed as a decimal integer enclosed in single + quotes. The CHECKSUM keyword value is a 16-character string which + is the ASCII-encoded value for the complement of the checksum for + the whole HDU. If these keywords already exist, their values + will be updated only if necessary (i.e., if the file has been modified +> since the original keyword values were computed). +- + FTPCKS(unit, > status) +- +>2 Update the CHECKSUM keyword value in the CHDU, assuming that the + DATASUM keyword exists and already has the correct value. This routine + calculates the new checksum for the current header unit, adds it to the + data unit checksum, encodes the value into an ASCII string, and writes +> the string to the CHECKSUM keyword. +- + FTUCKS(unit, > status) +- +>3 Verify the CHDU by computing the checksums and comparing + them with the keywords. The data unit is verified correctly + if the computed checksum equals the value of the DATASUM + keyword. The checksum for the entire HDU (header plus data unit) is + correct if it equals zero. The output DATAOK and HDUOK parameters + in this subroutine are integers which will have a value = 1 + if the data or HDU is verified correctly, a value = 0 + if the DATASUM or CHECKSUM keyword is not present, or value = -1 +> if the computed checksum is not correct. +- + FTVCKS(unit, > dataok,hduok,status) +- +>4 Compute and return the checksum values for the CHDU (as + double precision variables) without creating or modifying the + CHECKSUM and DATASUM keywords. This routine is used internally by +> FTVCKS, but may be useful in other situations as well. +- + FTGCKS(unit, > datasum,hdusum,status) +- +>5 Encode a checksum value (stored in a double precision variable) + into a 16-character string. If COMPLEMENT = .true. then the 32-bit +> sum value will be complemented before encoding. +- + FTESUM(sum,complement, > checksum) +- +>6 Decode a 16 character checksum string into a double precision value. + If COMPLEMENT = .true. then the 32-bit sum value will be complemented +> after decoding. +- + FTDSUM(checksum,complement, > sum) +- + +**L. Date and Time Utility Routines \label{FTGSDT} + +The following routines help to construct or parse the FITS date/time +strings. Starting in the year 2000, the FITS DATE keyword values (and +the values of other `DATE-' keywords) must have the form 'YYYY-MM-DD' +(date only) or 'YYYY-MM-DDThh:mm:ss.ddd...' (date and time) where the +number of decimal places in the seconds value is optional. These times +are in UTC. The older 'dd/mm/yy' date format may not be used for dates +after 01 January 2000. + +>1 Get the current system date. The returned year has 4 digits +> (1999, 2000, etc.) +- + FTGSDT( > day, month, year, status ) +- + +>2 Get the current system date and time string ('YYYY-MM-DDThh:mm:ss'). +The time will be in UTC/GMT if available, as indicated by a returned timeref +value = 0. If the returned value of timeref = 1 then this indicates that +it was not possible to convert the local time to UTC, and thus the local +>time was returned. +- + FTGSTM(> datestr, timeref, status) +- + +>3 Construct a date string from the input date values. If the year +is between 1900 and 1998, inclusive, then the returned date string will +have the old FITS format ('dd/mm/yy'), otherwise the date string will +have the new FITS format ('YYYY-MM-DD'). Use FTTM2S instead +> to always return a date string using the new FITS format. +- + FTDT2S( year, month, day, > datestr, status) +- + +>4 Construct a new-format date + time string ('YYYY-MM-DDThh:mm:ss.ddd...'). + If the year, month, and day values all = 0 then only the time is encoded + with format 'hh:mm:ss.ddd...'. The decimals parameter specifies how many + decimal places of fractional seconds to include in the string. If `decimals' +> is negative, then only the date will be return ('YYYY-MM-DD'). +- + FTTM2S( year, month, day, hour, minute, second, decimals, + > datestr, status) +- + +>5 Return the date as read from the input string, where the string may be +in either the old ('dd/mm/yy') or new ('YYYY-MM-DDThh:mm:ss' or +>'YYYY-MM-DD') FITS format. +- + FTS2DT(datestr, > year, month, day, status) +- + +>6 Return the date and time as read from the input string, where the +string may be in either the old or new FITS format. The returned hours, +minutes, and seconds values will be set to zero if the input string +does not include the time ('dd/mm/yy' or 'YYYY-MM-DD') . Similarly, +the returned year, month, and date values will be set to zero if the +>date is not included in the input string ('hh:mm:ss.ddd...'). +- + FTS2TM(datestr, > year, month, day, hour, minute, second, status) +- + +**M. General Utility Subroutines \label{FTGHAD} + +The following utility subroutines may be useful for certain applications: + +>>1 Return the starting byte address of the CHDU and the next HDU. +- + FTGHAD(iunit, > curaddr, nextaddr) +- +>>2 Convert a character string to uppercase (operates in place). +- + FTUPCH(string) +- +>3 Compare the input template string against the reference string + to see if they match. The template string may contain wildcard + characters: '*' will match any sequence of characters (including + zero characters) and '?' will match any single character in the + reference string. The '\#' character will match any consecutive string + of decimal digits (0 - 9). If CASESN = .true. then the match will be + case sensitive. The returned MATCH parameter will be .true. if + the 2 strings match, and EXACT will be .true. if the match is + exact (i.e., if no wildcard characters were used in the match). +> Both strings must be 68 characters or less in length. +- + FTCMPS(str_template, string, casesen, > match, exact) +- + +>4 Test that the keyword name contains only legal characters: A-Z,0-9, +> hyphen, and underscore. +- + FTTKEY(keyword, > status) +- +>5 Test that the keyword record contains only legal printable ASCII +> characters +- + FTTREC(card, > status) +- +>6 Test whether the current header contains any NULL (ASCII 0) characters. + These characters are illegal in the header, but they will go undetected + by most of the CFITSIO keyword header routines, because the null is + interpreted as the normal end-of-string terminator. This routine returns + the position of the first null character in the header, or zero if there + are no nulls. For example a returned value of 110 would indicate that + the first NULL is located in the 30th character of the second keyword + in the header (recall that each header record is 80 characters long). + Note that this is one of the few FITSIO routines in which the returned +> value is not necessarily equal to the status value). +- + FTNCHK(unit, > status) +- +>7 Parse a header keyword record and return the name of the keyword + and the length of the name. + The keyword name normally occupies the first 8 characters of the + record, except under the HIERARCH convention where the name can +> be up to 70 characters in length. +- + FTGKNM(card, > keyname, keylength, staThe '\#' character will match any consecutive string + of decimal digits (0 - 9). tus) +- +>8 Parse a header keyword record. + This subroutine parses the input header record to return the value (as + a character string) and comment strings. If the keyword has no + value (columns 9-10 not equal to '= '), then the value string is returned + blank and the comment string is set equal to column 9 - 80 of the +> input string. +- + FTPSVC(card, > value,comment,status) +- +>9 Construct a sequence keyword name (ROOT + nnn). + This subroutine appends the sequence number to the root string to create +> a keyword name (e.g., 'NAXIS' + 2 = 'NAXIS2') +- + FTKEYN(keyroot,seq_no, > keyword,status) +- +>10 Construct a sequence keyword name (n + ROOT). + This subroutine concatenates the sequence number to the front of the +> root string to create a keyword name (e.g., 1 + 'CTYP' = '1CTYP') +- + FTNKEY(seq_no,keyroot, > keyword,status) +- +>11 Determine the datatype of a keyword value string. + This subroutine parses the keyword value string (usually columns 11-30 +> of the header record) to determine its datatype. +- + FTDTYP(value, > dtype,status) +- +>11 Return the class of input header record. The record is classified + into one of the following categories (the class values are + defined in fitsio.h). Note that this is one of the few FITSIO +> routines that does not return a status value. +- + Class Value Keywords + TYP_STRUC_KEY 10 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 20 The experimental keywords used in the compressed + image format ZIMAGE, ZCMPTYPE, ZNAMEn, ZVALn, + ZTILEn, ZBITPIX, ZNAXISn, ZSCALE, ZZERO, ZBLANK + TYP_SCAL_KEY 30 BSCALE, BZERO, TSCALn, TZEROn + TYP_NULL_KEY 40 BLANK, TNULLn + TYP_DIM_KEY 50 TDIMn + TYP_RANG_KEY 60 TLMINn, TLMAXn, TDMINn, TDMAXn, DATAMIN, DATAMAX + TYP_UNIT_KEY 70 BUNIT, TUNITn + TYP_DISP_KEY 80 TDISPn + TYP_HDUID_KEY 90 EXTNAME, EXTVER, EXTLEVEL, HDUNAME, HDUVER, HDULEVEL + TYP_CKSUM_KEY 100 CHECKSUM, DATASUM + TYP_WCS_KEY 110 CTYPEn, CUNITn, CRVALn, CRPIXn, CROTAn, CDELTn + CDj_is, PVj_ms, LONPOLEs, LATPOLEs + TCTYPn, TCTYns, TCUNIn, TCUNns, TCRVLn, TCRVns, TCRPXn, + TCRPks, TCDn_k, TCn_ks, TPVn_m, TPn_ms, TCDLTn, TCROTn + jCTYPn, jCTYns, jCUNIn, jCUNns, jCRVLn, jCRVns, iCRPXn, + iCRPns, jiCDn, jiCDns, jPVn_m, jPn_ms, jCDLTn, jCROTn + (i,j,m,n are integers, s is any letter) + TYP_REFSYS_KEY 120 EQUINOXs, EPOCH, MJD-OBSs, RADECSYS, RADESYSs + TYP_COMM_KEY 130 COMMENT, HISTORY, (blank keyword) + TYP_CONT_KEY 140 CONTINUE + TYP_USER_KEY 150 all other keywords + + class = FTGKCL (char *card) +- +>12 Parse the 'TFORM' binary table column format string. + This subroutine parses the input TFORM character string and returns the + integer datatype code, the repeat count of the field, and, in the case + of character string fields, the length of the unit string. The following + datatype codes are returned (the negative of the value is returned +> if the column contains variable-length arrays): +- + Datatype DATACODE value + bit, X 1 + byte, B 11 + logical, L 14 + ASCII character, A 16 + short integer, I 21 + integer, J 41 + real, E 42 + double precision, D 82 + complex 83 + double complex 163 + + FTBNFM(tform, > datacode,repeat,width,status) +- +>13 Parse the 'TFORM' keyword value that defines the column format in + an ASCII table. This routine parses the input TFORM character + string and returns the datatype code, the width of the column, + and (if it is a floating point column) the number of decimal places + to the right of the decimal point. The returned datatype codes are + the same as for the binary table, listed above, with the following + additional rules: integer columns that are between 1 and 4 characters + wide are defined to be short integers (code = 21). Wider integer + columns are defined to be regular integers (code = 41). Similarly, + Fixed decimal point columns (with TFORM = 'Fw.d') are defined to + be single precision reals (code = 42) if w is between 1 and 7 characters + wide, inclusive. Wider 'F' columns will return a double precision + data code (= 82). 'Ew.d' format columns will have datacode = 42, +> and 'Dw.d' format columns will have datacode = 82. +- + FTASFM(tform, > datacode,width,decimals,status) +- +>14 Calculate the starting column positions and total ASCII table width + based on the input array of ASCII table TFORM values. The SPACE input + parameter defines how many blank spaces to leave between each column + (it is recommended to have one space between columns for better human +> readability). +- + FTGABC(tfields,tform,space, > rowlen,tbcol,status) +- +>15 Parse a template string and return a formatted 80-character string + suitable for appending to (or deleting from) a FITS header file. + This subroutine is useful for parsing lines from an ASCII template file + and reformatting them into legal FITS header records. The formatted + string may then be passed to the FTPREC, FTMCRD, or FTDKEY subroutines +> to append or modify a FITS header record. +- + FTGTHD(template, > card,hdtype,status) +- + The input TEMPLATE character string generally should contain 3 tokens: + (1) the KEYNAME, (2) the VALUE, and (3) the COMMENT string. The + TEMPLATE string must adhere to the following format: + +>- The KEYNAME token must begin in columns 1-8 and be a maximum of 8 + characters long. If the first 8 characters of the template line are + blank then the remainder of the line is considered to be a FITS comment + (with a blank keyword name). A legal FITS keyword name may only + contain the characters A-Z, 0-9, and '-' (minus sign) and + underscore. This subroutine will automatically convert any lowercase + characters to uppercase in the output string. If KEYNAME = 'COMMENT' + or 'HISTORY' then the remainder of the line is considered to be a FITS +> COMMENT or HISTORY record, respectively. + +>- The VALUE token must be separated from the KEYNAME token by one or more + spaces and/or an '=' character. The datatype of the VALUE token + (numeric, logical, or character string) is automatically determined + and the output CARD string is formatted accordingly. The value + token may be forced to be interpreted as a string (e.g. if it is a + string of numeric digits) by enclosing it in single quotes. + If the value token is a character string that contains 1 or more + embedded blank space characters or slash ('/') characters then the +> entire character string must be enclosed in single quotes. + +>- The COMMENT token is optional, but if present must be separated from +> the VALUE token by a blank space or a '/' character. + +>- One exception to the above rules is that if the first non-blank + character in the template string is a minus sign ('-') followed + by a single token, or a single token followed by an equal sign, + then it is interpreted as the name of a keyword which is to be +> deleted from the FITS header. + +>- The second exception is that if the template string starts with + a minus sign and is followed by 2 tokens then the second token + is interpreted as the new name for the keyword specified by + first token. In this case the old keyword name (first token) + is returned in characters 1-8 of the returned CARD string, and + the new keyword name (the second token) is returned in characters + 41-48 of the returned CARD string. These old and new names + may then be passed to the FTMNAM subroutine which will change +> the keyword name. + + The HDTYPE output parameter indicates how the returned CARD string + should be interpreted: +- + hdtype interpretation + ------ ------------------------------------------------- + -2 Modify the name of the keyword given in CARD(1:8) + to the new name given in CARD(41:48) + + -1 CARD(1:8) contains the name of a keyword to be deleted + from the FITS header. + + 0 append the CARD string to the FITS header if the + keyword does not already exist, otherwise update + the value/comment if the keyword is already present + in the header. + + 1 simply append this keyword to the FITS header (CARD + is either a HISTORY or COMMENT keyword). + + 2 This is a FITS END record; it should not be written + to the FITS header because FITSIO automatically + appends the END record when the header is closed. +- + EXAMPLES: The following lines illustrate valid input template strings: +- + INTVAL 7 This is an integer keyword + RVAL 34.6 / This is a floating point keyword + EVAL=-12.45E-03 This is a floating point keyword in exponential notation + lval F This is a boolean keyword + This is a comment keyword with a blank keyword name + SVAL1 = 'Hello world' / this is a string keyword + SVAL2 '123.5' this is also a string keyword + sval3 123+ / this is also a string keyword with the value '123+ ' + # the following template line deletes the DATE keyword + - DATE + # the following template line modifies the NAME keyword to OBJECT + - NAME OBJECT +- +>16 Parse the input string containing a list of rows or row ranges, and + return integer arrays containing the first and last row in each + range. For example, if rowlist = "3-5, 6, 8-9" then it will + return numranges = 3, rangemin = 3, 6, 8 and rangemax = 5, 6, 9. + At most, 'maxranges' number of ranges will be returned. 'maxrows' + is the maximum number of rows in the table; any rows or ranges + larger than this will be ignored. The rows must be specified in + increasing order, and the ranges must not overlap. A minus sign + may be use to specify all the rows to the upper or lower bound, so + "50-" means all the rows from 50 to the end of the table, and "-" +> means all the rows in the table, from 1 - maxrows. +- + FTRWRG(rowlist, maxrows, maxranges, > + numranges, rangemin, rangemax, status) +- + + + +*VI. The CFITSIO Iterator Function + +The fits\_iterate\_data function in CFITSIO provides a unique method of +executing an arbitrary user-supplied `work' function that operates on +rows of data in FITS tables or on pixels in FITS images. Rather than +explicitly reading and writing the FITS images or columns of data, one +instead calls the CFITSIO iterator routine, passing to it the name of +the user's work function that is to be executed along with a list of +all the table columns or image arrays that are to be passed to the work +function. The CFITSIO iterator function then does all the work of +allocating memory for the arrays, reading the input data from the FITS +file, passing them to the work function, and then writing any output +data back to the FITS file after the work function exits. Because +it is often more efficient to process only a subset of the total table +rows at one time, the iterator function can determine the optimum +amount of data to pass in each iteration and repeatedly call the work +function until the entire table been processed. + +For many applications this single CFITSIO iterator function can +effectively replace all the other CFITSIO routines for reading or +writing data in FITS images or tables. Using the iterator has several +important advantages over the traditional method of reading and writing +FITS data files: + +\begin{itemize} +\item +It cleanly separates the data I/O from the routine that operates on +the data. This leads to a more modular and `object oriented' +programming style. + +\item +It simplifies the application program by eliminating the need to allocate +memory for the data arrays and eliminates most of the calls to the CFITSIO +routines that explicitly read and write the data. + +\item +It ensures that the data are processed as efficiently as possible. +This is especially important when processing tabular data since +the iterator function will calculate the most efficient number +of rows in the table to be passed at one time to the user's work +function on each iteration. + +\item +Makes it possible for larger projects to develop a library of work +functions that all have a uniform calling sequence and are all +independent of the details of the FITS file format. + +\end{itemize} + +There are basically 2 steps in using the CFITSIO iterator function. +The first step is to design the work function itself which must have a +prescribed set of input parameters. One of these parameters is a +structure containing pointers to the arrays of data; the work function +can perform any desired operations on these arrays and does not need to +worry about how the input data were read from the file or how the +output data get written back to the file. + +The second step is to design the driver routine that opens all the +necessary FITS files and initializes the input parameters to the +iterator function. The driver program calls the CFITSIO iterator +function which then reads the data and passes it to the user's work +function. + +Further details on using the iterator function can be found in the +companion CFITSIO User's Guide, and in the iter\_a.f, iter\_b.f and +iter\_c.f example programs. + + + +*IV. Extended File Name Syntax + +**A. Overview + +CFITSIO supports an extended syntax when specifying the name of the +data file to be opened or created that includes the following +features: + +\begin{itemize} +\item +CFITSIO can read IRAF format images which have header file names that +end with the '.imh' extension, as well as reading and writing FITS +files, This feature is implemented in CFITSIO by first converting the +IRAF image into a temporary FITS format file in memory, then opening +the FITS file. Any of the usual CFITSIO routines then may be used to +read the image header or data. Similarly, raw binary data arrays can +be read by converting them on the fly into virtual FITS images. + +\item +FITS files on the Internet can be read (and sometimes written) using the FTP, +HTTP, or ROOT protocols. + +\item +FITS files can be piped between tasks on the stdin and stdout streams. + +\item +FITS files can be read and written in shared memory. This can potentially +achieve much better data I/O performance compared to reading and +writing the same FITS files on magnetic disk. + +\item +Compressed FITS files in gzip or Unix COMPRESS format can be directly read. + +\item +Output FITS files can be written directly in compressed gzip format, +thus saving disk space. + +\item +FITS table columns can be created, modified, or deleted 'on-the-fly' as +the table is opened by CFITSIO. This creates a virtual FITS file containing +the modifications that is then opened by the application program. + +\item +Table rows may be selected, or filtered out, on the fly when the table +is opened by CFITSIO, based on an arbitrary user-specified expression. +Only rows for which the expression evaluates to 'TRUE' are retained +in the copy of the table that is opened by the application program. + +\item +Histogram images may be created on the fly by binning the values in +table columns, resulting in a virtual N-dimensional FITS image. The +application program then only sees the FITS image (in the primary +array) instead of the original FITS table. +\end{itemize} + +The latter 3 features in particular add very powerful data processing +capabilities directly into CFITSIO, and hence into every task that uses +CFITSIO to read or write FITS files. For example, these features +transform a very simple program that just copies an input FITS file to +a new output file (like the `fitscopy' program that is distributed with +CFITSIO) into a multipurpose FITS file processing tool. By appending +fairly simple qualifiers onto the name of the input FITS file, the user +can perform quite complex table editing operations (e.g., create new +columns, or filter out rows in a table) or create FITS images by +binning or histogramming the values in table columns. In addition, +these functions have been coded using new state-of-the art algorithms +that are, in some cases, 10 - 100 times faster than previous widely +used implementations. + +Before describing the complete syntax for the extended FITS file names +in the next section, here are a few examples of FITS file names that +give a quick overview of the allowed syntax: + +\begin{itemize} +\item +{\tt 'myfile.fits'}: the simplest case of a FITS file on disk in the current +directory. + +\item +{\tt 'myfile.imh'}: opens an IRAF format image file and converts it on the +fly into a temporary FITS format image in memory which can then be read with +any other CFITSIO routine. + +\item +{\tt rawfile.dat[i512,512]}: opens a raw binary data array (a 512 x 512 +short integer array in this case) and converts it on the fly into a +temporary FITS format image in memory which can then be read with any +other CFITSIO routine. + +\item +{\tt myfile.fits.gz}: if this is the name of a new output file, the '.gz' +suffix will cause it to be compressed in gzip format when it is written to +disk. + +\item +{\tt 'myfile.fits.gz[events, 2]'}: opens and uncompresses the gzipped file +myfile.fits then moves to the extension which has the keywords EXTNAME += 'EVENTS' and EXTVER = 2. + +\item +{\tt '-'}: a dash (minus sign) signifies that the input file is to be read +from the stdin file stream, or that the output file is to be written to +the stdout stream. + +\item +{\tt 'ftp://legacy.gsfc.nasa.gov/test/vela.fits'}: FITS files in any ftp +archive site on the Internet may be directly opened with read-only +access. + +\item +{\tt 'http://legacy.gsfc.nasa.gov/software/test.fits'}: any valid URL to a +FITS file on the Web may be opened with read-only access. + +\item +{\tt 'root://legacy.gsfc.nasa.gov/test/vela.fits'}: similar to ftp access +except that it provides write as well as read access to the files +across the network. This uses the root protocol developed at CERN. + +\item +{\tt 'shmem://h2[events]'}: opens the FITS file in a shared memory segment and +moves to the EVENTS extension. + +\item +{\tt 'mem://'}: creates a scratch output file in core computer memory. The +resulting 'file' will disappear when the program exits, so this +is mainly useful for testing purposes when one does not want a +permanent copy of the output file. + +\item +{\tt 'myfile.fits[3; Images(10)]'}: opens a copy of the image contained in the +10th row of the 'Images' column in the binary table in the 3th extension +of the FITS file. The application just sees this single image as the +primary array. + +\item +{\tt 'myfile.fits[1:512:2, 1:512:2]'}: opens a section of the input image +ranging from the 1st to the 512th pixel in X and Y, and selects every +second pixel in both dimensions, resulting in a 256 x 256 pixel image +in this case. + +\item +{\tt 'myfile.fits[EVENTS][col Rad = sqrt(X**2 + Y**2)]'}: creates and opens +a temporary file on the fly (in memory or on disk) that is identical to +myfile.fits except that it will contain a new column in the EVENTS +extension called 'Rad' whose value is computed using the indicated +expression which is a function of the values in the X and Y columns. + +\item +{\tt 'myfile.fits[EVENTS][PHA > 5]'}: creates and opens a temporary FITS +files that is identical to 'myfile.fits' except that the EVENTS table +will only contain the rows that have values of the PHA column greater +than 5. In general, any arbitrary boolean expression using a C or +Fortran-like syntax, which may combine AND and OR operators, +may be used to select rows from a table. + +\item +{\tt 'myfile.fits[EVENTS][bin (X,Y)=1,2048,4]'}: creates a temporary FITS +primary array image which is computed on the fly by binning (i.e, +computing the 2-dimensional histogram) of the values in the X and Y +columns of the EVENTS extension. In this case the X and Y coordinates +range from 1 to 2048 and the image pixel size is 4 units in both +dimensions, so the resulting image is 512 x 512 pixels in size. + +\item +The final example combines many of these feature into one complex +expression (it is broken into several lines for clarity): +- + 'ftp://legacy.gsfc.nasa.gov/data/sample.fits.gz[EVENTS] + [col phacorr = pha * 1.1 - 0.3][phacorr >= 5.0 && phacorr <= 14.0] + [bin (X,Y)=32]' +- +In this case, CFITSIO (1) copies and uncompresses the FITS file from +the ftp site on the legacy machine, (2) moves to the 'EVENTS' +extension, (3) calculates a new column called 'phacorr', (4) selects +the rows in the table that have phacorr in the range 5 to 14, and +finally (5) bins the remaining rows on the X and Y column coordinates, +using a pixel size = 32 to create a 2D image. All this processing is +completely transparent to the application program, which simply sees +the final 2-D image in the primary array of the opened file. +\end{itemize} + +The full extended CFITSIO FITS file name can contain several different +components depending on the context. These components are described in +the following sections: +- +When creating a new file: + filetype://BaseFilename(templateName) + +When opening an existing primary array or image HDU: + filetype://BaseFilename(outName)[HDUlocation][ImageSection] + +When opening an existing table HDU: + filetype://BaseFilename(outName)[HDUlocation][colFilter][rowFilter][binSpec] +- +The filetype, BaseFilename, outName, HDUlocation, and ImageSection +components, if present, must be given in that order, but the colFilter, +rowFilter, and binSpec specifiers may follow in any order. Regardless +of the order, however, the colFilter specifier, if present, will be +processed first by CFITSIO, followed by the rowFilter specifier, and +finally by the binSpec specifier. + +**A. Filetype + +The type of file determines the medium on which the file is located +(e.g., disk or network) and, hence, which internal device driver is used by +CFITSIO to read and/or write the file. Currently supported types are +- + file:// - file on local magnetic disk (default) + ftp:// - a readonly file accessed with the anonymous FTP protocol. + It also supports ftp://username:password@hostname/... + for accessing password-protected ftp sites. + http:// - a readonly file accessed with the HTTP protocol. It + supports username:password just like the ftp driver. + Proxy HTTP servers are supported using the http_proxy + environment variable (see following note). + stream:// - special driver to read an input FITS file from the stdin + stream, and/or write an output FITS file to the stdout + stream. This driver is fragile and has limited + functionality (see the following note). + gsiftp:// - access files on a computational grid using the gridftp + protocol in the Globus toolkit (see following note). + root:// - uses the CERN root protocol for writing as well as + reading files over the network. + shmem:// - opens or creates a file which persists in the computer's + shared memory. + mem:// - opens a temporary file in core memory. The file + disappears when the program exits so this is mainly + useful for test purposes when a permanent output file + is not desired. +- +If the filetype is not specified, then type file:// is assumed. +The double slashes '//' are optional and may be omitted in most cases. + +***1. Notes about HTTP proxy servers + +A proxy HTTP server may be used by defining the address (URL) and port +number of the proxy server with the http\_proxy environment variable. +For example +- + setenv http_proxy http://heasarc.gsfc.nasa.gov:3128 +- +will cause CFITSIO to use port 3128 on the heasarc proxy server whenever +reading a FITS file with HTTP. + +***2. Notes about the stream filetype driver + +The stream driver can be used to efficiently read a FITS file from the stdin +file stream or write a FITS to the stdout file stream. However, because these +input and output streams must be accessed sequentially, the FITS file reading or +writing application must also read and write the file sequentially, at least +within the tolerances described below. + +CFITSIO supports 2 different methods for accessing FITS files on the stdin and +stdout streams. The original method, which is invoked by specifying a dash +character, "-", as the name of the file when opening or creating it, works by +storing a complete copy of the entire FITS file in memory. In this case, when +reading from stdin, CFITSIO will copy the entire stream into memory before doing +any processing of the file. Similarly, when writing to stdout, CFITSIO will +create a copy of the entire FITS file in memory, before finally flushing it out +to the stdout stream when the FITS file is closed. Buffering the entire FITS +file in this way allows the application to randomly access any part of the FITS +file, in any order, but it also requires that the user have sufficient available +memory (or virtual memory) to store the entire file, which may not be possible +in the case of very large files. + +The newer stream filetype provides a more memory-efficient method of accessing +FITS files on the stdin or stdout streams. Instead of storing a copy of the +entire FITS file in memory, CFITSIO only uses a set of internal buffer which by +default can store 40 FITS blocks, or about 100K bytes of the FITS file. The +application program must process the FITS file sequentially from beginning to +end, within this 100K buffer. Generally speaking the application program must +conform to the following restrictions: + +\begin{itemize} +\item +The program must finish reading or writing the header keywords +before reading or writing any data in the HDU. +\item +The HDU can contain at most about 1400 header keywords. This is the +maximum that can fit in the nominal 40 FITS block buffer. In principle, +this limit could be increased by recompiling CFITSIO with a larger +buffer limit, which is set by the NIOBUF parameter in fitsio2.h. +\item +The program must read or write the data in a sequential manner from the +beginning to the end of the HDU. Note that CFITSIO's internal +100K buffer allows a little latitude in meeting this requirement. +\item +The program cannot move back to a previous HDU in the FITS file. +\item +Reading or writing of variable length array columns in binary tables is not +supported on streams, because this requires moving back and forth between the +fixed-length portion of the binary table and the following heap area where the +arrays are actually stored. +\item +Reading or writing of tile-compressed images is not supported on streams, +because the images are internally stored using variable length arrays. +\end{itemize} + +***3. Notes about the gsiftp filetype + +DEPENDENCIES: Globus toolkit (2.4.3 or higher) (GT) should be installed. +There are two different ways to install GT: + +1) goto the globus toolkit web page www.globus.org and follow the + download and compilation instructions; + +2) goto the Virtual Data Toolkit web page http://vdt.cs.wisc.edu/ + and follow the instructions (STRONGLY SUGGESTED); + +Once a globus client has been installed in your system with a specific flavour +it is possible to compile and install the CFITSIO libraries. +Specific configuration flags must be used: + +1) --with-gsiftp[[=PATH]] Enable Globus Toolkit gsiftp protocol support + PATH=GLOBUS\_LOCATION i.e. the location of your globus installation + +2) --with-gsiftp-flavour[[=PATH] defines the specific Globus flavour + ex. gcc32 + +Both the flags must be used and it is mandatory to set both the PATH and the +flavour. + +USAGE: To access files on a gridftp server it is necessary to use a gsiftp prefix: + +example: gsiftp://remote\_server\_fqhn/directory/filename + +The gridftp driver uses a local buffer on a temporary file the file is located +in the /tmp directory. If you have special permissions on /tmp or you do not have a /tmp +directory, it is possible to force another location setting the GSIFTP\_TMPFILE environment +variable (ex. export GSIFTP\_TMPFILE=/your/location/yourtmpfile). + +Grid FTP supports multi channel transfer. By default a single channel transmission is +available. However, it is possible to modify this behavior setting the GSIFTP\_STREAMS +environment variable (ex. export GSIFTP\_STREAMS=8). + +***4. Notes about the root filetype + +The original rootd server can be obtained from: +\verb-ftp://root.cern.ch/root/rootd.tar.gz- +but, for it to work correctly with CFITSIO one has to use a modified +version which supports a command to return the length of the file. +This modified version is available in rootd subdirectory +in the CFITSIO ftp area at +- + ftp://legacy.gsfc.nasa.gov/software/fitsio/c/root/rootd.tar.gz. +- + +This small server is started either by inetd when a client requests a +connection to a rootd server or by hand (i.e. from the command line). +The rootd server works with the ROOT TNetFile class. It allows remote +access to ROOT database files in either read or write mode. By default +TNetFile assumes port 432 (which requires rootd to be started as root). +To run rootd via inetd add the following line to /etc/services: +- + rootd 432/tcp +- +and to /etc/inetd.conf, add the following line: +- + rootd stream tcp nowait root /user/rdm/root/bin/rootd rootd -i +- +Force inetd to reread its conf file with "kill -HUP ". +You can also start rootd by hand running directly under your private +account (no root system privileges needed). For example to start +rootd listening on port 5151 just type: \verb+rootd -p 5151+ +Notice: no \& is needed. Rootd will go into background by itself. +- + Rootd arguments: + -i says we were started by inetd + -p port# specifies a different port to listen on + -d level level of debug info written to syslog + 0 = no debug (default) + 1 = minimum + 2 = medium + 3 = maximum +- +Rootd can also be configured for anonymous usage (like anonymous ftp). +To setup rootd to accept anonymous logins do the following (while being +logged in as root): +- + - Add the following line to /etc/passwd: + + rootd:*:71:72:Anonymous rootd:/var/spool/rootd:/bin/false + + where you may modify the uid, gid (71, 72) and the home directory + to suite your system. + + - Add the following line to /etc/group: + + rootd:*:72:rootd + + where the gid must match the gid in /etc/passwd. + + - Create the directories: + + mkdir /var/spool/rootd + mkdir /var/spool/rootd/tmp + chmod 777 /var/spool/rootd/tmp + + Where /var/spool/rootd must match the rootd home directory as + specified in the rootd /etc/passwd entry. + + - To make writeable directories for anonymous do, for example: + + mkdir /var/spool/rootd/pub + chown rootd:rootd /var/spool/rootd/pub +- +That's all. Several additional remarks: you can login to an anonymous +server either with the names "anonymous" or "rootd". The password should +be of type user@host.do.main. Only the @ is enforced for the time +being. In anonymous mode the top of the file tree is set to the rootd +home directory, therefore only files below the home directory can be +accessed. Anonymous mode only works when the server is started via +inetd. + +***5. Notes about the shmem filetype: + +Shared memory files are currently supported on most Unix platforms, +where the shared memory segments are managed by the operating system +kernel and `live' independently of processes. They are not deleted (by +default) when the process which created them terminates, although they +will disappear if the system is rebooted. Applications can create +shared memory files in CFITSIO by calling: +- + fit_create_file(&fitsfileptr, "shmem://h2", &status); +- +where the root `file' names are currently restricted to be 'h0', 'h1', +'h2', 'h3', etc., up to a maximum number defined by the the value of +SHARED\_MAXSEG (equal to 16 by default). This is a prototype +implementation of the shared memory interface and a more robust +interface, which will have fewer restrictions on the number of files +and on their names, may be developed in the future. + +When opening an already existing FITS file in shared memory one calls +the usual CFITSIO routine: +- + fits_open_file(&fitsfileptr, "shmem://h7", mode, &status) +- +The file mode can be READWRITE or READONLY just as with disk files. +More than one process can operate on READONLY mode files at the same +time. CFITSIO supports proper file locking (both in READONLY and +READWRITE modes), so calls to fits\_open\_file may be locked out until +another other process closes the file. + +When an application is finished accessing a FITS file in a shared +memory segment, it may close it (and the file will remain in the +system) with fits\_close\_file, or delete it with fits\_delete\_file. +Physical deletion is postponed until the last process calls +ffclos/ffdelt. fits\_delete\_file tries to obtain a READWRITE lock on +the file to be deleted, thus it can be blocked if the object was not +opened in READWRITE mode. + +A shared memory management utility program called `smem', is included +with the CFITSIO distribution. It can be built by typing `make smem'; +then type `smem -h' to get a list of valid options. Executing smem +without any options causes it to list all the shared memory segments +currently residing in the system and managed by the shared memory +driver. To get a list of all the shared memory objects, run the system +utility program `ipcs [-a]'. + +**B. Base Filename + +The base filename is the name of the file optionally including the +director/subdirectory path, and in the case of `ftp', `http', and `root' +filetypes, the machine identifier. Examples: +- + myfile.fits + !data.fits + /data/myfile.fits + fits.gsfc.nasa.gov/ftp/sampledata/myfile.fits.gz +- + +When creating a new output file on magnetic disk (of type file://) if +the base filename begins with an exclamation point (!) then any +existing file with that same basename will be deleted prior to creating +the new FITS file. Otherwise if the file to be created already exists, +then CFITSIO will return an error and will not overwrite the existing +file. Note that the exclamation point, '!', is a special UNIX character, +so if it is used on the command line rather than entered at a task +prompt, it must be preceded by a backslash to force the UNIX +shell to pass it verbatim to the application program. + +If the output disk file name ends with the suffix '.gz', then CFITSIO +will compress the file using the gzip compression algorithm before +writing it to disk. This can reduce the amount of disk space used by +the file. Note that this feature requires that the uncompressed file +be constructed in memory before it is compressed and written to disk, +so it can fail if there is insufficient available memory. + +An input FITS file may be compressed with the gzip or Unix compress +algorithms, in which case CFITSIO will uncompress the file on the fly +into a temporary file (in memory or on disk). Compressed files may +only be opened with read-only permission. When specifying the name of +a compressed FITS file it is not necessary to append the file suffix +(e.g., `.gz' or `.Z'). If CFITSIO cannot find the input file name +without the suffix, then it will automatically search for a compressed +file with the same root name. In the case of reading ftp and http type +files, CFITSIO generally looks for a compressed version of the file +first, before trying to open the uncompressed file. By default, +CFITSIO copies (and uncompressed if necessary) the ftp or http FITS +file into memory on the local machine before opening it. This will +fail if the local machine does not have enough memory to hold the whole +FITS file, so in this case, the output filename specifier (see the next +section) can be used to further control how CFITSIO reads ftp and http +files. + +If the input file is an IRAF image file (*.imh file) then CFITSIO will +automatically convert it on the fly into a virtual FITS image before it +is opened by the application program. IRAF images can only be opened +with READONLY file access. + +Similarly, if the input file is a raw binary data array, then CFITSIO +will convert it on the fly into a virtual FITS image with the basic set +of required header keywords before it is opened by the application +program (with READONLY access). In this case the data type and +dimensions of the image must be specified in square brackets following +the filename (e.g. rawfile.dat[ib512,512]). The first character (case +insensitive) defines the datatype of the array: +- + b 8-bit unsigned byte + i 16-bit signed integer + u 16-bit unsigned integer + j 32-bit signed integer + r or f 32-bit floating point + d 64-bit floating point +- +An optional second character specifies the byte order of the array +values: b or B indicates big endian (as in FITS files and the native +format of SUN UNIX workstations and Mac PCs) and l or L indicates +little endian (native format of DEC OSF workstations and IBM PCs). If +this character is omitted then the array is assumed to have the native +byte order of the local machine. These datatype characters are then +followed by a series of one or more integer values separated by commas +which define the size of each dimension of the raw array. Arrays with +up to 5 dimensions are currently supported. Finally, a byte offset to +the position of the first pixel in the data file may be specified by +separating it with a ':' from the last dimension value. If omitted, it +is assumed that the offset = 0. This parameter may be used to skip +over any header information in the file that precedes the binary data. +Further examples: +- + raw.dat[b10000] 1-dimensional 10000 pixel byte array + raw.dat[rb400,400,12] 3-dimensional floating point big-endian array + img.fits[ib512,512:2880] reads the 512 x 512 short integer array in + a FITS file, skipping over the 2880 byte header +- + +One special case of input file is where the filename = `-' (a dash or +minus sign) or 'stdin' or 'stdout', which signifies that the input file +is to be read from the stdin stream, or written to the stdout stream if +a new output file is being created. In the case of reading from stdin, +CFITSIO first copies the whole stream into a temporary FITS file (in +memory or on disk), and subsequent reading of the FITS file occurs in +this copy. When writing to stdout, CFITSIO first constructs the whole +file in memory (since random access is required), then flushes it out +to the stdout stream when the file is closed. In addition, if the +output filename = '-.gz' or 'stdout.gz' then it will be gzip compressed +before being written to stdout. + +This ability to read and write on the stdin and stdout steams allows +FITS files to be piped between tasks in memory rather than having to +create temporary intermediate FITS files on disk. For example if task1 +creates an output FITS file, and task2 reads an input FITS file, the +FITS file may be piped between the 2 tasks by specifying +- + task1 - | task2 - +- +where the vertical bar is the Unix piping symbol. This assumes that the 2 +tasks read the name of the FITS file off of the command line. + +**C. Output File Name when Opening an Existing File + +An optional output filename may be specified in parentheses immediately +following the base file name to be opened. This is mainly useful in +those cases where CFITSIO creates a temporary copy of the input FITS +file before it is opened and passed to the application program. This +happens by default when opening a network FTP or HTTP-type file, when +reading a compressed FITS file on a local disk, when reading from the +stdin stream, or when a column filter, row filter, or binning specifier +is included as part of the input file specification. By default this +temporary file is created in memory. If there is not enough memory to +create the file copy, then CFITSIO will exit with an error. In these +cases one can force a permanent file to be created on disk, instead of +a temporary file in memory, by supplying the name in parentheses +immediately following the base file name. The output filename can +include the '!' clobber flag. + +Thus, if the input filename to CFITSIO is: +\verb+file1.fits.gz(file2.fits)+ +then CFITSIO will uncompress `file1.fits.gz' into the local disk file +`file2.fits' before opening it. CFITSIO does not automatically delete +the output file, so it will still exist after the application program +exits. + +In some cases, several different temporary FITS files will be created +in sequence, for instance, if one opens a remote file using FTP, then +filters rows in a binary table extension, then create an image by +binning a pair of columns. In this case, the remote file will be +copied to a temporary local file, then a second temporary file will be +created containing the filtered rows of the table, and finally a third +temporary file containing the binned image will be created. In cases +like this where multiple files are created, the outfile specifier will +be interpreted the name of the final file as described below, in descending +priority: + +\begin{itemize} +\item +as the name of the final image file if an image within a single binary +table cell is opened or if an image is created by binning a table column. +\item +as the name of the file containing the filtered table if a column filter +and/or a row filter are specified. +\item +as the name of the local copy of the remote FTP or HTTP file. +\item +as the name of the uncompressed version of the FITS file, if a +compressed FITS file on local disk has been opened. +\item +otherwise, the output filename is ignored. +\end{itemize} + + +The output file specifier is useful when reading FTP or HTTP-type +FITS files since it can be used to create a local disk copy of the file +that can be reused in the future. If the output file name = `*' then a +local file with the same name as the network file will be created. +Note that CFITSIO will behave differently depending on whether the +remote file is compressed or not as shown by the following examples: +\begin{itemize} +\item +`ftp://remote.machine/tmp/myfile.fits.gz(*)' - the remote compressed +file is copied to the local compressed file `myfile.fits.gz', which +is then uncompressed in local memory before being opened and passed +to the application program. + +\item +`ftp://remote.machine/tmp/myfile.fits.gz(myfile.fits)' - the remote +compressed file is copied and uncompressed into the local file +`myfile.fits'. This example requires less local memory than the +previous example since the file is uncompressed on disk instead of +in memory. + +\item +`ftp://remote.machine/tmp/myfile.fits(myfile.fits.gz)' - this will +usually produce an error since CFITSIO itself cannot compress files. +\end{itemize} + +The exact behavior of CFITSIO in the latter case depends on the type of +ftp server running on the remote machine and how it is configured. In +some cases, if the file `myfile.fits.gz' exists on the remote machine, +then the server will copy it to the local machine. In other cases the +ftp server will automatically create and transmit a compressed version +of the file if only the uncompressed version exists. This can get +rather confusing, so users should use a certain amount of caution when +using the output file specifier with FTP or HTTP file types, to make +sure they get the behavior that they expect. + +**D. Template File Name when Creating a New File + +When a new FITS file is created with a call to fits\_create\_file, the +name of a template file may be supplied in parentheses immediately +following the name of the new file to be created. This template is +used to define the structure of one or more HDUs in the new file. The +template file may be another FITS file, in which case the newly created +file will have exactly the same keywords in each HDU as in the template +FITS file, but all the data units will be filled with zeros. The +template file may also be an ASCII text file, where each line (in +general) describes one FITS keyword record. The format of the ASCII +template file is described below. + +**E. Image Tile-Compression Specification + +When specifying the name of the output FITS file to be created, the +user can indicate that images should be written in tile-compressed +format (see section 5.5, ``Primary Array or IMAGE Extension I/O +Routines'') by enclosing the compression parameters in square brackets +following the root disk file name. Here are some examples of the +syntax for specifying tile-compressed output images: +- + myfile.fit[compress] - use Rice algorithm and default tile size + + myfile.fit[compress GZIP] - use the specified compression algorithm; + myfile.fit[compress Rice] only the first letter of the algorithm + myfile.fit[compress PLIO] name is required. + + myfile.fit[compress Rice 100,100] - use 100 x 100 pixel tile size + myfile.fit[compress Rice 100,100;2] - as above, and use noisebits = 2 +- + +**F. HDU Location Specification + +The optional HDU location specifier defines which HDU (Header-Data +Unit, also known as an `extension') within the FITS file to initially +open. It must immediately follow the base file name (or the output +file name if present). If it is not specified then the first HDU (the +primary array) is opened. The HDU location specifier is required if +the colFilter, rowFilter, or binSpec specifiers are present, because +the primary array is not a valid HDU for these operations. The HDU may +be specified either by absolute position number, starting with 0 for +the primary array, or by reference to the HDU name, and optionally, the +version number and the HDU type of the desired extension. The location +of an image within a single cell of a binary table may also be +specified, as described below. + +The absolute position of the extension is specified either by enclosed +the number in square brackets (e.g., `[1]' = the first extension +following the primary array) or by preceded the number with a plus sign +(`+1'). To specify the HDU by name, give the name of the desired HDU +(the value of the EXTNAME or HDUNAME keyword) and optionally the +extension version number (value of the EXTVER keyword) and the +extension type (value of the XTENSION keyword: IMAGE, ASCII or TABLE, +or BINTABLE), separated by commas and all enclosed in square brackets. +If the value of EXTVER and XTENSION are not specified, then the first +extension with the correct value of EXTNAME is opened. The extension +name and type are not case sensitive, and the extension type may be +abbreviated to a single letter (e.g., I = IMAGE extension or primary +array, A or T = ASCII table extension, and B = binary table BINTABLE +extension). If the HDU location specifier is equal to `[PRIMARY]' or +`[P]', then the primary array (the first HDU) will be opened. + +FITS images are most commonly stored in the primary array or an image +extension, but images can also be stored as a vector in a single cell +of a binary table (i.e. each row of the vector column contains a +different image). Such an image can be opened with CFITSIO by +specifying the desired column name and the row number after the binary +table HDU specifier as shown in the following examples. The column name +is separated from the HDU specifier by a semicolon and the row number +is enclosed in parentheses. In this case CFITSIO copies the image from +the table cell into a temporary primary array before it is opened. The +application program then just sees the image in the primary array, +without any extensions. The particular row to be opened may be +specified either by giving an absolute integer row number (starting +with 1 for the first row), or by specifying a boolean expression that +evaluates to TRUE for the desired row. The first row that satisfies +the expression will be used. The row selection expression has the same +syntax as described in the Row Filter Specifier section, below. + + Examples: +- + myfile.fits[3] - open the 3rd HDU following the primary array + myfile.fits+3 - same as above, but using the FTOOLS-style notation + myfile.fits[EVENTS] - open the extension that has EXTNAME = 'EVENTS' + myfile.fits[EVENTS, 2] - same as above, but also requires EXTVER = 2 + myfile.fits[events,2,b] - same, but also requires XTENSION = 'BINTABLE' + myfile.fits[3; images(17)] - opens the image in row 17 of the 'images' + column in the 3rd extension of the file. + myfile.fits[3; images(exposure > 100)] - as above, but opens the image + in the first row that has an 'exposure' column value + greater than 100. +- + +**G. Image Section + +A virtual file containing a rectangular subsection of an image can be +extracted and opened by specifying the range of pixels (start:end) +along each axis to be extracted from the original image. One can also +specify an optional pixel increment (start:end:step) for each axis of +the input image. A pixel step = 1 will be assumed if it is not +specified. If the start pixel is larger then the end pixel, then the +image will be flipped (producing a mirror image) along that dimension. +An asterisk, '*', may be used to specify the entire range of an axis, +and '-*' will flip the entire axis. The input image can be in the +primary array, in an image extension, or contained in a vector cell of +a binary table. In the later 2 cases the extension name or number must +be specified before the image section specifier. + + Examples: +- + myfile.fits[1:512:2, 2:512:2] - open a 256x256 pixel image + consisting of the odd numbered columns (1st axis) and + the even numbered rows (2nd axis) of the image in the + primary array of the file. + + myfile.fits[*, 512:256] - open an image consisting of all the columns + in the input image, but only rows 256 through 512. + The image will be flipped along the 2nd axis since + the starting pixel is greater than the ending pixel. + + myfile.fits[*:2, 512:256:2] - same as above but keeping only + every other row and column in the input image. + + myfile.fits[-*, *] - copy the entire image, flipping it along + the first axis. + + myfile.fits[3][1:256,1:256] - opens a subsection of the image that + is in the 3rd extension of the file. + + myfile.fits[4; images(12)][1:10,1:10] - open an image consisting + of the first 10 pixels in both dimensions. The original + image resides in the 12th row of the 'images' vector + column in the table in the 4th extension of the file. +- + +When CFITSIO opens an image section it first creates a temporary file +containing the image section plus a copy of any other HDUs in the +file. This temporary file is then opened by the application program, +so it is not possible to write to or modify the input file when +specifying an image section. Note that CFITSIO automatically updates +the world coordinate system keywords in the header of the image +section, if they exist, so that the coordinate associated with each +pixel in the image section will be computed correctly. + +**H. Image Transform Filters + +CFITSIO can apply a user-specified mathematical function to the value +of every pixel in a FITS image, thus creating a new virtual image +in computer memory that is then opened and read by the application +program. The original FITS image is not modified by this process. + +The image transformation specifier is appended to the input +FITS file name and is enclosed in square brackets. It begins with the +letters 'PIX' to distinguish it from other types of FITS file filters +that are recognized by CFITSIO. The image transforming function may +use any of the mathematical operators listed in the following +'Row Filtering Specification' section of this document. +Some examples of image transform filters are: +- + [pix X * 2.0] - multiply each pixel by 2.0 + [pix sqrt(X)] - take the square root of each pixel + [pix X + #ZEROPT - add the value of the ZEROPT keyword + [pix X>0 ? log10(X) : -99.] - if the pixel value is greater + than 0, compute the base 10 log, + else set the pixel = -99. +- +Use the letter 'X' in the expression to represent the current pixel value +in the image. The expression is evaluated +independently for each pixel in the image and may be a function of 1) the +original pixel value, 2) the value of other pixels in the image at +a given relative offset from the position of the pixel that is being +evaluated, and 3) the value of +any header keywords. Header keyword values are represented +by the name of the keyword preceded by the '\#' sign. + + +To access the the value of adjacent pixels in the image, +specify the (1-D) offset from the current pixel in curly brackets. +For example +- + [pix (x{-1} + x + x{+1}) / 3] +- +will replace each pixel value with the running mean of the values of that +pixel and it's 2 neighboring pixels. Note that in this notation the image +is treated as a 1-D array, where each row of the image (or higher dimensional +cube) is appended one after another in one long array of pixels. +It is possible to refer to pixels +in the rows above or below the current pixel by using the value of the +NAXIS1 header keyword. For example +- + [pix (x{-#NAXIS1} + x + x{#NAXIS1}) / 3] +- +will compute the mean of each image pixel and the pixels immediately +above and below it in the adjacent rows of the image. +The following more complex example +creates a smoothed virtual image where each pixel +is a 3 x 3 boxcar average of the input image pixels: +- + [pix (X + X{-1} + X{+1} + + X{-#NAXIS1} + X{-#NAXIS1 - 1} + X{-#NAXIS1 + 1} + + X{#NAXIS1} + X{#NAXIS1 - 1} + X{#NAXIS1 + 1}) / 9.] +- +If the pixel offset +extends beyond the first or last pixel in the image, the function will +evaluate to undefined, or NULL. + +For complex or commonly used image filtering operations, +one can write the expression into an external text file and +then import it into the +filter using the syntax '[pix @filename.txt]'. The mathematical +expression can +extend over multiple lines of text in the file. +Any lines in the external text file +that begin with 2 slash characters ('//') will be ignored and may be +used to add comments into the file. + +By default, the datatype of the resulting image will be the same as +the original image, but one may force a different datatype by appended +a code letter to the 'pix' keyword: +- + pixb - 8-bit byte image with BITPIX = 8 + pixi - 16-bit integer image with BITPIX = 16 + pixj - 32-bit integer image with BITPIX = 32 + pixr - 32-bit float image with BITPIX = -32 + pixd - 64-bit float image with BITPIX = -64 +- +Also by default, any other HDUs in the input file will be copied without +change to the +output virtual FITS file, but one may discard the other HDUs by adding +the number '1' to the 'pix' keyword (and following any optional datatype code +letter). For example: +- + myfile.fits[3][pixr1 sqrt(X)] +- +will create a virtual FITS file containing only a primary array image +with 32-bit floating point pixels that have a value equal to the square +root of the pixels in the image that is in the 3rd extension +of the 'myfile.fits' file. + + + +**I. Column and Keyword Filtering Specification + +The optional column/keyword filtering specifier is used to modify the +column structure and/or the header keywords in the HDU that was +selected with the previous HDU location specifier. This filtering +specifier must be enclosed in square brackets and can be distinguished +from a general row filter specifier (described below) by the fact that +it begins with the string 'col ' and is not immediately followed by an +equals sign. The original file is not changed by this filtering +operation, and instead the modifications are made on a copy of the +input FITS file (usually in memory), which also contains a copy of all +the other HDUs in the file. This temporary file is passed to the +application program and will persist only until the file is closed or +until the program exits, unless the outfile specifier (see above) is +also supplied. + +The column/keyword filter can be used to perform the following +operations. More than one operation may be specified by separating +them with commas or semi-colons. + +\begin{itemize} + +\item +Copy only a specified list of columns columns to the filtered input file. +The list of column name should be separated by semi-colons. Wild card +characters may be used in the column names to match multiple columns. +If the expression contains both a list of columns to be included and +columns to be deleted, then all the columns in the original table +except the explicitly deleted columns will appear in the filtered +table (i.e., there is no need to explicitly list the columns to +be included if any columns are being deleted). + +\item +Delete a column or keyword by listing the name preceded by a minus +sign or an exclamation mark (!), e.g., '-TIME' will delete the TIME +column if it exists, otherwise the TIME keyword. An error is returned +if neither a column nor keyword with this name exists. Note that the +exclamation point, '!', is a special UNIX character, so if it is used +on the command line rather than entered at a task prompt, it must be +preceded by a backslash to force the UNIX shell to ignore it. + +\item +Rename an existing column or keyword with the syntax 'NewName == +OldName'. An error is returned if neither a column nor keyword with +this name exists. + +\item +Append a new column or keyword to the table. To create a column, +give the new name, optionally followed by the datatype in parentheses, +followed by a single equals sign and an expression to be used to +compute the value (e.g., 'newcol(1J) = 0' will create a new 32-bit +integer column called 'newcol' filled with zeros). The datatype is +specified using the same syntax that is allowed for the value of the +FITS TFORMn keyword (e.g., 'I', 'J', 'E', 'D', etc. for binary tables, +and 'I8', F12.3', 'E20.12', etc. for ASCII tables). If the datatype is +not specified then an appropriate datatype will be chosen depending on +the form of the expression (may be a character string, logical, bit, long +integer, or double column). An appropriate vector count (in the case +of binary tables) will also be added if not explicitly specified. + +When creating a new keyword, the keyword name must be preceded by a +pound sign '\#', and the expression must evaluate to a scalar +(i.e., cannot have a column name in the expression). The comment +string for the keyword may be specified in parentheses immediately +following the keyword name (instead of supplying a datatype as in +the case of creating a new column). If the keyword name ends with a +pound sign '\#', then cfitsio will substitute the number of the +most recently referenced column for the \# character . +This is especially useful when writing +a column-related keyword like TUNITn for a newly created column, +as shown in the following examples. + +\item +Recompute (overwrite) the values in an existing column or keyword by +giving the name followed by an equals sign and an arithmetic +expression. +\end{itemize} + +The expression that is used when appending or recomputing columns or +keywords can be arbitrarily complex and may be a function of other +header keyword values and other columns (in the same row). The full +syntax and available functions for the expression are described below +in the row filter specification section. + +If the expression contains both a list of columns to be included and +columns to be deleted, then all the columns in the original table +except the explicitly deleted columns will appear in the filtered +table. If no columns to be deleted are specified, then only the +columns that are explicitly listed will be included in the filtered +output table. To include all the columns, add the '*' wildcard +specifier at the end of the list, as shown in the examples. + +For complex or commonly used operations, one can also place the +operations into an external text file and import it into the column +filter using the syntax '[col @filename.txt]'. The operations can +extend over multiple lines of the file, but multiple operations must +still be separated by semicolons. Any lines in the external text file +that begin with 2 slash characters ('//') will be ignored and may be +used to add comments into the file. + +Examples: +- + [col Time; rate] - only the Time and rate columns will + appear in the filtered input file. + + [col Time, *raw] - include the Time column and any other + columns whose name ends with 'raw'. + + [col -TIME; Good == STATUS] - deletes the TIME column and + renames the status column to 'Good' + + [col PI=PHA * 1.1 + 0.2; #TUNIT#(column units) = 'counts';*] + - creates new PI column from PHA values + and also writes the TUNITn keyword + for the new column. The final '*' + expression means preserve all the + columns in the input table in the + virtual output table; without the '*' + the output table would only contain + the single 'PI' column. + + [col rate = rate/exposure; TUNIT#(&) = 'counts/s';*] + - recomputes the rate column by dividing + it by the EXPOSURE keyword value. This + also modifies the value of the TUNITn + keyword for this column. The use of the + '&' character for the keyword comment + string means preserve the existing + comment string for that keyword. The + final '*' preserves all the columns + in the input table in the virtual + output table. +- + +**J. Row Filtering Specification + + When entering the name of a FITS table that is to be opened by a + program, an optional row filter may be specified to select a subset + of the rows in the table. A temporary new FITS file is created on + the fly which contains only those rows for which the row filter + expression evaluates to true. (The primary array and any other + extensions in the input file are also copied to the temporary + file). The original FITS file is closed and the new virtual file + is opened by the application program. The row filter expression is + enclosed in square brackets following the file name and extension + name (e.g., 'file.fits[events][GRADE==50]' selects only those rows + where the GRADE column value equals 50). When dealing with tables + where each row has an associated time and/or 2D spatial position, + the row filter expression can also be used to select rows based on + the times in a Good Time Intervals (GTI) extension, or on spatial + position as given in a SAO-style region file. + +***1. General Syntax + + The row filtering expression can be an arbitrarily complex series + of operations performed on constants, keyword values, and column + data taken from the specified FITS TABLE extension. The expression + must evaluate to a boolean value for each row of the table, where + a value of FALSE means that the row will be excluded. + + For complex or commonly used filters, one can place the expression + into a text file and import it into the row filter using the syntax + '[@filename.txt]'. The expression can be arbitrarily complex and + extend over multiple lines of the file. Any lines in the external + text file that begin with 2 slash characters ('//') will be ignored + and may be used to add comments into the file. + + Keyword and column data are referenced by name. Any string of + characters not surrounded by quotes (ie, a constant string) or + followed by an open parentheses (ie, a function name) will be + initially interpreted as a column name and its contents for the + current row inserted into the expression. If no such column exists, + a keyword of that name will be searched for and its value used, if + found. To force the name to be interpreted as a keyword (in case + there is both a column and keyword with the same name), precede the + keyword name with a single pound sign, '\#', as in '\#NAXIS2'. Due to + the generalities of FITS column and keyword names, if the column or + keyword name contains a space or a character which might appear as + an arithmetic term then enclose the name in '\$' characters as in + \$MAX PHA\$ or \#\$MAX-PHA\$. Names are case insensitive. + + To access a table entry in a row other than the current one, follow + the column's name with a row offset within curly braces. For + example, 'PHA\{-3\}' will evaluate to the value of column PHA, 3 rows + above the row currently being processed. One cannot specify an + absolute row number, only a relative offset. Rows that fall outside + the table will be treated as undefined, or NULLs. + + Boolean operators can be used in the expression in either their + Fortran or C forms. The following boolean operators are available: +- + "equal" .eq. .EQ. == "not equal" .ne. .NE. != + "less than" .lt. .LT. < "less than/equal" .le. .LE. <= =< + "greater than" .gt. .GT. > "greater than/equal" .ge. .GE. >= => + "or" .or. .OR. || "and" .and. .AND. && + "negation" .not. .NOT. ! "approx. equal(1e-7)" ~ +- + +Note that the exclamation +point, '!', is a special UNIX character, so if it is used on the +command line rather than entered at a task prompt, it must be preceded +by a backslash to force the UNIX shell to ignore it. + + The expression may also include arithmetic operators and functions. + Trigonometric functions use radians, not degrees. The following + arithmetic operators and functions can be used in the expression + (function names are case insensitive). A null value will be returned + in case of illegal operations such as divide by zero, sqrt(negative) + log(negative), log10(negative), arccos(.gt. 1), arcsin(.gt. 1). + +- + "addition" + "subtraction" - + "multiplication" * "division" / + "negation" - "exponentiation" ** ^ + "absolute value" abs(x) "cosine" cos(x) + "sine" sin(x) "tangent" tan(x) + "arc cosine" arccos(x) "arc sine" arcsin(x) + "arc tangent" arctan(x) "arc tangent" arctan2(y,x) + "hyperbolic cos" cosh(x) "hyperbolic sin" sinh(x) + "hyperbolic tan" tanh(x) "round to nearest int" round(x) + "round down to int" floor(x) "round up to int" ceil(x) + "exponential" exp(x) "square root" sqrt(x) + "natural log" log(x) "common log" log10(x) + "modulus" x % y "random # [0.0,1.0)" random() + "random Gaussian" randomn() "random Poisson" randomp(x) + "minimum" min(x,y) "maximum" max(x,y) + "cumulative sum" accum(x) "sequential difference" seqdiff(x) + "if-then-else" b?x:y + "angular separation" angsep(ra1,dec1,ra2,de2) (all in degrees) + "substring" strmid(s,p,n) "string search" strstr(s,r) +- +Three different random number functions are provided: random(), with no +arguments, produces a uniform random deviate between 0 and 1; randomn(), +also with no arguments, produces a normal (Gaussian) random deviate with +zero mean and unit standard deviation; randomp(x) produces a Poisson random +deviate whose expected number of counts is X. X may be any positive real +number of expected counts, including fractional values, but the return value +is an integer. + +When the random functions are used in a vector expression, by default +the same random value will be used when evaluating each element of the vector. +If different random numbers are desired, then the name of a vector +column should be supplied as the single argument to the random +function (e.g., "flux + 0.1 * random(flux)", where "flux' is the +name of a vector column). This will create a vector of +random numbers that will be used in sequence when evaluating each +element of the vector expression. + + An alternate syntax for the min and max functions has only a single + argument which should be a vector value (see below). The result + will be the minimum/maximum element contained within the vector. + + The accum(x) function forms the cumulative sum of x, element by element. + Vector columns are supported simply by performing the summation process + through all the values. Null values are treated as 0. The seqdiff(x) + function forms the sequential difference of x, element by element. + The first value of seqdiff is the first value of x. A single null + value in x causes a pair of nulls in the output. The seqdiff and + accum functions are functional inverses, i.e., seqdiff(accum(x)) == x + as long as no null values are present. + +In the if-then-else expression, "b?x:y", b is an explicit boolean +value or expression. There is no automatic type conversion from +numeric to boolean values, so one needs to use "iVal!=0" instead of +merely "iVal" as the boolean argument. x and y can be any scalar data +type (including string). + + The angsep function computes the angular separation in degrees + between 2 celestial positions, where the first 2 parameters + give the RA-like and Dec-like coordinates (in decimal degrees) + of the first position, and the 3rd and 4th parameters give the + coordinates of the second position. + +The substring function strmid(S,P,N) extracts a substring from S, +starting at string position P, with a substring length N. The first +character position in S is labeled as 1. If P is 0, or refers to a +position beyond the end of S, then the extracted substring will be +NULL. S, P, and N may be functions of other columns. + +The string search function strstr(S,R) searches for the first occurrence +of the substring R in S. The result is an integer, indicating the +character position of the first match (where 1 is the first character +position of S). If no match is found, then strstr() returns a NULL +value. + + The following type casting operators are available, where the + enclosing parentheses are required and taken from the C language + usage. Also, the integer to real casts values to double precision: +- + "real to integer" (int) x (INT) x + "integer to real" (float) i (FLOAT) i +- + + In addition, several constants are built in for use in numerical + expressions: + +- + #pi 3.1415... #e 2.7182... + #deg #pi/180 #row current row number + #null undefined value #snull undefined string +- + + A string constant must be enclosed in quotes as in 'Crab'. The + "null" constants are useful for conditionally setting table values + to a NULL, or undefined, value (eg., "col1==-99 ? \#NULL : col1"). + + There is also a function for testing if two values are close to + each other, i.e., if they are "near" each other to within a user + specified tolerance. The arguments, value\_1 and value\_2 can be + integer or real and represent the two values who's proximity is + being tested to be within the specified tolerance, also an integer + or real: +- + near(value_1, value_2, tolerance) +- + When a NULL, or undefined, value is encountered in the FITS table, + the expression will evaluate to NULL unless the undefined value is + not actually required for evaluation, e.g. "TRUE .or. NULL" + evaluates to TRUE. The following two functions allow some NULL + detection and handling: +- + "a null value?" ISNULL(x) + "define a value for null" DEFNULL(x,y) +- + The former + returns a boolean value of TRUE if the argument x is NULL. The + later "defines" a value to be substituted for NULL values; it + returns the value of x if x is not NULL, otherwise it returns the + value of y. + +***2. Bit Masks + + Bit masks can be used to select out rows from bit columns (TFORMn = + \#X) in FITS files. To represent the mask, binary, octal, and hex + formats are allowed: + +- + binary: b0110xx1010000101xxxx0001 + octal: o720x1 -> (b111010000xxx001) + hex: h0FxD -> (b00001111xxxx1101) +- + + In all the representations, an x or X is allowed in the mask as a + wild card. Note that the x represents a different number of wild + card bits in each representation. All representations are case + insensitive. + + To construct the boolean expression using the mask as the boolean + equal operator described above on a bit table column. For example, + if you had a 7 bit column named flags in a FITS table and wanted + all rows having the bit pattern 0010011, the selection expression + would be: + +- + flags == b0010011 + or + flags .eq. b10011 +- + + It is also possible to test if a range of bits is less than, less + than equal, greater than and greater than equal to a particular + boolean value: + +- + flags <= bxxx010xx + flags .gt. bxxx100xx + flags .le. b1xxxxxxx +- + + Notice the use of the x bit value to limit the range of bits being + compared. + + It is not necessary to specify the leading (most significant) zero + (0) bits in the mask, as shown in the second expression above. + + Bit wise AND, OR and NOT operations are also possible on two or + more bit fields using the '\&'(AND), '$|$'(OR), and the '!'(NOT) + operators. All of these operators result in a bit field which can + then be used with the equal operator. For example: + +- + (!flags) == b1101100 + (flags & b1000001) == bx000001 +- + + Bit fields can be appended as well using the '+' operator. Strings + can be concatenated this way, too. + +***3. Vector Columns + + Vector columns can also be used in building the expression. No + special syntax is required if one wants to operate on all elements + of the vector. Simply use the column name as for a scalar column. + Vector columns can be freely intermixed with scalar columns or + constants in virtually all expressions. The result will be of the + same dimension as the vector. Two vectors in an expression, though, + need to have the same number of elements and have the same + dimensions. The only places a vector column cannot be used (for + now, anyway) are the SAO region functions and the NEAR boolean + function. + + Arithmetic and logical operations are all performed on an element by + element basis. Comparing two vector columns, eg "COL1 == COL2", + thus results in another vector of boolean values indicating which + elements of the two vectors are equal. + + Eight functions are available that operate on a vector and return a + scalar result: +- + "minimum" MIN(V) "maximum" MAX(V) + "average" AVERAGE(V) "median" MEDIAN(V) + "summation" SUM(V) "standard deviation" STDDEV(V) + "# of values" NELEM(V) "# of non-null values" NVALID(V) +- + where V represents the name of a vector column or a manually + constructed vector using curly brackets as described below. The + first 6 of these functions ignore any null values in the vector when + computing the result. The STDDEV() function computes the sample + standard deviation, i.e. it is proportional to 1/SQRT(N-1) instead + of 1/SQRT(N), where N is NVALID(V). + + The SUM function literally sums all the elements in x, returning a + scalar value. If x is a boolean vector, SUM returns the number + of TRUE elements. The NELEM function returns the number of elements + in vector x whereas NVALID return the number of non-null elements in + the vector. (NELEM also operates on bit and string columns, + returning their column widths.) As an example, to test whether all + elements of two vectors satisfy a given logical comparison, one can + use the expression +- + SUM( COL1 > COL2 ) == NELEM( COL1 ) +- + + which will return TRUE if all elements of COL1 are greater than + their corresponding elements in COL2. + + To specify a single element of a vector, give the column name + followed by a comma-separated list of coordinates enclosed in + square brackets. For example, if a vector column named PHAS exists + in the table as a one dimensional, 256 component list of numbers + from which you wanted to select the 57th component for use in the + expression, then PHAS[57] would do the trick. Higher dimensional + arrays of data may appear in a column. But in order to interpret + them, the TDIMn keyword must appear in the header. Assuming that a + (4,4,4,4) array is packed into each row of a column named ARRAY4D, + the (1,2,3,4) component element of each row is accessed by + ARRAY4D[1,2,3,4]. Arrays up to dimension 5 are currently + supported. Each vector index can itself be an expression, although + it must evaluate to an integer value within the bounds of the + vector. Vector columns which contain spaces or arithmetic operators + must have their names enclosed in "\$" characters as with + \$ARRAY-4D\$[1,2,3,4]. + + A more C-like syntax for specifying vector indices is also + available. The element used in the preceding example alternatively + could be specified with the syntax ARRAY4D[4][3][2][1]. Note the + reverse order of indices (as in C), as well as the fact that the + values are still ones-based (as in Fortran -- adopted to avoid + ambiguity for 1D vectors). With this syntax, one does not need to + specify all of the indices. To extract a 3D slice of this 4D + array, use ARRAY4D[4]. + + Variable-length vector columns are not supported. + + Vectors can be manually constructed within the expression using a + comma-separated list of elements surrounded by curly braces ('\{\}'). + For example, '\{1,3,6,1\}' is a 4-element vector containing the values + 1, 3, 6, and 1. The vector can contain only boolean, integer, and + real values (or expressions). The elements will be promoted to the + highest datatype present. Any elements which are themselves + vectors, will be expanded out with each of its elements becoming an + element in the constructed vector. + +***4. Good Time Interval Filtering + + A common filtering method involves selecting rows which have a time + value which lies within what is called a Good Time Interval or GTI. + The time intervals are defined in a separate FITS table extension + which contains 2 columns giving the start and stop time of each + good interval. The filtering operation accepts only those rows of + the input table which have an associated time which falls within + one of the time intervals defined in the GTI extension. A high + level function, gtifilter(a,b,c,d), is available which evaluates + each row of the input table and returns TRUE or FALSE depending + whether the row is inside or outside the good time interval. The + syntax is +- + gtifilter( [ "gtifile" [, expr [, "STARTCOL", "STOPCOL" ] ] ] ) +- + where each "[]" demarks optional parameters. Note that the quotes + around the gtifile and START/STOP column are required. Either single + or double quotes may be used. In cases where this expression is + entered on the Unix command line, enclose the entire expression in + double quotes, and then use single quotes within the expression to + enclose the 'gtifile' and other terms. It is also usually possible + to do the reverse, and enclose the whole expression in single quotes + and then use double quotes within the expression. The gtifile, + if specified, can be blank ("") which will mean to use the first + extension with the name "*GTI*" in the current file, a plain + extension specifier (eg, "+2", "[2]", or "[STDGTI]") which will be + used to select an extension in the current file, or a regular + filename with or without an extension specifier which in the latter + case will mean to use the first extension with an extension name + "*GTI*". Expr can be any arithmetic expression, including simply + the time column name. A vector time expression will produce a + vector boolean result. STARTCOL and STOPCOL are the names of the + START/STOP columns in the GTI extension. If one of them is + specified, they both must be. + + In its simplest form, no parameters need to be provided -- default + values will be used. The expression "gtifilter()" is equivalent to +- + gtifilter( "", TIME, "*START*", "*STOP*" ) +- + This will search the current file for a GTI extension, filter the + TIME column in the current table, using START/STOP times taken from + columns in the GTI extension with names containing the strings + "START" and "STOP". The wildcards ('*') allow slight variations in + naming conventions such as "TSTART" or "STARTTIME". The same + default values apply for unspecified parameters when the first one + or two parameters are specified. The function automatically + searches for TIMEZERO/I/F keywords in the current and GTI + extensions, applying a relative time offset, if necessary. + +***5. Spatial Region Filtering + + Another common filtering method selects rows based on whether the + spatial position associated with each row is located within a given + 2-dimensional region. The syntax for this high-level filter is +- + regfilter( "regfilename" [ , Xexpr, Yexpr [ , "wcs cols" ] ] ) +- + where each "[]" demarks optional parameters. The region file name + is required and must be enclosed in quotes. The remaining + parameters are optional. There are 2 supported formats for the + region file: ASCII file or FITS binary table. The region file + contains a list of one or more geometric shapes (circle, + ellipse, box, etc.) which defines a region on the celestial sphere + or an area within a particular 2D image. The region file is + typically generated using an image display program such as fv/POW + (distribute by the HEASARC), or ds9 (distributed by the Smithsonian + Astrophysical Observatory). Users should refer to the documentation + provided with these programs for more details on the syntax used in + the region files. The FITS region file format is defined in a document + available from the FITS Support Office at + http://fits.gsfc.nasa.gov/ registry/ region.html + + In its simplest form, (e.g., regfilter("region.reg") ) the + coordinates in the default 'X' and 'Y' columns will be used to + determine if each row is inside or outside the area specified in + the region file. Alternate position column names, or expressions, + may be entered if needed, as in +- + regfilter("region.reg", XPOS, YPOS) +- + Region filtering can be applied most unambiguously if the positions + in the region file and in the table to be filtered are both give in + terms of absolute celestial coordinate units. In this case the + locations and sizes of the geometric shapes in the region file are + specified in angular units on the sky (e.g., positions given in + R.A. and Dec. and sizes in arcseconds or arcminutes). Similarly, + each row of the filtered table will have a celestial coordinate + associated with it. This association is usually implemented using + a set of so-called 'World Coordinate System' (or WCS) FITS keywords + that define the coordinate transformation that must be applied to + the values in the 'X' and 'Y' columns to calculate the coordinate. + + Alternatively, one can perform spatial filtering using unitless + 'pixel' coordinates for the regions and row positions. In this + case the user must be careful to ensure that the positions in the 2 + files are self-consistent. A typical problem is that the region + file may be generated using a binned image, but the unbinned + coordinates are given in the event table. The ROSAT events files, + for example, have X and Y pixel coordinates that range from 1 - + 15360. These coordinates are typically binned by a factor of 32 to + produce a 480x480 pixel image. If one then uses a region file + generated from this image (in image pixel units) to filter the + ROSAT events file, then the X and Y column values must be converted + to corresponding pixel units as in: +- + regfilter("rosat.reg", X/32.+.5, Y/32.+.5) +- + Note that this binning conversion is not necessary if the region + file is specified using celestial coordinate units instead of pixel + units because CFITSIO is then able to directly compare the + celestial coordinate of each row in the table with the celestial + coordinates in the region file without having to know anything + about how the image may have been binned. + + The last "wcs cols" parameter should rarely be needed. If supplied, + this string contains the names of the 2 columns (space or comma + separated) which have the associated WCS keywords. If not supplied, + the filter will scan the X and Y expressions for column names. + If only one is found in each expression, those columns will be + used, otherwise an error will be returned. + + These region shapes are supported (names are case insensitive): +- + Point ( X1, Y1 ) <- One pixel square region + Line ( X1, Y1, X2, Y2 ) <- One pixel wide region + Polygon ( X1, Y1, X2, Y2, ... ) <- Rest are interiors with + Rectangle ( X1, Y1, X2, Y2, A ) | boundaries considered + Box ( Xc, Yc, Wdth, Hght, A ) V within the region + Diamond ( Xc, Yc, Wdth, Hght, A ) + Circle ( Xc, Yc, R ) + Annulus ( Xc, Yc, Rin, Rout ) + Ellipse ( Xc, Yc, Rx, Ry, A ) + Elliptannulus ( Xc, Yc, Rinx, Riny, Routx, Routy, Ain, Aout ) + Sector ( Xc, Yc, Amin, Amax ) +- + where (Xc,Yc) is the coordinate of the shape's center; (X\#,Y\#) are + the coordinates of the shape's edges; Rxxx are the shapes' various + Radii or semi-major/minor axes; and Axxx are the angles of rotation + (or bounding angles for Sector) in degrees. For rotated shapes, the + rotation angle can be left off, indicating no rotation. Common + alternate names for the regions can also be used: rotbox = box; + rotrectangle = rectangle; (rot)rhombus = (rot)diamond; and pie + = sector. When a shape's name is preceded by a minus sign, '-', + the defined region is instead the area *outside* its boundary (ie, + the region is inverted). All the shapes within a single region + file are OR'd together to create the region, and the order is + significant. The overall way of looking at region files is that if + the first region is an excluded region then a dummy included region + of the whole detector is inserted in the front. Then each region + specification as it is processed overrides any selections inside of + that region specified by previous regions. Another way of thinking + about this is that if a previous excluded region is completely + inside of a subsequent included region the excluded region is + ignored. + + The positional coordinates may be given either in pixel units, + decimal degrees or hh:mm:ss.s, dd:mm:ss.s units. The shape sizes + may be given in pixels, degrees, arcminutes, or arcseconds. Look + at examples of region file produced by fv/POW or ds9 for further + details of the region file format. + + There are three functions that are primarily for use with SAO region + files and the FSAOI task, but they can be used directly. They + return a boolean true or false depending on whether a two + dimensional point is in the region or not: +- + "point in a circular region" + circle(xcntr,ycntr,radius,Xcolumn,Ycolumn) + + "point in an elliptical region" + ellipse(xcntr,ycntr,xhlf_wdth,yhlf_wdth,rotation,Xcolumn,Ycolumn) + + "point in a rectangular region" + box(xcntr,ycntr,xfll_wdth,yfll_wdth,rotation,Xcolumn,Ycolumn) + + where + (xcntr,ycntr) are the (x,y) position of the center of the region + (xhlf_wdth,yhlf_wdth) are the (x,y) half widths of the region + (xfll_wdth,yfll_wdth) are the (x,y) full widths of the region + (radius) is half the diameter of the circle + (rotation) is the angle(degrees) that the region is rotated with + respect to (xcntr,ycntr) + (Xcoord,Ycoord) are the (x,y) coordinates to test, usually column + names + NOTE: each parameter can itself be an expression, not merely a + column name or constant. +- + +***5. Example Row Filters +- + [ binary && mag <= 5.0] - Extract all binary stars brighter + than fifth magnitude (note that + the initial space is necessary to + prevent it from being treated as a + binning specification) + + [#row >= 125 && #row <= 175] - Extract row numbers 125 through 175 + + [IMAGE[4,5] .gt. 100] - Extract all rows that have the + (4,5) component of the IMAGE column + greater than 100 + + [abs(sin(theta * #deg)) < 0.5] - Extract all rows having the + absolute value of the sine of theta + less than a half where the angles + are tabulated in degrees + + [SUM( SPEC > 3*BACKGRND )>=1] - Extract all rows containing a + spectrum, held in vector column + SPEC, with at least one value 3 + times greater than the background + level held in a keyword, BACKGRND + + [VCOL=={1,4,2}] - Extract all rows whose vector column + VCOL contains the 3-elements 1, 4, and + 2. + + [@rowFilter.txt] - Extract rows using the expression + contained within the text file + rowFilter.txt + + [gtifilter()] - Search the current file for a GTI + extension, filter the TIME + column in the current table, using + START/STOP times taken from + columns in the GTI extension + + [regfilter("pow.reg")] - Extract rows which have a coordinate + (as given in the X and Y columns) + within the spatial region specified + in the pow.reg region file. + + [regfilter("pow.reg", Xs, Ys)] - Same as above, except that the + Xs and Ys columns will be used to + determine the coordinate of each + row in the table. +- + +**K. Binning or Histogramming Specification + +The optional binning specifier is enclosed in square brackets and can +be distinguished from a general row filter specification by the fact +that it begins with the keyword 'bin' not immediately followed by an +equals sign. When binning is specified, a temporary N-dimensional FITS +primary array is created by computing the histogram of the values in +the specified columns of a FITS table extension. After the histogram +is computed the input FITS file containing the table is then closed and +the temporary FITS primary array is opened and passed to the +application program. Thus, the application program never sees the +original FITS table and only sees the image in the new temporary file +(which has no additional extensions). Obviously, the application +program must be expecting to open a FITS image and not a FITS table in +this case. + +The data type of the FITS histogram image may be specified by appending +'b' (for 8-bit byte), 'i' (for 16-bit integers), 'j' (for 32-bit +integer), 'r' (for 32-bit floating points), or 'd' (for 64-bit double +precision floating point) to the 'bin' keyword (e.g. '[binr X]' +creates a real floating point image). If the datatype is not +explicitly specified then a 32-bit integer image will be created by +default, unless the weighting option is also specified in which case +the image will have a 32-bit floating point data type by default. + +The histogram image may have from 1 to 4 dimensions (axes), depending +on the number of columns that are specified. The general form of the +binning specification is: +- + [bin{bijrd} Xcol=min:max:binsize, Ycol= ..., Zcol=..., Tcol=...; weight] +- +in which up to 4 columns, each corresponding to an axis of the image, +are listed. The column names are case insensitive, and the column +number may be given instead of the name, preceded by a pound sign +(e.g., [bin \#4=1:512]). If the column name is not specified, then +CFITSIO will first try to use the 'preferred column' as specified by +the CPREF keyword if it exists (e.g., 'CPREF = 'DETX,DETY'), otherwise +column names 'X', 'Y', 'Z', and 'T' will be assumed for each of the 4 +axes, respectively. In cases where the column name could be confused +with an arithmetic expression, enclose the column name in parentheses to +force the name to be interpreted literally. + +Each column name may be followed by an equals sign and then the lower +and upper range of the histogram, and the size of the histogram bins, +separated by colons. Spaces are allowed before and after the equals +sign but not within the 'min:max:binsize' string. The min, max and +binsize values may be integer or floating point numbers, or they may be +the names of keywords in the header of the table. If the latter, then +the value of that keyword is substituted into the expression. + +Default values for the min, max and binsize quantities will be +used if not explicitly given in the binning expression as shown +in these examples: +- + [bin x = :512:2] - use default minimum value + [bin x = 1::2] - use default maximum value + [bin x = 1:512] - use default bin size + [bin x = 1:] - use default maximum value and bin size + [bin x = :512] - use default minimum value and bin size + [bin x = 2] - use default minimum and maximum values + [bin x] - use default minimum, maximum and bin size + [bin 4] - default 2-D image, bin size = 4 in both axes + [bin] - default 2-D image +- +CFITSIO will use the value of the TLMINn, TLMAXn, and TDBINn keywords, +if they exist, for the default min, max, and binsize, respectively. If +they do not exist then CFITSIO will use the actual minimum and maximum +values in the column for the histogram min and max values. The default +binsize will be set to 1, or (max - min) / 10., whichever is smaller, +so that the histogram will have at least 10 bins along each axis. + +A shortcut notation is allowed if all the columns/axes have the same +binning specification. In this case all the column names may be listed +within parentheses, followed by the (single) binning specification, as +in: +- + [bin (X,Y)=1:512:2] + [bin (X,Y) = 5] +- + +The optional weighting factor is the last item in the binning specifier +and, if present, is separated from the list of columns by a +semi-colon. As the histogram is accumulated, this weight is used to +incremented the value of the appropriated bin in the histogram. If the +weighting factor is not specified, then the default weight = 1 is +assumed. The weighting factor may be a constant integer or floating +point number, or the name of a keyword containing the weighting value. +Or the weighting factor may be the name of a table column in which case +the value in that column, on a row by row basis, will be used. + +In some cases, the column or keyword may give the reciprocal of the +actual weight value that is needed. In this case, precede the weight +keyword or column name by a slash '/' to tell CFITSIO to use the +reciprocal of the value when constructing the histogram. + +For complex or commonly used histograms, one can also place its +description into a text file and import it into the binning +specification using the syntax '[bin @filename.txt]'. The file's +contents can extend over multiple lines, although it must still +conform to the no-spaces rule for the min:max:binsize syntax and each +axis specification must still be comma-separated. Any lines in the +external text file that begin with 2 slash characters ('//') will be +ignored and may be used to add comments into the file. + + Examples: + +- + [bini detx, dety] - 2-D, 16-bit integer histogram + of DETX and DETY columns, using + default values for the histogram + range and binsize + + [bin (detx, dety)=16; /exposure] - 2-D, 32-bit real histogram of DETX + and DETY columns with a bin size = 16 + in both axes. The histogram values + are divided by the EXPOSURE keyword + value. + + [bin time=TSTART:TSTOP:0.1] - 1-D lightcurve, range determined by + the TSTART and TSTOP keywords, + with 0.1 unit size bins. + + [bin pha, time=8000.:8100.:0.1] - 2-D image using default binning + of the PHA column for the X axis, + and 1000 bins in the range + 8000. to 8100. for the Y axis. + + [bin @binFilter.txt] - Use the contents of the text file + binFilter.txt for the binning + specifications. + +- + + +*V. Template Files + +When a new FITS file is created with a call to fits\_create\_file, the +name of a template file may be supplied in parentheses immediately +following the name of the new file to be created. This template is +used to define the structure of one or more HDUs in the new file. The +template file may be another FITS file, in which case the newly created +file will have exactly the same keywords in each HDU as in the template +FITS file, but all the data units will be filled with zeros. The +template file may also be an ASCII text file, where each line (in +general) describes one FITS keyword record. The format of the ASCII +template file is described in the following sections. + +**A Detailed Template Line Format + +The format of each ASCII template line closely follows the format of a +FITS keyword record: +- + KEYWORD = KEYVALUE / COMMENT +- +except that free format may be used (e.g., the equals sign may appear +at any position in the line) and TAB characters are allowed and are +treated the same as space characters. The KEYVALUE and COMMENT fields +are optional. The equals sign character is also optional, but it is +recommended that it be included for clarity. Any template line that +begins with the pound '\#' character is ignored by the template parser +and may be use to insert comments into the template file itself. + +The KEYWORD name field is limited to 8 characters in length and only +the letters A-Z, digits 0-9, and the hyphen and underscore characters +may be used, without any embedded spaces. Lowercase letters in the +template keyword name will be converted to uppercase. Leading spaces +in the template line preceding the keyword name are generally ignored, +except if the first 8 characters of a template line are all blank, then +the entire line is treated as a FITS comment keyword (with a blank +keyword name) and is copied verbatim into the FITS header. + +The KEYVALUE field may have any allowed FITS data type: character +string, logical, integer, real, complex integer, or complex real. The +character string values need not be enclosed in single quote characters +unless they are necessary to distinguish the string from a different +data type (e.g. 2.0 is a real but '2.0' is a string). The keyword has +an undefined (null) value if the template record only contains blanks +following the "=" or between the "=" and the "/" comment field +delimiter. + +String keyword values longer than 68 characters (the maximum length +that will fit in a single FITS keyword record) are permitted using the +CFITSIO long string convention. They can either be specified as a +single long line in the template, or by using multiple lines where the +continuing lines contain the 'CONTINUE' keyword, as in this example: +- + LONGKEY = 'This is a long string value that is contin&' + CONTINUE 'ued over 2 records' / comment field goes here +- +The format of template lines with CONTINUE keyword is very strict: 3 +spaces must follow CONTINUE and the rest of the line is copied verbatim +to the FITS file. + +The start of the optional COMMENT field must be preceded by "/", which +is used to separate it from the keyword value field. Exceptions are if +the KEYWORD name field contains COMMENT, HISTORY, CONTINUE, or if the +first 8 characters of the template line are blanks. + +More than one Header-Data Unit (HDU) may be defined in the template +file. The start of an HDU definition is denoted with a SIMPLE or +XTENSION template line: + +1) SIMPLE begins a Primary HDU definition. SIMPLE may only appear as +the first keyword in the template file. If the template file begins +with XTENSION instead of SIMPLE, then a default empty Primary HDU is +created, and the template is then assumed to define the keywords +starting with the first extension following the Primary HDU. + +2) XTENSION marks the beginning of a new extension HDU definition. The +previous HDU will be closed at this point and processing of the next +extension begins. + +**B Auto-indexing of Keywords + +If a template keyword name ends with a "\#" character, it is said to be +'auto-indexed'. Each "\#" character will be replaced by the current +integer index value, which gets reset = 1 at the start of each new HDU +in the file (or 7 in the special case of a GROUP definition). The +FIRST indexed keyword in each template HDU definition is used as the +'incrementor'; each subsequent occurrence of this SAME keyword will +cause the index value to be incremented. This behavior can be rather +subtle, as illustrated in the following examples in which the TTYPE +keyword is the incrementor in both cases: +- + TTYPE# = TIME + TFORM# = 1D + TTYPE# = RATE + TFORM# = 1E +- +will create TTYPE1, TFORM1, TTYPE2, and TFORM2 keywords. But if the +template looks like, +- + TTYPE# = TIME + TTYPE# = RATE + TFORM# = 1D + TFORM# = 1E +- +this results in a FITS files with TTYPE1, TTYPE2, TFORM2, and TFORM2, +which is probably not what was intended! + +**C Template Parser Directives + +In addition to the template lines which define individual keywords, the +template parser recognizes 3 special directives which are each preceded +by the backslash character: \verb+ \include, \group+, and \verb+ \end+. + +The 'include' directive must be followed by a filename. It forces the +parser to temporarily stop reading the current template file and begin +reading the include file. Once the parser reaches the end of the +include file it continues parsing the current template file. Include +files can be nested, and HDU definitions can span multiple template +files. + +The start of a GROUP definition is denoted with the 'group' directive, +and the end of a GROUP definition is denoted with the 'end' directive. +Each GROUP contains 0 or more member blocks (HDUs or GROUPs). Member +blocks of type GROUP can contain their own member blocks. The GROUP +definition itself occupies one FITS file HDU of special type (GROUP +HDU), so if a template specifies 1 group with 1 member HDU like: +- +\group +grpdescr = 'demo' +xtension bintable +# this bintable has 0 cols, 0 rows +\end +- +then the parser creates a FITS file with 3 HDUs : +- +1) dummy PHDU +2) GROUP HDU (has 1 member, which is bintable in HDU number 3) +3) bintable (member of GROUP in HDU number 2) +- +Technically speaking, the GROUP HDU is a BINTABLE with 6 columns. Applications +can define additional columns in a GROUP HDU using TFORMn and TTYPEn +(where n is 7, 8, ....) keywords or their auto-indexing equivalents. + +For a more complicated example of a template file using the group directives, +look at the sample.tpl file that is included in the CFITSIO distribution. + +**D Formal Template Syntax + +The template syntax can formally be defined as follows: +- + TEMPLATE = BLOCK [ BLOCK ... ] + + BLOCK = { HDU | GROUP } + + GROUP = \GROUP [ BLOCK ... ] \END + + HDU = XTENSION [ LINE ... ] { XTENSION | \GROUP | \END | EOF } + + LINE = [ KEYWORD [ = ] ] [ VALUE ] [ / COMMENT ] + + X ... - X can be present 1 or more times + { X | Y } - X or Y + [ X ] - X is optional +- + +At the topmost level, the template defines 1 or more template blocks. Blocks +can be either HDU (Header Data Unit) or a GROUP. For each block the parser +creates 1 (or more for GROUPs) FITS file HDUs. + + +**E Errors + +In general the fits\_execute\_template() function tries to be as atomic +as possible, so either everything is done or nothing is done. If an +error occurs during parsing of the template, fits\_execute\_template() +will (try to) delete the top level BLOCK (with all its children if any) +in which the error occurred, then it will stop reading the template file +and it will return with an error. + +**F Examples + +1. This template file will create a 200 x 300 pixel image, with 4-byte +integer pixel values, in the primary HDU: +- + SIMPLE = T + BITPIX = 32 + NAXIS = 2 / number of dimensions + NAXIS1 = 100 / length of first axis + NAXIS2 = 200 / length of second axis + OBJECT = NGC 253 / name of observed object +- +The allowed values of BITPIX are 8, 16, 32, -32, or -64, +representing, respectively, 8-bit integer, 16-bit integer, 32-bit +integer, 32-bit floating point, or 64 bit floating point pixels. + +2. To create a FITS table, the template first needs to include +XTENSION = TABLE or BINTABLE to define whether it is an ASCII or binary +table, and NAXIS2 to define the number of rows in the table. Two +template lines are then needed to define the name (TTYPEn) and FITS data +format (TFORMn) of the columns, as in this example: +- + xtension = bintable + naxis2 = 40 + ttype# = Name + tform# = 10a + ttype# = Npoints + tform# = j + ttype# = Rate + tunit# = counts/s + tform# = e +- +The above example defines a null primary array followed by a 40-row +binary table extension with 3 columns called 'Name', 'Npoints', and +'Rate', with data formats of '10A' (ASCII character string), '1J' +(integer) and '1E' (floating point), respectively. Note that the other +required FITS keywords (BITPIX, NAXIS, NAXIS1, PCOUNT, GCOUNT, TFIELDS, +and END) do not need to be explicitly defined in the template because +their values can be inferred from the other keywords in the template. +This example also illustrates that the templates are generally +case-insensitive (the keyword names and TFORMn values are converted to +upper-case in the FITS file) and that string keyword values generally +do not need to be enclosed in quotes. + + +*IX Summary of all FITSIO User-Interface Subroutines + + Error Status Routines page~\pageref{FTVERS} +- + FTVERS( > version) + FTGERR(status, > errtext) + FTGMSG( > errmsg) + FTRPRT (stream, > status) + FTPMSG(errmsg) + FTPMRK + FTCMSG + FTCMRK +- + FITS File Open and Close Subroutines: page~\pageref{FTOPEN} +- + FTOPEN(unit,filename,rwmode, > blocksize,status) + FTDKOPEN(unit,filename,rwmode, > blocksize,status) + FTNOPN(unit,filename,rwmode, > status) + FTDOPN(unit,filename,rwmode, > status) + FTTOPN(unit,filename,rwmode, > status) + FTIOPN(unit,filename,rwmode, > status) + FTREOPEN(unit, > newunit, status) + FTINIT(unit,filename,blocksize, > status) + FTDKINIT(unit,filename,blocksize, > status) + FTTPLT(unit, filename, tplfilename, > status) + FTFLUS(unit, > status) + FTCLOS(unit, > status) + FTDELT(unit, > status) + FTGIOU( > iounit, status) + FTFIOU(iounit, > status) + CFITS2Unit(fitsfile *ptr) (C routine) + CUnit2FITS(int unit) (C routine) + FTEXTN(filename, > nhdu, status) + FTFLNM(unit, > filename, status) + FTFLMD(unit, > iomode, status) + FFURLT(unit, > urltype, status) + FTIURL(filename, > filetype, infile, outfile, extspec, filter, + binspec, colspec, status) + FTRTNM(filename, > rootname, status) + FTEXIST(filename, > exist, status) +- + HDU-Level Operations: page~\pageref{FTMAHD} +- + FTMAHD(unit,nhdu, > hdutype,status) + FTMRHD(unit,nmove, > hdutype,status) + FTGHDN(unit, > nhdu) + FTMNHD(unit, hdutype, extname, extver, > status) + FTGHDT(unit, > hdutype, status) + FTTHDU(unit, > hdunum, status) + FTCRHD(unit, > status) + FTIIMG(unit,bitpix,naxis,naxes, > status) + FTITAB(unit,rowlen,nrows,tfields,ttype,tbcol,tform,tunit,extname, > + status) + FTIBIN(unit,nrows,tfields,ttype,tform,tunit,extname,varidat > status) + FTRSIM(unit,bitpix,naxis,naxes,status) + FTDHDU(unit, > hdutype,status) + FTCPFL(iunit,ounit,previous, current, following, > status) + FTCOPY(iunit,ounit,morekeys, > status) + FTCPHD(inunit, outunit, > status) + FTCPDT(iunit,ounit, > status) +- + Subroutines to specify or modify the structure of the CHDU: page~\pageref{FTRDEF} +- + FTRDEF(unit, > status) (DEPRECATED) + FTPDEF(unit,bitpix,naxis,naxes,pcount,gcount, > status) (DEPRECATED) + FTADEF(unit,rowlen,tfields,tbcol,tform,nrows > status) (DEPRECATED) + FTBDEF(unit,tfields,tform,varidat,nrows > status) (DEPRECATED) + FTDDEF(unit,bytlen, > status) (DEPRECATED) + FTPTHP(unit,theap, > status) +- + Header Space and Position Subroutines: page~\pageref{FTHDEF} +- + FTHDEF(unit,morekeys, > status) + FTGHSP(iunit, > keysexist,keysadd,status) + FTGHPS(iunit, > keysexist,key_no,status) +- + Read or Write Standard Header Subroutines: page~\pageref{FTPHPR} +- + FTPHPS(unit,bitpix,naxis,naxes, > status) + FTPHPR(unit,simple,bitpix,naxis,naxes,pcount,gcount,extend, > status) + FTGHPR(unit,maxdim, > simple,bitpix,naxis,naxes,pcount,gcount,extend, + status) + FTPHTB(unit,rowlen,nrows,tfields,ttype,tbcol,tform,tunit,extname, > + status) + FTGHTB(unit,maxdim, > rowlen,nrows,tfields,ttype,tbcol,tform,tunit, + extname,status) + FTPHBN(unit,nrows,tfields,ttype,tform,tunit,extname,varidat > status) + FTGHBN(unit,maxdim, > nrows,tfields,ttype,tform,tunit,extname,varidat, + status) +- + Write Keyword Subroutines: page~\pageref{FTPREC} +- + FTPREC(unit,card, > status) + FTPCOM(unit,comment, > status) + FTPHIS(unit,history, > status) + FTPDAT(unit, > status) + FTPKY[JKLS](unit,keyword,keyval,comment, > status) + FTPKY[EDFG](unit,keyword,keyval,decimals,comment, > status) + FTPKLS(unit,keyword,keyval,comment, > status) + FTPLSW(unit, > status) + FTPKYU(unit,keyword,comment, > status) + FTPKN[JKLS](unit,keyroot,startno,no_keys,keyvals,comments, > status) + FTPKN[EDFG](unit,keyroot,startno,no_keys,keyvals,decimals,comments, > + status) + FTCPKYinunit, outunit, innum, outnum, keyroot, > status) + FTPKYT(unit,keyword,intval,dblval,comment, > status) + FTPKTP(unit, filename, > status) + FTPUNT(unit,keyword,units, > status) +- + Insert Keyword Subroutines: page~\pageref{FTIREC} +- + FTIREC(unit,key_no,card, > status) + FTIKY[JKLS](unit,keyword,keyval,comment, > status) + FTIKLS(unit,keyword,keyval,comment, > status) + FTIKY[EDFG](unit,keyword,keyval,decimals,comment, > status) + FTIKYU(unit,keyword,comment, > status) +- + Read Keyword Subroutines: page~\pageref{FTGREC} +- + FTGREC(unit,key_no, > card,status) + FTGKYN(unit,key_no, > keyword,value,comment,status) + FTGCRD(unit,keyword, > card,status) + FTGNXK(unit,inclist,ninc,exclist,nexc, > card,status) + FTGKEY(unit,keyword, > value,comment,status) + FTGKY[EDJKLS](unit,keyword, > keyval,comment,status) + FTGKN[EDJKLS](unit,keyroot,startno,max_keys, > keyvals,nfound,status) + FTGKYT(unit,keyword, > intval,dblval,comment,status) + FTGUNT(unit,keyword, > units,status) +- + Modify Keyword Subroutines: page~\pageref{FTMREC} +- + FTMREC(unit,key_no,card, > status) + FTMCRD(unit,keyword,card, > status) + FTMNAM(unit,oldkey,keyword, > status) + FTMCOM(unit,keyword,comment, > status) + FTMKY[JKLS](unit,keyword,keyval,comment, > status) + FTMKLS(unit,keyword,keyval,comment, > status) + FTMKY[EDFG](unit,keyword,keyval,decimals,comment, > status) + FTMKYU(unit,keyword,comment, > status) +- + Update Keyword Subroutines: page~\pageref{FTUCRD} +- + FTUCRD(unit,keyword,card, > status) + FTUKY[JKLS](unit,keyword,keyval,comment, > status) + FTUKLS(unit,keyword,keyval,comment, > status) + FTUKY[EDFG](unit,keyword,keyval,decimals,comment, > status) + FTUKYU(unit,keyword,comment, > status) +- + Delete Keyword Subroutines: page~\pageref{FTDREC} +- + FTDREC(unit,key_no, > status) + FTDKEY(unit,keyword, > status) +- + Define Data Scaling Parameters and Undefined Pixel Flags: page~\pageref{FTPSCL} +- + FTPSCL(unit,bscale,bzero, > status) + FTTSCL(unit,colnum,tscal,tzero, > status) + FTPNUL(unit,blank, > status) + FTSNUL(unit,colnum,snull > status) + FTTNUL(unit,colnum,tnull > status) +- + FITS Primary Array or IMAGE Extension I/O Subroutines: page~\pageref{FTPPR} +- + FTGIDT(unit, > bitpix,status) + FTGIET(unit, > bitpix,status) + FTGIDM(unit, > naxis,status) + FTGISZ(unit, maxdim, > naxes,status) + FTGIPR(unit, maxdim, > bitpix,naxis,naxes,status) + FTPPR[BIJKED](unit,group,fpixel,nelements,values, > status) + FTPPN[BIJKED](unit,group,fpixel,nelements,values,nullval > status) + FTPPRU(unit,group,fpixel,nelements, > status) + FTGPV[BIJKED](unit,group,fpixel,nelements,nullval, > values,anyf,status) + FTGPF[BIJKED](unit,group,fpixel,nelements, > values,flagvals,anyf,status) + FTPGP[BIJKED](unit,group,fparm,nparm,values, > status) + FTGGP[BIJKED](unit,group,fparm,nparm, > values,status) + FTP2D[BIJKED](unit,group,dim1,naxis1,naxis2,image, > status) + FTP3D[BIJKED](unit,group,dim1,dim2,naxis1,naxis2,naxis3,cube, > status) + FTG2D[BIJKED](unit,group,nullval,dim1,naxis1,naxis2, > image,anyf,status) + FTG3D[BIJKED](unit,group,nullval,dim1,dim2,naxis1,naxis2,naxis3, > + cube,anyf,status) + FTPSS[BIJKED](unit,group,naxis,naxes,fpixels,lpixels,array, > status) + FTGSV[BIJKED](unit,group,naxis,naxes,fpixels,lpixels,incs,nullval, > + array,anyf,status) + FTGSF[BIJKED](unit,group,naxis,naxes,fpixels,lpixels,incs, > + array,flagvals,anyf,status) +- + Table Column Information Subroutines: page~\pageref{FTGCNO} +- + FTGNRW(unit, > nrows, status) + FTGNCL(unit, > ncols, status) + FTGCNO(unit,casesen,coltemplate, > colnum,status) + FTGCNN(unit,casesen,coltemplate, > colnam,colnum,status) + FTGTCL(unit,colnum, > datacode,repeat,width,status) + FTEQTY(unit,colnum, > datacode,repeat,width,status) + FTGCDW(unit,colnum, > dispwidth,status) + FTGACL(unit,colnum, > + ttype,tbcol,tunit,tform,tscal,tzero,snull,tdisp,status) + FTGBCL(unit,colnum, > + ttype,tunit,datatype,repeat,tscal,tzero,tnull,tdisp,status) + FTPTDM(unit,colnum,naxis,naxes, > status) + FTGTDM(unit,colnum,maxdim, > naxis,naxes,status) + FTDTDM(unit,tdimstr,colnum,maxdim, > naxis,naxes, status) + FFGRSZ(unit, > nrows,status) +- + Low-Level Table Access Subroutines: page~\pageref{FTGTBS} +- + FTGTBS(unit,frow,startchar,nchars, > string,status) + FTPTBS(unit,frow,startchar,nchars,string, > status) + FTGTBB(unit,frow,startchar,nchars, > array,status) + FTPTBB(unit,frow,startchar,nchars,array, > status) +- + Edit Rows or Columns page~\pageref{FTIROW} +- + FTIROW(unit,frow,nrows, > status) + FTDROW(unit,frow,nrows, > status) + FTDRRG(unit,rowrange, > status) + FTDRWS(unit,rowlist,nrows, > status) + FTICOL(unit,colnum,ttype,tform, > status) + FTICLS(unit,colnum,ncols,ttype,tform, > status) + FTMVEC(unit,colnum,newveclen, > status) + FTDCOL(unit,colnum, > status) + FTCPCL(inunit,outunit,incolnum,outcolnum,createcol, > status); +- + Read and Write Column Data Routines page~\pageref{FTPCLS} +- + FTPCL[SLBIJKEDCM](unit,colnum,frow,felem,nelements,values, > status) + FTPCN[BIJKED](unit,colnum,frow,felem,nelements,values,nullval > status) + FTPCLX(unit,colnum,frow,fbit,nbit,lray, > status) + FTPCLU(unit,colnum,frow,felem,nelements, > status) + FTGCL(unit,colnum,frow,felem,nelements, > values,status) + FTGCV[SBIJKEDCM](unit,colnum,frow,felem,nelements,nullval, > + values,anyf,status) + FTGCF[SLBIJKEDCM](unit,colnum,frow,felem,nelements, > + values,flagvals,anyf,status) + FTGSV[BIJKED](unit,colnum,naxis,naxes,fpixels,lpixels,incs,nullval, > + array,anyf,status) + FTGSF[BIJKED](unit,colnum,naxis,naxes,fpixels,lpixels,incs, > + array,flagvals,anyf,status) + FTGCX(unit,colnum,frow,fbit,nbit, > lray,status) + FTGCX[IJD](unit,colnum,frow,nrows,fbit,nbit, > array,status) + FTGDES(unit,colnum,rownum, > nelements,offset,status) + FTPDES(unit,colnum,rownum,nelements,offset, > status) +- + Row Selection and Calculator Routines: page~\pageref{FTFROW} +- + FTFROW(unit,expr,firstrow, nrows, > n_good_rows, row_status, status) + FTFFRW(unit, expr, > rownum, status) + FTSROW(inunit, outunit, expr, > status ) + FTCROW(unit,datatype,expr,firstrow,nelements,nulval, > + array,anynul,status) + FTCALC(inunit, expr, outunit, parName, parInfo, > status) + FTCALC_RNG(inunit, expr, outunit, parName, parInfo, + nranges, firstrow, lastrow, > status) + FTTEXP(unit, expr, > datatype, nelem, naxis, naxes, status) +- + Celestial Coordinate System Subroutines: page~\pageref{FTGICS} +- + FTGICS(unit, > xrval,yrval,xrpix,yrpix,xinc,yinc,rot,coordtype,status) + FTGTCS(unit,xcol,ycol, > + xrval,yrval,xrpix,yrpix,xinc,yinc,rot,coordtype,status) + FTWLDP(xpix,ypix,xrval,yrval,xrpix,yrpix,xinc,yinc,rot, + coordtype, > xpos,ypos,status) + FTXYPX(xpos,ypos,xrval,yrval,xrpix,yrpix,xinc,yinc,rot, + coordtype, > xpix,ypix,status) +- + File Checksum Subroutines: page~\pageref{FTPCKS} +- + FTPCKS(unit, > status) + FTUCKS(unit, > status) + FTVCKS(unit, > dataok,hduok,status) + FTGCKS(unit, > datasum,hdusum,status) + FTESUM(sum,complement, > checksum) + FTDSUM(checksum,complement, > sum) + +- + Time and Date Utility Subroutines: page~\pageref{FTGSDT} +- + FTGSDT( > day, month, year, status ) + FTGSTM(> datestr, timeref, status) + FTDT2S( year, month, day, > datestr, status) + FTTM2S( year, month, day, hour, minute, second, decimals, + > datestr, status) + FTS2DT(datestr, > year, month, day, status) + FTS2TM(datestr, > year, month, day, hour, minute, second, status) +- + General Utility Subroutines: page~\pageref{FTGHAD} +- + FTGHAD(unit, > curaddr,nextaddr) + FTUPCH(string) + FTCMPS(str_template,string,casesen, > match,exact) + FTTKEY(keyword, > status) + FTTREC(card, > status) + FTNCHK(unit, > status) + FTGKNM(unit, > keyword, keylength, status) + FTPSVC(card, > value,comment,status) + FTKEYN(keyroot,seq_no, > keyword,status) + FTNKEY(seq_no,keyroot, > keyword,status) + FTDTYP(value, > dtype,status) + class = FTGKCL(card) + FTASFM(tform, > datacode,width,decimals,status) + FTBNFM(tform, > datacode,repeat,width,status) + FTGABC(tfields,tform,space, > rowlen,tbcol,status) + FTGTHD(template, > card,hdtype,status) + FTRWRG(rowlist, maxrows, maxranges, > numranges, rangemin, + rangemax, status) +- + +*X. Parameter Definitions +- +anyf - (logical) set to TRUE if any of the returned data values are undefined +array - (any datatype except character) array of bytes to be read or written. +bitpix - (integer) bits per pixel: 8, 16, 32, -32, or -64 +blank - (integer) value used for undefined pixels in integer primary array +blank - (integer*8) value used for undefined pixels in integer primary array +blocksize - (integer) 2880-byte logical record blocking factor + (if 0 < blocksize < 11) or the actual block size in bytes + (if 10 < blocksize < 28800). As of version 3.3 of FITSIO, + blocksizes greater than 2880 are no longer supported. +bscale - (double precision) scaling factor for the primary array +bytlen - (integer) length of the data unit, in bytes +bzero - (double precision) zero point for primary array scaling +card - (character*80) header record to be read or written +casesen - (logical) will string matching be case sensitive? +checksum - (character*16) encoded checksum string +colname - (character) ASCII name of the column +colnum - (integer) number of the column (first column = 1) +coltemplate - (character) template string to be matched to column names +comment - (character) the keyword comment field +comments - (character array) keyword comment fields +compid - (integer) the type of computer that the program is running on +complement - (logical) should the checksum be complemented? +coordtype - (character) type of coordinate projection (-SIN, -TAN, -ARC, + -NCP, -GLS, -MER, or -AIT) +cube - 3D data cube of the appropriate datatype +curaddr - (integer) starting address (in bytes) of the CHDU +current - (integer) if not equal to 0, copy the current HDU +datacode - (integer) symbolic code of the binary table column datatype +dataok - (integer) was the data unit verification successful (=1) or + not (= -1). Equals zero if the DATASUM keyword is not present. +datasum - (double precision) 32-bit 1's complement checksum for the data unit +datatype - (character) datatype (format) of the binary table column +datestr - (string) FITS date/time string: 'YYYY-MM-DDThh:mm:ss.ddd', + 'YYYY-MM-dd', or 'dd/mm/yy' +day - (integer) current day of the month +dblval - (double precision) fractional part of the keyword value +decimals - (integer) number of decimal places to be displayed +dim1 - (integer) actual size of the first dimension of the image or cube array +dim2 - (integer) actual size of the second dimension of the cube array +dispwidth - (integer) - the display width (length of string) for a column +dtype - (character) datatype of the keyword ('C', 'L', 'I', or 'F') + C = character string + L = logical + I = integer + F = floating point number +errmsg - (character*80) oldest error message on the internal stack +errtext - (character*30) descriptive error message corresponding to error number +casesen - (logical) true if column name matching is case sensitive +exact - (logical) do the strings match exactly, or were wildcards used? +exclist (character array) list of names to be excluded from search +exists - flag indicating whether the file or compressed file exists on disk +extend - (logical) true if there may be extensions following the primary data +extname - (character) value of the EXTNAME keyword (if not blank) +fbit - (integer) first bit in the field to be read or written +felem - (integer) first pixel of the element vector (ignored for ASCII tables) +filename - (character) name of the FITS file +flagvals - (logical array) True if corresponding data element is undefined +following - (integer) if not equal to 0, copy all following HDUs in the input file +fparm - (integer) sequence number of the first group parameter to read or write +fpixel - (integer) the first pixel position +fpixels - (integer array) the first included pixel in each dimension +frow - (integer) beginning row number (first row of table = 1) +frowll - (integer*8) beginning row number (first row of table = 1) +gcount - (integer) value of the GCOUNT keyword (usually = 1) +group - (integer) sequence number of the data group (=0 for non-grouped data) +hdtype - (integer) header record type: -1=delete; 0=append or replace; + 1=append; 2=this is the END keyword +hduok - (integer) was the HDU verification successful (=1) or + not (= -1). Equals zero if the CHECKSUM keyword is not present. +hdusum - (double precision) 32 bit 1's complement checksum for the entire CHDU +hdutype - (integer) type of HDU: 0 = primary array or IMAGE, 1 = ASCII table, + 2 = binary table, -1 = any HDU type or unknown type +history - (character) the HISTORY keyword comment string +hour - (integer) hour from 0 - 23 +image - 2D image of the appropriate datatype +inclist (character array) list of names to be included in search +incs - (integer array) sampling interval for pixels in each FITS dimension +intval - (integer) integer part of the keyword value +iounit - (integer) value of an unused I/O unit number +iunit - (integer) logical unit number associated with the input FITS file, 1-199 +key_no - (integer) sequence number (starting with 1) of the keyword record +keylength - (integer) length of the keyword name +keyroot - (character) root string for the keyword name +keysadd -(integer) number of new keyword records which can fit in the CHU +keysexist - (integer) number of existing keyword records in the CHU +keyval - value of the keyword in the appropriate datatype +keyvals - (array) value of the keywords in the appropriate datatype +keyword - (character*8) name of a keyword +lray - (logical array) array of logical values corresponding to the bit array +lpixels - (integer array) the last included pixel in each dimension +match - (logical) do the 2 strings match? +maxdim - (integer) dimensioned size of the NAXES, TTYPE, TFORM or TUNIT arrays +max_keys - (integer) maximum number of keywords to search for +minute - (integer) minute of an hour (0 - 59) +month - (integer) current month of the year (1 - 12) +morekeys - (integer) will leave space in the header for this many more keywords +naxes - (integer array) size of each dimension in the FITS array +naxesll - (integer*8 array) size of each dimension in the FITS array +naxis - (integer) number of dimensions in the FITS array +naxis1 - (integer) length of the X/first axis of the FITS array +naxis2 - (integer) length of the Y/second axis of the FITS array +naxis3 - (integer) length of the Z/third axis of the FITS array +nbit - (integer) number of bits in the field to read or write +nchars - (integer) number of characters to read and return +ncols - (integer) number of columns +nelements - (integer) number of data elements to read or write +nelementsll - (integer*8) number of data elements to read or write +nexc (integer) number of names in the exclusion list (may = 0) +nhdu - (integer) absolute number of the HDU (1st HDU = 1) +ninc (integer) number of names in the inclusion list +nmove - (integer) number of HDUs to move (+ or -), relative to current position +nfound - (integer) number of keywords found (highest keyword number) +no_keys - (integer) number of keywords to write in the sequence +nparm - (integer) number of group parameters to read or write +nrows - (integer) number of rows in the table +nrowsll - (integer*8) number of rows in the table +nullval - value to represent undefined pixels, of the appropriate datatype +nextaddr - (integer) starting address (in bytes) of the HDU following the CHDU +offset - (integer) byte offset in the heap to the first element of the array +offsetll - (integer*8) byte offset in the heap to the first element of the array +oldkey - (character) old name of keyword to be modified +ounit - (integer) logical unit number associated with the output FITS file 1-199 +pcount - (integer) value of the PCOUNT keyword (usually = 0) +previous - (integer) if not equal to 0, copy all previous HDUs in the input file +repeat - (integer) length of element vector (e.g. 12J); ignored for ASCII table +rot - (double precision) celestial coordinate rotation angle (degrees) +rowlen - (integer) length of a table row, in characters or bytes +rowlenll - (integer*8) length of a table row, in characters or bytes +rowlist - (integer array) list of row numbers to be deleted in increasing order +rownum - (integer) number of the row (first row = 1) +rowrange- (string) list of rows or row ranges to be deleted +rwmode - (integer) file access mode: 0 = readonly, 1 = readwrite +second (double)- second within minute (0 - 60.9999999999) (leap second!) +seq_no - (integer) the sequence number to append to the keyword root name +simple - (logical) does the FITS file conform to all the FITS standards +snull - (character) value used to represent undefined values in ASCII table +space - (integer) number of blank spaces to leave between ASCII table columns +startchar - (integer) first character in the row to be read +startno - (integer) value of the first keyword sequence number (usually 1) +status - (integer) returned error status code (0 = OK) +str_template (character) template string to be matched to reference string +stream - (character) output stream for the report: either 'STDOUT' or 'STDERR' +string - (character) character string +sum - (double precision) 32 bit unsigned checksum value +tbcol - (integer array) column number of the first character in the field(s) +tdisp - (character) Fortran type display format for the table column +template-(character) template string for a FITS header record +tfields - (integer) number of fields (columns) in the table +tform - (character array) format of the column(s); allowed values are: + For ASCII tables: Iw, Aw, Fww.dd, Eww.dd, or Dww.dd + For binary tables: rL, rX, rB, rI, rJ, rA, rAw, rE, rD, rC, rM + where 'w'=width of the field, 'd'=no. of decimals, 'r'=repeat count + Note that the 'rAw' form is non-standard extension to the + TFORM keyword syntax that is not specifically defined in the + Binary Tables definition document. +theap - (integer) zero indexed byte offset of starting address of the heap + relative to the beginning of the binary table data +tnull - (integer) value used to represent undefined values in binary table +tnullll - (integer*8) value used to represent undefined values in binary table +ttype - (character array) label for table column(s) +tscal - (double precision) scaling factor for table column +tunit - (character array) physical unit for table column(s) +tzero - (double precision) scaling zero point for table column +unit - (integer) logical unit number associated with the FITS file (1-199) +units - (character) the keyword units string (e.g., 'km/s') +value - (character) the keyword value string +values - array of data values of the appropriate datatype +varidat - (integer) size in bytes of the 'variable length data area' + following the binary table data (usually = 0) +version - (real) current revision number of the library +width - (integer) width of the character string field +xcol - (integer) number of the column containing the X coordinate values +xinc - (double precision) X axis coordinate increment at reference pixel (deg) +xpix - (double precision) X axis pixel location +xpos - (double precision) X axis celestial coordinate (usually RA) (deg) +xrpix - (double precision) X axis reference pixel array location +xrval - (double precision) X axis coordinate value at the reference pixel (deg) +ycol - (integer) number of the column containing the X coordinate values +year - (integer) last 2 digits of the year (00 - 99) +yinc - (double precision) Y axis coordinate increment at reference pixel (deg) +ypix - (double precision) y axis pixel location +ypos - (double precision) y axis celestial coordinate (usually DEC) (deg) +yrpix - (double precision) Y axis reference pixel array location +yrval - (double precision) Y axis coordinate value at the reference pixel (deg) +- + +*XI. FITSIO Error Status Codes +- +Status codes in the range -99 to -999 and 1 to 999 are reserved for future +FITSIO use. + + 0 OK, no error +101 input and output files are the same +103 too many FITS files open at once; all internal buffers full +104 error opening existing file +105 error creating new FITS file; (does a file with this name already exist?) +106 error writing record to FITS file +107 end-of-file encountered while reading record from FITS file +108 error reading record from file +110 error closing FITS file +111 internal array dimensions exceeded +112 Cannot modify file with readonly access +113 Could not allocate memory +114 illegal logical unit number; must be between 1 - 199, inclusive +115 NULL input pointer to routine +116 error seeking position in file + +121 invalid URL prefix on file name +122 tried to register too many IO drivers +123 driver initialization failed +124 matching driver is not registered +125 failed to parse input file URL +126 parse error in range list + +151 bad argument in shared memory driver +152 null pointer passed as an argument +153 no more free shared memory handles +154 shared memory driver is not initialized +155 IPC error returned by a system call +156 no memory in shared memory driver +157 resource deadlock would occur +158 attempt to open/create lock file failed +159 shared memory block cannot be resized at the moment + + +201 header not empty; can't write required keywords +202 specified keyword name was not found in the header +203 specified header record number is out of bounds +204 keyword value field is blank +205 keyword value string is missing the closing quote character +206 illegal indexed keyword name (e.g. 'TFORM1000') +207 illegal character in keyword name or header record +208 keyword does not have expected name. Keyword out of sequence? +209 keyword does not have expected integer value +210 could not find the required END header keyword +211 illegal BITPIX keyword value +212 illegal NAXIS keyword value +213 illegal NAXISn keyword value: must be 0 or positive integer +214 illegal PCOUNT keyword value +215 illegal GCOUNT keyword value +216 illegal TFIELDS keyword value +217 negative ASCII or binary table width value (NAXIS1) +218 negative number of rows in ASCII or binary table (NAXIS2) +219 column name (TTYPE keyword) not found +220 illegal SIMPLE keyword value +221 could not find the required SIMPLE header keyword +222 could not find the required BITPIX header keyword +223 could not find the required NAXIS header keyword +224 could not find all the required NAXISn keywords in the header +225 could not find the required XTENSION header keyword +226 the CHDU is not an ASCII table extension +227 the CHDU is not a binary table extension +228 could not find the required PCOUNT header keyword +229 could not find the required GCOUNT header keyword +230 could not find the required TFIELDS header keyword +231 could not find all the required TBCOLn keywords in the header +232 could not find all the required TFORMn keywords in the header +233 the CHDU is not an IMAGE extension +234 illegal TBCOL keyword value; out of range +235 this operation only allowed for ASCII or BINARY table extension +236 column is too wide to fit within the specified width of the ASCII table +237 the specified column name template matched more than one column name +241 binary table row width is not equal to the sum of the field widths +251 unrecognizable type of FITS extension +252 unrecognizable FITS record +253 END keyword contains non-blank characters in columns 9-80 +254 Header fill area contains non-blank characters +255 Data fill area contains non-blank on non-zero values +261 unable to parse the TFORM keyword value string +262 unrecognizable TFORM datatype code +263 illegal TDIMn keyword value + +301 illegal HDU number; less than 1 or greater than internal buffer size +302 column number out of range (1 - 999) +304 attempt to move to negative file record number +306 attempted to read or write a negative number of bytes in the FITS file +307 illegal starting row number for table read or write operation +308 illegal starting element number for table read or write operation +309 attempted to read or write character string in non-character table column +310 attempted to read or write logical value in non-logical table column +311 illegal ASCII table TFORM format code for attempted operation +312 illegal binary table TFORM format code for attempted operation +314 value for undefined pixels has not been defined +317 attempted to read or write descriptor in a non-descriptor field +320 number of array dimensions out of range +321 first pixel number is greater than the last pixel number +322 attempt to set BSCALE or TSCALn scaling parameter = 0 +323 illegal axis length less than 1 + +340 NOT_GROUP_TABLE 340 Grouping function error +341 HDU_ALREADY_MEMBER +342 MEMBER_NOT_FOUND +343 GROUP_NOT_FOUND +344 BAD_GROUP_ID +345 TOO_MANY_HDUS_TRACKED +346 HDU_ALREADY_TRACKED +347 BAD_OPTION +348 IDENTICAL_POINTERS +349 BAD_GROUP_ATTACH +350 BAD_GROUP_DETACH + +360 NGP_NO_MEMORY malloc failed +361 NGP_READ_ERR read error from file +362 NGP_NUL_PTR null pointer passed as an argument. + Passing null pointer as a name of + template file raises this error +363 NGP_EMPTY_CURLINE line read seems to be empty (used + internally) +364 NGP_UNREAD_QUEUE_FULL cannot unread more then 1 line (or single + line twice) +365 NGP_INC_NESTING too deep include file nesting (infinite + loop, template includes itself ?) +366 NGP_ERR_FOPEN fopen() failed, cannot open template file +367 NGP_EOF end of file encountered and not expected +368 NGP_BAD_ARG bad arguments passed. Usually means + internal parser error. Should not happen +369 NGP_TOKEN_NOT_EXPECT token not expected here + +401 error attempting to convert an integer to a formatted character string +402 error attempting to convert a real value to a formatted character string +403 cannot convert a quoted string keyword to an integer +404 attempted to read a non-logical keyword value as a logical value +405 cannot convert a quoted string keyword to a real value +406 cannot convert a quoted string keyword to a double precision value +407 error attempting to read character string as an integer +408 error attempting to read character string as a real value +409 error attempting to read character string as a double precision value +410 bad keyword datatype code +411 illegal number of decimal places while formatting floating point value +412 numerical overflow during implicit datatype conversion +413 error compressing image +414 error uncompressing image +420 error in date or time conversion + +431 syntax error in parser expression +432 expression did not evaluate to desired type +433 vector result too large to return in array +434 data parser failed not sent an out column +435 bad data encounter while parsing column +436 parse error: output file not of proper type + +501 celestial angle too large for projection +502 bad celestial coordinate or pixel value +503 error in celestial coordinate calculation +504 unsupported type of celestial projection +505 required celestial coordinate keywords not found +506 approximate wcs keyword values were returned +- +\end{document} diff --git a/external/cfitsio/fitsio.h b/external/cfitsio/fitsio.h new file mode 100644 index 0000000..d6c83ed --- /dev/null +++ b/external/cfitsio/fitsio.h @@ -0,0 +1,1934 @@ +/* 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.31 +#define CFITSIO_MINOR 31 +#define CFITSIO_MAJOR 3 + +#include + +/* 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 /* apparently needed on debian linux systems */ +#endif /* to define off_t */ + +#include /* apparently needed to define size_t with gcc 2.8.1 */ +#include /* 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 ffifile2 (char *url, char *urltype, char *infile, + char *outfile, char *extspec, char *rowfilter, + char *binspec, char *colspec, char *pixfilter, char *compspec, 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 fffree(void *value, 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/external/cfitsio/fitsio.pdf b/external/cfitsio/fitsio.pdf new file mode 100644 index 0000000..31ebd2b Binary files /dev/null and b/external/cfitsio/fitsio.pdf differ diff --git a/external/cfitsio/fitsio.ps b/external/cfitsio/fitsio.ps new file mode 100644 index 0000000..769e1bb --- /dev/null +++ b/external/cfitsio/fitsio.ps @@ -0,0 +1,11353 @@ +%!PS-Adobe-2.0 +%%Creator: dvips(k) 5.96.1 Copyright 2007 Radical Eye Software +%%Title: fitsio.dvi +%%CreationDate: Wed Jul 18 13:51:22 2012 +%%Pages: 136 +%%PageOrder: Ascend +%%BoundingBox: 0 0 612 792 +%%DocumentFonts: CMBX12 CMR12 CMR10 CMBX10 CMSL10 CMTT10 CMSY10 CMMI10 +%%DocumentPaperSizes: Letter +%%EndComments +%DVIPSWebPage: (www.radicaleye.com) +%DVIPSCommandLine: dvips -o fitsio.ps fitsio.dvi +%DVIPSParameters: dpi=600 +%DVIPSSource: TeX output 2012.07.18:1350 +%%BeginProcSet: tex.pro 0 0 +%! +/TeXDict 300 dict def TeXDict begin/N{def}def/B{bind def}N/S{exch}N/X{S +N}B/A{dup}B/TR{translate}N/isls false N/vsize 11 72 mul N/hsize 8.5 72 +mul N/landplus90{false}def/@rigin{isls{[0 landplus90{1 -1}{-1 1}ifelse 0 +0 0]concat}if 72 Resolution div 72 VResolution div neg scale isls{ +landplus90{VResolution 72 div vsize mul 0 exch}{Resolution -72 div hsize +mul 0}ifelse TR}if Resolution VResolution vsize -72 div 1 add mul TR[ +matrix currentmatrix{A A round sub abs 0.00001 lt{round}if}forall round +exch round exch]setmatrix}N/@landscape{/isls true N}B/@manualfeed{ +statusdict/manualfeed true put}B/@copies{/#copies X}B/FMat[1 0 0 -1 0 0] +N/FBB[0 0 0 0]N/nn 0 N/IEn 0 N/ctr 0 N/df-tail{/nn 8 dict N nn begin +/FontType 3 N/FontMatrix fntrx N/FontBBox FBB N string/base X array +/BitMaps X/BuildChar{CharBuilder}N/Encoding IEn N end A{/foo setfont}2 +array copy cvx N load 0 nn put/ctr 0 N[}B/sf 0 N/df{/sf 1 N/fntrx FMat N +df-tail}B/dfs{div/sf X/fntrx[sf 0 0 sf neg 0 0]N df-tail}B/E{pop nn A +definefont setfont}B/Cw{Cd A length 5 sub get}B/Ch{Cd A length 4 sub get +}B/Cx{128 Cd A length 3 sub get sub}B/Cy{Cd A length 2 sub get 127 sub} +B/Cdx{Cd A length 1 sub get}B/Ci{Cd A type/stringtype ne{ctr get/ctr ctr +1 add N}if}B/CharBuilder{save 3 1 roll S A/base get 2 index get S +/BitMaps get S get/Cd X pop/ctr 0 N Cdx 0 Cx Cy Ch sub Cx Cw add Cy +setcachedevice Cw Ch true[1 0 0 -1 -.1 Cx sub Cy .1 sub]{Ci}imagemask +restore}B/D{/cc X A type/stringtype ne{]}if nn/base get cc ctr put nn +/BitMaps get S ctr S sf 1 ne{A A length 1 sub A 2 index S get sf div put +}if put/ctr ctr 1 add N}B/I{cc 1 add D}B/bop{userdict/bop-hook known{ +bop-hook}if/SI save N @rigin 0 0 moveto/V matrix currentmatrix A 1 get A +mul exch 0 get A mul add .99 lt{/QV}{/RV}ifelse load def pop pop}N/eop{ +SI restore userdict/eop-hook known{eop-hook}if showpage}N/@start{ +userdict/start-hook known{start-hook}if pop/VResolution X/Resolution X +1000 div/DVImag X/IEn 256 array N 2 string 0 1 255{IEn S A 360 add 36 4 +index cvrs cvn put}for pop 65781.76 div/vsize X 65781.76 div/hsize X}N +/p{show}N/RMat[1 0 0 -1 0 0]N/BDot 260 string N/Rx 0 N/Ry 0 N/V{}B/RV/v{ +/Ry X/Rx X V}B statusdict begin/product where{pop false[(Display)(NeXT) +(LaserWriter 16/600)]{A length product length le{A length product exch 0 +exch getinterval eq{pop true exit}if}{pop}ifelse}forall}{false}ifelse +end{{gsave TR -.1 .1 TR 1 1 scale Rx Ry false RMat{BDot}imagemask +grestore}}{{gsave TR -.1 .1 TR Rx Ry scale 1 1 false RMat{BDot} +imagemask grestore}}ifelse B/QV{gsave newpath transform round exch round +exch itransform moveto Rx 0 rlineto 0 Ry neg rlineto Rx neg 0 rlineto +fill grestore}B/a{moveto}B/delta 0 N/tail{A/delta X 0 rmoveto}B/M{S p +delta add tail}B/b{S p tail}B/c{-4 M}B/d{-3 M}B/e{-2 M}B/f{-1 M}B/g{0 M} +B/h{1 M}B/i{2 M}B/j{3 M}B/k{4 M}B/w{0 rmoveto}B/l{p -4 w}B/m{p -3 w}B/n{ +p -2 w}B/o{p -1 w}B/q{p 1 w}B/r{p 2 w}B/s{p 3 w}B/t{p 4 w}B/x{0 S +rmoveto}B/y{3 2 roll p a}B/bos{/SS save N}B/eos{SS restore}B end + +%%EndProcSet +%%BeginProcSet: texps.pro 0 0 +%! +TeXDict begin/rf{findfont dup length 1 add dict begin{1 index/FID ne 2 +index/UniqueID ne and{def}{pop pop}ifelse}forall[1 index 0 6 -1 roll +exec 0 exch 5 -1 roll VResolution Resolution div mul neg 0 0]FontType 0 +ne{/Metrics exch def dict begin Encoding{exch dup type/integertype ne{ +pop pop 1 sub dup 0 le{pop}{[}ifelse}{FontMatrix 0 get div Metrics 0 get +div def}ifelse}forall Metrics/Metrics currentdict end def}{{1 index type +/nametype eq{exit}if exch pop}loop}ifelse[2 index currentdict end +definefont 3 -1 roll makefont/setfont cvx]cvx def}def/ObliqueSlant{dup +sin S cos div neg}B/SlantFont{4 index mul add}def/ExtendFont{3 -1 roll +mul exch}def/ReEncodeFont{CharStrings rcheck{/Encoding false def dup[ +exch{dup CharStrings exch known not{pop/.notdef/Encoding true def}if} +forall Encoding{]exch pop}{cleartomark}ifelse}if/Encoding exch def}def +end + +%%EndProcSet +%%BeginFont: CMMI10 +%!PS-AdobeFont-1.1: CMMI10 1.100 +%%CreationDate: 1996 Jul 23 07:53:57 +% Copyright (C) 1997 American Mathematical Society. All Rights Reserved. +11 dict begin +/FontInfo 7 dict dup begin +/version (1.100) readonly def +/Notice (Copyright (C) 1997 American Mathematical Society. All Rights Reserved) readonly def +/FullName (CMMI10) readonly def +/FamilyName (Computer Modern) readonly def +/Weight (Medium) readonly def +/ItalicAngle -14.04 def +/isFixedPitch false def +end readonly def +/FontName /CMMI10 def +/PaintType 0 def +/FontType 1 def +/FontMatrix [0.001 0 0 0.001 0 0] readonly def +/Encoding 256 array +0 1 255 {1 index exch /.notdef put} for +dup 62 /greater put +readonly def +/FontBBox{-32 -250 1048 750}readonly def +currentdict end +currentfile eexec +D9D66F633B846A97B686A97E45A3D0AA0529731C99A784CCBE85B4993B2EEBDE +3B12D472B7CF54651EF21185116A69AB1096ED4BAD2F646635E019B6417CC77B +532F85D811C70D1429A19A5307EF63EB5C5E02C89FC6C20F6D9D89E7D91FE470 +B72BEFDA23F5DF76BE05AF4CE93137A219ED8A04A9D7D6FDF37E6B7FCDE0D90B +986423E5960A5D9FBB4C956556E8DF90CBFAEC476FA36FD9A5C8175C9AF513FE +D919C2DDD26BDC0D99398B9F4D03D5993DFC0930297866E1CD0A319B6B1FD958 +9E394A533A081C36D456A09920001A3D2199583EB9B84B4DEE08E3D12939E321 +990CD249827D9648574955F61BAAA11263A91B6C3D47A5190165B0C25ABF6D3E +6EC187E4B05182126BB0D0323D943170B795255260F9FD25F2248D04F45DFBFB +DEF7FF8B19BFEF637B210018AE02572B389B3F76282BEB29CC301905D388C721 +59616893E774413F48DE0B408BC66DCE3FE17CB9F84D205839D58014D6A88823 +D9320AE93AF96D97A02C4D5A2BB2B8C7925C4578003959C46E3CE1A2F0EAC4BF +8B9B325E46435BDE60BC54D72BC8ACB5C0A34413AC87045DC7B84646A324B808 +6FD8E34217213E131C3B1510415CE45420688ED9C1D27890EC68BD7C1235FAF9 +1DAB3A369DD2FC3BE5CF9655C7B7EDA7361D7E05E5831B6B8E2EEC542A7B38EE +03BE4BAC6079D038ACB3C7C916279764547C2D51976BABA94BA9866D79F13909 +95AA39B0F03103A07CBDF441B8C5669F729020AF284B7FF52A29C6255FCAACF1 +74109050FBA2602E72593FBCBFC26E726EE4AEF97B7632BC4F5F353B5C67FED2 +3EA752A4A57B8F7FEFF1D7341D895F0A3A0BE1D8E3391970457A967EFF84F6D8 +47750B1145B8CC5BD96EE7AA99DDC9E06939E383BDA41175233D58AD263EBF19 +AFC0E2F840512D321166547B306C592B8A01E1FA2564B9A26DAC14256414E4C8 +42616728D918C74D13C349F4186EC7B9708B86467425A6FDB3A396562F7EE4D8 +40B43621744CF8A23A6E532649B66C2A0002DD04F8F39618E4F572819DD34837 +B5A08E643FDCA1505AF6A1FA3DDFD1FA758013CAED8ACDDBBB334D664DFF5B53 +9560176676ABB71BBD0EE56B4CC492C0652750227CEC7B86E4740EB7B8775564 +332769DD30794E501BBB0E4E5CB665F3628E10B1137CC8BC5C0A64A310B5E27E +5FD6E3B04DA3914C15987E638A72790AF4073CE9CDBF6E3C749CB4DFF9C54951 +A58C386C54BC4E98B102B5E91E8567D2EEEF048F2CBD5D243701D20909290B4B +A3083F632D8552D42DEE0C69A4B14D8B15AA082DECC12B2ECAE6F663E6D09F81 +EE2979EF41FBF12C9D8BF23B77E0A20088EBD107C5BF9DD6F03FFC3AB65B69A7 +54953327E1D4AEF5A146273392BBDB321D4CC9A8FFFCFE5C515B466E21546CC7 +C6209E5A76F916B03DB98BC6CED334F33E7B373D42761696F5A876CA6F93F16E +15A07E2E102148CA4F62A99C +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +cleartomark +%%EndFont +%%BeginFont: CMBX12 +%!PS-AdobeFont-1.1: CMBX12 1.0 +%%CreationDate: 1991 Aug 20 16:34:54 +% Copyright (C) 1997 American Mathematical Society. All Rights Reserved. +11 dict begin +/FontInfo 7 dict dup begin +/version (1.0) readonly def +/Notice (Copyright (C) 1997 American Mathematical Society. All Rights Reserved) readonly def +/FullName (CMBX12) readonly def +/FamilyName (Computer Modern) readonly def +/Weight (Bold) readonly def +/ItalicAngle 0 def +/isFixedPitch false def +end readonly def +/FontName /CMBX12 def +/PaintType 0 def +/FontType 1 def +/FontMatrix [0.001 0 0 0.001 0 0] readonly def +/Encoding 256 array +0 1 255 {1 index exch /.notdef put} for +dup 11 /ff put +dup 12 /fi put +dup 39 /quoteright put +dup 40 /parenleft put +dup 41 /parenright put +dup 45 /hyphen put +dup 46 /period put +dup 47 /slash put +dup 48 /zero put +dup 49 /one put +dup 50 /two put +dup 51 /three put +dup 52 /four put +dup 53 /five put +dup 54 /six put +dup 55 /seven put +dup 56 /eight put +dup 57 /nine put +dup 58 /colon put +dup 65 /A put +dup 66 /B put +dup 67 /C put +dup 68 /D put +dup 69 /E put +dup 70 /F put +dup 71 /G put +dup 72 /H put +dup 73 /I put +dup 75 /K put +dup 76 /L put +dup 77 /M put +dup 78 /N put +dup 79 /O put +dup 80 /P put +dup 82 /R put +dup 83 /S put +dup 84 /T put +dup 85 /U put +dup 86 /V put +dup 87 /W put +dup 97 /a put +dup 98 /b put +dup 99 /c put +dup 100 /d put +dup 101 /e put +dup 102 /f put +dup 103 /g put +dup 104 /h put +dup 105 /i put +dup 107 /k put +dup 108 /l put +dup 109 /m put +dup 110 /n put +dup 111 /o put +dup 112 /p put +dup 114 /r put +dup 115 /s put +dup 116 /t put +dup 117 /u put +dup 118 /v put +dup 119 /w put +dup 120 /x put +dup 121 /y put +dup 122 /z put +readonly def +/FontBBox{-53 -251 1139 750}readonly def +currentdict end +currentfile eexec +D9D66F633B846A97B686A97E45A3D0AA052A014267B7904EB3C0D3BD0B83D891 +016CA6CA4B712ADEB258FAAB9A130EE605E61F77FC1B738ABC7C51CD46EF8171 +9098D5FEE67660E69A7AB91B58F29A4D79E57022F783EB0FBBB6D4F4EC35014F +D2DECBA99459A4C59DF0C6EBA150284454E707DC2100C15B76B4C19B84363758 +469A6C558785B226332152109871A9883487DD7710949204DDCF837E6A8708B8 +2BDBF16FBC7512FAA308A093FE5F0364CD5660F74BEE96790DE35AFA90CCF712 +B1805DA88AE375A04D99598EADFC625BDC1F9C315B6CF28C9BD427F32C745C99 +AEBE70DAAED49EA45AF94F081934AA47894A370D698ABABDA4215500B190AF26 +7FCFB7DDA2BC68605A4EF61ECCA3D61C684B47FFB5887A3BEDE0B4D30E8EBABF +20980C23312618EB0EAF289B2924FF4A334B85D98FD68545FDADB47F991E7390 +B10EE86A46A5AF8866C010225024D5E5862D49DEB5D8ECCB95D94283C50A363D +68A49071445610F03CE3600945118A6BC0B3AA4593104E727261C68C4A47F809 +D77E4CF27B3681F6B6F3AC498E45361BF9E01FAF5527F5E3CC790D3084674B3E +26296F3E03321B5C555D2458578A89E72D3166A3C5D740B3ABB127CF420C316D +F957873DA04CF0DB25A73574A4DE2E4F2D5D4E8E0B430654CF7F341A1BDB3E26 +77C194764EAD58C585F49EF10843FE020F9FDFD9008D660DE50B9BD7A2A87299 +BC319E66D781101BB956E30643A19B93C8967E1AE4719F300BFE5866F0D6DA5E +C55E171A24D3B707EFA325D47F473764E99BC8B1108D815CF2ACADFA6C4663E8 +30855D673CE98AB78F5F829F7FA226AB57F07B3E7D4E7CE30ED3B7EB0D3035C5 +148DA8D9FA34483414FDA8E3DC9E6C479E3EEE9A11A0547FC9085FA4631AD19C +E936E0598E3197207FA7BB6E55CFD5EF72AEC12D9A9675241C7A71316B2E148D +E2A1732B3627109EA446CB320EBBE2E78281CDF0890E2E72B6711335857F1E23 +337C75E729701E93D5BEC0630CDC7F4E957233EC09F917E5CA703C7E93841598 +0E73843FC6619DE017C8473A6D1B2BE5142DEBA285B98FA1CC5E64D2ADB981E6 +472971848451A245DDF6AA3B8225E9AC8E4630B0FF32D679EC27ACAD85C6394E +A6F71023B660EE883D8B676837E9EBA4E42BA8F365433A900F1DC3A9F0E88A26 +30F40A9C74C8E7773BE601C0E245E7FC10C02939848C3D28D823057B3EA23EC9 +E2BFF7851FB65FB12A4318A21D2C88EF9245D4C7BF21C81C4FC4CE0149E96278 +48F7BF97A5C3691E6CE038033AF54DE91D320EABF2B2E98617FD4145BFE5EBBF +1A5634DA07B00257A51966FFC009CA416D37AD30F4BEE6D72AB64CB2D62466F0 +42D66D6D33EF9107272835EA9C1C8FA04562864BE684FD5F8E6DF19916FF8346 +F39F3E1FA2975B5CED24EB160FEF6149954F76C359E9D15A209EDD7445BB6303 +6AFF9E1373CDD50B93DB137F3896D282F5C04AF0AC84AA02D5421A73E3AFB735 +80EBE676C2870822BA89F4BA85034D6431BC4A44CC264DDD10EA146913E9063B +038BD787A659C6A88C42FD5FBC51234664A8A1621A38C73ACB7199592AA5EA84 +B78BB5DBA7CA43E30CA8D3957CFF478712556EEAB1990C97B9BAB47ADB75E327 +B361D3039A3629B09EB84CF814773858F1D354AD8D526E85EAFD50944CBCE042 +367D1153D7B4EC4DD597DB0F57379DD63304869DEA4FA008D411E30ECFD62E41 +29F51841A9E07E066F06BB4829F87B7722562686FB801D6690F73233053B2EEC +473B9899F284296BB634358A22DBF361C2A57C59438C429EFB04B80BE083E238 +A6749D6AAD1F17F318B880D26D230B86F80A99C27B87F7605DE40A0848543DE2 +4AF7D5BF61CA6B00FA156DBB9ABE7BB0EC79CE8FD5A2FC5BE17050F2A74E8905 +75A9982E12698B289E2213DAFAAE40DD22B424562D7417E8B82A5E3DDA814669 +D4CC949397B0DD2B0787016CB8FDFD09318719D7C47DB72E094C12C6DF3EDE88 +663D6357CE7BFE51FD4A93887F022E3A22DCBE39CA86E104CE54C05AA0B5E469 +0AB9DA29D19ED5E783225ECD1122BFFF5E4E63488FF215A9F555C190ECDAF130 +B34E6BFC66AB1F7D000CC09CA9872299E23EC9B0A8ED04135C982978B9D7C4E5 +F9C7968B540837CC667196C7B40F579E8258C8C7AC569AB787BB112C046D0068 +86AEC034183A3DE02FB82C2CD3E252746C012F2385493F59DDEFD5AA95A26C61 +BA64D85CE90339A0C19F538C6E0516513B9E7661505AB81F45AEA04618CEB379 +12F6BB94E2812F2B3C69BAFC9135B439D74C6A94C0317E1B2A2EA514536AA990 +7B94105AF7DA13148A807CB2F679DBDEE10B09BDCC8BDC7365166C910AA450C7 +4FD88CA8BB639F1BA53A4B108D2E1633C5DB38E64DF4881E0FC1118559A078E8 +52A0E2A87F98461EE7B0B56DFB6C3F340430E3D5C41C12141E4332951B4DF03A +6824E8CEEA4AE8AC9F51981184287EC2710E4A51DB5E09DCFF4DFCE6B82A0B5C +D2E658D38FA7B2854778650A78AF230E97A4C195865C404EC18A5FA99EA30FE8 +755EE5B11397D3CCAECD81EE3F3D1EE1A1B1FD6B669270912948134ECBA3D5B3 +011CD3286DD6729DBCFC9A8FC93356BC70A876AB636E5C8EFA89881D3A6E0592 +D4CDDBBD21A67033AB3354BA1333206874D2F7D0E571AAD845A96057F005F730 +C98EBDFB006EF8874CC386A0B611389F9CF5204C65AD951306B0E090BB287EF9 +CB7DB38F2A58E2ED6813F2138922E28FA44792E5C57A70C6152B4A6E73462F8C +869886D1E5718C85F1CD5E7EC4BD73C6A4E3218A7118D42CFE42BB83A6B72729 +0E096EA902F81330771203309DEA91A560B56DE63911EBB6147600BB9B6A5166 +A818B7B8CE58279E22D776B2CF423897110CFF24807319C84A2063CA464C7616 +8260B36124AFE08188795E334864712574CC3DE12C3F0962D51734A44289BFE2 +2525F5D641EF82F25C1EAEA56C621525EC86FB6D09D99941D18094B7D77719E0 +3957294DBF2D221A49807B44323098D816A21C8F1BB888BC1075E2D6FD770014 +829050B6F8E297853701C467BFE76C1E52E8AFD5AF67B3FAD5A608C23E925F0A +87E7F56B0D05F4D4276CE619190A983BB19417E280B53060AF777BCEA0D1F4D1 +B803687D9C2050D0B2EBF3EFCB9FE42E02AFFA5233A49D27550BF8B32B1A3A3D +A8F58FC0FEC58439D12CC87CEEBED10BB6D9BE84DDDA7FE7A7283D2639212CEE +2DA539DE06298BF452F896FE103A4BF864E5399C3F220FFFD82AF1975FCFA69B +8C9782842F016682EFAE7269A34FDC5ED188E3E6E24FAA0345D9558B19521D16 +9FB47306CD1D15F36C9386347FCC9906A5420F4F6E030B007DF4D311B8E102DA +A653AB0BA300D9467B092165B016C6F28E8D417E1B93A6A176329A67C5E0FF79 +167F9CD8E2EDD33D5B11348EC7BF4C88C3226377DBA6C4723CBE67A7118F3CBA +34EF78E08A4211C39205325D138840E3F3FAA9CCEE74569B8F59CCE7E7D35D3A +0999DB04EC83483BA245883D764A3CF556949BE4B121C6483ED26D192405EBFF +089A97C4BA9649916F1A922F48CD04B7338EABDAEADAC52F42CF48272EAB9192 +C17906F59CD98D5A54443BD474385AF6B51B9C14EA8AEA27AE3473DC12DE901B +42DC378872B001B2C929C54117E6EDAC6202BF1028AD2793FEA3620935539071 +9BA89FBFB98229EF30B0382D9D603BB451634BFD08901432F16B31C9245D3645 +28456E44DC8B6C9C052B9446107116AFCC2EC0F39F0CC7B5E146FD9E995594A2 +DDDE2BD3DDAE13B9659969623BDE57219731BD3893B63B77D5218B94FF535FAF +2595C30EB0A003B144461C8115C5EC93595B4FAC2BC0D084E326E1F2CD14AC0D +D9B4C1467961E2FFC04BD2DCDC1A16E9582653B1CECD98AEB6ABF1E88086FD2F +347774D628A4E70B6803EA481996C07D6D84661D003CF3C92D32791A06405C2D +BDDED14EA55744AA956D04CADD4B2F5A0FBA5D29CFC84CC54CBCA53B19FBB69B +6199174100938FC1E16E7EC73BF85B058E235E282B17116250CC9965706D80F4 +EAE76E2F7E96E901CE29AFBDE9CF9E19CC2432DE75B4E67DBB7BF62C6A3A0C4A +1800F457B745F18AAAACAB8DEA973869EFED9464631EA2CBE95F78133B4DA06D +394AB1CB50B6EED8E868AEA0EABDB783EFB7F0431A450D9C48492A04BFE3A0A7 +9BFDE3B9DB2235EE46EF0D3E754F95819B6FD407E974856C83228A3C2A076966 +02E3F0F2F27CAE5C820FCF3AC5FF38C715B3DC3CE9AC77FB13382D2B606F469F +768D1B82CE3D66F1AB552902B3BFC8E1B3A5A45D3C3D914D304070AAB6C49393 +028CD984C44C2D8DEDEB61AB45A4B2237A236C0D3088AAC5A7F89EDFF9A09639 +8B1887E2CB29D9E9B5630A45879069E58DA52E5603D026339E637D7B83E6550E +CBD4349FB018A01749E171C987C30F8C36651501653A4421869F04A9B8BF792F +41B8D1E9F9101B658EF726DE26BA616A20A667FE7B571F68FF3BC77B127EF315 +64D1D39C07799B45A40CA2D4FD81633368EC9654282A91B8F38066B3D781E8EF +3DCF9CB050096E84E8DE000568251ADFB56AB4AECF2D29BB47110D277E5A1634 +D2813F9239951645D505C53CA91481C2B0F9DB72CC4C3EBB12D2BEB7B4201C23 +BE8EFE99CCC6A0D520F6282F0876E6A69CF5ED38AAA9066196A9456C5DEC58F3 +341797EFFF3040BB65E2479165581DD67AD36EDF24DA29DE341106F258C5FEA2 +D538201FBB6C0C3A132487E01488FAF2EE5A61E7079547278DFA812C5D822454 +F5B0E67620BEBAED135E63C8A55B29F685CA3CCCCBA06E2AEEECBD92E29B985A +23B93078BF8A6F41125529C1C171D7A8C1AFD9017CC089570E45E873E74D7FEA +7193C263492D4A6E73C65531C324FEA1CF9C4EBB6E501562EAA0EC4DA6C5FF7C +49DAFA4DA93D9F35F06616002A66468D7ABF3B3CAFE293A7CE0D1034B971B063 +EF162C62E480D259E5AD6383DDCCB0F2CE0059E7E04681106CA6378D59F3EEE7 +0AF84F11FCB538E4155CEB05D48678FBADF22F99B9D6589629FAAA181F78BD79 +9D97F53E9FC10919970EF6CECCC6464540FB4A8EAD144B5662A4B0BAD31DEFA4 +9C1F280C18BBB0FB005896075E3F4C52C9F11A2C0DD60CFE581C771A72273053 +796AB48AD2500600E3355CE0C91E26DBF8C135C6B12FB3B2E89B9011CCAA3D2C +AF8E2F38BE8D2BD82966B0037DA0B64A955AFC5181D3F2DA0F4FAE47875B5D3B +ABCF48A2C3D1ADF69F5A3BB51D456B19085363318C3D371221A7319EDA644EA0 +C30EB56AF819CAE5DEA47A7495B463E388562FB76C94621CA7F0DF9FA6C080EC +9F8AFC21AAAEAE2359869EFEFC8BCC433B683EE1DBE927463CEB19CCBE08ACA5 +B925E88AB69BF8EB7A0781F57FADC2111659F9A480B8B4158806CDBFD950C4A7 +0D792235486658CF8A96B0FDA2EE36468788225B794AE541683D2F5568B1239B +2956E585AE96E5C0FEF1D632895B3A0E0FCC57EEDBAF40F395547F77D7F4F6D5 +23066F8B08FD44B1F26E89083C70FF24CE2AA2168AA8B09BDFD763286A243BC3 +0431A22C8CC88A524E2C3DB01F1B65A1540A8C2EF5CEA9303DFC3F3CDB20EA23 +600F870EC83EF9992A976ABA1D086EE4018DD3F7004301A7A431694DAAB46B29 +538000BA28F999C70C3F72C1DB5CD7B1A4FDE6A0A87BA98332EDD260D30CE677 +7176F2FE3D12C3434985AB6CC12AB6BD4872F5376AE6E6CA9C4871007D6DEF15 +6D15C96C0433ACFD3AD8DB22100ED3D696FFA7DEDDA0449A14F435E9868DC2CB +8C1306330A68C38FCC010442A51DDC47BBFC474BCBA5A404A5705A0812B1DDB6 +CDE7EFD4D833C7DB0D4C91561C7369AEF96E849D23FBC55429427FDAF7E2DEFD +18E2BA193293C0BF21F7241626E30573F2BB36C0BDF9A1065B2AE02DCE822156 +0AD58921ED46E65C40FE54C0F9C58AD8531536208F5A6F538836B74090AD8191 +486F29CED44DF9C4A547FDE7C40F833887845BDDCC68D84F2F445CD4D871FE87 +52681595A6D0DC3FE1E2B00C53AD686E866FEFC39F6C7FFCAFF8191C13F7598D +3EBDCD33CA548FE31C67AA26568BE071A4D5750F9246695A4184042DB2FB0680 +C0E1FF57CF2E9E360828CCE1C7F22D5CC921A995DBAE0D2469D4C5147D607923 +455D9390580E120416B814A310E5588A0D54160728CCE53712D266C090F7175D +96725CE1CF85B0E3772347D9DB0CB282F04A2CE1A1358675ADAB4F5504554189 +B141B5FA5BE5D2DD4FD88C91AE99E6504365853F95620C378657DBC98D06A4F0 +38DAC9CB25A11D8A9D224C9CAD1369245B4A8E19AA55523BA8402CF056FA7818 +DA762A53F9A2F02F17E23C97A38C14ECC34B985B1EABF9900698E122F11EB4AA +E5524C3DA713271DAB8969B693A7CBD313F28120C7A68F4B3037AC043C992D7E +F5829F11930CA70B580A161635228ECF20C5BA4D3B1587FD7A1D52C2530FAF7D +C2C9431C7B4521296D391A3436642230E0E5D74E6D5E94277373513DC443B09C +0BC80C1EA1F22A7F2F4CE3627E852B5A636DEC22ECE7FE76F53FFC41AAF1B490 +15E8060C6448F07997AE8D012B640630D72B11D77FBFF41F65B6EE2D70345A6D +E6137355349A55DEA19D1DF5551B54F1E2F57E85568954AF23B2CA6D60859F11 +2BCF6FB935BC106B9BBCD8E8DD551D82C7D348E03B71BD4E052686F1A0D59091 +EE647EC64FBF77F558358129B938993D0F75673B45F41D2EDE54FACD6DDEF68A +A8D55C257CEFC0E0D382C60A73C49DEC67AC68E5066749A1C00AE616A2C71A4E +14A95123F58FB1D940E2A0E51EFA67C2F2BDEFF85376D211DD8F15D43F2CCE7B +47B0CE25CEF1D450EFE17476BC3CFF661C1EFFC82E80E0A69FCCB25F39E51454 +E53912FF49EBFB3CD9593F6FAE81F1F0A9E167282286F8407F9F0023538D75E5 +76171E5A7005F053ECA82809C7073A1A480198BAE0085BA9BD1EF107D4324B24 +F96E24BB4FDD80B195546854618282F770631B5F3FEFDC035E6954AE76304038 +9ED525495644EA353E32F65140DEDEF41CD343B5333954D3A4C413BC3365B0B7 +3ED0DB1A27C60237A0E82407753C1F9817C4A0D7F07D328FE2216B34CB8A233D +FC5A420704C8F1D42B57E69424267E5BFA8C2AE65BA324262DDA2812D893050F +7D50D4487A5BBE53D7CF041DA6EF76A659DE8EE07DE413E940AC2160C994648A +6D0D0A59860B4080977C18CF6540B5F5B3B4DB52EE9CDE105D965E9FE5999208 +02CFC8639767B033A3EBC2C42066F6E7CE3B8D6E2F25030D14957F7FA8E59A7A +5685A8A8479545C373DE7778A5A65D7ED6139A2C8FADBE8AACD08D56C42D11E2 +CF01E2D78D4301556A8D7AFFF7642B10A36E93546EC8B2059870C472F5CD463C +DE1DBB211CFF689095891B9980D9C9438FF7EC687DA72C548502B5EA0FFDFEE8 +D982B934E7A9F4D62428FC6719894C0ED4B0040BBA5D0FCBD0E3D80F2E245513 +4C78489DB71F2EDF17CEF7784370D080B75122290F59D4B4CAA0484A809CAD27 +94FE9EDD79994079695C3DC4440C6B2FE4AB0129F2514EFFD8615425F7E74567 +D93EADD0FB68FD8C6B9E3C1275F4FD32B8CFF3D8D04B185E2D702E46289456E7 +F6A2A0D83DCF29862BD08AD7AF05E3380AEE207F44E5B8C11A89E954AE0BAB81 +C5A8D1449693A0E5AE02D0AC8D1BF1CD96DBA29A747CF41292027ED8C05DD325 +8B92300CFA2FD33F14CEE073F8CA93E33BB952B2E58E666B1E12CB5634136777 +6D155B1855064842120EF87ED8B1CB6C95D74EF97C0B85DE90E266DD14793F70 +27038C774A64E5757DE6FFFDE5CCB2BFA4B771BD7D02FCF0530E0768DB74FA39 +2BE1C80A37A77AAC1B4972AA0335A6E51787781D482D01CCE8CD8FC732BFB1CD +69E5BF80E44F7F484277E5D8B9611B8614ABB9C8865F26DA08108D355EA7B451 +2364F41F4EA9BA074CFD14377B092F4274F18F42B30C83ACFAF87513E8CC9716 +7ADFE45917E3B9D034F4ECE1455318BA74C68CD534B7DA3E3C1C94A10E7F5738 +46EF5ABAA7F58C0CB25A1F78F8FCF83DC2FA4F74B7F01E3141E1886C457FD285 +A44A1A57E968358BC29CAC2BC79CD14FEF92F522C9873ECFE82A01BC4191BAA4 +5D5212A7AFACEF600F738F4E6C691BD5D8D6F494187F43D42B1E876E6B12A02E +8357B376CA901EF9945D7703E28549C291731B78BC185AB2D3EA810CAB3A500E +2D2DF7934A616B3B0705F0928D97A742DB11392C51901CAB772C44113F1A5873 +54C74B49EA48DE55089AFFB75B288185955DFEF25610BEFF0789A40381A1ADFB +95628B44B810062EF1AE97D6101B13B020276040FD886422F69A92254B222986 +500384843D82F4A706D5160CE4E35556253C2B73B539D101F166873974CB1819 +26D751F2E4DDA3D93FC7128C2515A75C676ACCAF8C07E2863951ACED319EB268 +87C5802B7C54317A414BF9DA26EA1B53C5A8A9536F54FBA1699E1FEC82403359 +66741ED2B98756BD5DE594C8CB2E0C24BB372EB2EE6F7910940CE6EE8D2770DE +6343C52B4065B6AC1B3DE9C4575B8061DE6F05961E68FE6527BA66EC51AF4948 +8CEEC967F6E376BAA1DCCC8D50DC72C76DEE3644F890D4DFAD6E4B9115F43F9F +25CB66AE9E46331F5233609A390523BFD6D642F6BEA65B7500C3F7C28D03C2D8 +A3B5C7ACCEB8147C62865F8DD2F844F294E8AA932F5D9482FE0A1461EF74BA57 +BF1848B7CE0CD572A583A6837F9D27B6844E62DB08DF1AC678861485877454E2 +FADCD5C87B57340824621B978982092C1334625551D36494A1E0246336B4104C +B823C8ECEB49EAC4F8360867BA86FA2929918ABFEBC195C559BAF28B2396DE2C +5A550C3A17CDB40C4E2A4683590B0BBE2031F525216083CA6D9DE3247D0B77A7 +7348225427CBD78F4C8DD795D010D66B5D16F513066C021F54DB36BC6902190E +53470B96270C8DCE6AA8F836F926EC56E5A4C69BA37EEAAF22AB1D09860279BF +97935068A4D11319146344CA8CA2B87E81D08345C699A81F0F4BAF56E39969AF +A8094449E595CFFAF5CEC0540940DBBF79273B13D1A884BB2C354509CA95E582 +34284D9EE6992D5F3D2FCC09F99D1B8B31B59E651C17A174D98E2AE214332D44 +9F3769402AE4D2E6707450545C6A5322637648488BDE496DFA32BEBA617BDE88 +D07982B6948B94C91247FAAE6AC69A8727FD4AF3154C5F6B435379119898DC8C +3110AC36907AA6653E28D292D7AFE2C3E54B6D29800BC7176B4F0D00E09904D2 +F5DF5805E4C8E4348F017ACB5C62DD8EE155F56AB87315F95BF1A7AF147B71B6 +AE4952942EE6676E491E9CA622504DCE2ADC678AABB16886ED89B6309EEEEE6E +08D8133000ECFE0BE713E5D144DF17E98DDFC61C688E9A89302773835DDF9254 +A4985D6567AF8DFEB4335E1CD6B9D7DE23B009A7BEEED3BE0D7F77BEFB5BF2A1 +07A44B1F9CEF1EEB6D3F49804D6BC47AE26DF2F878E8ED4861FA691AF085A254 +688418486DAF652BE04054A8A8CE7EB42C5B30146C3ACC47502AD1BFB1A92DEE +B3D317D4D2FB276BF7C4F11678AC10CBB01C3DA178A5C101BA5B995D18136261 +EE1EDCBF883B6BA4335CC33BCBB116EE70B1372E01E64DCFD534672D9C84FBDD +79C8D2BDDE8D5C55F852E41FB5C1076D91B0D5C0E13F70B66904E08243EB3287 +C6257181962EF755E7A603AA23D63BCABF36F18684582182CC20CEA4F1DA0665 +1A8F1A4AF7C09F0351460919B10537E19A0ECA06DD9C00368137115979D3058F +635AEB09C1C561D020B5E19358F216B8D17582A518FDEB4AD7735FFA2C746D5F +163CA092531649865A1DA92CC8FDA32490826FAF4A9BA50653C2C3667F61D880 +D6523745251E5BADE8DB233EF3A62E7C92F21AA31618EB0A08D47F406FC07C54 +FC04404F5BF9DF6D2D53334DC93138323AE39E95F9CB2260995970E173C997B3 +414701687EFD2B2088E50ECBDF14FBCC612AEF323683D2800DC9B9EB054B8CF9 +3295728A29DFA6E22A98540806D6C89C34B188C29723DF52C50BBD4EF02C887B +335A72FCC0F7C358AE98D26FF44F1632CAA1774AE6F8852EE8BC262FF77A2E24 +8B8833B7C9F9C65F94C889A06095776BC75361EA184FB5C5C8BA401A8D2D9771 +D0D71BEADEDFD70EF617B92FE12FEAEAFA5F6013A28DA3C666BEE78D103C8947 +3901E3B368BEE322935020EEE395B9701F65615D510809D8AF431537805E0B5A +908D1B2C659582D0E54294B5BAFDF5FF482712179CE8F6820C3E72A73EE0A4C2 +43F792139AE3F8AB7A8D22A6D4C222B2B63CE37F0431157AD317A63E05067704 +85F00AD9B822B1506DA0551ED23099D53CF6E5127B5AB291B33EFBD26C21E9AA +4096BA233B76B87EEFBBF8721EA8C37B933C5D901C4E451C1B05A5920CDAC618 +F9431CEED497EA77D36CDF497354E158B9016CC888DDE52EF00302C140B29C1E +392F5974EF8EB272C054F3906F5D0B493C2478AEB0A83E469850BF76AE766B4C +884B10B49EDB704B33AA96020522C5218D7258210572E8B274FBB05B51380F5F +DD14C84263128C4D037314263E8B1ED48C816F06B39E32D5A894E114F9B1A2EA +F255A824E41E768E70ABBB14964CA00C9C3EC281D14BCFB681BDC943BCF320C3 +0DB0B18DB7FDE42A3A2DEBF1746BE74E18BC0A8081DFAFDBCD64050ABBBF3A66 +72D0D7CA15E88ADD7F0A1E1F70D397D838AFECE3352FAF0B149D5DA940B518FA +9594FF7171A2A6B77CD9CF96143DF0B708EF569CC135A2240343B3B50633437B +BFCB0416AE4AADA1A480CFC134C1DBB9472279C4966B99B473272AF27150B683 +09C57E73A83E7339B022F0388640EF9561C518AF312EBF0EF120CDFFF9F193AC +FD6FCCCC337922405224C8015431BA7610EDE19CE9176A6C1F552AC7413ABFCF +EAE1FC65D1F56917B125E498E37D2E7AD170F0DB13DFD8D430523B774DF5B2B9 +971F0F78F0931E81A848B1E2D901BA1A011957B56831AA755DE617F8A40B843B +470D96140340CA72B45C2BF0C0CB635A6D7B25A084AAD2830288ACFA119C3AF9 +5ACBEE18F38B244B7FD29210A7BF1D3A5EBD17E2B268C36CF439120B1947EBC2 +CF769AD003B5C178F5FBC3AB5D901682CAF6AEC1D66B3F3DF730C27F09430EF3 +81F32BE8B781338BCA00F8BB26B89804E750BEB587E558F48AE34A0516B1B4F8 +342A97EEE51D352EB39C2819A200790E5BEBBC50EF4FC2DB326FB61F969EC2C0 +98CD4F78F741C11D2744F26B443302392316A5C80A9ED122C7D27C1C89D098E3 +ED365F63F5F2DB3889A4EF86587722E0059123D95D3C471751D8D8E84941BB5A +D14766DF319F648FB6FC6B39E94450BBAD6F83E43B92B72B5D4F6804031782D9 +9E40AAF10A291F048E4A0B6E726B1E81942615DD0A862087048BD3B261E8F8A4 +25D82D60D90E58A9F3C03CCF9593F0A8738F397E058AC523438F58B2AD206B85 +98B61EB8536BDEE0219867CD40F10F622EABC2D52297B257EA0DD929B0BB7938 +158C6A6BA70514C27CAD1F310C493FEFC4EBCA10CE4F77686C374E1125F69855 +9869DB3887046E82DA5EDDBDECABBEF1E050B26A91A6CF23D3F06B4206D5573F +EDC6CB4DA302A355CD3CF80B54B270FA1844FB5DA988D08A754D30CC5F8DB71A +5AFF8F08A67CF1DF916E3370D961EBC802E2AE36B25F0B2BD2F01DDC9D52E45E +18D579D5C4A750F27262D2364A1CB2BB36B116626A9C1B6E35E22E81FF695E1F +713B9461726F5427EBABD116AF543A84981796A6FB49F72BB7AEAB3ED5E771EC +D1823DCED5EE170126069B5369BFBE2C1225EF0E5757B2ECC2360F8EB34EE3F2 +5F39660B7F714A82EFF3A61B7C9D26A32FD4C6EEEFABDB9914BF7DD6FB240198 +81F121394D0C2386738E343645A9CE8585145ED6957EB06466D5037B1FB3210D +CF0E79E0920167C5DBD605FDF8A66F96CE741DA7DD85D629280681BFFCEF027D +C9390E2EE59F24B714AE30A802F112BCA54AB3FCF419F65B4B27CB7A543B55ED +350D9BE596B15AD6F79730DC86D82DD075390869201CD6FA2D22F77FEF7619AE +6FB42C939079757BDD06BE3B8A7CE60AEA6D1B753658F858AA2584D4A4CBAF7F +0D701BBC0379973E5753B848BCB7CF56FF30E5AC9D2E83F33B4D87DA222F1306 +1877A2D810123D951F5CE12B17862EFDD5A86DA3A3987D973F70541D70B0CB3D +0F214AD4B1FFFA21A29E5C7856CE49FA93528E6D53570E3D8D6B17FA27B28AB3 +FD2BBCC63139FD7F86DE6A84A2204CB30FD14080D45EA14EA784A5471ADE8A70 +2864A204F10AA5B2A2C870521B97AD75CE3F6C078F1BACC99BBFC0E2F2387022 +6F72BF44DDB62051E1B81A1419CF2F033B77FE46C2208E7DA22F83392EC97328 +9A5E3DEF8BD6323E16D1D14FCFC890BEFAAC9F879198703CC9FD0FEEAA639EC1 +B787D77B325A213B71ED56F1ED09DAB41AD4F8E314FA8154BAACF480F110B939 +86477401D4ADE8FB386CE8959027A8B91C56C53028C7AD7EEB61DD986D303572 +86324689E1AFF282E10F789421EC2BBFC5637C18936B1F2B4D02C8C8D5039503 +D425992185042BA95E50C1ADD6563F3C661AAAF32E29F9A4BD3ADBA7A55962F9 +ADD1CA560CDF1D0BEF0C57E4EA11587CD17CAB65D9DD830CB738549C6F614612 +FACA8E3031BBF29682324D9E12BD792F9F3A24D7250D2AB55FC984C6CDBE070F +5F6282F51C4DEAD075EFA1820E044174FDBFE9AABB68E6254F4B59E8F442DBCF +D2AE8ED7ACAFDD486DD36530A71F9A0D9FF91B19FAF723B4909C9F99D5CB29F6 +2CB6384D6A3544359AE23E09770F82FF7C9327BD392BD0F6B607897B6D826C50 +B84B2E0722B0E27ADD479E5607B4500B8ED238B99C40B2BB47F02352BDACAD22 +262DBFBD38A9D4D1BAB79053F4F172397F8F7C00CBD37164A0C89B7E31EDB3C2 +25D32798F7F83582CCA56EC0F419316EDB2F3299E40BFFFE63C5C2A0ACDE3C9A +414C2BF9F341F40FD4C8A5B1950EA05A26DD6CE187E181E6AA167F26AF6A4E5D +6E4AE9430E0069749299D2B9D8ED6A3E647BF2707948B8F6D16D4DFBBC71BF61 +5EBDC349EAA9EC847880990795B75B118423D9153B05D5D6BF0D20E909F8B22D +0DC9A8CD8CA5B43D92F4CB54344B375926EEFA5BF058B7BAB54B5C97BD5653FC +F05BB1A5991B068C26B0420DECA3F1943D093997A3A8BB061BB25CFFC0CA1DCD +D85CB203491D176C4C183BB124F22D57678841F08419028F250C30D1237A0213 +E5A392E60B27D2BE5240346B99EAFF9B57F108DA66B6AB826546C6411864F7CD +FE404BEE7D58F28580C448AAB599D12B00A3FDB2BB93737971A5E13FD7CFA970 +656D45145A54DB1BDD0701097445ACF73861FEEB01835F35790E98EF162D5F37 +E1A5617CB2BC22074305FBDC8024E741C7BB0FA0E17F123AF953742E01AF56A0 +AFF35515ED55F9621AAA309D36964B0187F56FE1F02662B7DFD745F2F3BCCC43 +23C8FFD86F382E99DFF681A7D22CD133153C40C3FCBDF86DBED11FD36F9B6B3F +C5FDBEFB72496F3C209BD61EB9558CB2BB1672CAD20F8086DF3492147770C0F5 +95444F8731D0A1F6036C3099DAC1993B2C838794554E8A3B96614090077BAB84 +A5954FF123B90550E1D1A4AB2CA6E8214C473B3CF56746959C062AB6C28EC09B +F22096BF7FB6321338085C726C42745088742C6C55D4EA9854CAA71E7BAFEC70 +A29302F3C909A6E193ACDC6496F7929713DDD1F4B813D73E794377CC0B956B9C +2D07F5AF8C01D7D9F932F114547E4EBB953ADCED8B20E83FF41B1F4FA5A02915 +0C63DC0619BD7E3B5E6FA60157A21794D7299C5774C0FF7D929FE9BC1EABB7F3 +193E83584567271D2123E6B069DC23889B93CBF2ECE77CD8CCD649AE853784CE +A79337DC93FB835373CAB6E183F5CFEDCE41C418E8C0C48D02BDDDC0CD3D3B4B +285195948E36529F28A6F925FDF16248F9331CBF5A08DC0ECDE2C36FB0A0AB89 +2DC0EA84D93C834DDC1BF962D89153901ED7784D9E68BD06357F279CCB7C602B +CCF4F389CD7CB4197D2A9271D65097AC31DC2FFEF76884D0910639237DD0C020 +F0DDBBAD4C2883ED7313DFA0539D989C1FABB6A5ED54FC791B196AFE787363DE +6688123AAD777F27E4DD19C32B7FAFB9E8E0552DE8DA00570B1AC3E8CA4C7866 +6CDE42B4CB89FA30F0AC964EC7F2F77D3F1980313330F26E545BC7A8F5072AA3 +2FB0329361BC8BE2C2B48FD060C89CAED6DD9BF6462BEE5FBA59C569D8EEEEFD +8C1BBEA3B73A537D1256C7D6CC73A9CFA3DDB34252A3A4509536D62802A615DA +4AC07C441743B45A15D85181482717316C5B12F6E52BF9A1C287801F3677CFEA +6B60F505F9234792D6E82DCE04C72C308C0D17B80B14B291AA4B437DB7CE5E35 +436CC41F868ED6162F25BE5C9FE406FE656FAAA39A72CD5DE07569F21C55198E +54678E869AB6959798013ED6CB2D9CAF568F43D2624ED5F602B7F16F9D27C12D +48EE40743E1DA97E6702FC3634CEE0AF9E6FB3C4587346219C4CDC0B6CCF7F99 +E40B48B6C7F5BB21DA0656B683E71A4CC310B001A180D9E00BE05215B170D2B5 +C93C2636B61BA3A840076A5F41B8DB3E0B2A38C3848DB0CCD419E5E14DCD1397 +AD9811172CE82D7CC7D825B6722205E0072FC807FB647E0442333D42CF799579 +C7B7406D147A69C59E03536C8D2EF372AD9C374B977CE1A0784C83073742008E +480EC148196F146C5278FF1EC8252E708B9042389C11547351F093CA8488DC3E +1D5126DF92EB568240BBBDD84B2129B6E8FD6999651566BFB632984240E14F4A +E8F5BC9C04ACFB19B327E5246E5BA5B73A0A308D930A588E57174574ED2FE506 +9512E3CD02D75AFDC1A36BFB585493E4164F3894679FA602BEDD8CE9E821D187 +47CD38794364E336B65126F61CD3CA861F9919F24CDD410E5F03AAFC4E45E30A +486F6CB28CE7A6A2125C55CCCC11BA659E72234CE9D5F6 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +cleartomark +%%EndFont +%%BeginFont: CMSY10 +%!PS-AdobeFont-1.1: CMSY10 1.0 +%%CreationDate: 1991 Aug 15 07:20:57 +% Copyright (C) 1997 American Mathematical Society. All Rights Reserved. +11 dict begin +/FontInfo 7 dict dup begin +/version (1.0) readonly def +/Notice (Copyright (C) 1997 American Mathematical Society. All Rights Reserved) readonly def +/FullName (CMSY10) readonly def +/FamilyName (Computer Modern) readonly def +/Weight (Medium) readonly def +/ItalicAngle -14.035 def +/isFixedPitch false def +end readonly def +/FontName /CMSY10 def +/PaintType 0 def +/FontType 1 def +/FontMatrix [0.001 0 0 0.001 0 0] readonly def +/Encoding 256 array +0 1 255 {1 index exch /.notdef put} for +dup 15 /bullet put +dup 102 /braceleft put +dup 103 /braceright put +dup 106 /bar put +readonly def +/FontBBox{-29 -960 1116 775}readonly def +currentdict end +currentfile eexec +D9D66F633B846A97B686A97E45A3D0AA052F09F9C8ADE9D907C058B87E9B6964 +7D53359E51216774A4EAA1E2B58EC3176BD1184A633B951372B4198D4E8C5EF4 +A213ACB58AA0A658908035BF2ED8531779838A960DFE2B27EA49C37156989C85 +E21B3ABF72E39A89232CD9F4237FC80C9E64E8425AA3BEF7DED60B122A52922A +221A37D9A807DD01161779DDE7D31FF2B87F97C73D63EECDDA4C49501773468A +27D1663E0B62F461F6E40A5D6676D1D12B51E641C1D4E8E2771864FC104F8CBF +5B78EC1D88228725F1C453A678F58A7E1B7BD7CA700717D288EB8DA1F57C4F09 +0ABF1D42C5DDD0C384C7E22F8F8047BE1D4C1CC8E33368FB1AC82B4E96146730 +DE3302B2E6B819CB6AE455B1AF3187FFE8071AA57EF8A6616B9CB7941D44EC7A +71A7BB3DF755178D7D2E4BB69859EFA4BBC30BD6BB1531133FD4D9438FF99F09 +4ECC068A324D75B5F696B8688EEB2F17E5ED34CCD6D047A4E3806D000C199D7C +515DB70A8D4F6146FE068DC1E5DE8BC57030ACE57A0A31C99BEDB251A0ECAD78 +253AB321023D15FF7F55A3CE81514C1E7E76240C1FB36CD4874DDB761CC325F5 +D588700B294849D690F93526EF438A42B9B5B0508584EA3766D35F5B8D51C458 +ECB9FBD23A49576EAB06BACB7EA6D300985500835F4FE597D4A1110C8EABE6FC +CE3E1F95CFD3A42446F25355381D476B2FFB6EF247BF58A6FFC5EC0E4CC207BE +46485F8E07350B37DCA8C1864E62614332A1D3C9DEDDD6492181949A2C3498C9 +EC2A81C1F4FF989A4654E375F509D24D969B97D2A9940FAF43BBB286E08559C0 +F8D9674B0A294B36D3A050F7DED8C80E1D230812F6B8387B17948FD29FF050E2 +AAC5EBE5D96AFD0879534E2F4BB81613A1571750F9CF4215199F93813D815B5D +1C79E11A0FCBB627CDE569F88C741CD502627777BB058ECAC09B6ACCFACA69B9 +8F8168B0B5A1A6EB13E884B348FBB2ACF9EB180F6E27D57F8503710CE037A34A +F8B157201657C825E2A4B4A7696B58B7A988C05E43E66F0FF277A7694C555C54 +AFB1D32F6DE102136FC810E1F3B5CEA42476EAC7AAFB390E3252B2169DCDEE6E +328507BD0E24734A85AAA263E0D2F64BE1607455BC855785BC27F8B30FE917B4 +23AB3C812975355942E955501AF85A3C0CE836911AF679EA44AD6A7D042A6549 +0C471FE294E8490024D93ADCADED460FAB7FBCDC29EFEBD2A9A127E11869E659 +961B29206CE63944B6FA4B9315BCC528EB1E0223CE94C795A5D5231A7FC8545D +6B287B965F8EEDDB67A6774129DD01D5A21694ABE320BB2553043D4C42ACFF91 +1009372CB03381035BEEEEFD05631E026A0980A72A67B3703323A4E7C94FFCEE +8D0B7407F9CCC043D3D184BEA4728385D6AB2FB0641DD8F5BA7E04035D30D628 +7E97D31C1486DFD5B1D076B84B4ABA4829ED4310321F1F24B847C44E00185A69 +37711A +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +cleartomark +%%EndFont +%%BeginFont: CMTT10 +%!PS-AdobeFont-1.1: CMTT10 1.00B +%%CreationDate: 1992 Apr 26 10:42:42 +% Copyright (C) 1997 American Mathematical Society. All Rights Reserved. +11 dict begin +/FontInfo 7 dict dup begin +/version (1.00B) readonly def +/Notice (Copyright (C) 1997 American Mathematical Society. All Rights Reserved) readonly def +/FullName (CMTT10) readonly def +/FamilyName (Computer Modern) readonly def +/Weight (Medium) readonly def +/ItalicAngle 0 def +/isFixedPitch true def +end readonly def +/FontName /CMTT10 def +/PaintType 0 def +/FontType 1 def +/FontMatrix [0.001 0 0 0.001 0 0] readonly def +/Encoding 256 array +0 1 255 {1 index exch /.notdef put} for +dup 33 /exclam put +dup 34 /quotedbl put +dup 35 /numbersign put +dup 36 /dollar put +dup 37 /percent put +dup 38 /ampersand put +dup 39 /quoteright put +dup 40 /parenleft put +dup 41 /parenright put +dup 42 /asterisk put +dup 43 /plus put +dup 44 /comma put +dup 45 /hyphen put +dup 46 /period put +dup 47 /slash put +dup 48 /zero put +dup 49 /one put +dup 50 /two put +dup 51 /three put +dup 52 /four put +dup 53 /five put +dup 54 /six put +dup 55 /seven put +dup 56 /eight put +dup 57 /nine put +dup 58 /colon put +dup 59 /semicolon put +dup 60 /less put +dup 61 /equal put +dup 62 /greater put +dup 63 /question put +dup 64 /at put +dup 65 /A put +dup 66 /B put +dup 67 /C put +dup 68 /D put +dup 69 /E put +dup 70 /F put +dup 71 /G put +dup 72 /H put +dup 73 /I put +dup 74 /J put +dup 75 /K put +dup 76 /L put +dup 77 /M put +dup 78 /N put +dup 79 /O put +dup 80 /P put +dup 81 /Q put +dup 82 /R put +dup 83 /S put +dup 84 /T put +dup 85 /U put +dup 86 /V put +dup 87 /W put +dup 88 /X put +dup 89 /Y put +dup 90 /Z put +dup 91 /bracketleft put +dup 92 /backslash put +dup 93 /bracketright put +dup 94 /asciicircum put +dup 95 /underscore put +dup 97 /a put +dup 98 /b put +dup 99 /c put +dup 100 /d put +dup 101 /e put +dup 102 /f put +dup 103 /g put +dup 104 /h put +dup 105 /i put +dup 106 /j put +dup 107 /k put +dup 108 /l put +dup 109 /m put +dup 110 /n put +dup 111 /o put +dup 112 /p put +dup 113 /q put +dup 114 /r put +dup 115 /s put +dup 116 /t put +dup 117 /u put +dup 118 /v put +dup 119 /w put +dup 120 /x put +dup 121 /y put +dup 122 /z put +dup 123 /braceleft put +dup 124 /bar put +dup 125 /braceright put +dup 126 /asciitilde put +readonly def +/FontBBox{-4 -235 731 800}readonly def +currentdict end +currentfile eexec +D9D66F633B846A97B686A97E45A3D0AA052A014267B7904EB3C0D3BD0B83D891 +016CA6CA4B712ADEB258FAAB9A130EE605E61F77FC1B738ABC7C51CD46EF8171 +9098D5FEE67660E69A7AB91B58F29A4D79E57022F783EB0FBBB6D4F4EC35014F +D2DECBA99459A4C59DF0C6EBA150284454E707DC2100C15B76B4C19B84363758 +469A6C558785B226332152109871A9883487DD7710949204DDCF837E6A8708B8 +2BDBF16FBC7512FAA308A093FE5F00F963068B8232429ED8B7CF6A3D879A2D19 +38DD5C4467F9DD8C5D1A2000B3A6BF2F25629BAEC199AE8BD4BA6ED9BBF7DABF +D0E153BAB1C17900D4FCE209622ACD19E7C74C2807D0397357ED07AB460D5204 +EB3A45B7AC4D106B7303AD8348853032A745F417943F9B4FED652B835AA49727 +A8B4117AFF1D4BCE831EB510B6851796D0BE6982B76620CB3CE0C22CACDD4593 +F244C14EEC0E5A7C4AC42392F81C01BC4257FE12AF33F4BFEA9108FF11CF9714 +4DD6EC70A2C4C1E4F328A1EB25E43525FB1E16C07E28CC359DF61F426B7D41EA +6A0C84DD63275395A503AAE908E1C82D389FD12A21E86999799E7F24A994472E +A10EAE77096709BE0D11AAD24A30D96E15A51D720AFB3B10D2E0AC8DC1A1204B +E8725E00D7E3A96F9978BC19377034D93D080C4391E579C34FF9FC2379CB119F +1E5BBEA91AE20F343C6420BE1E2BD0636B04FCCC0BEE0DC2D56D66F06DB22438 +452822CBEAF03EE9EAA8398F276EC0D92A7FB978C17805DB2F4A7DFBA56FD6AF +8670EB364F01DE8FCAFBAF657D68C3A03112915736CEABAA8BA5C0AC25288369 +5D49BD891FABEFE8699A0AE3ED85B48ACB22229E15623399C93DE7D935734ADA +DA7A1462C111D44AD53EA35B57E5D0B5FC0B481820E43222DB8EFCD5D30E15F9 +BA304FA879392EE0BCC0E1A61E74B3A1FC3A3D170218D7244580C7AA0DC65D19 +741FA5FE6F8CBF60250ACC27454BBF0897CA4B909C83A56672958752ED4B5E79 +E18660764F155E86F09EFA9F7685F2F5027EC85A775287B30E2069DE4E4D5712 +E7D033481A53A2702BA7542C71062173039030CF28D8B9C63B5596A9B42B33E7 +D922944A38713383D3648A4AF160A3B0C8F3379BA4372BE2E7EA49AABA75AEEE +C5DDE1D8BF68483C3D21271280ABB91D54CC819680322EAB72E1250A760BC8DA +726405EFE420635B5B7F0B48752C06083E92BDE06401C42A2C528C8A60381227 +CEBEF0C9440DC034DAD9C19FB27DB399BDAEE22053591D6538587C768C1B7B0B +7D1E222D2D8AF3A6473CC4C0D6C3E0DB49068CEB8C9BD1C5CD486A50DAA10BC7 +7D6286142355E3F21DD254E27C00C442728A0BAEC9D3F17AE9CE320D365152E9 +EB0D5E3874F2BCEDA98521D23FCFC30B4B69DAD2ADBE80E5964ED0ABEF6C73B6 +DAD30E2C5061E3747FE536E1A5D190D028F2130AF608F5DDF9DDDF1E77DC8437 +ECB3EC93B33505DF47884DDBD1DC6BBE4098DF04A29AF6FA3AE344600D0AAB53 +B3820DD7ECB600A3B8001C51AF2CA7A39AE1485A087FD1752DF68F55B52B4DA7 +48030F2AA7E570B3D56C4EAD367B9B73FBC0A7356253233006178B9A6BC19081 +B815B5988AE76FE6FAFD7AC239072B1106A3F509381AAEE79B2F2154CAC4727B +D199CDC8B4D05DF4BA006982512ABD7539E28D937B0F87FF79A3F84C29ECF943 +A8DCB8BDF8EA9E7A0E7CD60BC2308C96B3E889C797D0FF28FF4847016B3DA141 +E76FC6BE78A6EE9CE07E651FF86E720A1A1F075972D36E5C55162E3FE26BCE3A +814BFEB12D4C5FD24340CFFED499C7CA183E57EC4F12CFFBE3291D43F7270575 +C6C3306F832EF182ADD0AA14C4D8669A17C09F632406AFA195F90C4DDC39779E +EC0A77E590211592D6EE19563963225C06C2F13265EBB5A6CFB7C17D9E77650D +11958305727AF662AE73AD0E3ED5F7E7086C5A0C3548A8129575980B06C715AF +DD55C8DF869BED0A7883491030B1A7E82C5EB04E5A7D952E716DD8F2EF6275EE +087614CFAB55FCE2BBECD7E8D9C90FD8359E929D5E0A416A23BD58158318B4FF +87B095EB63F7F052B3A77F136FD66EB2C52BD46CD7DB3091A4B78A607112B12C +4D171B2A00B78B0E1C44B0D90C20D9244281F5123DC1F6063F91E9E3E48DE78B +C862D848BAD073A4FCB5EEC9FF54B5AB8E234CCC3C7439C62ABC4A13EF1B8897 +ABBF21F900C564C9A305FC36FC7224932F766E6E72C2EBB55953DFE2AFC2E3FD +33A0C6F0FDFF086E9FD796E7242596AE85B877223532667625E371D2156E4C04 +0D7FFCD3337B93DF066CB6FE1E13960719EB7CB409EE805C08ACD2C06303ED9C +E34C898787A43C1B428B896551C6FEB50A831C6F8CE2073EFC662EC286CB7555 +A3B42E58772E82FEE206948B8C439FEC5E4ECB9E11DC3A4CBC7611E30890E408 +637A01A2118441B4F9467A98BB2A1B03BB2F5D8E3DB7D1D15C188D9E856088EC +B762F07B1C06024F7EF53A2FBD60C0A1F4C0275D07164545250ECEEF8CB15B04 +A2D8AC44DDE818C4E3CBD2A5FA0FE49750886CD7CFAAF8B780255F89DF7F4F5C +BB594FE7C1597DA71813C2952AD3E811524459EB71D29696B450C924B6A5C843 +8F36A0F1D7DFE796FB9564333666D74AE614D0D698FAFF20F83C86524C894BB0 +272221C060544F3B653CB0E4E4F82B20D7530B3806E6A5830852C58070177815 +E287C847F19F64E854F1463C23DDD80093D6FEB8BAA22C5F05C21F99FBA7193A +EB7CD49CFDF4308C6C68CC955A45FCFB54FCADA9A3BFBDE086B057DE88BE335D +280F5338D7E66AD39FD08F9B55884F1F377FB6869FBABE3EAA4B7ACCD85BE672 +724B4B8F236B0889B6E7049CBA558A89F17863E82DF145DB8C7ED1F36332DE23 +3C0053B74E850FA14F9EC9EFC23AF18E153CC96FB0FFD910347370E57F0D81E9 +4A83E2D189EE5635E85A2BEAB5B1CB974546BFB2FC2ABA1E15DC0EC1BB3AF1DB +B2F93538B92F504CBD7AAFE36F5F3AD45EB16378F169B17869FE81464CB826CB +400D2F5441A496B6C60A4F15FD20ECCAC1F8F91015E7E1C1A10B7992A1554E52 +9FBEE905A3005336E49CB04BA7223F1674C0BBDFA06ACA34F7BFDA56906E04A7 +4DD79EC7E79B021A5008F3B1E04712D689366F520B0FA66A558F957011992728 +561BF4B75C2BE07C4024C172085E51CCC5CFA439F570297154CDDBB3AA25CD6A +3004B936488851BA1E814260C06CD5479DCAB1A6AE21A5F4563024F973D738B4 +0DDB6C6DD2E3AC21B4F6D95CF9AACA782919F5D3E613D61F3224A982AF485C8D +EA0037410EB70AB7D3EC174C6D5DE5C9C5A1220EF7C2B74499ADCEEFF077D1D3 +50C1124535F88C3C3F66477E42F1932665AD323E06B398D2805B9CEA632F5B1E +50FA587B102A35E2F15EC22DD66E4DF06A3F4BB717A3ED7FBBE2458EB4D896DD +AF00D1BC71FE1CCA27890ECBF9F0AF01D3E65CAA29427FAF06B3BE1E640522E0 +73B213D04491B93DB29113EF72211E31F4C5A7FD58451946CFC15FD805112FE2 +547D1131A46710DFB75659A33695FFAF3CDD40AE5260AD6766DA81DAB0A6E96B +E89D57AAEF32B5EDBBE9F7CC033BB2595CA3FEDA2ABAC8E5395EBC35BC112FE9 +67EAF1F123228538091483050847F8FB5194203609502D3A09CDE811EADC18B9 +F039593782C27EFA7697182D6367E88E326AD5622C5A457FE2644FEADA88615D +9DE3E483BFD9329667953CDB86F9D2F0D4F02DAB8A98FDEB1D17CAAED9B6E2E6 +0C55C1FEE25AB98FF59FC235876029CE03E4A713B75B3163BE3B2DC0D4472DBC +473E10400C0F57E627AE97FD0C1CB0F78FD8E2FA831A3D2B1C2BB3F2D4E812A4 +194C8732B0C525361DC8480CB27C30CD4DCFF01318D2EB4F5234B4A42EA8C23E +7B3EECA41B8E4F54D5458B37EF0FB2F49EB19F4EA8AD2B53820FA36E93DD309E +48847F5C01B1118ECE7D0186E6B8953344EB775D655AAAD7BCDA642EA2E39A15 +855C027CBC0E3FA752900EEB464E2D39404D1B85072B40834748C6F9C74C5B6C +3CEDE988343FD984CFE4B856A481E60E2E65D3BB41BAF2FA80AC0BFE381071C4 +573C6ED65C524FF777F34D82E9661E4A75E3878CC77BC59218244612219C5A92 +E95B90EC2C38614665550026F1730D11162F19D841681C04C401E102C047541B +97B9264D86F47E25A347696AE5EF0FF3ECD9BA32C92901DEDD816F7D73ED1216 +0A98771892472CD625A8F7F19DEFCF5CA2AE57F8AD3898F2C1005B187DEC6F2A +A31C32720EBC934178E0E9979013B3C9AEDA4051DF63D8C903A399DC88F83DCB +A73F1B2083819D1BBEA5235F8FE1D098F32A2BA6274424A99A4975FE4BFD59AD +79B40A8003CC0AA728EA79D6BDCBBD73DF45B7918BC099C5BE4A068BF64A30B1 +C39442CED98AAE1BD495F6CA32D564A72E3BF753B49E4178927E4BBC0F06048F +96DE7C30AF580B0BFFDB330B3B87D7F6532A24F403680BD9F15E758CDF04EB94 +E83C7E644FDE5BEE7CE73EFAC75669E41BDFB20A5B8ADE1137378DD8102A0DBE +19499A623770417CBF5211395A6BA9F4490F4707A46F1F9B3FBE642DEA0CA053 +9ABC307B1E71DC2B069DDDBB4EAE378BCC75AD61DA900AF8BA6DF0E27A8D2258 +DC80205305AB6ABFE3726703E60869BFAFF1874F3C0E05FAD9C05D7D89ECECA9 +DD2AF5F777D7514208697E712B52448B364D3ECEFD8127043DDC9D0757B7CC37 +5CDE8001D007A6E961EA24D7FFC92410F3B13A32946F12A50DFFA256249BC8D7 +C1842FB84AD51B41008EC4604F6B70990510EE13E6DA34F864A572D99A13FFC7 +3609EF2BB1FCDEDF37A6018248C545E086EAD1BA1143E74AC60B684E755E59E7 +36557B915F92EF78FC177621D49F777A2AF39F3C2AA6EC74750AAAE08BCC21CA +A71CCDC91DD45E6050D83ABA49ECE425B55EEE137C55619037F1C30530BD0A6E +CD2004B6A040405064D7E87C55536680364E09248BFAA3FDF95CDA0708E55F4C +F7D0A92A93DEE0C7B69638F171B28B7F854CCC6EBC6AEE14864BF5144EA36D46 +A9C297225AB0325E28EF6BD06D7E40E3A724EA1E50C4C6163B195CFFD5DD291D +D7BBE9AF4324A69394117EFD62F08D6BA6A8F0AC3E2353492999AF28FBA758C3 +A50B6840CC72054355E6CBDBD86F683537A4115049BC1616BA35C2B0B6F5CC32 +3F6831DE4E6029310738DE23D36D2C6E82F04EB675FB89789F74AFE3B8854250 +51812FBEFBCF162947554324FADAB765C74B6DA89F60A734076D44BBE45263B1 +3FEFEEA90EC7948F23F34D4049087AF6563692417DDBCDD5A9552A373C2528F8 +0318D3C0669279F292127CBA40B0ABE08A1476BC9EBFA8BD5D622BC5CE7DBA20 +C689BDAF50D5B1EAA89E296787CC53845DB2BA54FDE363DCC98A7BA256663869 +E9E02E09077884DF1A2A41AA698B7EDE8DAFA621B552DDA91AD1E671D636FB36 +91C62B4D2D4112F2C169E0023EB7521F570CECC54ECA5EBA462049AABBE2ADEF +E3234BFD71B26DFDD9D34DFA69E5E80FD90406E6505A6798F030A4B5172A7BC2 +C9B765A86ED55C0590E0432719BCD7BDE7CCC7F6B33BD467063D886276C8879D +E04897A4623111C14A1EDBBF69E2FEDDFEAEB2A785C6D2F0711DF4B93AAA291E +7F4E0CF9CC3FF0D31953C594DAD014097DA02CBD5AE8828C7E7B5BDA09188B05 +0D7263F164E1E78CC430ACAD1E8FA71001E9BCEFAE47C79846916A5F819CA366 +5734089BCDD458CA1A9E8E17BFF357A91F9A7A8A6E1DEFB121353AA80F1906A5 +AF7CD2E59EE6776FC0DA6574DA0DE522918CAC4E566F13FB9B64EFE79F3A3BC0 +689E3B0676741C90FF3BF85C7A0FA9716F4ED0E329512B66BFB8AEB56C3DD6B2 +24F8D6E23751A8485F7EB46719E9D22618FEE86D5E01ECCF4C6E74368A8E9B49 +245D80E7484DFBC916FB2447852B36EF3F99A82B6C106F786707D7689DCD7AEC +A0C51AC1A3F67034C16B74994403FAE7743BF02149BEBEF554814BEF31B79184 +3FAB4D2C887E1BEE81B465D12DCDDAD03DE5ABE9E763C440B2CFD42FD16D96EB +C21FE788C8C2688F79F148AA7090BE64B0EA710D376222FD1590301BA9A2E715 +D33B8C1D95F2589AB0EE476F7046537E27DBBCDADEA1E7357C9D7FA92C2F93A6 +7BDDF58A44966590821023380C97CDE37EF6D449E35EF32BCA6E69DC8458511E +8DC8AB63171A6018AC9A334829E5978484C4C6E917A5F1C254E6669F4037C691 +36980250A80673E0F18C9E0FBA1E5CCA3BE30B8E7B7188062B25F8E1E16528A2 +F217C18D6A1955482E5463FBF097ABAF7314E449C6FEE56E2695407A8AA9648C +61AC2BF3B2D9CB6317A9B16CE931D318C8BC9676CD908505568C197D90C2BB46 +06431C999EB68C8216409E4CABACB2BB34A05B697B9DD1E91471A404B4969519 +E25209EF4EDD420944BED17B18DB3566FCB8059699FE416789191EC2B35086AA +2E10C139E3C9FA0A535DEE9255A867A26656213E85851DE5F51F9780D3A6E572 +F1F5CE64DA176CA810799DC1C60A8FD2A5ED42E613021A19928EC4572059B2C1 +EE441E79CDF7DD4AF7B6E3D3230419ACAED329388044B107DCB4DE91B71EB838 +904B1F969738BBDA064FFE75C6623639BE9924602DDF0C166B433B9D54ACDA5E +018680477FB8F10621FF32319E58DB672D744959A33E7314A1B3CDE0C038F7D6 +0C8A195AF191E36B0325334A711CD8E25D9C1D257E46A734779E486567481108 +E0281DE96907D460546578DE83A0A01A9ABF64402B48DEF739F4308E14145753 +719CEF720FE5CF8DAD7845E74D502B69DC18D172C3A27411259B8042F3FF82C3 +B157BE242C351830255CF0EDA96577375A70657BD9A2E9FFC54AF0AE563D73F2 +E510279FEF48D79F5F7745DBB492F1D74DA738E6A4FE4364799B5BEC93B4CAF6 +B06B9B8C8D164F8FA1FBBA693204064F2C1806C39910910E02ECA8D092558CB8 +33338B359D56483B7B99A1D8137204EC1AE70ED3D75881FC3B00BB9349AD934C +81A9F285312FDDC77FA923B18B1873D288C2AAF2E6D0AF90BF25A982B843789D +5662D6A2DD58E065026885601ABED4B09CAAA3116DEE6B430B15BE0A121FC1BB +FDEA5A501F0798CFFFFEAB5101E707F1A00C8E014A3561FD39972EA9AB108EBB +960AEA7FF60C301AD6CBFCAA7D35CBF6F8462A4D76C4FBA6F3DF6BB762DF7900 +9F69529AB4EAF96C2866444B257160E8822533A7A1240C83EC18C364F577407B +4CB314678D2511735308A1660AD94B8B818CEA4A3DC00C5A1C978F8BB4E0491C +49328F6CDF95BF620AE53056364423841D84418B23C2A447B0CCF8D8633FE2E8 +4A4AC1C6C74627EECDC994059F1BAE9E6B10FA80D767B3FE97BFFAD413DCB0A8 +495039744B48266278194D60422D6E7C74D0DB45ACF217797D0C0678EEB60759 +6231438CFEFB346553A7A447B50807EBB6E885B5A49CA9A350EC4A8C76EDFBB3 +A4DA1C9E3EFA193CDF08553302998F20055C84420A4C5252F764CC4B7A4BEF6A +A09170EC417B296DD9E2301CD8EABE4A087E648E0525A9FFAF26374C47FDC123 +82F18C9884843864F418ACB08041E7896FDD395225532460A8194A8DB4DBD824 +1C68C6665F85059E365EC0972EC6465E2D8867449907DA6692A021F026F437BD +D02654BC11381BB6557663E0B0B8C4F2FF69E4776F4EABA69311BC1AF8155F7D +6D3A418BDC912CC7CF1A4BBC8A1376D8B4DEEB6585416959BCA4AA08D4520C33 +EB054DE53140992D0707210593BE62B3659E3E493C4562C2E99CECA143791DAC +679896BCDA0699E405957E17DDBD243E65CDD7C9C8629F29A2078658746A7779 +0F75BE24E2DDBB672B95F26366BAF036B3C23BE4132D7362E76D4183A469E0F7 +29174711ECAF4FD9A923E72FE58DF2854C5537E3626317D471D1E8A922C9BBA4 +CE9163A4086AC4A231C2BF35FBC39A5BBCFE41843CAC7D81A054509D31572BE1 +596E0B0B563DF2BF0E57DB4943DAEE35CA26C8433FEE4FC61145C77F65DADE75 +62DA18DFABC7F4194906F53884E62E77D8AB3E099776AB93B2B4D0C98FA44C71 +597202A2643942795EE8CE098FE26F1AF8134F1E75FAE18D563B1FF43A511C9E +EAFB9EFCF61490A1A4FD2CF354927B72C5EDD5D62B2F3F5006D6130562A13BCB +1B988A994A8D68B051A5A821CCD5D0F8D9D49FE7CD04EECCFD7A554CCDFFD77E +27AC4AB5BF9FE40F90EBD066C483796CE1A364E95C5E0CF2154834760522F128 +B2DBD1F4F73347D42635B2875A23597C35A0823CC6F71E49598125411BC9B2C2 +72470D36DD967C947AFB031BFCF770FE50551A134DF8C5D1AB1F09819569A57E +E23D4E87C0B52CD02B0A2E3FAA7D27A94359E82AF047756BB769BC5950A75207 +78ABD49D174F2F69810AFFA9336A52D6B93B004DCA5CDE58475C0210E0BA1D20 +FD4FFD6838EC56A0922472D4C4EE0CC481574BC30618179E733EA40A48847E14 +A75BE7717CC5DDCB5B0718074EAB6FF07CFFE794D335B3A13EB968EA8FC5B08A +13B38AD1C2C964E4B07E90B9732C458216B028E07DD593A5B767A2B415EFE7DA +951FC07800F11C7E2EF9BDD152BC6815B7F32117F49FE08BD79BEB949003512A +327F3F8FAE1767E7842348BA4373649F1A21DB2C56C081BCF9FA4EA86C8DFF00 +FF45C4F1386CF8C2C4120F3F6019CEBB639F2D272D08C1763A470D4BF6330DC8 +43C069A6333113C3A0C93471486EFE9BFC02B760C7CBB2E9156087D09EE8A178 +5EF50B34994094C3F0015EA2ADB6C920F4302FDEF128711994875551C4E883E2 +DDEFFAAE11F2234AFDD96400BB69C1B4E6EFD75734C586A10A54A98E7D790F28 +DEF7C7DF61FB23BF91AA700AE585EBDE74E215DA49F4ED466F46129022722086 +8884D8E026F35C4BEE7E866DF8E0846D5EC3534069B713FAB02D4B4EE3B44E1B +656F30D629D40AA1337786C1FDA08EA1217AFA4A6E2498B334DAB5461A70DFBB +5AA5686C89FFA4EE82D81CE2B28334DC5C032487CCE998616F48150BA1281911 +076E626E5BFCC56A0A4CDC559F878F14C2BD7A5148C1D8CC303FF9EC473354D2 +D4FB0F0F2AD0CF182A28074ED6552E179222570DE0E0D44E8FF4DB36C3AD6487 +C4BA53C8548714A69FCF8E3E5202F09469D7447C6519AE902C1D611A720BAFB5 +59E27A6DBA73624F44B4ABE0988BA3450F82E03521CCE8EDE8BE7EE1223B575A +DF9A52650E85545525E6F121FF2D1531F156EA9D5594239AEA2CD09EE28ACB15 +A445E11FD1C031188DB61881F474D49425C084489A88A47D681EA68E7FC4B1F9 +DBB552063A02A0EB51125E9B2CC646B940D46FF457415F9565892DEAC030F08B +E4C10DC38D825C7597394C844CB863CE6C843F67F2E1C42C4EF86AC7FB727BF0 +224B5E91BAD99CC6638AB2C64469A81D8B1789981872ED037B3A34BDF3130137 +80FE80FDA65EFBC11A08B98A1AE595F980B577E22D3CB7FED1D4016F5290ADF5 +47D7D9BAFE39F294582F2C084003E9C83FDB9EBC87C8B477CB8BB359EDD9BBC9 +9368D6605E1468A20909831BF602EFCEC0D5EBA99A2223E5A269275C8B221B3A +F9226654185929F794E1979ED18B4CD36152F973433AC67BE24B9D953254FBBD +B644CDF3BF0E29A2C72113DC486E46DED2CE8F8DFA8B0F8478D1F18C9AA8E054 +A31C3DBE84ECEDD85DF6AF9467AC2990ECAA3384FBCA1BBE598AA0D6813C859E +1520B88BF30ADA910A6AC3068A5B8CFD76B7F0F6F4AF4C32450D628B5320C384 +F23A2B5E8756895584155226A30F8B0437E028978491DCD00E79C0ED58DF261E +79B9DA17E57AEE03EE92102EAB2D63E69A88EE0B1E2087ED0C0CF6475EBDC3BE +0324D1FC8F7B90D8D807533E5436F2C2583B9629EC390403437FDAC908557894 +03054A6DD6A3586043A9C8BFD0C7EDE1229DBB9F69F7A5D20F55664D061F6517 +0051C6B3CD7338241FB403F2AF77DAB1A8EBE1650156D40863EC1957372BFDEA +BA8D0BB1193CC5BEB5A68C8274802E14FFA3ADCEBE19070325B1BDB960CF2988 +C0F5A9BFD843C515ADEC8B8AB02B2891EDD7502D9F28F4E58D8F67D1ACAFD0C3 +3531E0C7D1554344CCF90AC8696E83A3F968252981CAC09653956F4343B99D3D +4F17CB8BBE4506B354439B70F2024871D16668F9DECD8EDB872BE5E6ACC406F1 +1DF4E3ADF60EFED57D1C426292970199BB663405236C6A907B6891C6190E87F2 +78D9142220FF295C7BF44AF61470798FB8CFBEE6973C69DA1CC24ECB058AA753 +DDBFD92FBB15560EA19D5D92F0005B74F06F0EA5901D231996E0866389DCA433 +E62BE48479687084C1D67BC592E592939F806FA8BF5F0D3F644B1FA6F056DE0D +51D3F212C6818CB6166317058C2A0C07AE2E324CD90D4EC83CF4819B10CC348C +6DBABA024A5FCDAE6E288F82DA060BCD16437F07DCA43BF1E5A1B402F16C78FC +075BEE900B4021A1019C4A5ADC33230047FF11FDE8FB775DDA267040A22B4E5D +6012F7E72B8BC8DD3A81369A08FB81C6C4873C2147D03D4181D6D8032DD2B610 +9C44CAB50C5BD8F489EBF01C72D4198B66EEA4E976462F8874143640B82AE57C +A51EDEDE75A9A55D31587C14F8DEFFE69F75EA7B95BF725CE9991FB2F07AF568 +5AFEB39447B728B99BE0502BF28DE1D92B15926BE4E3DA2E7BB44A24836A97C6 +EE3A2080E01DC6514180DAF9C055F4C94929D34F193920020505E62804461630 +9F42C652F9D5681C91BE23DCB0C634247E739135F925EF3D5424767D5F5C5879 +C46F2E32C7B3BE9E90FD6ABE693A6016AB77670129B58B8FE719FA97FE320842 +6488CB85B6BCC0012975D22E75A2E086131DE676AB825A386C086FBE1B65DDDD +A19F06AA4C1D3EC84751C649F4A62CFDC48A7CF88CFEC68B959C211B60DCB045 +6BFF922FD7349B98E1769394E6CEA4F764AC4B6536AAE4E6BE69099A39A6A33C +97671C3AB4E7A94DCB829FBA97DFE5F71B1728FC81F826699DFDB0ACE9BC60E0 +6EC15D35EC479F3F53EE4D0398BAC138FED504A84A13B78568E3F9C86BAE8B88 +61830A80F8B994D0D66A8FA3FCD6C5099C29FC285ABF096EF9A3BFDBB522157C +DCE9F0D6AAFB1F8D7B0A3C573D0C170357175DD56EAF37BAAEF4C92FBE17E26C +7D2BDAACB9B8F33D09651FBE0D49A8BE66B78D075485BCD38DED5056FCE48A12 +D28E9670EC7CF4E9A277D6ABC2F7AB30BFF290B5452582F372FC9DE6CEA9EC0B +84328269F14FE7F47620B1042B283C54161AFCF84B46E6B1410587295E4F8958 +C1800F120B59639C85D46D46A4C64309931A8C91F138EB52F779189EF75B9157 +D624045F4B8846856ADF0AD735FC6FA41F7B6C002E9D1EBD92468E86C843AFB7 +4D78E3D54D866029DE5DF865EE3F7313AC358EDA70A792E22F2F806EF86A6B57 +64AAD565C57E64B1A6635B7394B4B5729189319FCAC8529ADE30633B9BDEE0D2 +AF1F8944EFDC7C408FD8FC270822CC01E7BA355C856219B3AC5D05CA37EB0EF1 +6766D62383AEDDA1F7CDAD1DF0172E766BB46C5FCCDDD61BB019D283EDEF312A +B2DBA38C9BB0928FB93F50E8516AB353BE04403D132805B5AAAA17163AB9C847 +F1B54946B0775FD21325C82E4EC7F2186C54B4396BC4B0B913A59E4444D11B39 +8AB56F2FD5788A9BA45DA5499A50BA74D28707F62086907BF8342E0C753A31A8 +DE293B592F51D74DECA52858CCF76C69BAF2224F640069BEF2604983FB478173 +792D68030D7A6E3FE083AFFE9488D872897ABFC88CA8BFE484A75201D73058D4 +72A8A26A50BA1F2B50CBF98D46DFED0BA057619BF370E435A0400147928D7C06 +28DF2A03527E3BE925D6A664E4640E63BD22D54A038D934B3DB5B500E075B8AB +06DB5279274E65FF870F1E5106E190AB0FD8849EEE2D605FD4F0DED2C3F86831 +4EECBFAAD8B2A895F08DBA692A8176F9080045519CC6C46B52F0F31DF112AD79 +8E46B9899C5527A011AB63FA443ACE90F09434C295A5D9E6753AF2645407488E +D29E7711546F87265C130B76B4632242E43962A5C886D4DB6316A2F3420FCAEA +3055AB5A9E1325EA870CE87F34BB2B3110E4919E1AFEE67606B00B03DD6824F0 +20BA42968B81DAE198C88057438E36056D46C550E3E5E03A99BD4B07E66A2179 +BBC5B3FB06D5D72022C53A3F3A1B759472D5A50D7F7A1F4E31D3F7A30EDC1D45 +4B00AEB5DF680145A123CCA3BBD801CA64B2CBEEA99842720F8DCE432909AE78 +5AD3F29AC69D302C62256CC4D47AC92EE11D2A3E1C666CEA24876491BB167548 +9E3A990252DF8254CB5E7141F57B78FD1FEB38BE135815C6FC86EF81B5994711 +E43083C3234C55DAD97CCCE4FF3F55C5A6C22ADD2C549513A465CFA3D8A9AEB6 +331374DC05A4F496BE33F9263172FB6FE1CCD19EE9515C5155ABECA9492DC743 +BE4142D63FB5E17D55C9FE642F07995502AECD9D555603D15B5BE420A65A6E98 +4F341BA13E44DBBC1DC8CF0D561198A2B40FAF35F7ED5FEB4429BF71F5C88637 +CB114F1377FD3227EDF592733EC68F4EFEAB14FE7C26DA7031075E04289FA6DC +8A79F81E4E18CAE8380CC585E7DA3DCDA3FCB53929AA8D772D53FC6D821EBB14 +EB472017FB56CE9410FEEFF14EA69C188993922DAFEC805F4C8028537A9D6365 +AE1A6BEF37CE8E02B995C41382984802AD3D12AA9FAA36837F9F9F8F60D16B81 +474238F136F442CA9B14620F83E4046E41EB0FD02BD04DA7863DF26624B5489A +B8BA35B0B3A8D128FA10E01DC9B622C26CC57CA79CCDEEB7E174698EFDCC0CBE +879AE1434B3EC5AF48E6C2EC5652DEFE0ECD7415FA46BC0C80FCC57CC808B3DE +CBE4CC7B62AD3B092487F7A23C38A2D9102DEBB1CF4C1EE7FEDE1E8BBCDF7F73 +54CAB1E591F9B3B3159D879A9492394B32F2CC43EE7EBA6E293AF12D7FC4ADF4 +DAF8F2F48A777E927A915DE1FD9125B52D406BACB0BEA149F6F6D79D92D06413 +5D68461A772D531F2E76D1947D2ED5BFFCD758E062B5435BFD180F7E3734D5DD +ABD86A1C2BA643955A36C482BEBAC608F588C43E6EA7EA2AE01D0346D28F50CD +BA8F9FD23674AB19A2B879E0DD19029EAC5D74D16B186CF4BE3382E74E361427 +536A00347E536701808C1D31A617D1F9269110B76A0D59C7B84D98C8FE308733 +C9497B807A77D244FEE03ED7FB5EB4D6ABB74A7129F23AF628BC01BEC6C43ED2 +D62F4E2133006FDB94D33CD31F9FFE57C8C9E31DC6D7A81A2C6ABF1D971EE222 +96A4D79F7232190EB796A43ECEB88F1C64A88A10C3AE8E98711EFDF984BF270B +55C5B9082D54DA7D32B168CE573597DC5A453D76953DBDDDBC1798F8A645AEB3 +78B6B5BAF60C9AFA9D5F818740EDAA977EBEA0F68E531550E607E6FCB04F3E22 +BC9D6440E1E153C8D780213DAB08CF8CFEB03018942AF980642745D711C7DB1D +BEFD825627798897ED8185D80234B6C087FBB602ADB1263C2A2A0F59AFCA7B09 +EA4ED3BCF936C2DAEA9C8DDAA90130C24AD1A1BA47711CC760FF72EB3F27C165 +CA1FDCF1250C6CA4A788AAEEE08902AB4EB03C6EDF281CA2F5B074C859DE3963 +27F7CFC53CC91C80F779ABB25F7A6601453DF5606B72EC562F615A92C1DCED58 +3911BE7784B6E8B17F8993E4D5693A327F9C289701F39ADCF583BB4EFDE1F835 +1A59BBC2E6B73CF422D877B0B423E4E8FD116F5C66A4BEB706A3D42E7EFBB5E5 +E73CD03D7A91719337CC8E13F9D8DA255185FBE3F4FB6DBE8EA90AF036A09BE3 +5047B59BC18C1C3658ACE003B6535E42043E4D7E6D79E0B48B3D0DCA36C046D1 +D5ACE0B6F91CB78BCBD144F3FAA3D9D711C9D11EC30B6CCDBF43CD490E9AC229 +9ED2CFAC4F53927040CC8FD26004450889A1167FA34247B7C283A46E4C0A8C20 +AB43314A34EF0BC02C5558746D35F2315624FB9D4A8ED13E3D1A8B80B872798E +CCB9775F985E31E8228B03949B4E35DCF7A41C834E53CE3C163EEECE81A8278C +FEA3A9E3264627D33738170C12F4EB23EF8F00811723FA4FE56A0EFE8ED5EBE4 +90455B690EAE1E8F1092C1AAC07FC418A6790C2DDA6DF739B9B586B68263EB63 +718EAD2B11037C5D26FF31FB2E56AB82773921B00EF07DECAEE2A8FD71AB232C +86865012F1FCC80CBDC4B0E881819601CE2FC5AC36875F2FB5C088436BB11159 +813020F0433EDF6D96FD162F5E3241F88BA7025F2B010208DD1DF737FFF1185B +812864C3049CE325E06610404C8DE9322187DAA7FD90FFDF2DF3C86D94E8E792 +377C1C1F10FDC78E1FDEAF718A2857C4922FA300C8D3FAC136BA2957C675FBDD +21E3A9E29C797142BA6D30FABB0D5E97AABB49D113A55C4838B253AB8D7443E7 +15596B3BDF01C88C17135A74AF78551CEB6B0041BD17ECAF89321E6948E1C531 +B227A1F071FC3558501BFFC842A4F8B80C14D9213E0633485A66F899BCB473D7 +3C72329610575B6279C781714761468C785E426DC9393564979B1D6A6D55AE9B +4954010208883EC964F35E8363129682AAFA2D40E1ED08A4A1DF27F3DB5474E2 +92B917B45D9473AC94EE40662DF06AC9D004541B6F88DF5AA4A36756620CBE83 +1254ED1C3C9CA39B09E0D4148DA552B00FC60FF68E7159F556998EB8A66C8EC3 +3B7842ACEE888BCBD1FA183BAB95B06B245ACEA49F8CC51A2EB01053E99E9A87 +A5198C2FC26E270961FFF61A093A084594E6C0298CF96B251C5F8395ACDA26FC +461E6DB774F6220F8FA04C68519E19CF69EFA73E9A1BDFFE833B228DC19571BA +34B7AC21EB2BF8B1876BD11E128F002AC9AD6A9785CBBFE2D5FBAC307BE7CE5B +DDA7C12820028FBDBEF1343638CE166E43B95E6518A83828AA3C3628779FB2E4 +CE32DED584715FD18C95D38FA85772DC8650EFC42F980A1ADC012ACD93B7E1E5 +FA6453179ADC6F17C94FEA1F4CC2EF6A2A975C687ED81DEB7111F0897742B373 +30720766409C534C5E0A42D7221337FF3C4C59BBD239518F3976DC55FDBB8C1C +8DB9CB4B05B1D9AFBAF0FA1D82B1564AD7FC92B6CB3582F7DA309403EB78916B +BFDC6F918E26A39755E5AD6394D985C92F7927FB1287FAFF2F60248236F918DC +2E8557C6805B01090A037E8D5C529E2D70976A9CBF3785F4BAFAF9923DB40756 +7B6CE8EE83559893E3930790E5917EC3421FEB042C0CBF6CE74F16C44FA08025 +82EDA0833C0486CDA66ACB450094BB65F54C83829B47DAAAC9E4CF115FC275C8 +6BE583008180F2E2C9B003712ECE32333199BBF9772A471EADE59355FF264DD7 +ADFD42FECCD00892FB545DFE555AAA4B273B82BD2740CB8C9ACD144DEEA94188 +D1AFEDFA1FC63557F9E527C00AE7D14762FEC2814487CB60E406F8D4A47365B1 +F7B0E0A56CEF011CC11345674611EBFB5A7C587C34F786498FEE4F0F999AF42F +D955CF2ECC5B64BB1C100310DE5B6C7D106A80FA4ACE0184A6E18FAC544EEDCF +307334E1C2A0CB6E488B21DA3BDDC5B593D5ABD6006D1E2BB56336EA88D56DAA +62DAFDDC379B06EF80E9F3661C6B7AA6787ADD06155F3DBCABAB6BA3A46C9047 +5D295774447BC007D5423B9840E2ABACB5B811B30ABFF547A8A6E2C18A92DEF5 +D30890D49388E80E6EC7626FE3236AABBF64B21E5525FFB7C802511129EBFAD3 +D1E19814500A465DA92054F93FF77864698288510AB599237D0DBE1EECF81C46 +F706515DF10A1D0FB06939473BC72429A42CE6E15BA2C97720756D80DDDC171E +7E8202D385C2E5B4A5A011933CE920E98A09527DDBF49FB4DAF2E736B1E42F57 +354C91CAACB68BEE8FDD10F4DECF25ABA4EFFC4588DFDB9E98640737C015EA04 +A33D5AAAF9AC4A7D288BC9D4A8AAB9B852516E215DF7614B10BEF003EE1D0572 +E4654DBA4D71959D403B936339D41C381FC1A206BFA6505DF3082D9FF767EF67 +437E8C2A14A8B6F0FB98C80DCC42A30C57C8AC3FE83570A1B4AB404374B85F45 +A1056E389A7148CAF714CF6DC26A04E3DE8E2E7FD26F6CDE3E836AE65E593A9D +3FA7A24A32E3E99A152009C8713FF8960FC93A2E49B8F442B81A90F98B99E140 +5F0E0253DE8ACE69F1248040510DEAEE069307FBD02B821D1DAEADE6C41111E8 +37A80AB702B8D79977DD73429695C13DF81ED3B562FF4C168AE03ECD24909A41 +22C579987CBB22700D1D34BB16E5D0B4BEDB4660D34EF5CF0A4FE507198EE14D +9FAF7C97CF769EA9159E1D8210B063141913DD402BAFD515CD746A7CBC061A74 +CD6D6DF78AA722FF543C5379A1AF5102B75C06F73E075BD8531353892E1733D5 +8143315C0C780BBB21D6954119C0AB1D4C89EA67C0AFDD4607AF07D509F481A9 +9045776F08003CB429316307E66E1F9490E8547FE0336BDD8B070290558E0DCE +DB08FCF371A8A9FE905E9C3BA4CBD4F12BB2F512838D395BBCAB1488C58122C0 +CD6D3634C0F6E193E2F2E8C632BB9185B20D94503E02244938D4400F0DD8FB81 +3AB0CBCF32E462A223F9680A14AC8876917ADEDCC9B181D584AA307CFF3B66B6 +F59FC840A9E8D1BE101AA1DE41934C22A1017A8AF69D257433C2D2C5A0474F9D +362A669B4044B3990BF83E8906C5B7E2B45D688CF12CC1FF38F2EA47743676CB +55FCD3C6261C6F5AF002869128882E39E089A6FA108195A8B86CB07913FFFA6F +6D8F8D5C9E897D63C174825286953B9DCB09B8475D0675E09C1D26286107E89C +C75F92D14002B1289A5E11F059F28FA27DF23FF395EEDFD5F22374FF67F0B60E +81D249898A228A6A89141B23918E977BA79C5F5E6CE84FD35F51B136D81428E4 +E4D205612F6DEEC1CE6AE571B30A33DE004A8F096656A3BFAF8BAE8A2C73AA53 +D7984D6777540082F3674304D2C3F17775BDF86A27563CC2BF95F190DB3E28E9 +FBD0716C0F1B0D56A96E2E870882F03A3E160621FB469355859954858C9CC2AB +06EA8F87EA163B9ABC176D704D3C17A37508864381659070B071B80C79D6D60C +7858A32F5DE87B1F818E78048CE81E229FD7BD91286ECD773147F94E7A184450 +D1060F0FBC5A8DB06BB4009B3F5F50EFEECEF8FE970E3FBFEB5ADEDE9EAC6A49 +AECCEB5378A9CE274BC7F25D03CF477C2054D313FD988A4D913D20ED3981CE47 +8674501E487FEFE5DD91CB0E5ED24BE1D2D45C88DBE1378B11F6B7076FE56BF4 +8E8925E65FBF23330B9C4A943CD96EEC06A6073AED304CDF520CD2AC1CFFAC7A +6B8D8FFB7327834C9DADF578F250A51BE64D27A2B65A16DD0204635560B47075 +3A054F7159EE483CA06345D3D55EFACD47AD32A9D7D7404ED0CB742A3AB8C47C +2C5CC71EA3E1405D6E114DD53D85C2350D46A8E4BDFD1667C65A8152D9F3331D +6235F40AE36EDB507325E21496725F3351C239207C0C4BBAEE2DC7D2797B8818 +BEBAFCA4FEDBBBBBF3FDB633A0C21A8BBA856D4A3119394FB00AD092E314558B +99CD5B138D4A42BB7B621DFCC2A2E2AD262479E878D8F26195A643BA0D13F9C9 +FCF3B6BE5774DE6F4564FB82BA3B2B9BA29A5F406F1A135D46DB10C80CA11E1C +39C9A74A18D8EEE86C85556F8B9203E00DE0B1A164134E48FCE7F37AAB98A79B +EAD809EA81192ADD3D3C6B35E521AC99E190262E5454C7170B081CB8AE338D09 +D489BB694D228CE9C05DA95297BF106A3B71A99A5F199F122971F5C4B0B9C53A +4344FD111B92AF456697E0B85142B71FA56802C392C886A408558A297EC3C717 +FB803CA1002361034D40420699FFE3C149800137EBA3AD99AAAD74B471038675 +8B073F692D278E0A088D3F51360C2C79A83FCDABE9963A4D636631310E7C6D35 +EF02828CD45CEBF9892C2761D934E07AF32BE74852C13FE63BD3DFF3619CEFA1 +25F5FAC01306FD99A573F0F5F29116967FFA22C9236EE8EB052488C4CC204855 +EEDB80B30838AEF66DD960389684231FAAB83750575E4C9BAE860D9B0F838927 +791AE22921BAF254FE561771B7A9164362D6BD462A82763F6D19737ADA1C2B92 +BD72443D7521795F9D3702F83B04BCF992667CA255A3AA539CB71F25F2D0ED57 +9E0B180D1C199211FBC17EE282E7CA9E078593E6340BA651AB949482A0760790 +E4C3F1446653CFF964B9A3142FF4FBE8C75CFBAEFFEEE1810D38033CBDA2FE9F +B42BBD97740EF0C118C7954FBE81FDDDC74608D036A3BBB75EEED4E1A4A56381 +0F57C993C4651E4753A27684D170EBC495D09202AC0CDC5F10267EF26EF4E7D0 +908F4524C91AD27F43737253BF0617559F2EB99EB26643D8C28B61F8968CEE7A +A79A818887ED9BC3AECE4A35AB15752A368D09594F93B7A741282DB5C6E42144 +EAD79AADE23733A43500563C3AD34E0421D1E3B4642EC1D70F0054E3DB6CC218 +FF930B11B1CCC3E4C90BC523D4635161C89CD9FF8F2C4F6E4127ABF479914610 +4D95589775902AE3993E1CE3D4868A1055BFF961CCB244AE25C76C4CE556B8AF +98129765EA10B35FFF3D24DE1CA68BA55E133084CD2562832630E302C3823EB2 +5D7293D0C760EC1788BA6BC9ADB7AF6DA83C951E0A23AD98EFAD64AD387F7764 +21320EAA8DD04EFC4C2BC011185DAC3DC1FEF1461F3F9ED515E2240433D855E2 +0229E1D5269092D0FF790539D2946A608E82E1FCA5E3A1254B27AA134C300FFE +C7C724824AE8B8436577129608078274BB69FB6A026D0CA48B97314F596EB375 +390574158057D3E1C3EA4AC61BEC73F81C706A6C7A9B42EEACD6A4B5C4E69FD2 +C68AB11CF02CE3D9B7148BAEC69F92FD7DAF6C9D772BE60AD4579BCE18396E4B +60EED65E6E2E62B283F135675C188F58C831B3A7ADCACACD39871EF8905B3264 +567AEEE25FB31D64C6596D60597315F1AD8F74E5577E6C966F8F65B001850D1F +39F0234F0478F6DB136F17F20262CC072B25202BCC8A67EECB03A2834136EE5E +8FDF55397F3572922DF82D25376DA73083419420003E99A020198ED0ECA44A72 +DCFF31392F59E14720BA027A5A5E81B3C32BE7DBEE039CEA50AFD5CF9CB9080B +3952949A3165C5AE1EF9D8C76E0C901DF5013469D55C8AFE1554A74D6C565533 +FD00D77FAA0311910C9C191ECBE1A0FE30A4FCDC3909D4F6322DE2DE90865099 +ABAA1A087DE9B4642DB649ECA28D40631EBA0B3902EA6D70F9260EA9DACBCE35 +8EFFA26B2E8BF4567406788443950D8A71339F595C83E28111FC90181AF60EC8 +9A7EDDE1989A2678B8C570F5D0BC178C4018626B9AC851604F03C98469EFDFCB +BC34589B59973E918756A2C1BB7D1811BEAA17D193DEA92EB273D50AE5C0FC30 +661B330B083311E5D971D70DE46E2239532FAC9A6D8FD913E6DD03F42ED6148C +4F04E6D136D41C24BD9B973109A9B63233E51176EE64D247DE1C5CBB43F568BC +DBD5A6AEDFB68E5A0C8DC465E9949A96665AB78F41132F96F3680B5A9A79DC44 +828FBDA04368F277C40F629961EAA9F3B253276EE252478A9F409C2FB6447C65 +37AB9FC9A216970D7BF6912FAFC92BA0C000A58950291FB3E47F0DFD493EC7CC +A99555CACEB7EC87F4250AB92E7136500138087A19053E9152B6F007B8D3DE8C +96224FF8D464BD3289C08AD1E05B9D063375F38FD42CE97ADAC4E5B83B8A88D2 +B2634B95A0DC0AE6A407E62D153BFC434B42680FD0F62F5FCF0818182F182D1D +6B9EBD47149F8506CF38BA61D8AE460A8B660F40DEFBC9243154E5683EA8D574 +8D48276FE5128F972D96E42D89E374F7D8C72E70F84F028263507BC96F6B2B92 +EF4B992CA46361BA3EDB888A6E5C57688198EF10A616F7DCCA55B4E1645FFBEA +C2201D5503101C400B147CA23A805AA95FF059120C677C32ADC486DCA6E775EC +23BB704624D3755E09505C395CC3AB83D68F10E2D6E1497BC179F3CA82A3DFC2 +38275D10D3253264BA9C32DA47C0088660037C7A789C1DAF75D0476BA9ED5B62 +CD30BA0E268DF3537F8298BDDCA16B1C970C2863B21CD839FF6B713438447A4C +C58C1F0ECE39E126AAC2353E31B6FB808253501CF26AA3AC48433D4CE5A946BF +10347C814A195929213655345791FAEB7986B1DD4F2B0C9E7116B11A4F1157CB +933B48B488B7DFE700423AD4FAA7E26F003B87B6255BB607A3A639830D68D663 +A3350CDD992B528E3D2176DAF79AC03F6455B1BD626ED15299042B46F03BB46B +992109329C6F67F1CD92A620FB4D806558D6CFDD75DE4F7D6C558CD5325302BF +ABBF40001F90CC940511F63F32F112EB958944E37A603642C0CDFC7941D9EC65 +F1F5B2805EC17A6662FABEAC3F1A7CE8A4958D64FCD57759F5CA294236B5834B +0071972DBCFAE0D89A1BA76FA1EEF2C8ACB12E45C8D8939B3EF1DA3970ECD2A3 +30AE415757A89E44CED997FFF9F6378FDB532ADAFB25872A690137609D91405F +4FDA7B432D432325F1467A7C85363A9E2825F86D5B9F9523E53FC5B58D82CB28 +90D43D172B2CBABDA71E5B83928A6271468C197108584BE45647AF5C9BD930E4 +18E321AE20B3D28980B1CA53F8B2696043BCC4C925F218B0AFF8E8C2BC1B85A9 +134BD28FC51E5F4E803761720601C5D9D87921116C342D832BB14EFA08032E5D +7C0C4F14F118883DBB1CA0313B6658E3BB5A7ABBA4970CAB64E66515031BEEA7 +F0311CD7DF8CCADCB38103DBB1D60CE59FEF567B2755D0A65100C8F8EA622025 +4AFEC5D179796C4F87808A76B3F420A228544CC12427AE7A5E2FB6CD76D4668D +BD5A22FF8161EF3FB20EE9FE64EFC4D1E466DEF81D20A395B020BDB7358E80D0 +6CCBBB8725B9AB973B060770E4CB902F429D75295D1E5ADA0BDC01D0DA7A4ED2 +A21346CC735F3E6662B87BCDED41C39EB2174C5ABD9C89A4A6554B3523E08BAD +F208FFE1095E6641C548DC0B7116851695AE8813E691347526DA61EC59DB43E1 +03BD503968825F7EA207E22EA04656780C15E1E9D0A00CF8CEEC4D3FD48A4E93 +7E82A2D0F952F5ED616618739ADDA48480DA4665526260E4269F135C89C2F28C +28B435A1A40C924B79934D6CC536A58D2F102CB46E4C3F6F5390008A7C7B5E28 +4044E385A5D6FBE641B6FB074C4E15DB9D25152E503EB7DB52F45913FBD962C4 +550310BC3592CF1C56A7E19A73261219812CA9A818856901E9F0FC46FA53FD67 +20A7AF35375DC845C8A9BC82F46C061F46233CE3F963C6AC49CCE0936A1813CC +F7904CBE756A07106AC3D9B58C28EB405FE50A12710C7FA7B4F6900E163125DC +43672E2C565C6959C412F7CC333F49E0FF5B1AE666E0770255C43E1779A67D7A +BD794057140D8D1478B7B3C43C84C2C2E56DCA12A1A536F80B16BF9C5244FFB6 +906F2729E0D6C3A6AE9A837CF39F81668CE7B299F4EC9825892A961935E4C81D +7A9FE5D9431283C53770E41DB77A70500A9B21D63B2F073D75D8E11579FF7C63 +3D1BD1D11EA3C49A594D1D83A733ADB8D887AABCB81C32E3913FC4B2DD1DFF11 +10C193CD5D5D5FDC8080F9B99C9B29A86ACFD94EAC9E052790D6A46E5A5E946F +6AB9541056CC23323C09CBA556F1B0F28BA2C30E039B3552DDBAC17B9311BF1F +648D3527E8650B3FC89CF81256E9A4A9054D9F1A9839BF7E0B875D25EAC8AFA8 +2B5663DAD7CC7DED3206BF5957291DF837535DB23BA63F9F7ACA7141E1490A68 +327E35FB7888C160C2D47BC4A7CD84194FF52646DF43AC83A51489481CBA4D20 +1E5094E7AC3EE66A5828BF1D87A530D7786577F164AC3D5C0D624FC6CF1DDFFF +C2 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +cleartomark +%%EndFont +%%BeginFont: CMSL10 +%!PS-AdobeFont-1.1: CMSL10 1.0 +%%CreationDate: 1991 Aug 20 16:40:20 +% Copyright (C) 1997 American Mathematical Society. All Rights Reserved. +11 dict begin +/FontInfo 7 dict dup begin +/version (1.0) readonly def +/Notice (Copyright (C) 1997 American Mathematical Society. All Rights Reserved) readonly def +/FullName (CMSL10) readonly def +/FamilyName (Computer Modern) readonly def +/Weight (Medium) readonly def +/ItalicAngle -9.46 def +/isFixedPitch false def +end readonly def +/FontName /CMSL10 def +/PaintType 0 def +/FontType 1 def +/FontMatrix [0.001 0 0 0.001 0 0] readonly def +/Encoding 256 array +0 1 255 {1 index exch /.notdef put} for +dup 45 /hyphen put +dup 46 /period put +dup 47 /slash put +dup 48 /zero put +dup 49 /one put +dup 50 /two put +dup 51 /three put +dup 52 /four put +dup 53 /five put +dup 54 /six put +dup 55 /seven put +dup 56 /eight put +dup 57 /nine put +dup 58 /colon put +dup 65 /A put +dup 66 /B put +dup 67 /C put +dup 68 /D put +dup 69 /E put +dup 70 /F put +dup 71 /G put +dup 72 /H put +dup 73 /I put +dup 75 /K put +dup 76 /L put +dup 77 /M put +dup 78 /N put +dup 79 /O put +dup 80 /P put +dup 82 /R put +dup 83 /S put +dup 84 /T put +dup 85 /U put +dup 86 /V put +dup 87 /W put +dup 88 /X put +dup 89 /Y put +dup 90 /Z put +readonly def +/FontBBox{-62 -250 1123 750}readonly def +currentdict end +currentfile eexec +D9D66F633B846A97B686A97E45A3D0AA0529731C99A784CCBE85B4993B2EEBDE +3B12D472B7CF54651EF21185116A69AB1096ED4BAD2F646635E019B6417CC77B +532F85D811C70D1429A19A5307EF63EB5C5E02C89FC6C20F6D9D89E7D91FE470 +B72BEFDA23F5DF76BE05AF4CE93137A219ED8A04A9D7D6FDF37E6B7FCDE0D90B +986423E5960A5D9FBB4C956556E8DF90CBFAEC476FA36FD9A5C8175C9AF513FE +D919C2DDD26BDC0D99398B9F4D03D5993DFC0930297866E1CD0A319B6B1FD958 +9429B9D40924DC059325D9D4CC0344F3F997A99E6CC0676735EBCD685AAC9142 +08DAFEC78BB41AFC2F1C219910BDF41D6279284EF600B69776CA15BC8A34347C +30783C52AFA60FBE3E353E2AE354CF87B558776A22C776C7A0B5AB5CE1F941EF +C2D9CAC37294BF407A671F10E4743BF842143F4F7DFEE643BA3BBD8BB9E3F24A +BCCF7F0ADF8BA500620C81033EAE8C4EF2C1DEF13AC575F1B3BBB66F093D3B78 +5412B82B67FFA087AF57182B2230F9F2137180CA58A7D9B2C822FF04BE6CD01D +43B2CA7058C7B953F6D9B5D6E91ECBAA5CDE1159B0E59C83DBAD96D6C8C8BAB1 +374EF652D10C0F3EE7104472C98DD3572AAF2D45A70BF7061447E21EE3C3BF23 +DF39C2D1B35B42CD5297BEBE6BC94F7C9DC6E61EC67E4F677256FED9064BD3E4 +B51A71B1D27CA4E5AA9E1D8080E6DAB5310711EEF87C40859FA935B19524AE83 +63B163FA8397BDFF443227FEDF7DB27DC35D89FB1C5E435DA0619A5C88AFC73B +89A2DF5E767C5B536BC7167A840A0C32BD57A14DE69A7D0D819AC36FF32F908A +5070F32983BB007437E3500799DF5E0AD3710A4C0000F0098D5BE99F2EB9C1C2 +C444FD9552D0DCA098A94B3BF176F511CEE13DB7EFFAED7C47B5ADCF8D4700F5 +7A5FD1B49560969BF5C44F3749370663A04776F749DDD7B50674D93254426C4B +EFE264BEE7810EC93784B7C01A7F29EFD92547E13A2C7851A2E709FBD5B87850 +4A44F08F56A542DBE072D2FBC58D9E6468E1AB858DC35240E30D31C7AC13D6C5 +7D2BB634BEE96FA0E10F842B11A789F72A333DD6DDCB1BC23227EBC406E50B40 +30AF0C48E6359AB0C46898CDAF1118E46BFF8B00F54EACBC2AC262AB898C42B9 +2E080C10DE923C1F7BD11CA700DA95F7E94C4C77EB24EFB7D5DD39CF43896343 +AD32237117AD4B4C24EF2B9AAB4228720C9F9EDA61ECC327D71641E9657DB045 +3E35C6ED9AFD4B88FBBC287F2B08E20CD064866D01062364E81E3594D85A3216 +396597A1DFC0BE35873C5BF3B12F115AE64354DE66EACD29A904A98FB23A530A +64D6DBD4198DF302D6EC7460A65E40609374E2031E0A3E8A3CB33C9823F1CEDA +6857069DCB4384BD963FE143AB8C46C2A6953B5EE8CA955E084517BDEB8A6EAE +7004A47347A65802F42FB1C3E2BCDFEED75859F600C521207B1F12360D8B5B5E +BCDDD9A8B43BE2886777EAB5A7006AB74FE08677A20579E5E0B6B949759F8346 +347F3AF2C105AF9A10341270B338AB4FDC2288B79D98A54F8457205DF24ED632 +B28F3773A7D9B85113CB50A9C472C537EC2F2AEC0243B6211664D78DE60EB3C9 +717285D76DD931E6BFC44864BFC7F99BFDB8A2954BF0C5712F21B490CD7E416F +12EBFE13341161A9053FF78435759B60B0F54888463C4B456CBAC7490988317F +17CCF703B1298BFF7B8F1002A86171BD2B7E9D8C4E20D426C132E7A9A1020653 +B82A9B932ADB1C912AB90997600AF5D56DBCE887D79EB994466A78D9040C2034 +1E537DA15822B32E17DA808628519B5D964B961125D6CE8158FD72D84EC1021C +68FB5F28CCC9FC14CA321D290B9A2F8A9196B06F787DC618E6C1957AE2DD8C3F +C2D2BE2A46B8A2CB9BA13255AA74ECFAAB942328B176D6E13C3810CF28B976BA +58E89F16B00B2BE618A07955CC9C46A95F5A07FB240D95452CE799C8709D45A1 +85994D7BAB6792B58694594B881C240F3C36129EFBFA4AC5B5074E390AB2E39F +CE9E366EA4E95A5DFF8CA2E144E2F0050F512414FBDA7A7EA24D2804D22FAA04 +C2261EE07DD605953453B94243250098753EC7FAF186A2BB5FD481DE92D3A6B1 +E03DCF5C2E2EB80A30D0569ED52550B5ACB2BBF67A86D4F4E65D868B2178AC94 +14C344582F4ADB60E79ED161C0B7DB7CC6FF8C391FC1B2A290B33DEAB127C200 +C309335A5601E10DA4BE03730AF8297EF872B08C54110804CE0FAD959F416B9D +5E13E390C820F540C109F6D0F71AF41B0587CA7450810019F8CAEC40CFC3C617 +5D0163AAAF368C1DDC966CAE0BC65A31083D01CA513CAA2A5D3FDD301A557CAD +148344C2463CD950F033DF4686BC452D6206760EDB2EB00026F5318440AC8795 +D1F9C0595F392BC030B70806C8FEE3DE6C140547CA3CAE9B4B3F1663CFDB5519 +6F2790055E57B004E48FDCEC080F8C8417FCB849E34D5C85CDE82D435089956A +C0F3E924EB2C4328CBFDA7D115BDBC58716D98B4F49A6F2E841AD8B31F2148D1 +34A68D8E23ED7AF5D891A520ADE49714ACD727EE708BCB6668939DCE322AF08E +53F071980E0077FC9BA5A10B3BF911664F61327233022B59E72F38B2A64E90AA +C858FEFC4476C42C6398128D10F889CB7E0B96E4D13EFE60CD99998388CCB554 +C7561FCCE0BC98152D9E4FB1D7AB69A5BBB87CE684CB2795F90A682210E75F9A +539CADFF715672E0122236E42A8F54DD833D4F99E672B390CB63849D911943E4 +CB0C6C9693751380E7ED446DADB9C7CD98E042FEA6E76E473AB981D8A5CED403 +9A06577BF4960204938151DACE2CB21BA653A580DC8E57500B172839CBDE3751 +8A48345DD78508B4D5B35083973E026272994D3D58C6BC0F505BFCF708388ECA +514F0DB510FF93CF4673EF0A19A89B0E08F3235BABE22638A843CD1B92DC72F6 +7EF613DCA9053AE8177BF3650C44CCCE8F1B7CCE6EE269F54F8857B5576BA1D7 +8403538A00B1675276C38CD38CBC7B73906FAA9A34DE35A9F03CE84A0E60B499 +CFBA23F6C2EB6580B07EF12D68446359FF50E039E0AC2186499E9B589B6DD483 +0D432890F26241863A182314369ABE41338B2883A7CCA07439FE96465A5DE153 +490D3DDFEC94DFE203CA50520AB3553F6FF5630404C2A2BCA0E65935210CFDDD +433842A96E7646942824D43D0F9F5DDA9195850737EDE2D65B769292D5427023 +AEB225160606F8D821F08AB9B33E8C30D492E1C57A3259B1CFC1C56C5747EFE4 +792D365BD95A08758383C496F6D2729EDD5297E8C1571C2711F65FD6D6BA0126 +BCC6FCADF50C3075B9339D9194E79A4377D728EFD1E597635425407FD3BF22E1 +062866AC4580ED0DCE3329BD884F5E022385FB0D020CAD3ACBB9B0F696C4C911 +8C26C714194A606A95441101F427247208E296212B4FDF1E68CD054122C64CD6 +C0CD1EE077F2630FC30DA75EA8C3DEB388418C004557EBE97A136308B2E8F079 +2A225D5684449EC17A1A288265CDC0A0FAE05DF0F03AC4C61B13C2AF9C5AEB97 +4E63BB904FC564A28601A9F79E43155F3560A67B2B49EBDB1399962A2C0F55ED +73FC547D024FE86F65FE2AB8EE5B65377F9458077B0C6D31BE033B258B87E1DE +2C9646601F49B17A69341E8064F6DF881046488C2FA69AA0F9BD635057256127 +C4DC77D9D71C895D3040F9E6EF7653BE1F904978749D5F98F531C5564FE02F4D +000DB6DF12C17EB6B9C13726B0DD1D8A23F5CA8B984A350DE453F808963140E4 +3248089231E94E9B298DC62B46610176ECF3B8A1C77064CA3E90480C27FB8B01 +5E06A2CC70485242DE5CAD12DBE8037433E39E978DCA6D59923C557FDEBCBAFA +48B435C528845F0B7BEED2323CB3622C19816F2AAD6BC7027E36BE571A1C8FF9 +F2090A806CCA9B4A869E5E15EB363C608EDFD82C88A11E6644978790A0AD0647 +A786BDC3B6F0531B1D5F01AD551C3C759EFAF72E146CC7BB125ACD1DD6197DC5 +2B3AAA053519F55E433569CEECBA8009C20B4AAFDE97B8EA362C8639ADB0F771 +0D7AFD709E4E16C1CCAC746A2307505A462F77B1B977F4EF3BC32CD87968D463 +C7B02975D3269F1E3AB3CB1565CBE061F928AB2943B43408891B9D3C6FF864B8 +FD69D33E79951101FACE7C635F8B2BD3F45C27F4E2F119C969F552EC2C5D047D +EC3D0C212AA56F51240DB8861DF7398C3897B8DD8F1B2400FB49197AED7803AA +FC07426EF806A7CB2DD05B8978996754E72BD2A3078E578191221EC40C71F050 +79257CA1E7B0EAACB2C0DEEF23C4B2BCA93AF1BB58D94BCA90BC12A50FF9FC32 +253C51DC821ED1FA42318788342A8C1812EB45E1A1AA66D9CCFE0D7A107638CD +EFB007C6A6BE3D58B81AA688D787F7D0553677EFE62A3E59C2874A88D5719E8F +9F1E12B507D39FF42589C1475DC5F0974B423DB8DBCBA2C04075518EF5CD747D +C582F0DEFE4F674617DE189EF2030FB38194C08D3FF6D0CC1969494D86A74342 +337F5A3EAB685175FCF927B27E00F69A3F0B5B65E6FCB9C310ABBD731CA8D647 +F87D8B2BAC0D46E4CB24290D064B536CC0FEE46BE2E0982DC832F17A8668A78D +313F9943C5CE466123BC3B80122B69278A3B61FD4C5ACEFFA8B62EB6A1DA2AEC +7A7FB0D4B1A5D3B59235430FA156F45CA682AD86F3FF33D94C0867AD1E1D7AD8 +7439A4E3ABE919C6E3DE041175E0E3A1F2A878CF7144ADD7091DC52E5BE98830 +0AB8869740D57C6013C92CF23FE175EBD5C26330CA8B83F1B3EA50228085651E +64F36D1E8B1FEC84FE96CD2BC8AF45198C9CD8388301B1BAE9926BD8A014CEAC +3F8D5AA5C6DD2232A3F2FEB58E86F2835FB055ABA8567A3E53D0D7DD404E0D99 +7F2D19678CF28BBC35AFDD80DA0F1F47060CBCEA4EC5358FA8B000C570169AA7 +6718FF6DD3EA7622AB8725FD764C2EC14E2DCDA0548EB94981931C0A2FBFE083 +37D342A73ADF7BD3282E25390EA45D5AE7052C058AD6780ED90AA5007669AD9D +839B78DBC708D312C5E109E7756AE6D7D6BB2191FE1B6EC56B0721E6A53C8CB1 +6519FB1F37A8F38EC33E4A5766672E4785475A0F8431CEE6D66FDFA4680BCB28 +4EFAAC29A37BC21D9B884F508738D5F0225ABCDF34CEBD4D173321E328F877E8 +590DA7057B8EC485BC92A14DFAA31A63A6244D1AF09F29F0E36C6F9CFF260EED +10CC0D2730237B4C7D32BFDD509B6E3502A463284271A3991982887CF56DD662 +0654E725CE1642E0816092D6EA59CB60244DE99E733C2C8E990EF76B70B20B00 +734092F47103AE3ADD001D19F588546D8F9E8405290CA23BB3D3FA81F034EE89 +EEC731F922E6F28BBF12798A0A4D6856DC7D1987829310C32CB81B5F85B92FA4 +3DBA34BD1950E4E4ADD08B54F12F923A36195790A7D91447E91C75DB1D17F351 +D2FE11A9158534ADF9D06E2CA4BC2F11E9914AE70550D52C5667DE9DCDDEEF24 +E57C9F372DE435F3FFBB3BD498BD0B56B1B53534CE9F74BDF113E06268AA1050 +CC6D925F17CE2F7AF499C5DA843EFE205E640079F47F5B5BA0F8FA27FB30DBBC +3D3F95DE3D72DA6E726F7D599AB903EA90506732D051B5541C5952D522F354EB +F8B4E8C34660F9A53F9299DABF9C0CB5EE6F0685E522042814F58A0C5B54E0CE +58BB142FF89520E7DCD0321598AEEBB393008EFC4774E2B3BAA16C714BA68DE0 +C8814BDE1B37E87B1F7D4E8177A23C793F8D3F6CBC6833848CCC0885D01F20C2 +479E8F80CD07715EDB2BA7139837DB3E090F2FF71A66016E6805C6CACB18BA97 +035C6F33D8CB3CEB57D55B07AB10FBD469B14933299737F2535C97643662868E +41B46920C640A039A4DCE141371BCA56E66A2A6C0FF74A4CF52A851291B7B674 +8B7B676307320B1DE482A28741FE51E8FCCCB79DF636DDD2CCD73F2B46C29C2B +6B32C6C58AD847A41486323E5B75AB58BCF81FAB2E7D794191D6C66F1DD1F742 +6B0D0356D9460AE3287B98D3613E576238C10B6D18D4FC4FF25ED3A7A85EEDD7 +5FACEAB91FB2F0E6ABB6499AE02C3B1E6D2032BDF5E1ACC575620FD8199058DD +499B758C1B5C0B69F930F3A4C73E2E363168FD7E03352DBAA3FAEB32B29C34EB +7DB8B049FE0C20AE7B36DA2F783F8AF72CFBDFC698CEC9362FB188DE13CF08B4 +830993A117A1F06996B000787DDFBEDA484266BF6956FB0B27CF5613CF11DF1C +2BB3677E8CD75742BE2DF91F646817E1EC1F60EE24AC8B116CA297F57CF6317D +0F098301800B7664C0C19D12BC6C85688F105D3C91F18C6166EB9E586980FD40 +1928B4190E3DFCFB858DCFB0B8A536D14B9AF13BA62BD71969E9141ADEEDAB35 +1DA0931BF97D7749C4F93577D4DFFE52B373D4E88D85BE4F9E87B858AB32C26F +D4417FCA56EA2EBD9C96D3596D09781E09AFFFB1B4441293EA965FE8F78487A0 +5B327A789E1104D30C62248A62660C3F5710E0DE61F6F5DF848507F1CB7DCCDA +5F4D645939ABBAE1472F2704FEBF7AE27D7075946A02E322D43DB7A48F75A467 +5E081127E8CC6C1F4E674A6A8883E67BF4CD67DC016F0734DB7D2D1CA01FCEE6 +DD0B05A18A0FE8D678524A936B4AC20E512606DC99F412B589DE60841A0B67D0 +82F5F8202C1C561A60CD6779FB9DB36D5C210973BC6460EF7A01271CDB1F1D63 +FADD31E847C3B1EB0B9D4803A59CF6101D2178B0C68660E5078DCE5079FCCEEE +1DBE1E9FB0ACC7C973CD013E06A8D0D99B73E863987676E852F08756EE0DC548 +D2ACE7C12532CF8A53253722C0AC57FAA8E9B95916E30B46A8481C6607CC8979 +012E32098D7BF25586677CF9349456D708D37D676AB9E72877A4AA616389EACC +D4568F515BFDA5F56938455FD44F42AED19E9933930F0BE06B999D82C9A1A37E +BBB23B6F155C8DF5CD9374D76DE34F099B42DD6DE794BF68684DB697EA5ACDED +49880D90DBEC3E5E2F5EC08F1EA04B29174D918F6DA252FDF531D7873BF74871 +224BCB8A5AD6D87583AEEB673FB02EADFE76CFB7D08ADD021FC30DDE27E77D7F +50F3B5D09196AB64CE919D4E991D84D1D583763D5ED14EF6B106399908DBAED0 +D3900D1E15205C0F4B5AFF9104926FEAF219FEB0690552827DC496C10556441F +60FB15A6D64B52B2F84C6E49DFDF59816D67553AD81ED5573B000FDD6519864D +C384B3D60EEF2B1BB5C1141764AD6807DBF37BE031D670AF8632BE22A7076AA9 +99555DE31320B458C792EBD69279F58EC7317B1A52D0E51E950D76FB2C254C11 +CF91B8EB98EA650ACCE188585E642D17A211DA2574E509EB4E87EBFE313CBFDE +51F501C45B182D75EBFEA3EC9B95691E097E46D792BC095C6B0BD266B097BAB7 +9B8ED5797C17FDDB538AA00DB25482823B875CCD6E5EE55A76FCECAFFE8631B5 +3E4C3E9EC1E5408A2A2010FF96502D1CB940350524C797A89A5FF8E6AB47E4D2 +80BBE24B89EC689BFB00B9C5B469620DBB2F0281B757FF3AD2E84EFD0CA3D456 +A481978D788B40C8EF363A41B2C85E0AE198216E29A17D67D0FB19919879EA31 +57B62FA6123AC99A36840C6432895DC4528BE0D2FB5FE4F24F5DE3DFC5CE5E57 +CA4BE5EC998712BE30C680B8DD1F730CED756221598A0A8E5C448800491D8907 +A80C58E0D78D4A53C7B651177453802785A39C887AD73E87908D1E6AF244C67A +9A5029E17CA9F1BAFCC5802E842C33B39275F046C809F2AC427279361B3FCA39 +154E1175EFDB43DCD748DC7ABC240C8AED3F4FD78FF4C7AA684AADCEDC564AED +B35CF787921E74B51F4BAF2A8EC4BB2C3A78D2CD697BBAF16A14236BF7607F81 +6093A211924ECEBC1AA68511F7D9963DEC9689E801CA25DB41B89782A6471A62 +92965E950E21D5E41CE5496D95553FE6B9BDD94424914A3DA9F878A28BCF77B3 +BDA77E647D03250DFF74A2D17CD59BCC2763171F0EFA759C453A435F0AFC814E +D64E3EDD354A334150402AC48A6F84693B2C10E91C67537B45A5308502E96460 +871481EB917739EB412DC4262012D9ADED07CA03BABCACDD04A6C3A87F4938CD +6EE57009BB556DBCAA1E208D1B2C03F01E47EC0826C99C8A8DFB89212193E8A0 +2B2865539F8B7888664DE8CD3E5A193E6320EE7E111EF5A3EB73DF7ED921E4E7 +A26B1B1D18D92D78858086F7133DF3ECC8A80E499C576DAFFA25DA530769829D +FC01632F7E18C5F481DB0006621F7DD4320ACEF939AEF57A41A11E6BF8A183DC +6B2380A390E5F3568AAA53ED30292C6B28EC7EC45C17F471229E37FB8C1E5582 +B480FA1B647B69280035A0823C512F4AA0286201B2C9B7CA85D5FA65648F1479 +C86882D06BAFC16E0BE7EF0F4E16FBC4ADED7F49A2DCC9E151E26F1502748B2C +0BC4FECC8B11398034491060133041AF08BF8EF5B1103852F3834F6DD659494D +4AFD896D3E85560664B0F1F031ADACC315BC6140CF38668BBDEC82C202890144 +64BB345B4866F14CABC63D744F3E158AEEB004AC1A550502C2AD5F9595170EC0 +BE4A0A8D1232E0DEDA7F829B0D1A599BAD2007F9111EC24F1E08022168C2C34A +B1DBC6A844531D7A8D21731C042A7B380A1FB1D6B82E6EA341B123F8CC078324 +95A5049CC0C46392EB9116A03FE0F1B6168D272D96D8281A1527CA80A16A7449 +F31C1948E9D6A0E6550B686D985786CB6AC7B58DB428755875CFAE0572D2FD6F +7AA794CEC2AF0B7099AFC1DA5B3ABB479A7530B8EB9BCE9688BCE0276BA736BA +E88737ACD9E781828D687114ED67DCAFD4E7404C7C0E924A9141CC27692BEBE9 +5E8660B935A6F18F321E0AFA5B606F67A4D8CDDE5B424E504D52B2CB8E831A5D +F77DCCA96FC7D75E24F2D44D897FBBC3E35C98EFC52875E149E2C583AF6C10CA +BEDDD89A099F97D70AD5CA26C0CCF9E65352819C2FA1174C157E69D48AB817FC +007ED04651DDEB2FF2D8D7D405B401DAEBCE6151353441860A41731CE520F75F +6FA578EAFE5631925738B325F8AF2B0E15A5B84E9E1E203119ADF574989F3B33 +84404AF0FF3EF287A226E8C9877DAAFB358E5F1A5F5420767C0B6836FFE901F7 +CD1DF68CA5778B4649759E1F3F9FB5C5748E295ACD163EBF1EFB64785625ECB7 +A5060407B009B0CBEF3FF5935CB10233BD332B73F9C69AF4ADF799CFA08EF25C +A6CCCA840FAEBBC99C8639A75667BF652CF3FC25542FA6D873AEDC7AE369BB1E +C15BEB51ACB928C61F97E45559FDC572003885B999B4B91F66459BA96082C763 +A9F7A5C7C7F27B9CFAE1E80ADA4570300158A71016DA3600579434AF7F35AE65 +8B267F947E13216E437A98485CCBC940389CFDB05BA3E663DACF32F21978FDB5 +18528E1CB7AE189E007FA0778D62B9D05592E25559747E578C17156BB507D6DF +4B4198BED9F33ABD6E93F7D084DB0824F0C351F8D820BD9530CBC821CBDB276A +8C58D133DB076D9BB5689DCA2257FBC741C5F59AECB0BAFF81E0B899291B6F72 +5CAF3CE6A729BABE19AD5E6C31A790ACE92E952E82638B6AE9C0F61FF3A30003 +22F75DD843E2317CBA51FEC548525A4C8EDE18D2FEA0F6D8FC00B33E44938D2E +88663593AC4D1652680D51F1DACC46413C43902F77790B1C2E266293EBFB9C78 +833A274EDBF9BC80D19212D2635532B935766D0006697A7EC775AA3657152D72 +53540266F89330E3E19671E73E4BBB5F9A716EB13CD23FEC5B5358C7E004F101 +6100FEBFBE124B765BE98D24BF2E89AAD4A27C75A3549CD3907BD582E577B1C9 +1543C1AA7625E5AC9AFFCA8BEB637E5EA8C2BEAA30C8A7741D954422171D5086 +07E87A41204D0CCBD3A22E2D5184764921F19ED5981C52B539B06EDFF7A3EC02 +DF85F0132F038BE6CA49DB6A4F5A17CD5FA0AEC05478BE9605A3F7BA1159A76F +03A38668E6F2C72AA7E4DDE730641216CF7B9A61BFEA6EF937A54F2A2962A320 +9498FD339342ECD80A0FB71DE820AE65B80A5B7A8E9196D2530B8DEB0A1FB8C7 +15EAAD601F6836A6C8C0605BBB6A336A523A6CAF13B064D04F48F002B579266A +A46A47426ADB627668386804EB7AA726E1D65249F6440447BEC02306368C6B9B +0C736C4E5BD2362B7C9F0F7540B5DEBF391141BAE24EE7995303C1967B3DA371 +E0E052DB1D5259725163265BDFEAD1798D71912B4C7AE63BA1CEF08FB8A15E3A +25D67D9E3A70A040AFC254086D02FCDA5BCC1EF37450BA967EA9AA5C952AB4F2 +64F3D7487617B261CB8DDA5AD34429E3474A5C12D7DABD9F7053B22935909AE7 +D1D554D0C24436476ED68B1D209DD7AA80A6A547E045178D1763D596A5CA7AC6 +4E38DFD247A13D54EE7F2FF5A3442EDDF7F4894CA301A05ADD6E9C5020076497 +F2BF7F15BC210DFCE1A8DB7DB5ECA33EC5A7D635C089620177BD3E1F981D9CBA +B1299C0CFB41F53253A367F7B05C110C3702C570EE29503EFCAB28EAE54FC81C +922A0A80EC12E1A5D7C191603CCEB1FC7F41B06F2D91DADB13716D2D1F6BBB2F +927AB0F59DEA31F198D5422989AF7AE03D5F4AEF146323F8977F79540DC4200A +89615E702B72127B7FAFEA8152B53EA8ED9BE3BE7EE935B61ABD0B9C3DFE96E5 +B1A47626E4B62D96A9AEA785524FE73F68FA218DD58918D55E8338000DF0F5AC +6ECC8E6ED15AF45B45E74AED4F06C132E8076E21C79F6D313494BA23EAC84742 +2F778B9948CAD5BA1D589A3EA21516EC56A5E40D4CC0A5919180C2736D6570CC +FC03087BB9333C11310C8E5D814CB9AE8EC2D2973437F8B80C8CF48930E228B1 +4B35BFF4A7423DFD51D881829038D15AA5E3AC6AB976D3549CCBBD59034F1E7B +A539B629EB8C2FD450677510A7056C1D6184C20AE04B8BE7525526C9B19BC64B +1B3F6BFFB675B7D218D58C8D87F4EB1A28082A923D404CC58859AE55F0F8F433 +3063E52630170B2CB806BC95469581FB149AC66F00C26466ED3E1BA6459309DF +AE6050F36FEF2453D0CDB3DDAED0FD1E5C7B21A9469E62E9CC09 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +cleartomark +%%EndFont +%%BeginFont: CMBX10 +%!PS-AdobeFont-1.1: CMBX10 1.00B +%%CreationDate: 1992 Feb 19 19:54:06 +% Copyright (C) 1997 American Mathematical Society. All Rights Reserved. +11 dict begin +/FontInfo 7 dict dup begin +/version (1.00B) readonly def +/Notice (Copyright (C) 1997 American Mathematical Society. All Rights Reserved) readonly def +/FullName (CMBX10) readonly def +/FamilyName (Computer Modern) readonly def +/Weight (Bold) readonly def +/ItalicAngle 0 def +/isFixedPitch false def +end readonly def +/FontName /CMBX10 def +/PaintType 0 def +/FontType 1 def +/FontMatrix [0.001 0 0 0.001 0 0] readonly def +/Encoding 256 array +0 1 255 {1 index exch /.notdef put} for +dup 12 /fi put +dup 45 /hyphen put +dup 47 /slash put +dup 48 /zero put +dup 49 /one put +dup 50 /two put +dup 51 /three put +dup 52 /four put +dup 53 /five put +dup 54 /six put +dup 55 /seven put +dup 56 /eight put +dup 57 /nine put +dup 65 /A put +dup 66 /B put +dup 67 /C put +dup 68 /D put +dup 69 /E put +dup 70 /F put +dup 71 /G put +dup 73 /I put +dup 78 /N put +dup 79 /O put +dup 80 /P put +dup 82 /R put +dup 83 /S put +dup 84 /T put +dup 85 /U put +dup 97 /a put +dup 98 /b put +dup 99 /c put +dup 100 /d put +dup 101 /e put +dup 102 /f put +dup 103 /g put +dup 104 /h put +dup 105 /i put +dup 108 /l put +dup 109 /m put +dup 110 /n put +dup 111 /o put +dup 112 /p put +dup 114 /r put +dup 115 /s put +dup 116 /t put +dup 117 /u put +dup 118 /v put +dup 120 /x put +dup 121 /y put +readonly def +/FontBBox{-301 -250 1164 946}readonly def +currentdict end +currentfile eexec +D9D66F633B846A97B686A97E45A3D0AA052A014267B7904EB3C0D3BD0B83D891 +016CA6CA4B712ADEB258FAAB9A130EE605E61F77FC1B738ABC7C51CD46EF8171 +9098D5FEE67660E69A7AB91B58F29A4D79E57022F783EB0FBBB6D4F4EC35014F +D2DECBA99459A4C59DF0C6EBA150284454E707DC2100C15B76B4C19B84363758 +469A6C558785B226332152109871A9883487DD7710949204DDCF837E6A8708B8 +2BDBF16FBC7512FAA308A093FE5F00F963068B8B731A88D7740B0DDAED1B3F82 +7DB9DFB4372D3935C286E39EE7AC9FB6A9B5CE4D2FAE1BC0E55AE02BFC464378 +77B9F65C23E3BAB41EFAE344DDC9AB1B3CCBC0618290D83DC756F9D5BEFECB18 +2DB0E39997F264D408BD076F65A50E7E94C9C88D849AB2E92005CFA316ACCD91 +FF524AAD7262B10351C50EBAD08FB4CD55D2E369F6E836C82C591606E1E5C73F +DE3FA3CAD272C67C6CBF43B66FE4B8677DAFEEA19288428D07FEB1F4001BAA68 +7AAD6DDBE432714E799CFA49D8A1A128F32E8B280524BC8041F1E64ECE4053C4 +9F0AEC699A75B827002E9F95826DB3F643338F858011008E338A899020962176 +CF66A62E3AEF046D91C88C87DEB03CE6CCDF4FB651990F0E86D17409F121773D +6877DF0085DFB269A3C07AA6660419BD0F0EF3C53DA2318BA1860AB34E28BAC6 +E82DDB1C43E5203AC9DF9277098F2E42C0F7BD03C6D90B629DE97730245B8E8E +8903B9225098079C55A37E4E59AE2A9E36B6349FA2C09BB1F5F4433E4EEFC75E +3F9830EB085E7E6FBE2666AC5A398C2DF228062ACF9FCA5656390A15837C4A99 +EC3740D873CFEF2E248B44CA134693A782594DD0692B4DBF1F16C4CDECA692C4 +0E44FDBEF704101118BC53575BF22731E7F7717934AD715AC33B5D3679B784C9 +4046E6CD3C0AD80ED1F65626B14E33CFDA6EB2825DC444FA6209615BC08173FF +1805BDFCCA4B11F50D6BD483FD8639F9E8D0245B463D65A0F12C26C8A8EE2910 +757696C3F13144D8EA5649816AAD61A949C3A723ABB585990593F20A35CD6B7E +0FA0AD8551CEE41F61924DC36A464A10A1B14C33FAFB04862E30C66C1BC55665 +6D07D93B8C0D596E109EE2B1AAB479F7FAA35279ADB468A624BE26D527BFF5ED +E067598E1B8B78188FA4BCFB0B51692D07B0BEBB930C6F0997B437E2C51B876B +61A563A2673932C2045833FAA35DB22ADE12102335D5DC734AE3AC5EEE6658D7 +92EB62131E1DFBA441F53EFF9021D9D4C491F26BE8F54C61165CAD778CE8695C +EEAF70E3B20C64D4C2B34A084B5770BAB2A974E898F62BFE90F132A37E2DCA4F +43E13DB13C94DFA8ECE2B7374827AE168634FA007F8981ADA046CED3448BF453 +FCD9A4F194FA648F9FC0971734BB69CB73439CB0DD021D44A7C11BF295E81733 +4DFBA460FF3D654F9FB337E99E6D66FBA87A817EB9CA1536C84833870E3626DA +55D48DE850D3E6F6B29DA0E7C9D681283586F208DB8D58042E3A7CE55BE84822 +C98237911453E479EAB65AFEBA3F61A763B40E74535BE56C9D8D06DDF9441741 +5C9D9D917439368736619717FAB4F06E2C329AE0BA411F3FD522D9C33AD8369B +D7DCC9DF993778482F35F965973DE876FA19E109AA198A00658AB3F0D8E3DDD1 +08A573F2D525202AFC57E05D141E6C0BB811E1FE280EEA002B7A45BB363AD06C +318D320D2C81AA5DCC842CEF66E7DF7670588CB39C9F42EE7763A3A17372432A +173BDEF7ECCEA297CCDD76A835C36DCE9DB8F8CB66CC71B4920CF5BF055A5260 +5B41A5373BA6E4F63C85671D979EA5EC30D22163E6D206168A3827F465279870 +CA80E6632872F721BBCC622EE4214BF723551C846765495FA9921E11FE1A950A +53150C3F5D8595958A47E0B16064CC3AFD65DA294FFD111153F4F233BC5468AE +69585C16CFBFCA32C4B96C161F47B56661DF84FCD8ADD3EC086CFB6BB5179BC3 +A5469A1CFBC8620BC711F42D0D3139BCE4E38698D9C574450DB43B5A19FA6D54 +0368BA9F7A8DBF96DCD0B8968CD194264E6DD10A958846C278B8C2BAFE7AAF8B +44C84C955F1A89A13E62A054BC76CABBBF6296DE00A79CD7C8C61C70F127618E +9975B59A880685E126F57AD80F8F4D376E1B476BDFDAC868FB6AFAD9D694B561 +001623C4D9F55366D053B52F2B09EC08B81901AE0986C5350312E626006038AD +AC15FE313FCEE1A2E61F8992AC00CA7BB7F997707EA377D37EA6FF35BFBC2866 +A572B31491F9B80445685DBA5E62F166E80589F768FC95BBC79158C23B2F1BD1 +25816F1486A64F76D99A638AC0DC101FDF390811B3C118C2D972B2E7587F6F24 +7F1DB2DD922D237A7D18FF08FD665355CFBBEE799D3BFF11CD94CFFDBA3E725E +DCF4CDE4307E3B199D91893A365D04F43A5305BDD2538E28A0788E061F3A621A +B4A04E5063B47F0109C1693A284FA43E8F1EA9B68145FF51C005D3FA40713BA8 +1879BFC3CAA881B9D885A0C1AA8BB9A8C848963020A5B15F862E7DCC78F25D7C +56437215999EB78142C128C6CB1E6E75EBCBB1E4614E8516FEB1E68400C61326 +D9F9E8A41216901F77D9466455E2A0B45FF50B27B55A1E1DD4F243C92BA6B175 +8F7695CFA1E91CDD8651AEBA3D258FFABA6280BF2420A98FA7CECD552D152CFD +A8CCC94C032087A28D68332769DD2CB4ECECB15717C245BA305CB616CC72644D +C78326E77FA602364A7B1630CA0BD0282FA781E14282982C1AD13479B6178D28 +1CAA541FD3F4316F4FF81C53496DCDF5F86E0D7C870FFCD85B36C936B1E08D78 +CEF3823546BE4329B97EFA4E2880AF3361C0DD67F77C8BA6F1CE3822B7FBE567 +064ED0477949BDA06483F8DD04F891473C8FBF73A61F7C06B20FB8B5F0BF4B77 +1429190979A4BDB29D77E94D5FB486A93B8B61DBC84AE06B4E06CBDA3A942043 +9F9926F541DDE4E9B734A985F9054493508C5F7EF9ADE372C520840F15F705D2 +51826A537FD158C4105443E38116307A1D4608E9C6FEC3353F57E65E150EAC0D +B923BA82331D83E41C97ECAD6F8F32242DFB52EAFE2209B9F41DE483C8549B97 +407186E3A04011C8301B6C9A6D36D8170F40523FEBA5E498B8E5B7BB28581B5A +08892C2894316068537336338E46F5113462AEA9DA7102ADD13EFD2EBFBC27EF +00B38C25E4E23843C50C888B22BE1C76AC3F85CE148B7EA0F47D72E56726D13C +ADE5A5D539A8379887352298B7ED202D909C8BBFAA0BBE7CD378B80488333B88 +E38B1C3C8DD17F32583A642A1D0A4408B0C244185C1376364E723D83803813BF +106524811B9116A47F0A3205513EB80A4F798B330C9C4560F98FC3A2FA181680 +C83D6BA02AEB9E57310A60C39D31D5BC8AE28BB3171F90B69C11688521BA0845 +3F7D6E4CDB496E3F8B46D080156669BE81DE017A70B3B7D5EFD202F26514335E +E06DF49F24576733B1BC191480D8C4E613537A80FBB7C408E7562D31833E3405 +2C16297EFEA28E55E929640DDC8D9C12D15A89A03C8637336BDD0D712AD5A2EE +34012D38FD1AC70D13C3833B0A055B545F9EE98A463A2B3B424CC138AF6CA929 +A7BD80C3A804D5F80153DCA086B862A7D621B47F109EB93D67A73934B066B546 +BF15F27517D10CB8C83F7B319F423C16B42AA435231B9C4A05053D7109715804 +660B7052FAC3EF49E66AA763DCF07FD1AEFB5ADBC9913D3329AF36A7C5359038 +963383D68357C48B254DC2508706B1D5B71B66E2589214A336ACB11EA0E4070E +3F993DACD86186718E488D4734F024DB314643A9488841654924DF275A1A2221 +2FCDFDB677FC3FB49409A644CEB01B260A4210613C0AB3FA3BBA7CFD4CF2867B +B261875B9C592124D8A5609FCE7B376A39F6AC86E94F40587F69BE0E8B4C2E64 +DDBFADD699BCAA66A856556CDE4620A3C104EE29F9523D0ECBE6552A1B8211CF +66D12AFAA3E9611858D51B46039B5EF0F3E9A020A6370B34E80F83195A643F15 +86934B5BA8B60ECCD0D3C9496F8D32E70ABE513FCBB85A77D992E73AF8B955DD +5AE1FDC1CD972E81DF6816DBBDB583FC1410D2486F7479F1A466182482EFFE44 +DAC31E1F059370329DE19FE4629F33433576C1E3E6B49F122F2F7333C36AC744 +BA663E209AB384C9AC13B5AF8E9D0D7182D5F4D1EA874AE24DBA41DDB6586535 +8CA487EE4084602F3F2A7A46A1DEF17DE9B98728FB8113FD1BC2A62D3EAF7F83 +EEB13A260689B6BC369064D027601C48AF68CF0B507BD5AA8426833B11D5F086 +E0759BF00A3044F0719679D27D8D8AFA8FC303917255FBE6C1317702DAAB2C88 +4669AE63FDC111F7FDF28CDBADD14E9D82CB39340F567FFDFA4716CB99CED1C5 +871E005ED9EF2A8C3BD948CAD403F180EC8649FA7C4258DB0E1CA5F1ACA24DBD +B59A2D1D7D920103457822D0F03F7D4CA5FA2D944B13FAFE51C031FC9416E5E3 +4987815ABCD52F279B7E1A86F659576B57860004E6E22A6FF097760138E683C0 +638CA1E808E4B88CE859772171D8F864F99FA82FBB5909E75603C5010D49C62F +256EF48E28DB392A15716DCDEE0993D3C8B9D7A8DEE99AF3401FA9A39521767B +F04344EBC10A08F880FE13DB7E11AFE3372B8691EA9807B11BAE3C4B7536B7CC +B58A3DED042A85AE55ADA24E74A0A50BC5747F9C8E816A132CBB3987FAEBF451 +22FE85DD817E8E6B9DA980F189103C5E972614AE92FD7506CC61589B2284BC84 +A4486A73B619585F82FDE4410A3841729C2A26E7E6FD5052FEEAEFF792EC1B58 +0131564B3C5FF80F3FDB1132BC1D83FD8D0B4961AA68000BFB9A0989036BB0E2 +CDDB585566EADC55623BDEA1F218DFD343324776DD201CDC9AEE023DF5B98195 +AD23DE4BB8EC522DDC094D15A76FD38B85964AAAD1E2DF9513B87281F7106032 +D833B6E2019EA1E9D05C7AB6CB34DA376FD9BA187E8BDA5DF7EE06134DA2F577 +115F6E891601CAE2BFDB23FEB902E2B1A43F7A834632B6A7784EF9E9A659DE56 +5E7E66BD19DC11ED276BC0E117C8968E955629700B639BAE108F342BF38220BC +A4C110641D1F70F6843206711275F5C33A6100CD20825B2ECE39EBA78631E349 +FAAB77A671146B5A712B40F644B86BB1BF3EE8F75E49ABBC757485C1A53BD9B9 +34BCFF603FFF6AE40A641E9626B0F9259F79B06BA573D6A42DCFA21A7D69BF63 +E1BCCFE3816EB1247C9F6DFC07A59656D89D69DB5DAB731BC84B77EFE8860EE2 +AD317C5F30763FF2C1B9C00C0FD480282FC591C84D7E5AB2A795B1D2B3E50E5D +6061F96ADC97AA2DA0B04E96F8E3961CCE37109474963F05E9C15FDF55214DD5 +210DD9C43391D12018AE5480429C9D0738CCB2B0178A8824968536C99CB0BE77 +B649AB272EDCF07F46DE28AC2DB08DEABD47C1EADB1B2193EB614BC26FF58583 +B4543394A1C8DCB80606EE803B273D8E313179922EA334B26E58EDA4C2BDFF7D +3A7D83E0EE66A4FC9C6BC9863F8F9752D66CD473F28194B9F26D624F30FF8DBC +77A700D39436E16839628F46B0F3637AA4E8BF1B6F5B95CFECD5626B13634939 +C46CE51287BCD099E31DADA771D251961E311640C0623956B2AADBD1F18A5F74 +8DAFFBD0C0AD49F4EF2880A60AE0DF30AD1425B79DB0CDCF9472D2EA009E9829 +56EA40DF5941215E176B1AF92C6374AF78D9E6622CB915537EB9F5DD807EFE7C +9749B43CD46272AE2101C1172A5210A4F71421E75C874046540050485E27FC7E +C4A5DF6E3679343F161F3BDEDE5BF274D8B052224E987F4A135D2D8FE7F84EE1 +E0BF07ACF7C40DC3A9C50CEDFB0EB5EDE8FE341E696ADC8710EFA61526629305 +AC390632DE1B80CBE8B55B2A9600D8B94CBB43F4E0728C08B250A8F4FE484720 +E6A260E226D420DE06A87617A3264792743639A0FE4A2ED4D9627E78B0222991 +A583A5A87AEA5D695EBEF083D770C5771D7304E17ED5FDA30485E82FC8C41B86 +4D68C82CC51D87AF64B277E0DF3E97BD5887C7E1D913185D2FA7F29FF0BEC2BE +14A3E6D029DEA2D9DB4A08B5B3ED8157FEDA81F552EF0918ADCDE7580C48FD58 +B0FC972EE053225A5B2264685C7A14AFF0706B13EA016721DAE4F8525B8C1E80 +A3213A33114A3007B7EA5CD2B1AF43487F191D22A5C106D3BA83A22673131EE2 +5B370CC9430E2FCA3B5E3D181FF1FE29DF3A9E6823F243708D19260EC2BF31CB +BCBF8684B4E663F3B93AE10A636CC9E2F075C29F2AC4951E85DEE75B87FE2438 +C389B91443C821D4183C0C35E916FF970B51856FB04B61FF8A265206898EA5AB +CF60214736A9A0A9C320F462E7B6F7CB43C084589B88DF4217C69434107D8DFB +A9F3D9C248D26EC0F1225F842581F27265516AFCD19B9FDB23A18ACBEDA6AE5B +0C8445E30DA6F2611E3173B7E7A6C2BE8BCEE2FC8D688AE82CA35AA85B47C48D +A64FDC522636F50ABFC0B2EA41EA35D67B308CBF6C2A86B0CCB58A025DEF703B +FD186ED17A94B64DE3F9830621D11F0D94F518030A232D7C5927625B2B61F414 +ECEC88EC56CE35BD4AC7F7D4257BA661D2856B8043F236CE60FBAC9186E0B245 +9CA6DAA942CB5E4D5A4A59A48AEDE5EB78FE118F766FA9A16ECBB6CDEB641421 +7267F61338AEC49F83A9DEA6DD1F7FBC53ECF8DF0C6C2EB871518A94CD832E53 +1DA38A4390638D9115136C9D7E3FF7494DD7C2033D39D21FAC65512164664997 +785058E339CBD84ADC911F1FF4633667B7C172748F83235C241EF9083A29B907 +AC96C96B45CB517099E62B7E1DFC89390DBF51D02F1BF2A1AFD3AE38E3026990 +5E9F5A6FA9746C9E61DB1A6EB4F484C4EFCEC8F915F46E196ADBA0D2FDB82E3B +1A97DA40A76874B349BA4D480C360FEC74B1CC187C76128624D1C063D077BB8D +F3E50D3CB072C7ACA0F3346BFB1D112EBED706BC035F8C8A6A23474BC914E551 +1CA4FA8B354772352F7A0EEBDBCE9CBD523CC03E2C5C03084FD372DC9F7113A8 +8ACEBCDA2378AD0570714AD3879C7A05F120D18DC6C0AC7C5950117B9C8D24A1 +D59CBFEAAC8E439E52FE8438B59A7164CAF45C78E8396FE323FF38EA05B2107C +F20D2000F220EDF53987710F0C0E2DF4230391E545C2D21CC810A8E902323A2B +BA82A938A9221298D214BA068ACA55C690B7B7A508BD6595860E880F7AA9F030 +27E4243410CAFFD55BA49F5CA06A8FF416F194D3E3AB7192ACF871C7478DF987 +16540ABBB52520CBAEEB922BF441D940E813995D4ED3D8DD707A631707D5F553 +D73223CDE27A4699C2B377C319C1A0896AA544018FE978F5E65B24B278A108B9 +8F3D7FAEFB97C51F9AC0CC60BD9CC620B315C91027B83F0153BA8F7CF627934D +6D44536CE65F562ABF7A46DD66401FA9158F3B603ED6EE02C14B1919613FEC58 +8243E4215173307F4EAE1FDAB515F26419DE06FF2DF62D0E0E60563BCA2A7634 +4B58344AFFA0F104229A6BBAAB5CC149573A19C5903CBC58009807EB095ED97F +BFC664A073337252DFCF946620B1679778D6EEA5C1015B0F73E4DBC798F90AE1 +5CC47F90690314CDAAF1FA54C47C9DA655DAD56FE577468C6199C346CE0D5C14 +F3190325FEE8CD9F9545625F5F00EBA1B449F55FDD4E53FAE7C5286468677ED6 +2F324DB5A9C804C8A2BEA08625C37E2341E5C5A49C06C15F23BBBD08D56FA07D +2E6FDF1CCB9143BDD8EF3C48079B16324FA502C29DB5120878BDAD0A783CFF92 +19AF72D3844B77A18BF1D2BCE1C41134761176794957AF3B8634D9C5A269671F +5676B9D71F99E56EF7F84374F15099EB73515268827EAE0B114A10FDA6171656 +249A963A32C88761299004EC6848BD76B6D8D68379EB302D2528ABBEB85CA4DB +DCB6B0C376D86530F4FE8E17B93952C50D2136CDFB1E217748CEBF8AA6B46BAB +73034165C62F02628CA60EACBEE01864323CD45EAC63832BDACEE0C39BF6952C +A07095AD3D82C80E2875ED7B998F8D9F5D26D7416AA70E53760C04D2F07CF734 +E9AC0CBE98C1642B93075D96E35AF27717A52EEB4898F148BCD6F792194681E5 +F0A396D7E2FC5676FA380A34EFC2085A7D0A5DDBA1476D0823BB77CEF027B833 +A144BC9D0DDD83A6986D4153664226DEEA70796247B400AB0C53148D61BC69C6 +BA4C4A288ABD172909EDEDB5CE6494B4F86A42572D0BCBCF857A64A13503EC6E +4B495640064B9C4299C0B6AE174DEEB07DB173AB3CD0A00F03E9545386A3AE24 +717387668FD6EDFB94402E3512A7A6F14ED8F079ABD4E161D42B40BE5027E3E4 +22D8EFD20D83D85AEDA43557FC14973ABDA50ED56948FAC97B44BF6EBBB6DAB2 +E05C768D80E843837DAAFE5F4781812CE3364155672C77AB66301A94E2A9E6DB +85EF7C07FA61CB474C2CFC6A2F7D90962B4C0D9B95F391420EA0938BFA21461F +B979C547DD7144D2C73A0DB184D4E1152F82A559F09EBF4620BC1B9313E03FEC +0CA4C8D1EBDE10514F3ED772C73E76E67E3B20805A93BBC4E9646827276C758F +B57CEE6AF20227134072637C357683DE58A7830FA2227FA344860C2E4B332268 +22E6346071A229CA44AF26E180BA8AF72645D356C8AC2DD443F8C9E4775316D4 +F832981E22EBCC3D707DF08F12114D104341F83E27734D81A56005E5C0AA55F9 +DBF4FB0D4B17BDBBDDB80B3EAA35925C70B846BC4C49D0ACFDDAAB55280E838E +A962BB1FB63870089E1670EA0F1E6F810830E100D00759E16688AF88C5E832A7 +01EBEAB36927754AD34FBCF10F6B787B6CB1F90EE1C119A62E88B81BA82E71B2 +97ED389F742FEF4B76CE6A17D0516BCFBEFE6F7185A54397B3538E190CC19E2E +4CCB233AD9126D64977FF2E89C12CBA5EB2ABF6E91289728CEB3CF67950765BD +B6CB85DDBC55A6F452561F0AF5350DFEFAC063CE5F25C6F00C7D2CEF0B880324 +A82CBFF49C6796AA0C943440218E59C2A98E43C2D2764CAD05C0C5C4EA10F598 +F97288CCEE303179D86191444516A79AE6C4FD1E9E60615E3B547CAB7D471F32 +0CD1AF2B3F69C8C047FE1A01845D1F1A6EFED268601F87F8A92211BAB650913A +0A2D98053C511FA63A8B3813E2E2B3FEDC870C5E6206101C4DD70AD1CA223A32 +FB276691EDF2421D12A4B330C4BC655A1218AAF015A384B8057A71B8696FBC73 +CFA6A9CCEC4AD7B0789E290F483E96E741325E6062C5F0B37C50F75A81D00344 +966F1FFDB35329F1653AC58EEE4C4B9E97F89EE4A3DAC15D67B6FA4C814A0995 +7460E20953FBD68438F0FCC139724701242D7C45AD30C72141A695453D003D72 +AFC26291C1F4BF991BEA7404DD85C825B840301B9215060870B1E5177D0AF621 +83FE4365ADF6FF293B7F9627ABA242F0A3E4A608570E46682A0CCF1284961F63 +B3E68FAD31F3452B5ACE03F95505B3BC194C783DE0F8BF66CAB795BAC0544191 +4D98F68ED7E13FA2A535722B667F6DA5E91E74A01D815F8F3CCFB5B4820E0797 +3553604F9341EC1B735D9F96457879D61A216A449D7B8A94C928154358253459 +AF775289AD8E86DA52D05A2C58387ADC92B18E1F0D1C04CFBEE4877F49953FF2 +42A7654DC8812CB057B305A68B6C22C8BAAE86855EC1ADFF0B3E00D60C853807 +20B98B49E7B0A9E1583E685716F6C2CC58162127FC8558AB128B53554A679763 +512DC6999BE36C0110423659F580491D0071B4AC2374D5D305AB506B24E63E44 +BBD84371A903BF4CC762A0E0D3142BAF794A332B629C0C8F87366F0130FB6E7B +84BF4D812759F2AC831D1E3703177215908039D66CF07D17258BA2B34EBC5D68 +E86A5F0252A7FD1EDACB59639A56C710049D5612818EBEE157A773D8AF209CD4 +4A6816A6BA050D5C23B85C53E6C50CD2ACE0B218C8F87A54671E271EA0A8477F +4A1DD5D223C0C644580373A57CD67EF1CCF7FADAFCD53843526AA357F666B835 +4BB78097E85D4D9211DD8F506380056F03D9C9DD9EF63EE9AE3A5FF0F74C654A +EDB34C3E9894E6248A583032501A0C4B3816262300859EDDEFC8B4B095130B91 +A0E583759F16CB9E5B38C42CE172DE2DE760A9E994233F006CD772964A621DDD +686F4DE3DAA62A937ADCE746FADF3BEF3FC2F90C3661FCCCFAD07CD4C22A2F8C +4DF29BEC9C4ED7FB3006301DE829E18EC81C8B7F27EE3AEBE4292548C844523D +A6F07705F4F16891D5C310B62BC698014846F9887925163C732666B6595A19CF +4344B5391DC011EDA337BB0D00891E6CAA8A6DC61C87C467181950BDCF13A7E7 +BECB71FBD2BFCD3D93135D5A508D172C3F63394A741E1F6764735A61B90353F7 +B67797C681A1AB28131E8E29CD12906A9EE1E2844451FE9DD9B0228AB93C7310 +F2FC310DDABB8418B1C29DB475E42233826FD9D0800196D6A825F380165E27EB +EE33F99FCEC55AB289E5D5BC4B02DE34008BA722EF503608673CAEEFC884E36B +2D2BF1B6410DE582CE243523ACDBBBDF75A886CA62C634F99A8216AAC0B762BC +4312371C42DF685CD54923B02A5C7610CFBC2F1AA39F0FB7EEE423B787308DE5 +634071E08F7659C70A6EBB94826EFF8C817AD6A01B13E91F0F15F43100B0DB6D +052DB9CED475535E482A58E3528B50D66BF785454A17DA4E4280AAF58F6441FF +0D18F744E745FCD33024713DA26FD532E640E9BAB1195BCA469E9FF2476E9008 +AEF4A4A68B5114A7C2A29448DB7A9E93AD6B06429D760AAD16B48FD9D5395480 +36315FE778F795CD7B4F4F5081F8636FE81681E643BBE83740B8393E22293B6D +008C845E6B3B5F518B34337644792AC0169B793093D308641F4A4DD3DE51AABE +95E16160C170D0DCA01374D0EF9C0C1138AB40F2DF6E4E7E5C45C8707182F8DD +DD59586336662C831FD0228595D89E5818A85E6D2C461A252BC1A2DC5895D28F +C2CF0BCE8DE71A094B7BFB56A0640AF9274CDF166B0AA5F9E32C5F595ED7CECE +10F6DE434D4CE74211A93C88625E508546DD3715A18800E4829D27A0F1067F0A +31BF17B596F13E728FBCC9FC0EA5D01AA51B5CD8B76258FC9A283D7E7B8CF14C +80D2EAE16E5F6BFE3F773B6609BA7473B9431C7DF9560AD9FC6E4D64DCE17C7E +9AA26089E18C5AD0FCB8F1838E58124B0028B2C2E4D5CDE965D02195C16E968C +51714CCF9206576B6D07128DE08529C4D94A1E2986E9060CCBF3AD61C605035B +F78A0F954F3E1E5227526C47631375202CE7B71F014053C68652EC5DD89001E2 +780310E4BDBE74B38CDABB12B604CA278AF003A951F7361A607A3AF0E1CD8C8D +065668CB80022EB608FFD13A49FFF3DDE556B4F8BB7D8B379120AAB86D71AD8E +64CFFB848D698536EF379DF427309125FDCAF1F521A767B8722DDC88A64F50AF +EA8043686A2BF63A8B447B6B588ED2985C773661881ABC98E09DE56F7F1FA305 +78055AB3C1C99FB40A4FAA548BD9CA0B95C29D74E6A32F3058EBD42AC47B427B +98D24AEEBDC0F957EB5887100659B402AB542EB5EC0A4CD278ACAC36BD87798C +C41B89E8888E23552B06434635D39D6E4011F9E155D648029FBB2D9F8837774D +BB8A7D722A8EBAF520E0CCB55EF124E5E189BD7332E0EA5A90B10AB63FE405A1 +6A40F4B356482ABADD9EFE8E7D2876F5BEE9405EAA684BEFCD179271C29AF089 +097C485E4D7911EDA8997D29A67B9C225D592EE5B3B79C3F8931FD7B1A6E3960 +328E1E2718E7F999714A6439F2DD59B5FA553106F3CA3F82D7247F61020C7440 +F88FE379239A75D25FE3B3F20EAADEE28864FE249EE86664C792D92FB6E0EB6A +9D3F154EE8B06501A347761BEAE8929E122790A718E2389AF824728904A8D449 +8DC150E294B5C9F4607019120F7255944A9A8D269ADB25FA6CF19FC52B50FE7C +B86962C94561EC3386D0D54E476AE255B82A242DF5676D26242C59391D0796E8 +63039DC728F0787DAD489F3DD3F7DE89561892A5E8A38A3B28B7E942CD331E9C +DA010D80EE13169C73F055F2941462B954C1602E2BA63DAD5578DA5ED29F68C5 +50A869EBA6FCE2683FBF6A22EC68D25B306A27B79AAF8E889E82E2986A8088DA +7934ACB4168DF49CB75BDA561940C8DD8A7800E4D0A7C1DA6A23C48AA319FFC9 +3AD59846534F647DA0263EAB436564B6A501473795D2E5B5BD1E071292F958F0 +909A4247E8714F06A959A5FD8407A4B5D701CAAA3D7AA7931E091EADA1821DB9 +A795A21744000A01933981BC2C3382CC6C2878BE0F3C91DAD353C9182ECD3DE0 +C8A7B8921F87CEF6A6124BEA3D7F77A4AE68731C0AA996A9BFD86D7E78BC99F7 +E6B1BA1CB38EB0F4F36CE7537C3476E85123D3DCFE3B925BD5564705E204B101 +FD0DBF5377E44978B52DBA6BCA2780DC42620ADB6EFCB026C148EFDE93DAE351 +9388DCF162AD385FD4F9F3A671FB07570878A52DE86C00255E93B45D928449A0 +36A0E59C8881CC43A72ABF81819AE18FBBACECB668E0F062591EE485CEEEE60D +F4F218CB633F7A0260B4884CF2EECF06E5705A197D2990DA5D9A7456129FB40F +C0C6B3B6B2FC22D4264BB48337A98C375DFCBF80080DF4AA0753293E91C96FA5 +12A0B8136C7C4C80FB032C68EA75660EBD1FFB27491B7A6D984054D35FECA9D1 +5FCCE108B2F3C4C23202CBF5B85BEE7F0F420FCD74CC5D558A016874EEE3B5D7 +8E046B303D46468BEAB588ED948712DE467C5ED1F42DF22C9B402C596D24B72D +2F609E7CACDD293B5BF0C9553D22C47EA88339AFD06EB3AE466EC2333C1C85A1 +00CA808A4264FFC89A485AAD982CE70C5EB9070BE28B380BC239FEE14C455994 +726DD99D74920AFA174CE979BF410E72A22D151FD7BAC732B7BDD1A4142BDB99 +70B521BAFCA51DED5829A9A36F7FAE6E41BFA981FF2F209C9F3DDD617890BA28 +E2CDAB7E51D49C22F338ECAE92B418855FC59577F8F199F5E372AF73701CD030 +273B84196E718BA54284A26D724ABF7E698A94A47CE3917467540AEC57B31E4D +3B8691B200EAEE8444AD5BA8C24A602B7EFAFDE2EF05BE950222229FE68CF9D2 +7B5F40456B0C5A884385AE655B9AA57095B4A0B0199E7C787CCE1E0C1CAB4E18 +506587C792DC259D0EA306972DB6170F008FC9582D4930BEC21D5DEED3670071 +85EB62A3BA27A30C9245F0380F3FD20A085DD77661C3408378172F830AE09837 +8380D1C07DA549314DFA349E9566259888581E212CCABA00BBAFF7FEABD4F848 +6672980B30A1968293CC7E8A6D7DE4A5CFC7BA3708A76122A3D2552F69B6E79C +0D6C120A098404EC2B2B67E80DE56BE7BAC2A05FA3EE6B68596E98E3A997303A +D0A072D846360D0321F3F856A8C7007F489B82BE253435DE87C8163ECD0489E7 +585ECEC657A0E040A9C1FF28C94BE2B2471AE2C5966778FAA120DB1DE340E3BB +9164EB236F63D8663566C306F263A79D03FA3C26F0A3A6DA8D0AA119EF05A515 +CECB66F7D06EE72C00E2AE1363D5DA802AE6FDD5DC07F994B332332FC17A7AB7 +258A0328BEC8F828CD3833F6066DADB4B62C42FE93F07E35D1C8483D12D52954 +42795EB0130CF2A6A2600FEDED32D288DFFEB25FBA5EB70E4B78601A0A653162 +5F6A3D4DB999E0AE48E8CAB9E332048EDE4ED293CC62B7867F3159C28D6D73B1 +3A951C22096D1A77A68E37C2F6CDF58FBA5CE7D6B4E5E3FA78AC2B0845F7CE15 +A27B284DEEAB751B1088C9E190F4B90D3D9E068AFE56EB07ED0C8E332A49E8B4 +65570124F18665724A3E52A204BDB06D68B452A4D63F4B0CA4DFA3CED2FEEA1A +D86ABA5D4B3EC7943FA2DDB74F93D3D4A1CF559D2EB3CBA0B986E939137EE099 +42CB3160219BB791799DDB079A10836EC5AC37E12137FC33266A7A6273987E57 +C97EC1F6207AE4517CA2D4E08CA90A5DD27015C65D22DADC6D06DEBC6BA5989E +F64BB70FC214E1536882F72ECCF238CB5BE51A261CE55661ABA44D00DBDAE487 +ABFE21C5178A131B5CB3 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +cleartomark +%%EndFont +%%BeginFont: CMR10 +%!PS-AdobeFont-1.1: CMR10 1.00B +%%CreationDate: 1992 Feb 19 19:54:52 +% Copyright (C) 1997 American Mathematical Society. All Rights Reserved. +11 dict begin +/FontInfo 7 dict dup begin +/version (1.00B) readonly def +/Notice (Copyright (C) 1997 American Mathematical Society. All Rights Reserved) readonly def +/FullName (CMR10) readonly def +/FamilyName (Computer Modern) readonly def +/Weight (Medium) readonly def +/ItalicAngle 0 def +/isFixedPitch false def +end readonly def +/FontName /CMR10 def +/PaintType 0 def +/FontType 1 def +/FontMatrix [0.001 0 0 0.001 0 0] readonly def +/Encoding 256 array +0 1 255 {1 index exch /.notdef put} for +dup 11 /ff put +dup 12 /fi put +dup 13 /fl put +dup 14 /ffi put +dup 33 /exclam put +dup 34 /quotedblright put +dup 35 /numbersign put +dup 36 /dollar put +dup 37 /percent put +dup 38 /ampersand put +dup 39 /quoteright put +dup 40 /parenleft put +dup 41 /parenright put +dup 42 /asterisk put +dup 43 /plus put +dup 44 /comma put +dup 45 /hyphen put +dup 46 /period put +dup 47 /slash put +dup 48 /zero put +dup 49 /one put +dup 50 /two put +dup 51 /three put +dup 52 /four put +dup 53 /five put +dup 54 /six put +dup 55 /seven put +dup 56 /eight put +dup 57 /nine put +dup 58 /colon put +dup 59 /semicolon put +dup 60 /exclamdown put +dup 61 /equal put +dup 62 /questiondown put +dup 63 /question put +dup 64 /at put +dup 65 /A put +dup 66 /B put +dup 67 /C put +dup 68 /D put +dup 69 /E put +dup 70 /F put +dup 71 /G put +dup 72 /H put +dup 73 /I put +dup 74 /J put +dup 75 /K put +dup 76 /L put +dup 77 /M put +dup 78 /N put +dup 79 /O put +dup 80 /P put +dup 81 /Q put +dup 82 /R put +dup 83 /S put +dup 84 /T put +dup 85 /U put +dup 86 /V put +dup 87 /W put +dup 88 /X put +dup 89 /Y put +dup 90 /Z put +dup 91 /bracketleft put +dup 92 /quotedblleft put +dup 93 /bracketright put +dup 96 /quoteleft put +dup 97 /a put +dup 98 /b put +dup 99 /c put +dup 100 /d put +dup 101 /e put +dup 102 /f put +dup 103 /g put +dup 104 /h put +dup 105 /i put +dup 106 /j put +dup 107 /k put +dup 108 /l put +dup 109 /m put +dup 110 /n put +dup 111 /o put +dup 112 /p put +dup 113 /q put +dup 114 /r put +dup 115 /s put +dup 116 /t put +dup 117 /u put +dup 118 /v put +dup 119 /w put +dup 120 /x put +dup 121 /y put +dup 122 /z put +dup 123 /endash put +readonly def +/FontBBox{-251 -250 1009 969}readonly def +currentdict end +currentfile eexec +D9D66F633B846A97B686A97E45A3D0AA052A014267B7904EB3C0D3BD0B83D891 +016CA6CA4B712ADEB258FAAB9A130EE605E61F77FC1B738ABC7C51CD46EF8171 +9098D5FEE67660E69A7AB91B58F29A4D79E57022F783EB0FBBB6D4F4EC35014F +D2DECBA99459A4C59DF0C6EBA150284454E707DC2100C15B76B4C19B84363758 +469A6C558785B226332152109871A9883487DD7710949204DDCF837E6A8708B8 +2BDBF16FBC7512FAA308A093FE5CF7158F1163BC1F3352E22A1452E73FECA8A4 +87100FB1FFC4C8AF409B2067537220E605DA0852CA49839E1386AF9D7A1A455F +D1F017CE45884D76EF2CB9BC5821FD25365DDEA6E45F332B5F68A44AD8A530F0 +92A36FAC8D27F9087AFEEA2096F839A2BC4B937F24E080EF7C0F9374A18D565C +295A05210DB96A23175AC59A9BD0147A310EF49C551A417E0A22703F94FF7B75 +409A5D417DA6730A69E310FA6A4229FC7E4F620B0FC4C63C50E99E179EB51E4C +4BC45217722F1E8E40F1E1428E792EAFE05C5A50D38C52114DFCD24D54027CBF +2512DD116F0463DE4052A7AD53B641A27E81E481947884CE35661B49153FA19E +0A2A860C7B61558671303DE6AE06A80E4E450E17067676E6BBB42A9A24ACBC3E +B0CA7B7A3BFEA84FED39CCFB6D545BB2BCC49E5E16976407AB9D94556CD4F008 +24EF579B6800B6DC3AAF840B3FC6822872368E3B4274DD06CA36AF8F6346C11B +43C772CC242F3B212C4BD7018D71A1A74C9A94ED0093A5FB6557F4E0751047AF +D72098ECA301B8AE68110F983796E581F106144951DF5B750432A230FDA3B575 +5A38B5E7972AABC12306A01A99FCF8189D71B8DBF49550BAEA9CF1B97CBFC7CC +96498ECC938B1A1710B670657DE923A659DB8757147B140A48067328E7E3F9C3 +7D1888B284904301450CE0BC15EEEA00E48CCD6388F3FC3BEFD8D9C400015B65 +0F2F536D035626B1FF0A69D732C7A1836D635C30C06BED4327737029E5BA5830 +B9E88A4024C3326AD2F34F47B54739B48825AD6699F7D117EA4C4AEC4440BF6D +AA0099DEFD326235965C63647921828BF269ECC87A2B1C8CAD6C78B6E561B007 +97BE2BC7CA32B4534075F6491BE959D1F635463E71679E527F4F456F774B2AF8 +FEF3D8C63B2F8B99FE0F73BA44B3CF15A613471EA3C7A1CD783D3EB41F4ACEE5 +20759B6A4C4466E2D80EF7C7866BAD06E5DF0434D2C607FC82C9EBD4D8902EE4 +0A7617C3AEACCB7CCE00319D0677AA6DB7E0250B51908F966977BD8C8D07FDBD +F4D058444E7D7D91788DEA997CBE0545902E67194B7BA3CD0BF454FCA60B9A20 +3E6BB526D2D5B5321EE18DD2A0B15E53BCB8E3E01067B30ED2DD2CB9B06D3122 +A737435305D42DE9C6B614926BFD44DF10D14402EBEDFF0B144B1C9BD22D7379 +5262FEEAFE31C8A721C2D46AA00C10681BA9970D09F1EA4FA77428025D4059BA +2988AC2E3D7246BAAAFB89745F0E38580546045527C8779A254DB08DCC6FB9B9 +0E172209FBE3857AF495A7F2B34BC895A39A30F903DC6E3202D29AC110D868F4 +7184CB78407B8B9D42F6375F67FD4B828592E4A977B9E71854D143CD1A9EDCD1 +767CC2929E071FBA4C3D17500E28A23F697B5D5CC68D5F56EAD14BD504E07182 +3FDC12F5404E74EC1C02AF00C1A6A17F958770ED4A024F5B3644DEFB61F2578E +56013D0B4E7CA3AD255E23DD63369A921D427EEE0E098E8148B16E8A5613A8F8 +A5F1099E15AD16EC554B644DF306F0CF3571055A81F1B464529DB49E919F88E7 +581066BEC4765E31BBE28C245BBF0B74610DBA30C63A71A4F3B60593A6B41C6C +636C980828CFE9A3362FBC02F1967F0F770A4790F90DEF9D56E0A76B0703FC58 +2841E6E8D984FB476D4FEB960FFB6B386EC6CBB9EB83704B0AF63F38C77090A8 +DAA165E6C6BC86601B14F8E9F504A9D578AF05128D8C1BCEA9D21057958D5DCF +653026524A2D101334AA3DF02A3CFA410836E6001561C00FB34AB04FF97302F0 +7CCD024F8C61577E82FF229A45F7FE22ACEDD95AE8052044A41EDF46B8F84346 +7275F5423171DF88188EE93BCFE0A84AE5C999E9C774A32B7A2826CEA8A8560B +2F61A42F967BCBE2081DCA5547D9EC53467ACF8A6AADFC54CEDB7305DD661ECC +3FE33D8C93D2425ED57BA83F360A384F6B94023EF8938DC136ED1F66CDB618EA +F40377CDEE0F17653E011F5CDE11C81A3FA5F7168681C02167B275AC0F73EF89 +521A152823FCFC811C71E5D05D99094EB69E5724B34217D101BAA302B5BFDDF1 +4DE66F7887BFD458C2A97A835C72E7A6EC2500577B0B057BA1B4773094EA1954 +589FE5B1D1B4520FFBEFB6ECD015B606A244E605E78D39EDC316D97D99862CCE +898341583D28F141A02877B76050B07694737E9F107F153E5A5C7393CD479A09 +114F07D12DE0185B971BD526301914EBAE20A38DC804C2319EFF3C8C4186630D +C91141528F408EEB02C718A0A3E0D39D1C9853F71113AC07DD209828B4873031 +3E7A4E45D95025D9C558CC0C313BA3333EFCEB2D95B7BDA88C062E5243DFBD99 +744C678DF4AE3478C68B4F7BF5C52DFD8A81DA0BD2C95229BA43D15986717CB8 +A925638049C2A15A6913B9819B3642F68A07C4FDB2552C97D29211FAE2F16E40 +076342D4C5A72E0D5185CE8EFB7A8D87D7F345E776512E8B41609794052B9A08 +87EE0DECF203189379B9DEFB8DEC9585DEF2B44C7F64B4DEE827C74B975BD1F0 +82D7E511A5A0FEBD52702F7E68157B509F303378B191BAFFB4229495AE65C558 +D72D9AC8286E61633B29CF90917E4A01030D6B82CBF263751BCA8AB219284B04 +80A8F4B34D0592EEAB82D64F9A5EB6A08A6CC5A7F3D3EE2B61710DB386AEA658 +7A059E9633123EFEC39FADEA37C4205112063F7BD3D2F8319A30DA796E55BA23 +00DE2B3C15511F87044ABDD9DDA9247B5E785A01A05B39F1A395E36AEA4D3D4E +ACACCB99E99DDB1AD1329069714FF050311B274E495FFE43D33DB3BE958098AC +61150EBC4F9DB7BFC8F6C81047DF0BABBBE67970D14524C82334B693724A0818 +0007E4E848CE4AC8F07E72F02D74CC5C06DE0A67A63156F9567DEB913E874E56 +B993FF1EF6774A9C582D8DABECD1EE1D1731457305A989E9BCCE8CCA4872B3C7 +7635D840A0FC8FD9A40CB7B2FAAAFAE3274A2BD0CF7F681E877830103D2DD3DB +761F3D925AED45E0427BCCF205201BF0DFC16C0109A4389F37F25AC6E4E70C85 +1BF6A47B91F62079EAD1D983BDD249D0EF82B21F89FE58CCF2612BA50EA48901 +6757149BA3C3FE22E61F80F62CCB355082A71F1C85E8819E71E8997CAD75ABB6 +070F0CEE40E3A9CDBCDE24D59F2DDEA887568EBE585EA8D40BB4D0677097E73C +1C76DE3BD95B2BD843B1522540C5CDBBE4E1E6FA4A77F93E724F4942757B50C4 +40E316CA4DAD1D9D36823BDAA73E3BEC72B03F6DB2E99C89D2C3F840C15CBBAA +0EC7330D23A3EC2780813F0C1F56C891FC040B958C7861DCA654743DB8FFCE0C +EF4EDF238CD820A3EC97CA497B2C2504E43B9F29707D7F92114E8053CE90C494 +4C3E681028257FAE5E2C60C3E1A33E40A7C5920A8E6FBCD4686135296590D866 +1359BCA5B650EF33AB7582BC61158A573F93C4ACD4BE3D0E36EF1A7683B85438 +1FFA47955F8C1EBB80BEBC9427F239EFB50BA42D8C72AC0F77A511AF3832F819 +FC3576C4A28B6154B8B14B9B88A84B2E9DD9AECEC59A9CD717151FAA26CACA64 +FF6B2A5F561F77E9DBD2285695F0B1DD8D1D174EC5ED48DA7324EE88698105B1 +E28FB0EDB30067AAAB1AE5EF6527DD1A83DA04B50E5E2B7F2A63F0E8F924B2B3 +734ED216DFF4E41E2C074741FEBDBB01A103B5A68961A12C571A0A35CEDB11E7 +DB6C9046AA97B45F756EF5B32CD3E1446C9616BB8A7F57308679AD7E5368648C +37F5CB0CB4C95D49C8DF2D30A8C17D8870BE3C4D9A077BAC5EDF794050C793D8 +D26A5DBAB2025FBE4841308D5FFCD592C37514D1EA9F7286EE8789D41BD9E38A +905038AD5FA8405AA7568F9B34F43DD17FD575EAB5067147CAD8E9C0A454B292 +74635B10585CE8D8F891A07360DEED0DAFE9836B006C3ED2B6E10674CC17CD8C +9A862C5C51EFEFB1AA02F313DB95969B90714C62F32BBE3062E2D805989EDA55 +0684402A5FFD3B330B194B1EE4490D05044859619A7A6A7AFEB4909B63CBE907 +34CA9F2B19D27DC59F737344E1298EF87FD4210D08FF55E143319DDDF5E94582 +1FD4C58EFB00CB9C840ACF598F300B9F6BBC4C83A89E7CBDE9F37C0EBC924777 +9337FD42AE2B208FC6CA3E0D9FF9AA07BF7BB68ED07EE307267BE7547020AF1B +B3D65F802A95A69A2CC366B51AD5771EC679B23253F21CDF12E8584ABEA58D92 +38F3B4710D434F5F967A9868BCAB5C4B57F5E1650356F9509050F03FAD247F06 +607D717C4C183CE793BA995DCC37C836BC994465C1785517466DED4A2CCA2AB3 +32F2D92C7DA918D720341298C650B4ECC780A457CF6CFB2120313BEE25A34AD9 +3B2C2B587AA791104E38D27C117C3B4255791862E6750FEC3C32A5B965D72FB6 +E333DBAA452661F88B7CFBF442A9CF467D01B1AEFE817317BE81CE3479E17E71 +6C24D0A6C08978B3DB9EA09567FFD14118259E858E88648222DFEB2A655E0A0A +98F08F3D13539005E85DDFD0B1CA60E48C0DC80FE0AF9550344B07548E9B3039 +E9ACAE903B7A3AB6FE24324319DD792FC5140A2A368D233140DA5690D5A8DE57 +A42AB04F6E9D167CC3D0348FA7CB3EB537AEF89B99C7C93F398C9360A0B1E2CB +CC7E403B1E439CE040F958BAA50E36980FB3EA7F789FDA23F4292E566BCBB731 +38BCA4A234E2A205D815FE2A450DB6586D56E8C1AC62A50DCA49CB76E122D050 +E1A3413CE5BA8BF96F34990DCA2668FCA06BA66D9031AD98650EFF39D881929A +28D99CFCA94FDB163F970F29F3A2617F83F4894EB997EA7AFB67A6293A08E00F +37D5A50FC0C360BC4B7F9C876A6351F9D4B3279C1083D6DC79F5A63EA48CD5F7 +02FB3C581F446AA9F19603AC165BAEE2EAA7B2574268947479540B5801FFB122 +AEA27CE208AE86BFE14B8D139C16ADFD5D7ECED40EB0E258B38E509A8A1E07F6 +EFEED540300ED1EF27AEA5A9B1B0FCF027F626B7258F4485B8A17399188CEDB4 +8CCD037D55F8EE4BA5D2E876DF7083409DCD95F644F43778567B61F3A41894B5 +485A87B1381E9E98C713E2BDE4594977B85C7A4F58D542C634F9F76B9403E23A +87D1091723B30E53CBB97A1406EBB78A7E06DA949505FA40F0D5CD0675EC4B6B +207B7210210E7145ED707377A9A1534FF7ABD8395083EC23928A03E3016F636D +1697B161377879568A752DDBE939BECABDE106790248103F1C6D75D474584F85 +33DD684D3E4927EAE6DA1C2020EB78B226A43708351B8844E4A491E82D018AA5 +AB46821A444373FF2CF81F996193D0C6AC98E2302BD0D7909BE77D97F1A6CC58 +959CDBFE69014EA8F95224F2D2EC70DFCF8A38EDFA77581CABF0921B863A6544 +24740A40AC91E315E97FAAB8D490AD44AE6805A9C7471AE945A40886A2DA1F03 +7B94629ACBFD5C307ECACB6DE6FDD477F439CDBAC935A94679F037F1FD6DEDA4 +F4EABA6C9837CD7679DCF343E823FE70D065D0379ED0EA0C51DF0700B7F97775 +4EEDBD920782FF119223FAA23689358F35C8A68566DA0D9097C6D0FBF163EE2F +656EEE4291F0DF40C5F4910DBBF3A9A33A0B349A2FF1D01811857F9335FF04B6 +EDDCEDA0172528348FED69047FFA80E2EEDB6A612082134CED2E167A061EB226 +CAB50190F731B40DAB9650D4BC581BFEE93A2CEB9E9BBF5860760405127A1E3E +7FBEC40343D0D2E8EB80083B354C095C483A8405B7BC144FCC23E8F25E745A4E +8C527635AC995A1065A7FCFD805C6CA7CF611C06845EEE14D07B81E3B404E727 +20CCC5BD3657D1575949FF993A71F5F8C5236135963F13D8C09B1F3B08CAB6B3 +58460385AE220AE727B0CB9C05020EB678240921E0735D7A28E6C17CE22AE05E +6C9DE888DC5D3E24C60BB94289910B3723F60A74A8B48ADD5E03B6886044DE90 +69236204BB7411A3D9119333D328F3A7BC446AE7C2C4C529D5E3D501EB99C900 +D040D670DA581CF03FE6243093192F5E91A13B79BEF60D605A2EF3BB93B569A6 +9C533A1AAE2CC70151A0BB5992A5B8269472E626D299F66D3DC25952AD9CC205 +25572F56F774E156EB78B0406C4DE584660E1AC7E629DADC0BC41E725BACEE76 +FE217A7B0190A4CB69E9F32F4F7A184BF53EED2B1659DAC33A2EE695A88D9F07 +D076E3ABCEA448A576DB7B0C5549A5A2D63710141F4E539DECF167EFAD871552 +7BC24AE52DF04152173D6E4CA827DEEDEFB7E9C3E72B5934C5DF97BC1EB23749 +F776539AD43E9200A04341EC0594EAB62A0C7050DCA43945E58C4890350F3ECE +5BA67C3D716D5360A5C1FCB6EAE1EC00D59B46402B16B14B821EEAB4758701D4 +4CECA59735D67B099AD9CC4BFF50B846CFE14E35E4D3D1F5A3ED2641ED940775 +0067986ABE6AA117DA321122636AF7FA88AF53A473BDC12A4844F46F43D94ED9 +CBB72CF2CCB37469C2E63661BCA5A298D5E9C6BD3D41853FB4867DF938DF0391 +2F7ADC04FD1B0D6B0D5478581D4A2E51BDA015A4FAA53378696FB50A3297DD3D +20C365B963BBEAC22789E0073AA921F0640F63DE0833764999F02B66E800991A +438223368F1AD8FADC75E2C5130F76148FBB0ABCE3B1270C6F1CAB55259B5290 +A10A1CD5419F9254ACE5834A4B8A71B8068EC21E6C4AFE9E790C74EDF7411289 +3AEF976BB7A42A7CC1AABE4634CC1700272E6B272BC6CA5F1549F3172537FED6 +C4047BBA4FE5776BEB53E8AA82BB17AB4796C1F91264A7B7E95EC14C0F74710F +9E9CFD51DB71C3CFA152D6BAD9E60B38D688C7CC5B587F37BC2301C2938DE2E7 +88E1607E5B0CE6AFD63989315A9AE3BACA1E0C0E0F6CBAFF02685282033A7532 +3E5B0AFB7AF6F4E3FB5801D175D02551F9746503A2E50AE9AA8AB56DE79BDE79 +74B4550F3890818AE801957A3B6A822614EB8D12FCA584EA8C41B814D30856A5 +97C3ABB9F665D1B72B8D19C4D2BEA46AE1332F8BD2A6FD2C2A1183E48F62C4DF +4175B4CD65A9B145D400D1CAD7E00D0D154182126764BFD3A267F85BC623A575 +92FFC5A063A9205A02C89CA4B40C4D489F17D383FAF081A3C0B2C94E21BAED8B +1D06A4495F917C37F8B04070E9A90F60AFE3A8F0073086F00C5A707A96D4E1D2 +BE5A637EC1C614997446262573426452FA5872FF661B519E24A34E3C58D4A07E +BBB6C294D4DED3EE01A853EF8E153D905B0598241F97301CA600C09B7D116901 +77EA1F6C40CBF7CAD5991EAF92890305BC710270FBD575B626A59EBEBE2A9A21 +D02700440C57557225CEAD5FC1EBA1763746A061A7F4DFF5558F6CF9160F7CD8 +7C84BE7407ED508BEB2338DE3C5207A7804FF21C4E0FEED254C0DE812FC8DFF3 +4B5B890B24BE5AC7C00DC1C193C88A6B33A442E184B4F5EFF79F4CACB8A56DC4 +05098C620D63A0135F8FE2959A396398A421FC16F8AFF99F702C1B10F92ECFC2 +2D86DB2489887879926E2AD34DB4F7A9CC460E12812C24FAB08ED0F2C3DF3FCF +AB41034C07225F20ADA5B5AD6AF5349319D3F937DD64B2E0A0887B880BE636CD +F814C498584F19BBB3FC146035E4EB4A3B7EB436E7713D968B24840EE00C019E +D08263764CF0839260BE71F875E5408AF4A8AB1603F7F694C2DB705F6E13E1C9 +4CCB224D0E2B610FDC729EE8E33366D0AFB0A014C3AAC6B915C2268C27A65624 +261E38226363108AB1C12BA960649782D0A02F87404350EB7F58C6C54463C1B1 +334F936DDA18C96B577D4409F18E63A79F708A816096A6FA1071B1FBBA3DD470 +C00F032534140440012988D81DBFFEDD0F6B02827CF76A92F638A685BB1686E5 +D0624C445A765E6527B4D2FA2B8BBF035D9B0BEAA0CA35D937FA9B4A79224387 +CD974CC0A052A5C78C4CA12379413303DBFD11DC6B78507875E6643D36679EC8 +3CBB213B5116E5A262300890D85018D550C69365D717BD3AD5579C906F8BA35F +1C07EE6B17F394FDEEDFF5340A86E28FDA9ABD8803D832117A74BA868326A1D3 +4004DE8942437EC0123075F2C6B97B306AA3C806FBCE7CAFC83C2CBC77E25C3A +082DFAD7D69D36ACE654CD17916062449BDA3C3AF3806416B16C718128326DDB +062CC9FFDE6FEC2D0451C4994F5C681A18D7E5310917B2994756AC8FD055F7D6 +92FBD23FF685754DC09F36AD98201040626C381FBC63217DDDF511ADE6D13101 +0043EFEBFB8C49652EF4FED438B5322D83C6B998BB46BD5B5F31BA2DADD76B78 +ECB3D44A2AD40518C22914CF53AC408E8015F2E828F6F4908FE0C85DFE30B8AF +7A1E91B6AC7E25BE17F971DEE8271DF880E440EC95E45A6C909AB0DD6259AFAE +35B3AF9B04936FCE351DA6D244D34EF22D0B8156A583845423E93A3E5C827287 +EEB8E89E19E4FA0D6406506B73285484A75082DF8E0219FB5543B57AAF63C47A +0C5616682F22DEE96A8CBC70BEB556DF3E719CE0E70FEAB87448B0FBF45EA6E0 +2006416E39CAAC51A4568736D8F0270144CEAA8CFF832AD16907E09E49444BC2 +80306B769125A50595838E76514A7FB0F1392AFE209D8817FDBCA2DBC8CBF8C8 +5948F7E471B6B94D7FAFC382B02824888D3C5860D1AF18F729678899932A1AB6 +A6A7A3FAC08BFFEC6FFA3CD9543D36AECD2AED91B0D8674EBD6A9728FE8BAB6B +4D3E8E175D0941775B3F96144DEA873B48F3F6B7B6A30D2FAB960AB555AA441E +69DA76ACF6894AEFF47BE6BCC094E42D735EC1B9D65474C31F0468FDD3CEAAF4 +333F60053CFEEAABF43569FD1C216FFD77EDD13C344A0024CBDBFAB9CEDEC95C +115192A65A818C166A5C4441FBC0A1379D0F3FE58922EE49BB91857A03BCA8F4 +0067F324EFA67755AC0943EA80D0EED33A817BCE9D5676F9963844F6A2DDA48E +C9B3D9B1E9F8CA3FF805CEE70807B028510D0AC5B13259280021D06260FAB24A +0143CFCEE0C3FDDD6D817C717DF925DF5022C37A19FF63F77D464F699F585E93 +CF29D5AC1F2CD04E992E5E714C1EEA2FD56400E45A42813E4BBF87BF0849834C +F54A730E4B399FF3BB4E14D9CBAAACD79CF06AF35C128CEFCA1C43C8A5ADC1BC +3AB187E52F70A2562F7A705C58396C3B9198C78AFB0BCAD890956F7D463C70FC +0DD0AD6AF96D778AEE93A3A79A514A16C0324421D5DD3F7BD1002252E0F24DDA +3EDB1856E47B979C235BCFE8D6B2FF68864FBAF0CCBC3BCA811760643E0B2E73 +551BA0BCDC4037F497911B8A5CCEA5AC79B2C8229F317B690108ED2C52E7429C +01DFA8C97FD5FF794EA69BCE240C02A9640CB5194CA6AE598C605AA92086AC82 +C983661FE88DB62A3B732F78F43B93E9C22A09B29FCD79E41053D4E27CE6F3EA +FF365338B03B87AA0D48063D02B8E4C88075FB2354A05EC177E3FC76FE4E4DB7 +7337F0AA30608A6775C928A39A515C20A285D0C4541A2A6F06E6844A6CA428B1 +68D522A3EA084A2EFD17E3FE423F2F55C9F19E842D2E30CB79026C2D2DA9CAEB +8C4DE6394771D9C2D071F1B17047FC66A2ADA0FDEFECD6FAD63EA5F2196CCBEA +A4DB2C62A0948DD2A8A0D30E7F6808FCB912704E647E1FCB8E54D27039F67009 +4E995339D198156D3F6522EC70AEB3F22ABD6699C36386B98E0992A57FD9BED5 +D9EBCD492CE373E46A35A906C17AD2C4C490A9F74039075C8AFD8EB52C9631F4 +0CAFAAF50E8C6B4C565D859F6DEB844846D560318367C21967D6100DBE5D3A1B +DD12DF6B8A3633BBD59EE865445D2F1E8814D8D64C3BC913183B59DF20361B66 +6F3DC614B9AC2C2CA4C30E1A01C1C2A2054FB4BE7D58C6BFFEFAA4E13087C434 +ADC6D8F1185F68B2003E7EA12A991128037E54D2A88EEA2E794D89BF531E107C +54278EE82F49454FC13638CD0B83C142BA3E0F5462CF92FDF0FA223EBF69D12F +3D99BA804FC467F40945B1B0AC23DF88BE1BDE21E296A82870F6322630C06823 +27629EA02637E6E3E10F16747B3EB3E479AB8A347FD46D581608D453684278A4 +AA58BC490F673269CBE7356CA4F9E523CDC3F5D98EA1B95CB686BCD17366B609 +72BB6B2167EAA0D66E36EF6A1170EA56A8485A659E55C8D50108F6455F1C4227 +6CB5DED36763E54089C6C04BE71D5B762254EA4374C136F98900F25B239652FA +71A14A43F046EC8171C490A5755F90DE4ED09F8AE5EA1A6A98EC026B612BCC35 +6879F1942C6B45DF7A50F4E49B8C8013164CA04DAC46F1D799071BA6402EDCD8 +F55BC6B064A2264A37D4BFE59754703B2C13BC4C2586A61EE4A9F9915F00FC13 +05072451125EB001CE97BE3933239DF6501A664B45FFB5E63467CDA463A5AA14 +504AFA4C8D4A976B459D8B41046615E06DD43760FFB72488397A34C4648C8B4F +C56454126529C50AB1D3B328136D31F79EFA1EA011C8C6FD6AC27FF2D4218B34 +3F99304558FE4B1C81BBF4807AA89ACF653EB74E9A2C85D8DEAE86F1A7B3B5F4 +077D09031A32E48C069539410F68EEE2A7ACE7B200CA099BE59009268219DB52 +27A2D68DE15C91ADBB30775AD1701708749754BC4F0BADF52540EFEBCEEA9FC0 +E00ED70078142201E897E93EEB6B8C033B2B21617162BB54060D3A14BA41D481 +9B2BCBFA81A176BF324C8425310DE6C313113A9DA8A950C6439996C957ECD22A +BEC6631437954D043A5F0F83508A7EB24EBE9058841DC13E42E5A817928965A1 +0566C3B7A4593733E519AB17F3995C6702E3378822FA575A537E720BF26BD14E +D08755D999FA4AF6D0BBC2A54CBC279C8E75514733DF7F2C19B84F043A4C9A03 +979FEF3A622C92ADC58FE94C3C6C6ED45472D1E48290173613F991036368E036 +1F8A871A66BE125074CAC7887A69AD92D96F157BE10A6604CE0647E5D2F6E4B0 +8E3CB6AD0E830C18B4C239094238357D1D371F643F50E81CAF234EC88E0CD6C7 +BF0B0626895B812E489153B01F94C0D86769A239879CEDBCF2402894B6E79566 +1AA9B48033407926F18C7399C641C3B0C898AC99AE6595E865726374CCE03954 +92D48AD6A336888DC372676F0E1BB9821F638924976163976175D2A687324741 +912DF146F8C7F6EA428814DF6F235728386BD3558CD588BB1699B43C1DD6B4E5 +CC4E75E69FE0158E8666CD4E86BE2E79148680287615041E559DF70923831A27 +63D40F0F873B3DCFCE27E0BA3F396EF61417E77DE37C0D11902FD961CF5174EE +8EB1DBC17A1B0FED2BB385C9475096D7EF37BE12B0D75F6C1883229F3A62A679 +828645A2E71DC2818B037613531463FFB0F84EBC614F6A9C3BDD29807805E580 +0200984E41C66B9C99A62EED7E91DF58C72EEA135B03B08292FF4532728086CB +5ADD2170758529CCFD0E652138B55D545DBFB5C1DD4FFEFFA87DB31F24DCECDC +203A0B9D242B8F0ABFA7484545FA7BC22CC5F30C28629086ABD681555FD8C060 +00F4B5444B0C79EA2F1EBA2CBF0C45689B6D4B09A1292DE9F6E92A9C52A4F601 +924EBAEDC0F478330826DC36A3E3AC35DE0B82145E378108A75D4DB79B78A713 +4A54643465AB3D1127B69467DF7AAB30EA92ECF0B5C475FD5922EE8310F8913B +7B1AD24A93EE946C8E6DAF09C3697934E94DD0074D8DB4582D26F9807786CD8C +D8CF959020822AA23D57077A73126BC5716FCFBFC6271C9F1BBEE9730198EDDC +41BF84898B24CD5A42D6D060C281B1810B323C5AC6032870041F007EFF54BD07 +63F24FED3250E1F67F98E4B73678A5BEFAB99103F09E59E5E80D4BC9A2977FE4 +3AE53E66D4185F52A001E6CFE374CB6A934187664F782F63E3C3359C66017383 +99693E9A5AF425BE05A681BF7073A3108C77C2A31A9ABFCB24384010E36CDAB1 +86219786B68AB2A6A7065167EF57C5EB79D790824A459A137E7CAE466F1EFF55 +8BA7CF2E9382C1BDBBEDB381E7BFF677DF252886AF2BF19B2E2D8C44E0C35957 +A0491E4723DA85E67BC6182EA0803EDF4BAFE302BFD52D84BB888A70232F7D64 +3CA598EF660B77E88A6A8A4A4D63AAEBB88E400D20581F2DB42ED3B2B184DF15 +6DC8E586726B5074EE71F347494C84B2365830A932A4F05F3EF70FAD933EE655 +12834498F3A58AC270D74CBAEF756CB8A499D3EA9B99C37DBD8D84970B188530 +82AFCAE63D692E6A72777CB1BB9523C75F9EBD4DC7F6E5D6F39862BB1D7AE93E +5BBD3E135E919421C3DE94A2EF81914B6EC5224906F83EF47292301C59C3836C +3F359945C40C61D9FC0AEA5A88A9A93DE5CB8DD195725FE145E79B7DF33B1FAA +F14A457C39D12AA5881FE5607251BADC4CA3A1FD901BEB971C765D568803D1D9 +832D5444A1D8332E125B7D865D5B28CD78C7043D8A86AB4DCFA769A183FB4B05 +749DFD67A669B7DBF6A6283CA516B94704D74F64C3FBEEAC15CD1A2D939CCF23 +3A7837791AA06E6167291EC2DB0C34846D7D158EF594C6499288ECB69EDFD50B +4AA7F1E37993781A3933174BDB9F8EA0D41BDF7896BECC4BD9E6FE17738AE1F5 +FC0B9D80D6247E861AA03A36B3BC648FC8610203AB27DB18B48BD494A5798406 +BD74B5D1631C2844DE39ABECFC6518C45A89E117D7C498348FAB6C512DE91210 +1C07826D1D6131AFC8B138B595641D80C8753C45ABA3EA94D2B175F382508B1D +B85D1133FAB93F41187C9399ECA60142A1356B1E5CFD821108C347CEA7ADBF08 +0B124290E9D736E2043039D49D7D297ECF51BB2D2A9BF589761AB8C1E7249722 +BB558C01661AFF993684BEEBD24DA09D0184440581B53FF4BF3C0129422E8F94 +F214FD0FE66F14DE284435437600C6B9CBB80CF7CBD3CECB34757BAC210DA30A +2DE252E3DE81792D101F032EFD210853B4D37CCAF643D71C4733C81202D5212E +4E75DE6E19A3F1905A22B668DEED9A8BCABC2365A1B2C6DA539FFC709B85038F +99A65DE1C6782F0C757D402F59F11968695061FD1EB1293BD54FC33E1C2CA4BB +AEA897043AA9FE619649D835D131E18DF17A9D1ED6163BBA1A9DD12B9BB42A74 +F67DEBE8DAED8AEA55A7F022159138F97FF379566A42354358FD09DC88FA9EAA +5CF2ED8B4D13C1339C07D398E96D0287FFCECA5C5C32218BB43BFE08196D3D1E +23973D34833CD9BACE28436A5FFD4816542473B7CEAAE489D7FC0F5012BCE6F8 +4D28102E67187E50603942FE7514770BB9157E67F1459F77B2D8FBAAE9A4C987 +295D1DACC6AD96A0818C9E875FEB5354BC2A1D28E2A1F45C8628D8D5B0C58651 +5083D10F070C42724CB17BD93D598ACE9762EB5060E2687FF9EFB1F58A95A5CA +1AF7B72010131F0618EF48E9BFD21C17B297812190CC427B569313B025C39843 +57E25E37934EA128680771B49E7A3F1978B3A6238EA54D83CFB84EC2A296791E +3990AC7AA99A9A5EC4A560E59F27DE1680C81E5930C136A83E8E7B4ACE6AAAB5 +57DFB10D605F501081A8808BBE3F6E297A90F5E09A3CDF26FC387399EF4D5C70 +E7A3E9AD46B0305E86DA98263A7F44357D4E950A63D7BEEB9D2E3DF3D066A6B7 +9E940CC10E8007B2CEA1C11521D2095EED15F3152F92B10E0B4E9AB74138F241 +4303245DED32F590E1FA35275F2A0A26FC74F9A04F1CE9280B9A5F136644B77E +9F4A7CD818309F8FF436B07EECB4E6D4168501F32B348446AB6D693C81FBA667 +80FCA9390E36759B14F7A406E66F7BFE7E91BCD495A53097E55EEAD39F8A89DC +FB4CFB345F075FA37C763528DCC7D80FAD8F674B107E60FA76597DE95BAEB3C9 +A3BA95633F1A44F20A2679BAC5D867A8ADE1270B3A87CF973424372BAF552056 +C9C37ED61E8A791D32997F15AA52A1F259FD02267DD7F43A218224DA371093A2 +2945C99A06AE36A43107BEA63FE56A9A2FC0EFDB46DF08C2752AF493035A5595 +EA90BD9847126368DF8E22FE380E50A893FB21914E78BAED3AE5F9F03F7027DC +D10B91264F4459B8D0136A8C4139C416C64E304128104B6EBB0F3978C69FBF5B +E090B5C295B98578A79FAD2AF4233821A08C375DF4C4429C93623C43907B99D4 +527E67CE7A73786E4D2F817A612AAC7B4D9E3F4A79BF382F15008DE76A9A8DF3 +47C60F100B09D971EC4690B1A04173A1CB66A2269BE80708FC29D82C552151CC +385F1F179889059D5D70D252C99D0F84D12FDC13FFDFADC11A731262BC8E2AAC +FD8FA176352F9D114322CAAC7278113C9B48C869D5EACE4215CD50A125EFAC99 +88312CF4E12FC1DA63029140E8EB1BFE1D79A7A338BA2188B599A1C224A0FBA1 +3CF40EFC11D7D3333FF371DED67318D2F6DC8B5F34A5728233AC9BB2EF214D67 +B3F2D0FDAA10F2C932F7AAC4EF557E31909175A3F61B0804CA0C762C53F20A83 +53A8A6FFC37E002EFC441614A8E540921635FE855CBF1338431AC8F66FB1E17E +6770C99ACCE479A366F00211F8915AEF2940F44E43FBDAB05FE2D64565BD2584 +59F65129BD9695C4787C6CEF7FBA70E2E0FE4FB7051F65AA824782F9887E5F96 +DF2527C15E39CD1D5BDDD48765BC0C528037F69F8C5F168810DFC2D974AA0E62 +20104E3F291DB79BFE816FAAC5765BA298797EA491EF82AF07769675D60F4E4D +F088426BF9CF41B0DF5D840BEA5ACFB5382015DD26ABB0851AC610B12553BBD1 +788032FDFAF98C64AF5E2FD0B08FDB9582CDBCCFDA38F256930349697AF7566D +F980076F5E072279EF92BE37DB65AC1D702B0E85C856482A8BA9B44CC43BF3EB +BB7504F1F2696B597A0FB8085E3F6E3BF178837363F750679CF2CCF7B01027F8 +747E1DA4597C8F93CE8F07D304FA72C64972CF08CD8B8F3331285EB0ECEDF8B0 +64B53AD633C6D90340797802DFB81D424DB73D68BADB7811765D83C2C57E3BAB +F543E327CB8B0356D4A84CA1BF0AAAD57FB9EA0E5C70090A04745C916A3EA2E9 +2AEFBFC64AECCA2DDA387A3A8AE1D27C6B031A28E8FB3F871576BBBBF7217CB5 +4ECD354B391D4661B278B1DAD9B3AA0F39883F31537E158075D73A5C4D37FAD0 +4181846C724986DA122D84914611B7C50DC964E21B40ECBCDE2FBCCA5A30E11C +5AF792173E502843DB785CD34583FF25251C712087589EA26D73257EB7B9D484 +72FCB8988317B1F886C5EB9791F4688F087DC8D2D0B82B5EC38CC026C45C406F +F0BDC2C47011AAAFC765C2E520BF3C90C85E0D4AD3D59E75585FA2AD70B3C527 +2383D7582779217F435224718C04EC8467BA8C9319F99F3F033B148B24CA80C8 +C2A35C0B3BB874DE6AA03D3DBED072A45DBC5C8A746F83ABB578F872D535EC21 +D16BE93537193CD36DFCAA9BC9FA95033EECDB3853532452385A71D1E92D12A3 +9AE044E7142F0911125B2C2C881678886AA4E924B5718172A3161C790E2E6F04 +9A28709CB38269D8D460A8415E68F64D6BD3C9ED9D9744DB7AE04B9F19B8DB63 +EF85CEF8DA88F8442E2AC150FDD3A15BCF9740CAE7FFA1DE92E2571CF2C0E0F6 +90F13F5ECB3CB88FE13D4B8963B51ABFB9040A25ACC10B08166441D7275106C7 +D5F58C5A80F4F5472181D985237B431B8DED88BFBE1215B6DB506B7838A26C4E +C129AADF9C36C4177C76930CF6008104E0B5E3A387A13CBBBB8109AEA38FC35B +E7DF40B4734AAF9A7CB819D94DFE944E994DD21EE7E9BC4014BA7F0A1FD7C098 +6F7A699E77867DA25507D723B6CF9174782B58AB59D89BAE119F145D61F83687 +2C2A1653B501E71415A0978D2FF39468686E2481CE4F06B981FC398F93555A87 +E93A098C9FF28BDCBAF0637CD63BEE7FA448538FACC04B11C068A16E687BAF4B +5D722363F7E1EF39B8DDCE5C947DC2A69DF4A4FCF40C397FB446A37E74502E49 +571846E8951AED1102A7B67B7038EA1C7ED210A077D83222E988C0E5D2B88BF2 +C71DF6C9F4400104F2E8DEA364D3A45CA1C1E6651C3972E89E5CF31881322FFE +AED481E2602E19D31A735C14F3C7A8D585C46ABF0C5C64BDD64EDD35D0ACF67A +5C91C26E2D53C47EEE126FCCAC1F7E9A67A0A02DBAC2C3EDFC15F55DD73EE385 +A7A32431AE18BD4F2C612FD5F5AF4380A259C0C38D22FEEE403A7B9CCC4EF62A +6CCFE803F270AA5FB5C4DF8419CCBF6AC611A721688ED51B82C1E5647F2B988E +2B84B5343C26D8495A23908A6EAB2D2376DB03F96D493A892B6548E0369988CA +A2ABCECD47663BFE45F4F3F910952056A5314843DBF4A8862ECF20B9C6427E86 +E90CF8132B2A03E1A0109C2C1973B5830B9AADC952F41D92EC44FA3E79C77155 +AD89D10EA1E3B30126889BA190C86ADD788045D0F933226A4AC2FF91A9406F6F +976F337400FF3921D372AD11C929109E7F2D7C2A494888CD85BD790C8119681D +83B11275991898106C693C0EDF1C19B8731A84A94470D2BF52F0123E5BB89E56 +39D5CCA023BF0625DA16DE7DB7611108CEA00E3CD4B8C03F31787EE00A94B542 +A88B4369139836FAFCFDA51CDD5ACCD351C567663F1140357C1414E3301174F9 +0FAAF917EC9C19DDA2F41C671B3175F49DA30593FA6C93A067560053822D9A9D +0A440C3DE6C4422E0EB2F7E86082F7734BA83D02C30EDA9A22354D1DCBC8D186 +6A43BF3DDC5209DD661A87CEDF97BA43262C6D418DAFFD943846120FC5E1A06A +5C7D847154E41CDBE66B0538189C130DC4094D7F1709F15460AA982DC438F380 +766D6937A65D1E01B70FC903E2D906FC31448FEBDB937051BD538019D849F81D +9C28FD74ADCA7996C9F319EA011A00AD195FFB5149F1B0B550ACB239D6D161A9 +E45FC9F9018F471885D62869831C55EBC118920B864BE49244DAE14794EACD02 +A096A5D16B4800B0A1AF3EAD075EA2476743425FE2214BDE468A20749C6FA35C +9CAC2363798D17891339B28F8BB6D0BE628DB425FAE39D65D3C034D041BBF7CF +4EF74A6AB6F656144930C034E57F02C528E054F65828BDE71083109F43FAF0FF +E520EB4BF5D92932BDE44D54797E58DFAFCBF5C819474A7B9282D087F819AFA7 +F13B47F2CDD0BDDA2D8ABE8D6A0886407EB8550B96844FEE859BB61C799975C7 +056361974EBA586DB2D3DDC859CFA0FF493C5C8DA81B2673AE6C94CAEECE4B0B +498E9F060E9948D0CB5C47040265075BBF6B40382EC26C5822DF3632F1F24973 +DE14BBECF72DD8535F05BF15276DBCD457432FDA386EA8131FE695CF61F11B37 +1C917751B54637348823C481D4C2B0DD4C98671BF1EB42919C6E11554D86490B +8E2823073B9F1DB5B04EE262840352032E1B9E4B688FCFB1144E43BFE455AE25 +F914AD37E6A694900C5CEA010A04631BA180B9D126989E46566101B868EF178B +6BDE396797925C90780A1840369DD3F7AFA7C6A2D46FFC8024B12C356D5FEEA7 +9270B37CD936AF7AA2F041836B00D3DA2F190F4E8C97FB7E9903446F8847BE6D +32A1772991AB24ADD997BB3B97452F21A25591459AA9F15A0B5395DE1D23B038 +07D3B35338E4BA1214570B83D730F9FE2F0C0695A331B8E391D1FD879FA2174D +3F83FCAE5E1441C51B7CC7AE625CE6F6EE29604FAFF1DD5C1FFA48599AA073E4 +E54E56028BD64FDACB47C715566D193332542E7A45F27F87F9BD0E7CFEC8EDD0 +C604549AA0831EE8CA7C68A8A4A6549B1E7C0BF2B6EEA70AFE13024C41557568 +D11E69C2F8D7962F5FFB6367016D93125A92DA39E25F30D5E4B85F66D34B0528 +DCD0D71BB767B7EC0A58272DBB90D0D0D0F661B783F724929CAAD9843E044752 +C5D1F2F6DBA6CBC67410623EC23D7C7EF15B18484315CEA679FC1526B576F54E +D1DFDD0E8ED5F2B1449A05501D0110DB989F62F815A30E84BCB0042A0FA14855 +77D07A326FA8F2B85E67E20935F9DF0068B107572BA80DCFFD90F922B79A85C9 +821363FF5AD881CF7C0212B2E6238B49813FEDCE319FC0FE40A42797235DFCA2 +0F1B939645B97D26A23CC5D1ED56CA253AA1E5C8E88E93C74F410835E3D5C2C9 +731649584619ECCBF3A50F95DD7EF36A8C1B5F63222863F0D8223CB787B88027 +54835558B8E4CD6FA1BD7E4024B6C7267C1266AE9E7D7CACD4448342BEDE1EA2 +717FD60DE846878BA67710AF4508F33E2D8D1827B7AD0A8C94531079BF2550BB +87B73A3848857F32B5D7AF2CC07DF0590D6090B7D820D701E669ACA06BA8F194 +02672D01ECD7E2BD4E29FE18176D8317BF9523D2900B7CCACE98E9B46DEDE394 +0CB3B65538418147C93E77FADEB94B66FD048E47A0F1C3BC92A51633F83E68EE +D239C0F2F5EB9EC68A048EB809B5B9F2DFE41F815CD33623CC2F6AA0C06C9F53 +ADF903E40B50945A7A288A016839EBA12CA78156F617ECF3BFC8AC106A0B1CD9 +9E2474C0DE1B88B5B18117CC68187DF5EF78C5B1AAE36D39946EEE1D931E704A +9D0A315054F5362FDE90A0C04F159F93715B4082554A6FBBC03AC4C2BB6E3A33 +8563FB20B664A350BE0CED555FF8DCF2AEF4FADEE74A09522BC31CF7E4FFA014 +DCA45475437866FAD01AAC47DA6951197066B69E6E850D4076C0F8B3F04355B0 +E26711458BA45DA22AB577B6D71C8697816ACCDCA0C59E1BD92FA8C730D09863 +ACFA216600EEE43FC54E9BBBCB873C64D0C9FE41DDCC573AEDA8A699F780950E +A63B8EA80A78E89724BC77DE5F7ED67FA860EE16D7E847FCFC198CFACF6FD4F5 +7A25146DD526769E6A4080E908C16F7CEB0EBEC4778A6A888414FD67BC2593FA +6D0111E37BE74CA96748A43A7345622CEDA9D1DD5482E8119DBA73E73A35FA0D +ADA16A14CDDB54D76DE1D407BB39C00AB23EA1CECAC9C4DBD64DB5AEC80AC9D7 +FAB6A1A3C4CA84F630A8D3A143A3490818A7BF0C8F16F6220BBEC2B8CCD528A3 +4A5F899039BB3E21B9554E3BD873E2C1DAE37F0700B43CD1939CE5D16773520E +7A915895E74BB21222996FB5623FC6F077F44AAB49FD6E8B6AE3C8A9A93DA0C3 +445FCFA9EC73F98378C5B4EF3E67978B7FED5FED1EDA85BBD4D0108D3E059E63 +28E11EC677315B68DD63C517853C1A65967BF1B859A9203DBC2F03C4AF6A20AA +E17C7103917CDB42303D88E4A1997B34201FCC208C941249624D039D069C2977 +B28AB8D215B245CC115024E3FD2ACB74184C8548DCE81F4D65D4CE268E360C03 +FB719F335050EFE6B13EAB0B9580437C6067F0251660588FB519409390F94326 +97C1C7A485E28C199DFB746BFD1CECE61C6F66D20946A54D9F05CC7F14BC7BE5 +9F0C229CD87F1A35D1AB547EBE307B0A5CB61F8E7661DFE503FF2EB3344EF7B1 +FED24165DAC30B5B6F39D1D1FA414A19E7FFEFCDF93808EDD4493AFDCA0BE31D +03EC07BBF75FFF1654E5D3AA64DAA505454A499E398C2F53DCB93BF548451764 +3E4D7004D97DEA315CB51588F654CDF6FEABADA788D6DCABAC86E3DC9BB7E42B +FBC1381C47277801E353B58400F9B9AA4DE2E1BF4E6433688C8569182F0F4568 +6D5E7931C695A5CC181A815B3D3252AFE7F25EAFCD8B07FB7B127731A25D951F +51506723A896F8DA4027296D11DD4123FE93BCC81914409D2E6381F1F2840852 +4214BDD7C2671B85AB24BF24F895E67A91DF23E21C5663797F049467329F3DB0 +A74763B8C6458A5C9FF1157F9DD08C37351A6F074E1E5A05F9827AC15FFF4A68 +93E1D010E02D82B63C1661BBF9113D415052C4C5070E6542E5E07223745355F7 +3DD76EDECF1C5328A29473B07BDEB0D4223C5D3DAA6BF3E639C2FF860AADA0EF +05A9BBEAABD8389112937B0F282A0D6C50A7CFFB0D214523EB8618583DF0EFBB +D98086ABDF5EC23F57F1101E86AFFD2CE314B4240380C6FFBE4530E6576180C2 +3A3695E4D70DAC730C2057578AA0CF3E8E464500C0F2C7ADFADCB41E1DB62D83 +86DAD27D800C797B01D3A701C62A3A1D0E1C18D76CEE1AC9F581CC1C697A6E50 +62053E7EB21754BA9D9763654CD7FC08B53A12408A4CD4B99F4061C41FD06922 +ADCD8518DFA056B825A24965B576EF1DE31E3A4E2E54AEFBAFC8BDF5E833AE8C +929405416A91A45AF0977E396ACE32C969C87FC93DEA7888A35B7C9E8ECCC350 +C9388F9953925A208E63514E9BCBDB99BD47D146DE4EEF08810C81DD6DC06EBB +DC1F0035A6DA0D99F32696E8B4A226435376E26C0ADC8846D28C8B875BD03663 +73592B3FB5D5A0C927140F27B6CFC0FE2A8DBDC77CBEE70C0E2005EBBAFB8BD0 +5B24447CEC27AD4225DB0DC1CBBBE69AE423F9622A4A8BBD9466962FB54B64AE +726060F18B3EC8D945009E8FB6C2ECA462758A78BE369F00AA4FB8534468F700 +A9376FB42130ED96EF9DA17B7B9589474D1427D0D8FF5152F6306527B12D4329 +3CF929105E04AF564DE552C4F06B466F16A8724945CDA9E098AB31A032CB2D05 +BBC3CD225A51D818A9695D6E32DA429E702632B32FECC25722652F67165695E6 +D9819D5B200EB3626FAE8FF7376DED2B1720CABD8AE2BC1A0A39B7BA427BCEFA +936F934DC88685CAAC7DE19929C9F99984CA938BE0044FB341AED41B19F97F41 +2C31E2C4DB232FF90BC0D15F1991DB60E7E944D90F7FF591136E1F260D3764C8 +8BA791589D455192A19ECEB8A9E80FB5506A479EE444322C4E8E8FF0DD9BCB8F +749C69C488E816625DF57EF673DB1A85BFB19D960461EA8E6718A1325D878D37 +FC7BC693F15D8A85AAF4C42216490BEA3D1638BE006CA2EC9D4DDEBE3C641754 +A78F6EB4B71D7517351A91A711CEE1FFC4FD57B0EF71E2959EAF82AB5494234D +FAAB49B1EC4DA7A2F3657458CC1CF6229901C2AF5339E2E36F92197762A8E446 +5F8F9D24A57B80850DE4264FA9F23C947C35801C2B557CFF02B15637D5B9B059 +9994ACD938E826E4DAAF9581A63EE82995E793ADE573CB2B9628BD7B899A1C1F +D437749137278A36B921206B70495E0DF379B7B7315FC02058EAC7F9128A3B4A +30E20BCFEEE17A99CFC9795F193E4356AEF6DB94A7CAC8880FAC9227A0BF78D8 +B7BC18F6DAC8E5CBA63996CFD5E4F78461AFECE7EF62C585B5E5DD40789AEAD2 +597E24874B3BA77799D67DF746E70FC9DA4CCD9BF40F9897092352B3A12682E9 +4A5A92795C8B52E212FBE7323349E867ED8FE78B8B42325C6AD4BEC036BC891F +0AFDCBDA2A75DB64EDD020A39A4AF0387B68D64BFED1C5FC11C58919C6FA0C96 +F617CA7860D5C10188376F31F1E323A95AB36138D4C8AEABFFF46CB6A3618ED2 +88C3BEC6184BDC2522388424FCAC44EC1DCF68E6B0A19B01A223BD7193EED9C3 +F5B838BE944E6C4CFEE7742EEFCCCF9F60EF632849E3306B990B2CA74B8F6AF3 +465A0BF22E1E59798302338B06682323B19C4E44FEFE84C0A210031F132E015E +E74264B3B0DA6E09E116572E707C45FE7010C8D291BE49657248E416B4F244F6 +E557295AC3633C4B84B5638EDA1B2767D0FD770654C2667EBE9494B1DC28825E +C12B1244900CF0F45EDD038BD90A98497250F897F1221A1733DBD4540D1B374F +827C8C10FA84F5A1F6A4C921426BCD3EAFB1B8C654131F15B4BA3E8D489F00EA +9BCECF58E9D15BFC1B16E4A08F0D5459D26559144278DD721DAC117E4C0F6EE3 +19930DF91DEDD9664287F11ADAD823DBAB597729C554DA905BC72A6686481108 +7FA7B78D41FFEDE9830955C6309749DE1ABD705BD346019D4ABC4C340C9A2A9D +CA7D20D6CEB59E8B7836043C49D33C86469D27CA62851DBB00E6B8F4E28714C4 +B5F277CC2CFF502F0E47352F296EE242318D73197E754E4620A9B7DBD075C93E +5225C68C47BAC50FCB58A6D33E1B55A39D131DBDD4E014C126DD320293BA9748 +4712E66FBBA2BA24BC8F4B1A285AA123F7B8146F4684CD922EBD333A7B493CE4 +F9C7BDE8FEC45429EC26DF8569130F0898C3D43F7CA90C210B103117911B27C1 +A14E0068595867748609ED646B5E08485E7467C36A528C738C1B7CFA362CA720 +C088CB808926A1D895DDFA944468AB702AA7F91D8A4BFA372E6D7640A8E24273 +75C1C8466DA13AC7BC57CC6ADAEC27CDCCB29BFA5473CED983B9E2EC94132E18 +6D77798D59EDAA1BA98A0618A1066374D655D55066BCF81FAB2E4511E1928ED7 +C77FB148D57D8EA24EE0720AB527AA3A76285B696FD2FF3546901E849A5B3802 +F21F7205C80F9A4A102DFE192C5C5A5403C6A8AE106C10746A550F7CDFC992DB +E3A64BE4A2726AF22D403EAD6AC921F48D6535E9E850BEC1E5B0AC79AF04502A +1FBD5043391104C6DD0B09CAA3654A7A21A52D6164FA2E7A4FDBE9CB328C3A53 +05406F4A2F45A1FD4B2CF998E94B7DAF2E3CB584A8AD83CB32844979EA560E0F +6EB5215F705141320F1BAF1504E210D3690FBC58830BACA500624F7A7ED3EEA2 +789738814E197DAAF6DA815029368B28818561F044171ADDCB73B6DE459C3E9B +64196B66F8A12A1BE100124EF86CAB8B7A10822DF4951766AF568D826E0B91E5 +E640EB8D3D8B188912C510D456231E46972B177019A807DA6AE89E76400E82B4 +EF3EC27BCD8F879A5A7A04BB4D5E290514EF7ED3A976A5B2D5B4F9359B283DE1 +1D7BFB356B3F3F02BB45FA93F0B442330A9AA29F8680260D1071BC27829EA312 +0ED69BFF2730E0F550F35CF2CF16AEC7D8CFDECE336F95789FA1C76B4C6BECB4 +D6400166ECB6FA2A44C587F15DD55F55A1F7FFD8F07CA99EDD2052218828B191 +D52CFD56538ECE62E946AF57C39CDAED30DE6FE58491181ED84E700092A061BC +821B6A5C32A0F436DA74826E0790EBC7D55F14223DDA0234680D41A5BAC55EBD +8D657911A3C10F9537AC9D3388EBBD5856F043A544AF4E60CA7243DA6D38BB28 +4646E77BF92BFA763150C7E4543E844B01097CEA61AF0CA802CD8055BEDD63C0 +6D91EE700452A400496DCAB0632DBC58C5285E8722F9BEB4860842B48B1D9FAE +B266B6381548CA943B5EEF047A6805D99AF761A42AA6850CC458E1C147E3ACC4 +E41653EE36F6BF8C938DF808E8DF9220690A1E36BF5C7924DE8944C261955F25 +A7976C3FF10C79A10D8EACBEE51C00E961588131E35B08BD2B07962EF33769D8 +8A7FA9BA8EA90C38B846963030FB463511992396C25B4F83D6E6842BB7408064 +B979DF1E0A1C20719C8F9430C1F46B36DAD548471A37B4328DDA480F3274900C +E33879F1C6AE71A76437DA0F6712297CE10B14552768C06315EB48500514BC14 +AD86EF94E5AE2390B6D9ABC097BF1A9CB666988EC3797A3464930867433F7F95 +D26D8DED9791F0C49EE8AEB9017CCBA02B4B68D639B58C864EAD6067B3D1FD22 +E989551D11DB9BF2A8D833391D25109F007D9E3F7ECDE5F21C64AAE0B0D4C4CC +C511D9E7251286551308E976F1C69422457B9F6544A768188EC36C7964D158C5 +09ED5CF20C62955EEBFB0C4A851943CB1BEDEFC216A5F11051C9074C8971A13C +59EF979D9C6C90E545E7569F16A202E0488548C6AAD14AA2D3F5C15E5807F39A +00752DDE0B2537CEF6F67A921342CD30B85CD65841BA2B92156F2ABA110F9193 +1D8F5C3E7BC15D1F2A1B45FC37DA6EB07B9D77EC0C8F507F49599A3CFFA76C0F +4EE4037576BE710FD8D22424899D80D389FF23B8760FFDCB88B5BBB17345D278 +42286B59AFC25036261678551AF7CB98432D66CB6C49E3ABE73B47B0568839B0 +9820B34D98B0B2312CE07F438A94F7DC4C5D50066AF6FC3399BB9D58AF4E4F7B +8271B178FB44B6C9A286FF4925C920DBC35D5AF5E24B60C5637C4D1343312EE2 +FA5625B6AD5ED764EA7D670761365EFDB46FB8153E23072885C3225ED394EFAE +6B319D543ABA7005821D8B857D06235F83FED9697FDB48074BE3E797D96811F0 +10BC6392B1B9D4A896B3993C0A77E94B57CBBE264C12463687BCEAE30C575B1E +16DE09B2D92E58155BC5AF9339C62F48DC0C66831B7F519B236A7F1AD6E2EF6C +7216A0304F415B8C95E46AC28A0C2EED00A5E22B193338BA67F78735D8C26F7A +994D25DD5FBD72BD3B1DCA1E2B0F7151439313824FA3811C01FEDE345439CC7D +604ED31A684426939CB3F2B9DB05D509A7B0EAAB0583662991FC3ADB4C50A515 +3B881A40DB960A9D6226C4B8BE7DE1CE38363BBF3537A68E269787913EF43FE0 +AEFBCFA0C199AD0337F48564CCA04DBB0442A4C8BC13B0072BE782817F59C8D7 +4A052705029F8986F888C498825819171C12E4CD2A33BEF7FAFC3447FB5CE42B +B3BC34F3F2B61E0E43048E32522CE84FBFD8B7E36F9C83320D5C8692AED52588 +A2359603E13A2F2E43B310D433B83713F94D77557C9C8EFBA9B5E5419B75BE5C +25E87490EFF8A2C54734079826AC3CC2B77B982F3D11141FEA98B417603EFE94 +240F453DD6E62737EFCD0A104ED9009B009C4A62A2188A7FAAA0778F36E3CC25 +D3E6D192B338E69B6C6A63ECB42EB981B08D2B65646EBF09DBCE03940135D76A +E22D04BA3C8293534A9BEC2969B54A12B346AD976FACA36C07376C9A8F2A198E +BF0BE85F63EE43845F761274E0D9CB7531C451516EF085BDF6A02833E38B8B87 +D141D8C9F39F6F032880A6E39022707C2D318613A800E1F5A312B300115EF120 +744D58CFF4C7BD46757945D541C1349225F2C0BB4ED39A2457B7D8E3DBE1C5D1 +D150794D5EB2081B8F4C13F73ED220A7FCF5E2DAB5CD77926FB66CE3063A21DB +1B76BDC2C8B2AA374B9F362404AA707E3E4751848540E83B712DEA3423FE49B6 +0AE4D96AAFCB9D6198918EF460E585BBC2A37B3318643CECF4D3F865E14692D9 +74267716BA6CC117B13CF185717F6FA0C8602041CE79E696CCA1FECA4AA4CD31 +A06916B369B7FAEA1F66AE081DF3D44236F012C557C8076772576CB116783153 +C2DEAB8CD542F0B08C75E00AC57C816B372E20C2660374BF09AC498FB3AC9A3E +1BE52953FA41686599DC7213845478725D54DA531A0375CEC8BA1827C27DBAFD +6555A364CD1AEDA1A5DB57 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +cleartomark +%%EndFont +%%BeginFont: CMR12 +%!PS-AdobeFont-1.1: CMR12 1.0 +%%CreationDate: 1991 Aug 20 16:38:05 +% Copyright (C) 1997 American Mathematical Society. All Rights Reserved. +11 dict begin +/FontInfo 7 dict dup begin +/version (1.0) readonly def +/Notice (Copyright (C) 1997 American Mathematical Society. All Rights Reserved) readonly def +/FullName (CMR12) readonly def +/FamilyName (Computer Modern) readonly def +/Weight (Medium) readonly def +/ItalicAngle 0 def +/isFixedPitch false def +end readonly def +/FontName /CMR12 def +/PaintType 0 def +/FontType 1 def +/FontMatrix [0.001 0 0 0.001 0 0] readonly def +/Encoding 256 array +0 1 255 {1 index exch /.notdef put} for +dup 46 /period put +dup 48 /zero put +dup 49 /one put +dup 50 /two put +dup 51 /three put +dup 77 /M put +dup 86 /V put +dup 97 /a put +dup 101 /e put +dup 105 /i put +dup 110 /n put +dup 111 /o put +dup 114 /r put +dup 115 /s put +dup 121 /y put +readonly def +/FontBBox{-34 -251 988 750}readonly def +currentdict end +currentfile eexec +D9D66F633B846A97B686A97E45A3D0AA052A014267B7904EB3C0D3BD0B83D891 +016CA6CA4B712ADEB258FAAB9A130EE605E61F77FC1B738ABC7C51CD46EF8171 +9098D5FEE67660E69A7AB91B58F29A4D79E57022F783EB0FBBB6D4F4EC35014F +D2DECBA99459A4C59DF0C6EBA150284454E707DC2100C15B76B4C19B84363758 +469A6C558785B226332152109871A9883487DD7710949204DDCF837E6A8708B8 +2BDBF16FBC7512FAA308A093FE5CF4E9D2405B169CD5365D6ECED5D768D66D6C +68618B8C482B341F8CA38E9BB9BAFCFAAD9C2F3FD033B62690986ED43D9C9361 +3645B82392D5CAE11A7CB49D7E2E82DCD485CBA04C77322EB2E6A79D73DC194E +59C120A2DABB9BF72E2CF256DD6EB54EECBA588101ABD933B57CE8A3A0D16B28 +51D7494F73096DF53BDC66BBF896B587DF9643317D5F610CD9088F9849126F23 +DDE030F7B277DD99055C8B119CAE9C99158AC4E150CDFC2C66ED92EBB4CC092A +AA078CE16247A1335AD332DAA950D20395A7384C33FF72EAA31A5B89766E635F +45C4C068AD7EE867398F0381B07CB94D29FF097D59FF9961D195A948E3D87C31 +821E9295A56D21875B41988F7A16A1587050C3C71B4E4355BB37F255D6B237CE +96F25467F70FA19E0F85785FF49068949CCC79F2F8AE57D5F79BB9C5CF5EED5D +9857B9967D9B96CDCF73D5D65FF75AFABB66734018BAE264597220C89FD17379 +26764A9302D078B4EB0E29178C878FD61007EEA2DDB119AE88C57ECFEF4B71E4 +140A34951DDC3568A84CC92371A789021A103A1A347050FDA6ECF7903F67D213 +1D0C7C474A9053866E9C88E65E6932BA87A73686EAB0019389F84D159809C498 +1E7A30ED942EB211B00DBFF5BCC720F4E276C3339B31B6EABBB078430E6A09BB +377D3061A20B1EB98796B8607EECBC699445EAA866C38E02DF59F5EDD378303A +0733B90E7835C0AAF32BA04F1566D8161EA89CD4D14DDB953F8B910BFC8A7F03 +5020F55EF8FC2640ADADA156F6CF8F2EB6610F7EE8874A26CBE7CD154469B9F4 +ED76886B3FB679FFDEB59BB6C55AF7087BA48B75EE2FB374B19BCC421A963E15 +FE05ECAAF9EECDF4B2715010A320102E6F8CCAA342FA11532671CEBFCF38BC60 +5BF06A0E01053B7F105ED5140FA4FA37A4F45ABFB58DD41780629C7FA7594F8E +9488B074D45BA0E761190A94DBFF4CF204D5812EB1DFBC0C456D6A044C558945 +8DF6D6AF8A51131AB5913EF2544E475F489FE776FA45E7C0EB62096CF4517450 +8A57DD2B80EF97DBD17036EB9B73FCB82DC4F671BCFEF06A6F86189A2012F53C +E518466A9385D08942279391EF5C2B3567F2E53B2148AAA0625BDB358ECD2C9B +730A59F8DF70FD3E09378E24AEE772A69C96E09C0D0703350A75F3843DEDD822 +B364988376CE8027EF12F0453575AE9AE46296E13925BACECA8B8808DA388222 +BA35B431F56BC3C349B8CA6D4AB83664A991FED51478BFE25B0399B54EF27B18 +65E2E39E57DFC5F57371FB4643D7EC0AD41840CA7C682EE7A1B4FE3E06E40238 +B07A2E7A7FB3F35502F1CCA283F9420F2FAB9EC0A8C8982BF4AFE69A3D2690B3 +CF5DB716B3CB9936C49A12944973F14C2D9A368E4067BDBF91E413A3321B3BFA +B84463D3C680604A4C4E4B49D0F9F9B59D5B68ECD3891FEEDE9B29B9C432212D +82505A8D423BA734A53DEB930A392BA6011C6EA6EBA7ABAA6AAC003721FB34A6 +1544EEE16F6AD73E6C5743EFF09975C186A9C769A4732F78AA09218AE576FB14 +70E1697B813AC1B09FFAB762678BA9320043C256766AF1BACD3702CED1228B94 +57E353589810F99AF74109EFDA0740CB0475DE7870AA7218120462B23441BB1A +60ACAF3B8B171E7509854D1C05C8FBF62979D2B52030B4C91F9D038EF26F2C11 +346D0E9955B491C3168E030B9CCE2964883B824F0DCF27EECA6C6E0478CF6B36 +AF2D33C9265A5F8D76FCC46AA30E6093E3822CE70C1ED22CDB8A8F910F5BCF45 +2C9D2D41B342B43DB5FE5EFCFEB5D378F78684296CAED9A39FC3834929073B6E +A934014457B89A61253E0924D3C6BB4FE4ADFBD0C9B3C61D08E3DB2B26785A1D +3B5E0E23258AE12276B63E9D79B2EAB305B8108A0987216C0EBF033A839BF611 +CB208A9B3B26699B201DD3662676B77C66EAFD20FAEB3021619FC123433D1CAE +53B0EDF00827F374A95DA1E89A01226DB951EC0F09366357A5A86C4DA2AD2A5A +0CCCB8304C4CDB9CDBA1068E98B2CCD4671E960ED12921F7B41100ABD48768C1 +BD5863591593D6ED9E604857B67D08C0CBE36A2E3D2EF5A9451345693A2F2FC1 +617F954916295D06A6A0805CA62EF2DAB67BC449674B238CCFAB92D628617FDB +9B3BA7FEE1FF29E7BB9CD5FEAB8F27E592B511CCFF9FDF4271CEF44F73CAA97A +30DDA0FE944A6C4B23D82797C4D2B53BE303031FC99A32D20F03D840E42BD088 +143E95C4E0CDA56BF86846A476E84C1ACC34A64C3A1EBC0A2B53380DEAB37DAB +692D21B1C485FD1D08080F8F3909C512F013BC14572FF66CF1ED026E2E81B3D9 +091D0C7196E99F937B4C2FB4719E8ACE48810BFAC898677969B176250A019DA8 +27BE209FCC3A0CD35219C47CC18E771C0A8E49B03CF3DD19F4118287DBC7C4A2 +2B23D2250E0C97C4C6EF38AF4960BB4E8963DBC38A11ACE3B46E1A3A36799FAE +3D53A874A1323CFBFF769064B7698CD5289E648D1B3968A48452362C9806DEA7 +D51B4C18267A88BE072D1C05B697417503C36206260DAFC1E4B989FDEA80FE19 +196EBF09046E43DD0138B1DFF846C34C7F14A126675BE530B671D23F3D5B4EF9 +9DE0484411942D59073707193132040B3786C02AF197A9520EF649BC55EAAFE1 +3F974A85D1FCA84BFE5B214F555C7F5F939977945559616160776D4369DBEE13 +0486617F2E364E8846175E02FE51D16728C08342DC1D8BC09C69A769E7165954 +A609FFD0174EA5236FE01BA8EF1BA4F43020C81683F9D83C7460A6B73650D81F +840A5085455A0CBBD4C148F065433693096FA50A32823F9E9CFE73921FE7A89B +4465255C2DE2722F176A410C0ABEC6619B7E5E2877B5F71DD65E1D83FD3AEBF1 +174CDEF7792366D9D8F965D7B894514552C8689AD98C3D47A5BDF0B62BE36A5E +EC4FED48090B02FADADE831409D8A12BF26C59DCED4FAB2D855B0AADE1E1E92D +7C12F644051C522C64DDC7D78663241BCD0077D29498143804A43C8D239B8240 +D27B1DC8264A08A7FD490BDB994C5F76175297570508141E3CA66C464E803F35 +3E203A80B56E0EF34C6CF7DF41E926F0B1C65C963E2EEFF7223D0F23C6637662 +8AD35E5E3DC3916E386945DE7F9459DEB136996E1DCD2C52EACD7EA23FEBD7FB +B2EE5067521FB56F7AB6A3DD411866A469FE6BAED525316A9E397096CCD9AD60 +227ED9887B9E199242512AE8A30ADB1647CA80A3DAF4F37367EE3A2D6E10B10F +20B04AADE3FB78AC92BDEFED7C7FEC071596FF33D00B9DF125BFD3E1A23F3E28 +06B1FB28E40FD8B9F7F56326F61D6F0ACC6B5BB5075FE00BF27D077531C87910 +7AC03290749574181B95B7968C79076CB25308FEC5F1D2E2D5EEE5EA026F841A +E1D347FA7149FAB5C64EF67A2A17C885D51C665DE67793C9E0E025A9AEC6C958 +EFBDD6E9C13CDF596CC58E28E284F6FE9700505C5B0F5D85501EB8C8668AF610 +5FF15864D7DA8F3A06CBEA58045321141099EF4FB952F9EF8848AF3A722646A8 +74336EF9927DC3451C787C2760E768DF4AD19EF9FCC27057C4602193DA5CBAF6 +404BE06ED2FF9DE5FDE00A9F2488A4FC0CAB5CE4A5C7767BAC50F2C84ADB20A6 +D0DB2AC57A490F7D0290664FC560202C2DBB5911BE6018945FC455FCA992F301 +C93F454A189878A215CEB421930E0CA6BC0C48CD297980CBAF9283B8D973078C +D40886E0C31CB1F44C36BFAC44CEA466011B72BC301552153893ABB2D2CB11B8 +AA14FB14B20E897D2A04D4B0643558B9247B8C9304286C4C26E8375258639E55 +8B004CD7B7CCE046DC730087543B0237A363C7A6B9A6D3CAB81310E2557CC57A +D43F163D5D81ADB9EB797C88E685E0E11C88400E3B0106DB9B8637B361A4FE44 +B911AAE93675C201493B5F2B24FA53A5D40E2CB99727AEE3740A620531C377CC +C9BF247FA8551E1B7AB95BA669C7EB321FE9950998496ECD49EB4131E352D217 +826587C051273B13878588F410EBB4C02820B8ECBA8FB7C860998B57FABE9C1F +816B32CE914F150EBCDF989A257F79F76F3603C9F891C2349909C87BF56EC7E7 +E7017616EDCEBE98B60C0F678D3B0F1DC838FF0A543E6A094F82A15EAA55987C +2A261E4CA71933EC92CBBCBB0E3F560659B6C3E402EBAF480B509892EC9242B6 +BFB23B9F70902A2AB6CCFED2E8FCA63DBC53244116C7612A94F198F36BA097C2 +7B63D740677C4B229AEF0256E6860F15FB800E60B522F9E70B0455D9C1871F22 +BDB77851B3885608E4FB1A8F830159DA098DE17C0EC61E54822DC61E21CD6C3B +288884430EE3017B35E742553AD08A264B38A42F3EFA5A709AB46563FF56CBF8 +73909325C6AF397E9524997CFBFA89376D9997D8EA7009A7D4433A73FFC98D13 +DBEDB05B41908A6D74CE3E28F5ECA3D8 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +cleartomark +%%EndFont +TeXDict begin 40258431 52099146 1000 600 600 (fitsio.dvi) +@start /Fa 193[71 62[{}1 90.9091 /CMMI10 rf /Fb 133[50 +59 59 81 59 62 44 44 46 1[62 56 62 93 31 59 1[31 62 56 +34 51 62 50 62 54 9[116 85 86 78 62 84 1[77 84 88 106 +67 88 1[42 88 88 70 74 86 81 80 85 6[31 1[56 56 56 56 +56 56 56 56 1[56 31 37 32[62 12[{}58 99.6264 /CMBX12 +rf /Fc 149[25 2[45 45 86[45 15[{}4 90.9091 /CMSY10 rf +/Fd 133[60 71 71 97 71 75 52 53 55 1[75 67 75 112 37 +71 1[37 75 67 41 61 75 60 75 65 9[139 102 103 94 75 100 +1[92 101 105 128 81 105 1[50 105 106 85 88 103 97 96 +102 6[37 67 67 67 67 67 67 67 67 67 67 67 37 45 3[52 +52 27[75 78 11[{}63 119.552 /CMBX12 rf /Fe 129[48 48 +48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 +48 48 48 48 48 48 48 48 48 48 1[48 48 48 48 48 48 48 +48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 +48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 +48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 +48 48 33[{}93 90.9091 /CMTT10 rf /Ff 139[75 1[79 1[108 +7[108 2[88 3[94 29[140 9[97 97 97 97 97 97 97 97 97 97 +48[{}17 172.188 /CMBX12 rf /Fg 165[56 68 68 93 68 68 +66 51 67 1[62 71 68 83 57 71 1[33 68 71 59 62 69 66 64 +68 6[25 45 45 45 45 45 45 45 45 45 45 45 25 30 45[{}38 +90.9091 /CMSL10 rf /Fh 134[55 55 1[55 58 41 41 43 1[58 +52 58 87 29 2[29 58 52 32 48 58 46 58 51 11[80 73 58 +78 1[71 79 82 4[40 1[82 66 69 80 76 74 79 7[52 52 52 +52 52 52 52 52 52 52 52 1[35 32[58 12[{}49 90.9091 /CMBX10 +rf /Fi 132[45 40 48 48 66 48 51 35 36 36 48 51 45 51 +76 25 48 28 25 51 45 28 40 51 40 51 45 25 2[25 45 25 +56 68 68 93 68 68 66 51 67 71 62 71 68 83 57 71 47 33 +68 71 59 62 69 66 64 68 71 43 43 71 25 25 25 45 45 45 +45 45 45 45 45 45 45 45 25 30 25 71 45 35 35 25 71 76 +45 76 45 25 18[76 51 51 53 11[{}93 90.9091 /CMR10 rf +/Fj 134[62 5[46 46 2[59 65 4[33 3[52 3[59 10[88 8[107 +25[59 59 59 59 1[33 46[{}15 119.552 /CMR12 rf /Fk 138[90 +63 64 66 2[81 90 134 45 2[45 1[81 49 74 1[72 90 78 12[112 +90 2[110 6[60 2[101 4[122 65[{}21 143.462 /CMBX12 rf +/Fl 134[123 123 1[123 129 90 92 95 1[129 116 129 194 +65 2[65 129 116 71 106 129 103 129 113 11[179 162 129 +173 1[159 175 182 4[87 1[183 146 153 178 168 165 175 +17[116 1[77 5[65 26[129 12[{}40 206.559 /CMBX12 rf end +%%EndProlog +%%BeginSetup +%%Feature: *Resolution 600dpi +TeXDict begin +%%BeginPaperSize: Letter +letter +%%EndPaperSize + end +%%EndSetup +%%Page: 1 1 +TeXDict begin 1 0 bop 861 1940 a Fl(FITSIO)76 b(User's)g(Guide)356 +2399 y Fk(A)54 b(Subroutine)d(In)l(terface)i(to)g(FITS)h(F)-13 +b(ormat)54 b(Files)1055 2659 y(for)g(F)-13 b(ortran)53 +b(Programmers)1667 3155 y Fj(V)-10 b(ersion)38 b(3.0)1727 +4058 y Fi(HEASAR)m(C)1764 4170 y(Co)s(de)30 b(662)1363 +4283 y(Go)s(ddard)f(Space)i(Fligh)m(t)h(Cen)m(ter)1522 +4396 y(Green)m(b)s(elt,)f(MD)h(20771)1857 4509 y(USA)1701 +5298 y Fj(Ma)m(y)39 b(2011)p eop end +%%Page: 2 2 +TeXDict begin 2 1 bop 0 299 a Fi(ii)p eop end +%%Page: 3 3 +TeXDict begin 3 2 bop 0 1267 a Fl(Con)-6 b(ten)g(ts)0 +1858 y Fh(1)84 b(In)m(tro)s(duction)3136 b(1)0 2118 y(2)119 +b(Creating)34 b(FITSIO/CFITSIO)2405 b(3)136 2280 y Fi(2.1)94 +b(Building)31 b(the)f(Library)58 b(.)45 b(.)h(.)g(.)g(.)f(.)h(.)g(.)f +(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.) +h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)131 b(3)136 2442 +y(2.2)94 b(T)-8 b(esting)32 b(the)e(Library)j(.)46 b(.)f(.)h(.)g(.)g(.) +f(.)h(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f +(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)131 +b(6)136 2604 y(2.3)94 b(Linking)31 b(Programs)f(with)g(FITSIO)40 +b(.)46 b(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.) +h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)131 +b(8)136 2766 y(2.4)94 b(Getting)32 b(Started)f(with)f(FITSIO)55 +b(.)46 b(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.) +f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)131 +b(8)136 2928 y(2.5)94 b(Example)31 b(Program)86 b(.)46 +b(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g +(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.) +f(.)131 b(8)136 3090 y(2.6)94 b(Legal)32 b(Stu\013)92 +b(.)46 b(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.) +h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f +(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(10)136 3252 y(2.7)94 +b(Ac)m(kno)m(wledgmen)m(ts)30 b(.)46 b(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g +(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.) +f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(10)0 +3511 y Fh(3)119 b(A)35 b(FITS)f(Primer)2918 b(13)0 3771 +y(4)84 b(FITSIO)34 b(Con)m(v)m(en)m(tions)h(and)g(Guidelines)1993 +b(15)136 3933 y Fi(4.1)94 b(CFITSIO)29 b(Size)i(Limitations)42 +b(.)k(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g +(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 +b(15)136 4095 y(4.2)94 b(Multiple)32 b(Access)f(to)g(the)g(Same)f(FITS) +g(File)h(.)45 b(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f +(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(16)136 +4257 y(4.3)94 b(Curren)m(t)30 b(Header)h(Data)h(Unit)e(\(CHDU\))87 +b(.)46 b(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.) +h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(16)136 4419 +y(4.4)94 b(Subroutine)29 b(Names)79 b(.)46 b(.)f(.)h(.)g(.)g(.)f(.)h(.) +g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g +(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(16)136 +4581 y(4.5)94 b(Subroutine)29 b(F)-8 b(amilies)33 b(and)c(Datat)m(yp)s +(es)44 b(.)i(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.) +g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(17)136 +4742 y(4.6)94 b(Implicit)31 b(Data)h(T)m(yp)s(e)e(Con)m(v)m(ersion)65 +b(.)46 b(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.) +h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 +b(17)136 4904 y(4.7)94 b(Data)32 b(Scaling)89 b(.)46 +b(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g +(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.) +f(.)h(.)g(.)f(.)85 b(18)136 5066 y(4.8)94 b(Error)30 +b(Status)g(V)-8 b(alues)32 b(and)d(the)i(Error)e(Message)j(Stac)m(k)44 +b(.)i(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h +(.)g(.)f(.)85 b(18)136 5228 y(4.9)94 b(V)-8 b(ariable-Length)33 +b(Arra)m(y)d(F)-8 b(acilit)m(y)34 b(in)c(Binary)g(T)-8 +b(ables)26 b(.)46 b(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f +(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(19)136 5390 y(4.10)49 +b(Supp)s(ort)29 b(for)h(IEEE)g(Sp)s(ecial)g(V)-8 b(alues)68 +b(.)45 b(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.) +g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(20)136 +5552 y(4.11)49 b(When)31 b(the)f(Final)h(Size)g(of)g(the)f(FITS)g(HDU)h +(is)f(Unkno)m(wn)k(.)45 b(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f +(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(21)136 5714 y(4.12)49 +b(Lo)s(cal)32 b(FITS)d(Con)m(v)m(en)m(tions)j(supp)s(orted)c(b)m(y)j +(FITSIO)72 b(.)46 b(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f +(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(21)1912 5942 y(iii)p +eop end +%%Page: 4 4 +TeXDict begin 4 3 bop 0 299 a Fi(iv)3311 b Fg(CONTENTS)345 +555 y Fi(4.12.1)61 b(Supp)s(ort)29 b(for)h(Long)g(String)g(Keyw)m(ord)g +(V)-8 b(alues.)62 b(.)46 b(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g +(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(21)345 716 y(4.12.2)61 +b(Arra)m(ys)31 b(of)f(Fixed-Length)h(Strings)f(in)g(Binary)h(T)-8 +b(ables)70 b(.)46 b(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g +(.)f(.)85 b(22)345 876 y(4.12.3)61 b(Keyw)m(ord)30 b(Units)h(Strings)i +(.)46 b(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f +(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 +b(23)345 1037 y(4.12.4)61 b(HIERAR)m(CH)31 b(Con)m(v)m(en)m(tion)h(for) +e(Extended)g(Keyw)m(ord)g(Names)83 b(.)45 b(.)h(.)g(.)g(.)f(.)h(.)g(.)f +(.)h(.)g(.)f(.)85 b(23)136 1197 y(4.13)49 b(Optimizing)31 +b(Co)s(de)f(for)g(Maxim)m(um)h(Pro)s(cessing)g(Sp)s(eed)44 +b(.)i(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h +(.)g(.)f(.)85 b(24)345 1358 y(4.13.1)61 b(Bac)m(kground)31 +b(Information:)41 b(Ho)m(w)31 b(CFITSIO)e(Manages)j(Data)g(I/O)91 +b(.)46 b(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(25)345 1518 +y(4.13.2)61 b(Optimization)32 b(Strategies)69 b(.)46 +b(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g +(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(25)0 +1771 y Fh(5)119 b(Basic)36 b(In)m(terface)e(Routines)2504 +b(29)136 1931 y Fi(5.1)94 b(FITSIO)30 b(Error)f(Status)h(Routines)84 +b(.)46 b(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.) +h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 +b(29)136 2092 y(5.2)94 b(File)32 b(I/O)e(Routines)e(.)46 +b(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h +(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.) +g(.)f(.)85 b(30)136 2252 y(5.3)94 b(Keyw)m(ord)31 b(I/O)f(Routines)36 +b(.)46 b(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.) +f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f +(.)85 b(32)136 2412 y(5.4)94 b(Data)32 b(I/O)f(Routines)53 +b(.)46 b(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.) +h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h +(.)g(.)f(.)85 b(33)0 2665 y Fh(6)119 b(Adv)-6 b(anced)36 +b(In)m(terface)e(Subroutines)2159 b(35)136 2826 y Fi(6.1)94 +b(FITS)30 b(File)i(Op)s(en)d(and)g(Close)i(Subroutines:)76 +b(.)46 b(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.) +g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(35)136 2986 y(6.2)94 +b(HDU-Lev)m(el)33 b(Op)s(erations)108 b(.)46 b(.)g(.)f(.)h(.)g(.)f(.)h +(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.) +g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(38)136 3146 +y(6.3)94 b(De\014ne)31 b(or)f(Rede\014ne)g(the)h(structure)f(of)g(the)h +(CHDU)99 b(.)46 b(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f +(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(41)136 3307 y(6.4)94 +b(FITS)30 b(Header)h(I/O)f(Subroutines)i(.)46 b(.)g(.)f(.)h(.)g(.)f(.)h +(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.) +h(.)g(.)f(.)h(.)g(.)f(.)85 b(43)345 3467 y(6.4.1)106 +b(Header)31 b(Space)g(and)f(P)m(osition)h(Routines)60 +b(.)46 b(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.) +h(.)g(.)f(.)h(.)g(.)f(.)85 b(43)345 3628 y(6.4.2)106 +b(Read)31 b(or)f(W)-8 b(rite)32 b(Standard)d(Header)i(Routines)67 +b(.)46 b(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.) +h(.)g(.)f(.)85 b(43)345 3788 y(6.4.3)106 b(W)-8 b(rite)32 +b(Keyw)m(ord)e(Subroutines)116 b(.)46 b(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f +(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.) +85 b(45)345 3949 y(6.4.4)106 b(Insert)30 b(Keyw)m(ord)g(Subroutines)108 +b(.)46 b(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.) +h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(47)345 4109 +y(6.4.5)106 b(Read)31 b(Keyw)m(ord)f(Subroutines)64 b(.)46 +b(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h +(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(47)345 4270 +y(6.4.6)106 b(Mo)s(dify)30 b(Keyw)m(ord)h(Subroutines)55 +b(.)46 b(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.) +h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(49)345 4430 +y(6.4.7)106 b(Up)s(date)31 b(Keyw)m(ord)f(Subroutines)116 +b(.)45 b(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.) +g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(50)345 4591 +y(6.4.8)106 b(Delete)33 b(Keyw)m(ord)d(Subroutines)87 +b(.)46 b(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.) +h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(50)136 4751 +y(6.5)94 b(Data)32 b(Scaling)g(and)d(Unde\014ned)g(Pixel)i(P)m +(arameters)113 b(.)46 b(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g +(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(51)136 4912 y(6.6)94 +b(FITS)30 b(Primary)g(Arra)m(y)h(or)f(IMA)m(GE)h(Extension)g(I/O)f +(Subroutines)117 b(.)46 b(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f +(.)85 b(52)136 5072 y(6.7)94 b(FITS)30 b(ASCI)s(I)f(and)h(Binary)g(T)-8 +b(able)31 b(Data)h(I/O)e(Subroutines)d(.)46 b(.)g(.)g(.)f(.)h(.)g(.)f +(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(55)345 +5232 y(6.7.1)106 b(Column)30 b(Information)g(Subroutines)121 +b(.)46 b(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.) +f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(55)345 5393 y(6.7.2)106 +b(Lo)m(w-Lev)m(el)33 b(T)-8 b(able)31 b(Access)g(Subroutines)60 +b(.)46 b(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.) +h(.)g(.)f(.)h(.)g(.)f(.)85 b(58)345 5553 y(6.7.3)106 +b(Edit)31 b(Ro)m(ws)f(or)h(Columns)106 b(.)46 b(.)f(.)h(.)g(.)f(.)h(.)g +(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.) +g(.)f(.)h(.)g(.)f(.)85 b(58)345 5714 y(6.7.4)106 b(Read)31 +b(and)f(W)-8 b(rite)31 b(Column)f(Data)i(Routines)66 +b(.)46 b(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.) +f(.)h(.)g(.)f(.)85 b(60)p eop end +%%Page: 5 5 +TeXDict begin 5 4 bop 0 299 a Fg(CONTENTS)3334 b Fi(v)136 +555 y(6.8)94 b(Ro)m(w)31 b(Selection)h(and)e(Calculator)i(Routines)95 +b(.)46 b(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.) +g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(64)136 717 y(6.9)94 +b(Celestial)33 b(Co)s(ordinate)d(System)g(Subroutines)98 +b(.)46 b(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.) +f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(65)136 879 y(6.10)49 +b(File)32 b(Chec)m(ksum)e(Subroutines)75 b(.)45 b(.)h(.)g(.)f(.)h(.)g +(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.) +g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(67)136 1041 y(6.11)80 +b(Date)32 b(and)d(Time)i(Utilit)m(y)h(Routines)69 b(.)45 +b(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f +(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(68)136 +1204 y(6.12)49 b(General)32 b(Utilit)m(y)g(Subroutines)61 +b(.)45 b(.)h(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.) +g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 +b(69)0 1464 y Fh(7)119 b(The)35 b(CFITSIO)e(Iterator)g(F)-9 +b(unction)2154 b(75)0 1725 y(8)119 b(Extended)35 b(File)f(Name)h(Syn)m +(tax)2330 b(77)136 1887 y Fi(8.1)94 b(Ov)m(erview)84 +b(.)46 b(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.) +f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g +(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(77)136 2049 y(8.2)94 +b(Filet)m(yp)s(e)62 b(.)45 b(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.) +f(.)h(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f +(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 +b(80)345 2211 y(8.2.1)106 b(Notes)32 b(ab)s(out)e(HTTP)g(pro)m(xy)g +(serv)m(ers)k(.)46 b(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.) +f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(80)345 +2373 y(8.2.2)106 b(Notes)32 b(ab)s(out)e(the)h(stream)f(\014let)m(yp)s +(e)h(driv)m(er)54 b(.)45 b(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h +(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(81)345 2536 +y(8.2.3)106 b(Notes)32 b(ab)s(out)e(the)h(gsiftp)f(\014let)m(yp)s(e)83 +b(.)45 b(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.) +g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(82)345 2698 +y(8.2.4)106 b(Notes)32 b(ab)s(out)e(the)h(ro)s(ot)f(\014let)m(yp)s(e)68 +b(.)46 b(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.) +h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(82)345 2860 +y(8.2.5)106 b(Notes)32 b(ab)s(out)e(the)h(shmem)e(\014let)m(yp)s(e:)70 +b(.)46 b(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.) +g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(84)136 3022 y(8.3)94 +b(Base)32 b(Filename)90 b(.)45 b(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.) +f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f +(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(84)136 +3184 y(8.4)94 b(Output)30 b(File)h(Name)g(when)f(Op)s(ening)f(an)h +(Existing)h(File)81 b(.)45 b(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.) +f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(86)136 3346 y(8.5)94 +b(T)-8 b(emplate)32 b(File)g(Name)f(when)e(Creating)i(a)g(New)f(File)57 +b(.)46 b(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.) +f(.)h(.)g(.)f(.)85 b(88)136 3508 y(8.6)94 b(Image)32 +b(Tile-Compression)e(Sp)s(eci\014cation)91 b(.)45 b(.)h(.)g(.)g(.)f(.)h +(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.) +g(.)f(.)85 b(88)136 3670 y(8.7)94 b(HDU)32 b(Lo)s(cation)f(Sp)s +(eci\014cation)47 b(.)e(.)h(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g +(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.) +f(.)85 b(88)136 3832 y(8.8)94 b(Image)32 b(Section)39 +b(.)46 b(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)h(.) +g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h +(.)g(.)f(.)h(.)g(.)f(.)85 b(89)136 3994 y(8.9)94 b(Image)32 +b(T)-8 b(ransform)29 b(Filters)54 b(.)46 b(.)g(.)f(.)h(.)g(.)f(.)h(.)g +(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.) +g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(90)136 4156 y(8.10)49 +b(Column)30 b(and)g(Keyw)m(ord)g(Filtering)h(Sp)s(eci\014cation)91 +b(.)45 b(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.) +g(.)f(.)h(.)g(.)f(.)85 b(92)136 4318 y(8.11)49 b(Ro)m(w)31 +b(Filtering)h(Sp)s(eci\014cation)82 b(.)45 b(.)h(.)g(.)f(.)h(.)g(.)f(.) +h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f +(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(94)345 4481 y(8.11.1)61 +b(General)32 b(Syn)m(tax)44 b(.)i(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f +(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.) +f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(94)345 4643 y(8.11.2)61 +b(Bit)32 b(Masks)43 b(.)j(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g +(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.) +g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(97)345 4805 y(8.11.3)61 +b(V)-8 b(ector)32 b(Columns)92 b(.)46 b(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f +(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.) +f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(98)345 4967 y(8.11.4)61 +b(Go)s(o)s(d)30 b(Time)h(In)m(terv)-5 b(al)31 b(Filtering)62 +b(.)46 b(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.) +h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)85 b(99)345 5129 +y(8.11.5)61 b(Spatial)31 b(Region)h(Filtering)59 b(.)46 +b(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g +(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)40 b(100)345 +5291 y(8.11.6)61 b(Example)31 b(Ro)m(w)g(Filters)h(.)45 +b(.)h(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f +(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)40 +b(103)136 5453 y(8.12)80 b(Binning)30 b(or)g(Histogramming)i(Sp)s +(eci\014cation)f(.)46 b(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g +(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)40 b(104)0 +5714 y Fh(9)84 b(T)-9 b(emplate)35 b(Files)2933 b(107)p +eop end +%%Page: 6 6 +TeXDict begin 6 5 bop 0 299 a Fi(vi)3311 b Fg(CONTENTS)136 +555 y Fi(9.1)94 b(Detailed)33 b(T)-8 b(emplate)31 b(Line)g(F)-8 +b(ormat)48 b(.)e(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.) +g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)40 +b(107)136 715 y(9.2)94 b(Auto-indexing)31 b(of)g(Keyw)m(ords)73 +b(.)45 b(.)h(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.) +g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)40 +b(108)136 876 y(9.3)94 b(T)-8 b(emplate)32 b(P)m(arser)f(Directiv)m(es) +87 b(.)45 b(.)h(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g +(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)40 +b(109)136 1036 y(9.4)94 b(F)-8 b(ormal)32 b(T)-8 b(emplate)32 +b(Syn)m(tax)i(.)46 b(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.) +h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h +(.)g(.)f(.)40 b(109)136 1196 y(9.5)94 b(Errors)63 b(.)46 +b(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g +(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.) +g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)40 b(110)136 1356 y(9.6)94 +b(Examples)72 b(.)46 b(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g +(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.) +f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)40 b(110)0 +1607 y Fh(10)67 b(Summary)36 b(of)f(all)f(FITSIO)g(User-In)m(terface)h +(Subroutines)1215 b(113)0 1858 y(11)67 b(P)m(arameter)35 +b(De\014nitions)2563 b(121)0 2109 y(12)67 b(FITSIO)33 +b(Error)i(Status)g(Co)s(des)2295 b(127)p eop end +%%Page: 1 7 +TeXDict begin 1 6 bop 0 1225 a Ff(Chapter)65 b(1)0 1687 +y Fl(In)-6 b(tro)6 b(duction)0 2180 y Fi(This)33 b(do)s(cumen)m(t)i +(describ)s(es)e(the)h(F)-8 b(ortran-callable)38 b(subroutine)33 +b(in)m(terface)j(that)f(is)f(pro)m(vided)g(as)g(part)g(of)h(the)0 +2293 y(CFITSIO)f(library)h(\(whic)m(h)h(is)g(written)g(in)f(ANSI)g +(C\).)h(This)f(is)h(a)g(companion)g(do)s(cumen)m(t)f(to)i(the)e +(CFITSIO)0 2406 y(User's)k(Guide)f(whic)m(h)h(should)e(b)s(e)h +(consulted)h(for)f(further)g(information)h(ab)s(out)f(the)h(underlying) +e(CFITSIO)0 2518 y(library)-8 b(.)50 b(In)32 b(the)i(remainder)f(of)g +(this)h(do)s(cumen)m(t,)g(the)g(terms)f(FITSIO)f(and)h(CFITSIO)f(are)i +(in)m(terc)m(hangeable)0 2631 y(and)c(refer)g(to)h(the)g(same)f +(library)-8 b(.)0 2791 y(FITSIO/CFITSIO)31 b(is)j(a)f(mac)m(hine-indep) +s(enden)m(t)g(library)g(of)h(routines)f(for)g(reading)h(and)f(writing)g +(data)h(\014les)0 2904 y(in)c(the)g(FITS)g(\(Flexible)i(Image)f(T)-8 +b(ransp)s(ort)29 b(System\))h(data)h(format.)41 b(It)31 +b(can)f(also)h(read)g(IRAF)f(format)h(image)0 3017 y(\014les)40 +b(and)e(ra)m(w)i(binary)f(data)h(arra)m(ys)g(b)m(y)g(con)m(v)m(erting)h +(them)e(on)h(the)g(\015y)f(in)m(to)h(a)g(virtual)g(FITS)f(format)h +(\014le.)0 3130 y(This)32 b(library)h(w)m(as)g(written)g(to)h(pro)m +(vide)f(a)h(p)s(o)m(w)m(erful)f(y)m(et)h(simple)f(in)m(terface)h(for)f +(accessing)i(FITS)d(\014les)h(whic)m(h)0 3243 y(will)j(run)e(on)h(most) +h(commonly)g(used)e(computers)h(and)g(w)m(orkstations.)57 +b(FITSIO)34 b(supp)s(orts)g(all)i(the)g(features)0 3356 +y(describ)s(ed)21 b(in)i(the)f(o\016cial)i(NOST)e(de\014nition)g(of)h +(the)f(FITS)g(format)h(and)f(can)h(read)f(and)g(write)h(all)g(the)g +(curren)m(tly)0 3469 y(de\014ned)40 b(t)m(yp)s(es)h(of)g(extensions,)j +(including)d(ASCI)s(I)e(tables)j(\(T)-8 b(ABLE\),)42 +b(Binary)f(tables)h(\(BINT)-8 b(ABLE\))43 b(and)0 3582 +y(IMA)m(GE)36 b(extensions.)56 b(The)34 b(FITSIO)g(subroutines)g +(insulate)i(the)f(programmer)g(from)g(ha)m(ving)h(to)g(deal)f(with)0 +3695 y(the)25 b(complicated)h(formatting)g(details)f(in)f(the)h(FITS)f +(\014le,)i(ho)m(w)m(ev)m(er,)i(it)d(is)f(assumed)g(that)h(users)f(ha)m +(v)m(e)i(a)f(general)0 3808 y(kno)m(wledge)31 b(ab)s(out)f(the)h +(structure)f(and)g(usage)h(of)f(FITS)g(\014les.)0 3968 +y(The)20 b(CFITSIO)f(pac)m(k)-5 b(age)23 b(w)m(as)e(initially)i(dev)m +(elop)s(ed)e(b)m(y)f(the)h(HEASAR)m(C)g(\(High)h(Energy)e(Astroph)m +(ysics)h(Science)0 4081 y(Arc)m(hiv)m(e)35 b(Researc)m(h)g(Cen)m(ter\)) +f(at)h(the)f(NASA)g(Go)s(ddard)e(Space)j(Fligh)m(t)g(Cen)m(ter)f(to)h +(con)m(v)m(ert)g(v)-5 b(arious)34 b(existing)0 4194 y(and)25 +b(newly)h(acquired)g(astronomical)i(data)e(sets)h(in)m(to)g(FITS)e +(format)h(and)f(to)i(further)e(analyze)i(data)g(already)f(in)0 +4307 y(FITS)h(format.)41 b(New)28 b(features)g(con)m(tin)m(ue)h(to)g(b) +s(e)e(added)h(to)g(CFITSIO)f(in)g(large)i(part)f(due)g(to)g(con)m +(tributions)h(of)0 4419 y(ideas)k(or)g(actual)h(co)s(de)f(from)f(users) +g(of)h(the)g(pac)m(k)-5 b(age.)49 b(The)33 b(In)m(tegral)h(Science)f +(Data)h(Cen)m(ter)f(in)g(Switzerland,)0 4532 y(and)g(the)g(XMM/ESTEC)h +(pro)5 b(ject)34 b(in)f(The)g(Netherlands)g(made)g(esp)s(ecially)i +(signi\014can)m(t)f(con)m(tributions)g(that)0 4645 y(resulted)c(in)g +(man)m(y)h(of)f(the)h(new)f(features)g(that)h(app)s(eared)f(in)g(v2.0)i +(of)e(CFITSIO.)0 4805 y(The)22 b(latest)i(v)m(ersion)f(of)g(the)f +(CFITSIO)f(source)i(co)s(de,)h(do)s(cumen)m(tation,)i(and)21 +b(example)j(programs)e(are)h(a)m(v)-5 b(ailable)0 4918 +y(on)30 b(the)h(W)-8 b(orld-Wide)32 b(W)-8 b(eb)31 b(or)f(via)h(anon)m +(ymous)f(ftp)g(from:)382 5178 y Fe(http://heasarc.gsfc.nasa)o(.go)o +(v/fi)o(tsio)382 5291 y(ftp://legacy.gsfc.nasa.g)o(ov/)o(soft)o(ware)o +(/fi)o(tsio)o(/c)1927 5942 y Fi(1)p eop end +%%Page: 2 8 +TeXDict begin 2 7 bop 0 299 a Fi(2)2452 b Fg(CHAPTER)30 +b(1.)71 b(INTR)m(ODUCTION)0 555 y Fi(An)m(y)28 b(questions,)g(bug)f +(rep)s(orts,)h(or)f(suggested)i(enhancemen)m(ts)f(related)g(to)h(the)e +(CFITSIO)f(pac)m(k)-5 b(age)30 b(should)d(b)s(e)0 668 +y(sen)m(t)k(to)g(the)g(primary)e(author:)382 928 y Fe(Dr.)47 +b(William)f(Pence)810 b(Telephone:)92 b(\(301\))47 b(286-4599)382 +1041 y(HEASARC,)e(Code)i(662)811 b(E-mail:)45 b +(William.D.Pence@nasa.gov)382 1154 y(NASA/Goddard)f(Space)j(Flight)f +(Center)382 1267 y(Greenbelt,)f(MD)i(20771,)f(USA)0 1526 +y Fi(This)40 b(User's)i(Guide)f(assumes)g(that)h(readers)f(already)g +(ha)m(v)m(e)i(a)f(general)g(understanding)d(of)j(the)f(de\014nition)0 +1639 y(and)31 b(structure)g(of)h(FITS)e(format)i(\014les.)44 +b(F)-8 b(urther)32 b(information)f(ab)s(out)h(FITS)f(formats)g(is)h(a)m +(v)-5 b(ailable)34 b(from)d(the)0 1752 y(FITS)h(Supp)s(ort)f(O\016ce)i +(at)g Fe(http://fits.gsfc.nasa.gov)o Fi(.)42 b(In)32 +b(particular,)i(the)f('NOST)f(FITS)g(Standard')0 1865 +y(giv)m(es)j(the)g(authoritativ)m(e)h(de\014nition)e(of)g(the)g(FITS)g +(data)h(format,)g(and)f(the)g(`FITS)g(User's)g(Guide')g(pro)m(vides)0 +1978 y(additional)d(historical)h(bac)m(kground)e(and)g(practical)i +(advice)f(on)f(using)g(FITS)g(\014les.)0 2138 y(CFITSIO)j(users)g(ma)m +(y)h(also)h(b)s(e)f(in)m(terested)h(in)f(the)g(FTOOLS)f(pac)m(k)-5 +b(age)36 b(of)e(programs)g(that)g(can)h(b)s(e)e(used)g(to)0 +2251 y(manipulate)k(and)e(analyze)j(FITS)e(format)g(\014les.)59 +b(Information)36 b(ab)s(out)g(FTOOLS)f(can)i(b)s(e)f(obtained)g(on)h +(the)0 2364 y(W)-8 b(eb)31 b(or)f(via)h(anon)m(ymous)g(ftp)f(at:)382 +2624 y Fe(http://heasarc.gsfc.nasa)o(.go)o(v/ft)o(ools)382 +2737 y(ftp://legacy.gsfc.nasa.g)o(ov/)o(soft)o(ware)o(/ft)o(ools)o +(/rel)o(eas)o(e)p eop end +%%Page: 3 9 +TeXDict begin 3 8 bop 0 1225 a Ff(Chapter)65 b(2)0 1687 +y Fl(Creating)77 b(FITSIO/CFITSIO)0 2216 y Fd(2.1)135 +b(Building)45 b(the)h(Library)0 2467 y Fi(T)-8 b(o)43 +b(use)g(the)g(FITSIO)f(subroutines)f(one)j(m)m(ust)e(\014rst)g(build)g +(the)h(CFITSIO)f(library)-8 b(,)46 b(whic)m(h)d(requires)f(a)h(C)0 +2580 y(compiler.)73 b(gcc)43 b(is)e(ideal,)j(or)d(most)h(other)f +(ANSI-C)g(compilers)g(will)h(also)g(w)m(ork.)73 b(The)40 +b(CFITSIO)g(co)s(de)h(is)0 2692 y(con)m(tained)25 b(in)f(ab)s(out)f(40) +i(C)f(source)g(\014les)f(\(*.c\))j(and)d(header)h(\014les)g(\(*.h\).)39 +b(On)23 b(V)-10 b(AX/VMS)25 b(systems)f(2)g(assem)m(bly-)0 +2805 y(co)s(de)31 b(\014les)f(\(vmsieeed.mar)h(and)f(vmsieeer.mar\))h +(are)g(also)g(needed.)0 2965 y(The)45 b(F)-8 b(ortran)46 +b(in)m(terface)g(subroutines)e(to)i(the)f(C)g(CFITSIO)f(routines)h(are) +g(lo)s(cated)i(in)e(the)g(f77)p 3538 2965 28 4 v 33 w(wrap1.c,)0 +3078 y(through)22 b(f77)p 459 3078 V 33 w(wrap4.c)h(\014les.)38 +b(These)22 b(are)h(relativ)m(ely)i(simple)d('wrapp)s(ers')f(that)i +(translate)h(the)f(argumen)m(ts)g(in)f(the)0 3191 y(F)-8 +b(ortran)26 b(subroutine)e(in)m(to)j(the)e(appropriate)h(format)g(for)f +(the)g(corresp)s(onding)g(C)g(routine.)39 b(This)24 b(translation)j(is) +0 3304 y(p)s(erformed)19 b(transparen)m(tly)i(to)g(the)g(user)f(b)m(y)g +(a)h(set)h(of)e(C)h(macros)g(lo)s(cated)h(in)e(the)h(cfortran.h)f +(\014le.)38 b(Unfortunately)0 3417 y(cfortran.h)28 b(do)s(es)g(not)g +(supp)s(ort)f(ev)m(ery)h(com)m(bination)i(of)e(C)g(and)f(F)-8 +b(ortran)29 b(compilers)g(so)f(the)h(F)-8 b(ortran)28 +b(in)m(terface)0 3530 y(is)i(not)h(supp)s(orted)e(on)h(all)h +(platforms.)41 b(\(see)31 b(further)e(notes)i(b)s(elo)m(w\).)0 +3690 y(A)f(standard)f(com)m(bination)j(of)e(C)f(and)h(F)-8 +b(ortran)30 b(compilers)h(will)f(b)s(e)f(assumed)h(b)m(y)f(default,)i +(but)e(one)h(ma)m(y)h(also)0 3803 y(sp)s(ecify)f(a)h(particular)f(F)-8 +b(ortran)32 b(compiler)e(b)m(y)h(doing:)48 4064 y Fe(>)95 +b(setenv)46 b(CFLAGS)g(-DcompilerName=1)0 4324 y Fi(\(where)33 +b('compilerName')h(is)f(the)g(name)f(of)h(the)g(compiler\))h(b)s(efore) +e(running)f(the)i(con\014gure)f(command.)47 b(The)0 4437 +y(curren)m(tly)30 b(recognized)i(compiler)f(names)f(are:)48 +4698 y Fe(g77Fortran)48 4811 y(IBMR2Fortran)48 4924 y(CLIPPERFortran)48 +5036 y(pgiFortran)48 5149 y(NAGf90Fortran)48 5262 y(f2cFortran)48 +5375 y(hpuxFortran)48 5488 y(apolloFortran)48 5601 y(sunFortran)48 +5714 y(CRAYFortran)1927 5942 y Fi(3)p eop end +%%Page: 4 10 +TeXDict begin 4 9 bop 0 299 a Fi(4)1896 b Fg(CHAPTER)30 +b(2.)111 b(CREA)-8 b(TING)31 b(FITSIO/CFITSIO)48 555 +y Fe(mipsFortran)48 668 y(DECFortran)48 781 y(vmsFortran)48 +894 y(CONVEXFortran)48 1007 y(PowerStationFortran)48 +1120 y(AbsoftUNIXFortran)48 1233 y(AbsoftProFortran)48 +1346 y(SXFortran)0 1609 y Fi(Alternativ)m(ely)-8 b(,)42 +b(one)c(ma)m(y)g(edit)g(the)f(CFLA)m(GS)h(line)f(in)h(the)f(Mak)m +(e\014le)i(to)f(add)f(the)h('-DcompilerName')i(\015ag)0 +1722 y(after)31 b(running)e(the)h('./con\014gure')h(command.)0 +1882 y(The)f(CFITSIO)f(library)h(is)g(built)g(on)h(Unix)f(systems)g(b)m +(y)g(t)m(yping:)48 2146 y Fe(>)95 b(./configure)45 b +([--prefix=/target/insta)o(llat)o(ion)o(/pat)o(h])764 +2259 y([--enable-sse2])e([--enable-ssse3])48 2372 y(>)95 +b(make)476 b(\(or)95 b('make)46 b(shared'\))48 2485 y(>)95 +b(make)47 b(install)93 b(\(this)46 b(step)h(is)g(optional\))0 +2749 y Fi(at)24 b(the)g(op)s(erating)g(system)g(prompt.)38 +b(The)23 b(con\014gure)g(command)g(customizes)i(the)f(Mak)m(e\014le)h +(for)f(the)g(particular)0 2861 y(system,)g(then)d(the)g(`mak)m(e')i +(command)e(compiles)h(the)f(source)h(\014les)f(and)g(builds)f(the)h +(library)-8 b(.)38 b(T)m(yp)s(e)21 b(`./con\014gure')0 +2974 y(and)34 b(not)h(simply)f(`con\014gure')h(to)h(ensure)e(that)h +(the)g(con\014gure)g(script)f(in)h(the)g(curren)m(t)f(directory)h(is)g +(run)f(and)0 3087 y(not)29 b(some)g(other)g(system-wide)g(con\014gure)f +(script.)40 b(The)29 b(optional)h('pre\014x')e(argumen)m(t)h(to)g +(con\014gure)g(giv)m(es)h(the)0 3200 y(path)e(to)i(the)f(directory)g +(where)f(the)h(CFITSIO)f(library)g(and)g(include)h(\014les)f(should)g +(b)s(e)g(installed)i(via)f(the)g(later)0 3313 y('mak)m(e)j(install')f +(command.)41 b(F)-8 b(or)31 b(example,)143 3577 y Fe(>)48 +b(./configure)c(--prefix=/usr1/local)0 3841 y Fi(will)25 +b(cause)h(the)f('mak)m(e)h(install')g(command)f(to)h(cop)m(y)g(the)f +(CFITSIO)e(lib)s(c\014tsio)i(\014le)h(to)f(/usr1/lo)s(cal/lib)i(and)e +(the)0 3953 y(necessary)33 b(include)e(\014les)i(to)f(/usr1/lo)s +(cal/include)j(\(assuming)d(of)g(course)g(that)h(the)f(pro)s(cess)g +(has)g(p)s(ermission)0 4066 y(to)f(write)g(to)g(these)g(directories\).) +0 4227 y(The)24 b(optional)i({enable-sse2)g(and)e({enable-ssse3)i +(\015ags)e(will)h(cause)g(con\014gure)f(to)h(attempt)h(to)f(build)e +(CFITSIO)0 4339 y(using)31 b(faster)h(b)m(yte-sw)m(apping)g +(algorithms.)46 b(See)32 b(the)g("Optimizing)g(Programs")g(section)h +(of)f(this)f(man)m(ual)h(for)0 4452 y(more)f(information)f(ab)s(out)g +(these)h(options.)0 4613 y(By)37 b(default,)i(the)f(Mak)m(e\014le)g +(will)g(b)s(e)e(con\014gured)g(to)i(build)e(the)h(set)h(of)f(F)-8 +b(ortran-callable)40 b(wrapp)s(er)35 b(routines)0 4725 +y(whose)30 b(calling)i(sequences)f(are)f(describ)s(ed)g(later)h(in)f +(this)g(do)s(cumen)m(t.)0 4886 y(The)e('mak)m(e)h(shared')f(option)h +(builds)e(a)i(shared)e(or)i(dynamic)f(v)m(ersion)h(of)f(the)h(CFITSIO)d +(library)-8 b(.)40 b(When)28 b(using)0 4999 y(the)f(shared)f(library)h +(the)g(executable)h(co)s(de)f(is)g(not)g(copied)g(in)m(to)h(y)m(our)f +(program)g(at)g(link)g(time)g(and)g(instead)g(the)0 5111 +y(program)g(lo)s(cates)i(the)f(necessary)g(library)f(co)s(de)h(at)g +(run)e(time,)j(normally)f(through)e(LD)p 3065 5111 28 +4 v 33 w(LIBRAR)-8 b(Y)p 3514 5111 V 34 w(P)g(A)g(TH)28 +b(or)0 5224 y(some)j(other)f(metho)s(d.)41 b(The)29 b(adv)-5 +b(an)m(tages)33 b(of)d(using)g(a)h(shared)e(library)h(are:)143 +5488 y Fe(1.)95 b(Less)47 b(disk)f(space)h(if)g(you)g(build)f(more)h +(than)f(1)i(program)143 5601 y(2.)95 b(Less)47 b(memory)f(if)h(more)g +(than)f(one)h(copy)g(of)g(a)g(program)f(using)h(the)g(shared)334 +5714 y(library)f(is)h(running)f(at)h(the)g(same)g(time)f(since)h(the)g +(system)f(is)h(smart)p eop end +%%Page: 5 11 +TeXDict begin 5 10 bop 0 299 a Fg(2.1.)72 b(BUILDING)31 +b(THE)f(LIBRAR)-8 b(Y)2507 b Fi(5)334 555 y Fe(enough)46 +b(to)h(share)g(copies)f(of)h(the)g(shared)f(library)g(at)h(run)g(time.) +143 668 y(3.)95 b(Possibly)46 b(easier)g(maintenance)e(since)j(a)g(new) +g(version)f(of)h(the)g(shared)334 781 y(library)f(can)h(be)g(installed) +e(without)h(relinking)f(all)i(the)g(software)334 894 +y(that)g(uses)f(it)i(\(as)e(long)h(as)g(the)g(subroutine)e(names)i(and) +f(calling)334 1007 y(sequences)f(remain)h(unchanged\).)143 +1120 y(4.)95 b(No)47 b(run-time)f(penalty.)0 1346 y Fi(The)30 +b(disadv)-5 b(an)m(tages)32 b(are:)143 1572 y Fe(1.)47 +b(More)g(hassle)f(at)h(runtime.)94 b(You)46 b(have)h(to)g(either)f +(build)h(the)g(programs)286 1685 y(specially)f(or)h(have)f +(LD_LIBRARY_PATH)e(set)j(right.)143 1798 y(2.)g(There)g(may)g(be)g(a)g +(slight)f(start)h(up)g(penalty,)e(depending)h(on)h(where)f(you)h(are) +286 1911 y(reading)f(the)h(shared)f(library)g(and)h(the)g(program)f +(from)g(and)h(if)g(your)g(CPU)g(is)286 2024 y(either)f(really)h(slow)f +(or)h(really)f(heavily)g(loaded.)0 2250 y Fi(On)30 b(HP/UX)i(systems,)g +(the)f(en)m(vironmen)m(t)h(v)-5 b(ariable)32 b(CFLA)m(GS)f(should)f(b)s +(e)h(set)g(to)h(-Ae)g(b)s(efore)f(running)e(con-)0 2363 +y(\014gure)h(to)h(enable)g("extended)g(ANSI")f(features.)0 +2523 y(It)j(ma)m(y)g(not)g(b)s(e)f(p)s(ossible)g(to)h(statically)i +(link)e(programs)f(that)h(use)g(CFITSIO)e(on)h(some)h(platforms)g +(\(namely)-8 b(,)0 2636 y(on)28 b(Solaris)h(2.6\))h(due)e(to)h(the)g +(net)m(w)m(ork)g(driv)m(ers)f(\(whic)m(h)h(pro)m(vide)g(FTP)f(and)g +(HTTP)g(access)h(to)h(FITS)d(\014les\).)41 b(It)0 2749 +y(is)33 b(p)s(ossible)f(to)i(mak)m(e)f(b)s(oth)g(a)g(dynamic)f(and)g(a) +i(static)g(v)m(ersion)f(of)g(the)g(CFITSIO)e(library)-8 +b(,)34 b(but)e(net)m(w)m(ork)i(\014le)0 2862 y(access)e(will)e(not)h(b) +s(e)f(p)s(ossible)g(using)g(the)g(static)i(v)m(ersion.)0 +3022 y(On)c(V)-10 b(AX/VMS)31 b(and)d(ALPHA/VMS)i(systems)f(the)h(mak)m +(e)p 2100 3022 28 4 v 34 w(g\015oat.com)h(command)e(\014le)g(ma)m(y)h +(b)s(e)f(executed)h(to)0 3135 y(build)35 b(the)i(c\014tsio.olb)g(ob)5 +b(ject)37 b(library)f(using)g(the)g(default)h(G-\015oating)g(p)s(oin)m +(t)g(option)f(for)g(double)g(v)-5 b(ariables.)0 3248 +y(The)37 b(mak)m(e)p 405 3248 V 33 w(d\015oat.com)i(and)d(mak)m(e)p +1279 3248 V 34 w(ieee.com)j(\014les)f(ma)m(y)f(b)s(e)g(used)f(instead)i +(to)g(build)e(the)h(library)g(with)g(the)0 3361 y(other)26 +b(\015oating)i(p)s(oin)m(t)e(options.)39 b(Note)28 b(that)f(the)f +(getcwd)h(function)f(that)h(is)f(used)f(in)h(the)h(group.c)f(mo)s(dule) +f(ma)m(y)0 3474 y(require)44 b(that)i(programs)e(using)g(CFITSIO)f(b)s +(e)h(link)m(ed)i(with)e(the)h(ALPHA$LIBRAR)-8 b(Y:V)e(AX)m(CR)i(TL.OLB) +0 3587 y(library)g(.)41 b(See)30 b(the)h(example)g(link)f(line)h(in)f +(the)h(next)f(section)i(of)e(this)h(do)s(cumen)m(t.)0 +3747 y(On)25 b(Windo)m(ws)h(IBM-PC)g(t)m(yp)s(e)g(platforms)f(the)h +(situation)h(is)e(more)h(complicated)i(b)s(ecause)d(of)h(the)g(wide)g +(v)-5 b(ariet)m(y)0 3860 y(of)43 b(F)-8 b(ortran)43 b(compilers)h(that) +f(are)g(a)m(v)-5 b(ailable)45 b(and)d(b)s(ecause)h(of)g(the)g(inheren)m +(t)g(complexities)i(of)d(calling)j(the)0 3973 y(CFITSIO)25 +b(C)g(routines)h(from)g(F)-8 b(ortran.)40 b(Tw)m(o)26 +b(di\013eren)m(t)h(v)m(ersions)f(of)g(the)h(CFITSIO)d(dll)i(library)g +(are)g(a)m(v)-5 b(ailable,)0 4085 y(compiled)28 b(with)e(the)i(Borland) +f(C++)f(compiler)i(and)e(the)i(Microsoft)g(Visual)g(C++)e(compiler,)i +(resp)s(ectiv)m(ely)-8 b(,)30 b(in)0 4198 y(the)g(\014les)g(c\014tsio)s +(dll)p 679 4198 V 33 w(2xxx)p 901 4198 V 34 w(b)s(orland.zip)f(and)g +(c\014tsio)s(dll)p 1924 4198 V 33 w(3xxx)p 2146 4198 +V 33 w(v)m(cc.zip,)j(where)e('3xxx')h(represen)m(ts)f(the)g(curren)m(t) +0 4311 y(release)44 b(n)m(um)m(b)s(er.)76 b(Both)43 b(these)g(dll)g +(libraries)g(con)m(tain)h(a)f(set)g(of)f(F)-8 b(ortran)44 +b(wrapp)s(er)d(routines)h(whic)m(h)g(ma)m(y)0 4424 y(b)s(e)37 +b(compatible)i(with)e(some,)j(but)d(probably)g(not)g(all,)k(a)m(v)-5 +b(ailable)40 b(F)-8 b(ortran)38 b(compilers.)63 b(T)-8 +b(o)38 b(test)g(if)g(they)g(are)0 4537 y(compatible,)29 +b(compile)f(the)f(program)g(testf77.f)h(and)f(try)f(linking)i(to)f +(these)h(dll)e(libraries.)40 b(If)27 b(these)g(libraries)g(do)0 +4650 y(not)i(w)m(ork)g(with)f(a)h(particular)g(F)-8 b(ortran)30 +b(compiler,)g(then)e(there)h(are)g(2)g(p)s(ossible)g(solutions.)40 +b(The)28 b(\014rst)g(solution)0 4763 y(w)m(ould)h(b)s(e)h(to)g(mo)s +(dify)f(the)h(\014le)f(cfortran.h)h(for)f(that)i(particular)f(com)m +(bination)h(of)f(C)f(and)g(F)-8 b(ortran)30 b(compilers,)0 +4876 y(and)k(then)g(rebuild)f(the)i(CFITSIO)d(dll)j(library)-8 +b(.)52 b(This)34 b(will)h(require,)g(ho)m(w)m(ev)m(er,)i(a)e(some)f +(exp)s(ertise)h(in)f(mixed)0 4989 y(language)e(programming.)40 +b(The)30 b(other)h(solution)f(is)h(to)g(use)f(the)g(older)h(v5.03)h(F) +-8 b(ortran-77)32 b(implemen)m(tation)g(of)0 5102 y(FITSIO)25 +b(that)h(is)g(still)h(a)m(v)-5 b(ailable)28 b(from)d(the)h(FITSIO)f(w)m +(eb-site.)40 b(This)25 b(v)m(ersion)h(is)g(no)g(longer)g(supp)s(orted,) +f(but)g(it)0 5215 y(do)s(es)k(pro)m(vide)h(the)g(basic)h(functions)e +(for)g(reading)h(and)f(writing)h(FITS)f(\014les)h(and)f(should)g(b)s(e) +g(compatible)i(with)0 5327 y(most)g(F)-8 b(ortran)31 +b(compilers.)0 5488 y(CFITSIO)e(has)h(curren)m(tly)g(b)s(een)g(tested)h +(on)f(the)h(follo)m(wing)h(platforms:)95 5714 y Fe(OPERATING)46 +b(SYSTEM)523 b(COMPILER)p eop end +%%Page: 6 12 +TeXDict begin 6 11 bop 0 299 a Fi(6)1896 b Fg(CHAPTER)30 +b(2.)111 b(CREA)-8 b(TING)31 b(FITSIO/CFITSIO)143 555 +y Fe(Sun)47 b(OS)1002 b(gcc)47 b(and)g(cc)g(\(3.0.1\))143 +668 y(Sun)g(Solaris)762 b(gcc)47 b(and)g(cc)143 781 y(Silicon)f +(Graphics)g(IRIX)285 b(gcc)47 b(and)g(cc)143 894 y(Silicon)f(Graphics)g +(IRIX64)189 b(MIPS)143 1007 y(Dec)47 b(Alpha)f(OSF/1)572 +b(gcc)47 b(and)g(cc)143 1120 y(DECstation)93 b(Ultrix)428 +b(gcc)143 1233 y(Dec)47 b(Alpha)f(OpenVMS)476 b(cc)143 +1346 y(DEC)47 b(VAX/VMS)762 b(gcc)47 b(and)g(cc)143 1458 +y(HP-UX)1049 b(gcc)143 1571 y(IBM)47 b(AIX)954 b(gcc)143 +1684 y(Linux)1049 b(gcc)143 1797 y(MkLinux)953 b(DR3)143 +1910 y(Windows)46 b(95/98/NT)523 b(Borland)46 b(C++)h(V4.5)143 +2023 y(Windows)f(95/98/NT/ME/XP)235 b(Microsoft/Compaq)43 +b(Visual)j(C++)h(v5.0,)g(v6.0)143 2136 y(Windows)f(95/98/NT)523 +b(Cygwin)46 b(gcc)143 2249 y(OS/2)1097 b(gcc)47 b(+)g(EMX)143 +2362 y(MacOS)g(7.1)f(or)i(greater)332 b(Metrowerks)45 +b(10.+)0 2591 y Fi(CFITSIO)26 b(will)j(probably)e(run)f(on)i(most)g +(other)h(Unix)e(platforms.)40 b(Cra)m(y)28 b(sup)s(ercomputers)e(are)j +(curren)m(tly)f(not)0 2704 y(supp)s(orted.)0 3033 y Fd(2.2)135 +b(T)-11 b(esting)46 b(the)f(Library)0 3283 y Fi(The)40 +b(CFITSIO)e(library)i(should)f(b)s(e)g(tested)i(b)m(y)f(building)f(and) +g(running)g(the)h(testprog.c)h(program)f(that)h(is)0 +3396 y(included)30 b(with)g(the)g(release.)42 b(On)30 +b(Unix)g(systems)g(t)m(yp)s(e:)191 3625 y Fe(\045)47 +b(make)g(testprog)191 3738 y(\045)g(testprog)f(>)h(testprog.lis)191 +3850 y(\045)g(diff)g(testprog.lis)d(testprog.out)191 +3963 y(\045)j(cmp)g(testprog.fit)e(testprog.std)0 4192 +y Fi(On)30 b(VMS)g(systems,)g(\(assuming)h(cc)g(is)f(the)h(name)f(of)h +(the)f(C)g(compiler)h(command\),)g(t)m(yp)s(e:)191 4421 +y Fe($)47 b(cc)h(testprog.c)191 4534 y($)f(link)g(testprog,)e +(cfitsio/lib,)g(alpha$library:vaxcrtl/l)o(ib)191 4647 +y($)i(run)g(testprog)0 4876 y Fi(The)30 b(testprog)h(program)g(should)e +(pro)s(duce)g(a)i(FITS)f(\014le)g(called)i(`testprog.\014t')g(that)f +(is)f(iden)m(tical)j(to)e(the)f(`test-)0 4989 y(prog.std')25 +b(FITS)f(\014le)g(included)g(with)g(this)h(release.)40 +b(The)24 b(diagnostic)i(messages)g(\(whic)m(h)e(w)m(ere)h(pip)s(ed)f +(to)h(the)g(\014le)0 5102 y(testprog.lis)h(in)e(the)h(Unix)f(example\)) +i(should)d(b)s(e)h(iden)m(tical)j(to)e(the)g(listing)g(con)m(tained)h +(in)e(the)h(\014le)f(testprog.out.)0 5215 y(The)30 b('di\013)7 +b(')31 b(and)e('cmp')i(commands)f(sho)m(wn)g(ab)s(o)m(v)m(e)h(should)f +(not)g(rep)s(ort)g(an)m(y)h(di\013erences)g(in)f(the)g(\014les.)41 +b(\(There)0 5328 y(ma)m(y)35 b(b)s(e)e(some)h(minor)g(formatting)g +(di\013erences,)i(suc)m(h)d(as)i(the)f(presence)g(or)g(absence)g(of)g +(leading)h(zeros,)h(or)e(3)0 5441 y(digit)d(exp)s(onen)m(ts)f(in)g(n)m +(um)m(b)s(ers,)g(whic)m(h)g(can)g(b)s(e)g(ignored\).)0 +5601 y(The)f(F)-8 b(ortran)31 b(wrapp)s(ers)d(in)h(CFITSIO)f(ma)m(y)j +(b)s(e)e(tested)h(with)g(the)g(testf77)h(program.)40 +b(On)29 b(Unix)h(systems)g(the)0 5714 y(fortran)g(compilation)i(and)e +(link)g(command)g(ma)m(y)h(b)s(e)f(called)h('f77')h(or)e('g77',)j(dep)s +(ending)c(on)h(the)g(system.)p eop end +%%Page: 7 13 +TeXDict begin 7 12 bop 0 299 a Fg(2.2.)72 b(TESTING)29 +b(THE)h(LIBRAR)-8 b(Y)2555 b Fi(7)143 555 y Fe(\045)48 +b(f77)f(-o)g(testf77)f(testf77.f)f(-L.)i(-lcfitsio)e(-lnsl)h(-lsocket) +48 668 y(or)143 781 y(\045)i(f77)f(-f)g(-o)g(testf77)f(testf77.f)f(-L.) +i(-lcfitsio)188 b(\(under)46 b(SUN)h(O/S\))48 894 y(or)143 +1007 y(\045)h(f77)f(-o)g(testf77)f(testf77.f)f(-Wl,-L.)h(-lcfitsio)f +(-lm)i(-lnsl)f(-lsocket)g(\(HP/UX\))48 1120 y(or)143 +1233 y(\045)i(g77)f(-o)g(testf77)f(-s)h(testf77.f)e(-lcfitsio)g +(-lcc_dynamic)g(-lncurses)g(\(Mac)i(OS-X\))143 1458 y(\045)h(testf77)d +(>)j(testf77.lis)143 1571 y(\045)g(diff)e(testf77.lis)f(testf77.out)143 +1684 y(\045)j(cmp)f(testf77.fit)d(testf77.std)0 1934 +y Fi(On)31 b(mac)m(hines)h(running)f(SUN)g(O/S,)h(F)-8 +b(ortran)33 b(programs)e(m)m(ust)h(b)s(e)f(compiled)h(with)g(the)g('-f) +7 b(')32 b(option)h(to)f(force)0 2047 y(double)24 b(precision)g(v)-5 +b(ariables)25 b(to)g(b)s(e)e(aligned)i(on)f(8-b)m(yte)i(b)s(oundaries)c +(to)j(mak)m(e)g(the)g(fortran-declared)f(v)-5 b(ariables)0 +2160 y(compatible)34 b(with)e(C.)g(A)h(similar)g(compiler)g(option)g +(ma)m(y)g(b)s(e)f(required)g(on)g(other)h(platforms.)48 +b(F)-8 b(ailing)34 b(to)f(use)0 2273 y(this)26 b(option)g(ma)m(y)g +(cause)h(the)f(program)f(to)i(crash)e(on)h(FITSIO)f(routines)g(that)i +(read)f(or)f(write)h(double)g(precision)0 2386 y(v)-5 +b(ariables.)0 2546 y(On)27 b(Windo)m(ws)h(platforms,)g(linking)g(F)-8 +b(ortran)28 b(programs)f(with)h(a)g(C)f(library)g(often)h(dep)s(ends)e +(on)i(the)g(particular)0 2659 y(compilers)40 b(in)m(v)m(olv)m(ed.)71 +b(Some)40 b(users)f(ha)m(v)m(e)i(found)d(the)i(follo)m(wing)i(commands) +d(w)m(ork)h(when)f(using)g(the)h(In)m(tel)0 2772 y(F)-8 +b(ortran)31 b(compiler:)0 3022 y Fe(ifort)46 b(/libs.dll)g(cfitsio.lib) +e(/MD)j(testf77.f)f(/Gm)0 3247 y(or)h(possibly,)0 3473 +y(ifort)f(/libs:dll)g(cfitsio.lib)e(/MD)j(/fpp)g +(/extfpp:cfortran.h,fitsi)o(o.h)191 3586 y(/iface:cvf)e(testf77.f)0 +3836 y Fi(Also)32 b(note)h(that)f(on)g(some)g(systems)f(the)h(output)g +(listing)g(of)g(the)g(testf77)h(program)f(ma)m(y)g(di\013er)f(sligh)m +(tly)i(from)0 3949 y(the)f(testf77.std)i(template)g(if)e(leading)h +(zeros)g(are)g(not)g(prin)m(ted)e(b)m(y)i(default)f(b)s(efore)g(the)g +(decimal)i(p)s(oin)m(t)e(when)0 4062 y(using)e(F)h(format.)0 +4222 y(A)f(few)h(other)f(utilit)m(y)i(programs)e(are)h(included)e(with) +h(CFITSIO:)191 4472 y Fe(speed)46 b(-)i(measures)d(the)i(maximum)f +(throughput)f(\(in)i(MB)g(per)g(second\))668 4585 y(for)g(writing)f +(and)h(reading)f(FITS)g(files)h(with)f(CFITSIO)191 4811 +y(listhead)f(-)j(lists)e(all)h(the)g(header)f(keywords)g(in)h(any)g +(FITS)f(file)191 5036 y(fitscopy)f(-)j(copies)e(any)h(FITS)g(file)f +(\(especially)f(useful)h(in)h(conjunction)811 5149 y(with)g(the)g +(CFITSIO's)e(extended)h(input)g(filename)g(syntax\))191 +5375 y(cookbook)f(-)j(a)f(sample)f(program)g(that)h(performs)e(common)i +(read)f(and)811 5488 y(write)h(operations)e(on)i(a)g(FITS)g(file.)191 +5714 y(iter_a,)f(iter_b,)g(iter_c)g(-)h(examples)f(of)h(the)g(CFITSIO)f +(iterator)f(routine)p eop end +%%Page: 8 14 +TeXDict begin 8 13 bop 0 299 a Fi(8)1896 b Fg(CHAPTER)30 +b(2.)111 b(CREA)-8 b(TING)31 b(FITSIO/CFITSIO)0 555 y +Fi(The)f(\014rst)f(4)i(of)g(these)g(utilit)m(y)g(programs)f(can)h(b)s +(e)f(compiled)h(and)e(link)m(ed)i(b)m(y)f(t)m(yping)143 +816 y Fe(\045)95 b(make)47 b(program_name)0 1151 y Fd(2.3)135 +b(Linking)45 b(Programs)h(with)f(FITSIO)0 1402 y Fi(When)31 +b(linking)g(applications)i(soft)m(w)m(are)f(with)f(the)g(FITSIO)f +(library)-8 b(,)32 b(sev)m(eral)h(system)e(libraries)g(usually)g(need)0 +1515 y(to)26 b(b)s(e)f(sp)s(eci\014ed)g(on)g(the)h(link)g(comman)f +(Unix)h(systems,)h(the)e(most)h(reliable)h(w)m(a)m(y)f(to)h(determine)e +(what)h(libraries)0 1627 y(are)32 b(required)f(is)g(to)i(t)m(yp)s(e)e +('mak)m(e)i(testprog')g(and)e(see)h(what)f(libraries)h(the)g +(con\014gure)f(script)h(has)f(added.)43 b(The)0 1740 +y(t)m(ypical)25 b(libraries)f(that)g(ma)m(y)g(need)f(to)h(b)s(e)f +(added)g(are)g(-lm)h(\(the)g(math)f(library\))h(and)f(-lnsl)g(and)g +(-lso)s(c)m(k)m(et)j(\(needed)0 1853 y(only)h(for)f(FTP)g(and)g(HTTP)g +(\014le)h(access\).)41 b(These)26 b(latter)i(2)f(libraries)g(are)g(not) +g(needed)f(on)g(VMS)h(and)f(Windo)m(ws)0 1966 y(platforms,)31 +b(b)s(ecause)f(FTP)g(\014le)h(access)g(is)g(not)f(curren)m(tly)h(supp)s +(orted)d(on)i(those)h(platforms.)0 2126 y(Note)i(that)g(when)e +(upgrading)g(to)i(a)f(new)m(er)g(v)m(ersion)g(of)g(CFITSIO)f(it)h(is)g +(usually)g(necessary)g(to)h(recompile,)h(as)0 2239 y(w)m(ell)d(as)g +(relink,)g(the)f(programs)g(that)h(use)f(CFITSIO,)f(b)s(ecause)i(the)f +(de\014nitions)g(in)g(\014tsio.h)h(often)f(c)m(hange.)0 +2574 y Fd(2.4)135 b(Getting)46 b(Started)g(with)f(FITSIO)0 +2825 y Fi(In)32 b(order)h(to)h(e\013ectiv)m(ely)i(use)d(the)g(FITSIO)f +(library)h(as)h(quic)m(kly)g(as)f(p)s(ossible,)h(it)g(is)f(recommended) +g(that)g(new)0 2938 y(users)d(follo)m(w)h(these)g(steps:)0 +3098 y(1.)62 b(Read)38 b(the)f(follo)m(wing)i(`FITS)e(Primer')g(c)m +(hapter)h(for)g(a)f(brief)g(o)m(v)m(erview)i(of)f(the)g(structure)e(of) +i(FITS)f(\014les.)0 3211 y(This)25 b(is)h(esp)s(ecially)h(imp)s(ortan)m +(t)g(for)e(users)h(who)f(ha)m(v)m(e)i(not)g(previously)e(dealt)i(with)f +(the)g(FITS)f(table)i(and)f(image)0 3324 y(extensions.)0 +3484 y(2.)41 b(W)-8 b(rite)32 b(a)f(simple)f(program)g(to)h(read)g(or)f +(write)g(a)h(FITS)f(\014le)g(using)g(the)h(Basic)g(In)m(terface)h +(routines.)0 3644 y(3.)41 b(Refer)28 b(to)i(the)f(co)s(okb)s(o)s(ok.f)g +(program)f(that)i(is)f(included)f(with)g(this)h(release)h(for)e +(examples)i(of)f(routines)f(that)0 3757 y(p)s(erform)h(v)-5 +b(arious)30 b(common)h(FITS)f(\014le)g(op)s(erations.)0 +3917 y(4.)52 b(Read)34 b(Chapters)g(4)g(and)f(5)i(to)g(b)s(ecome)f +(familiar)h(with)e(the)i(con)m(v)m(en)m(tions)h(and)d(adv)-5 +b(anced)34 b(features)h(of)f(the)0 4030 y(FITSIO)29 b(in)m(terface.)0 +4190 y(5.)47 b(Scan)32 b(through)f(the)h(more)h(extensiv)m(e)g(set)g +(of)g(routines)f(that)g(are)h(pro)m(vided)f(in)g(the)g(`Adv)-5 +b(anced)32 b(In)m(terface'.)0 4303 y(These)22 b(routines)f(p)s(erform)g +(more)h(sp)s(ecialized)h(functions)e(than)g(are)i(pro)m(vided)e(b)m(y)h +(the)g(Basic)h(In)m(terface)g(routines.)0 4638 y Fd(2.5)135 +b(Example)46 b(Program)0 4888 y Fi(The)32 b(follo)m(wing)i(listing)f +(sho)m(ws)f(an)g(example)i(of)e(ho)m(w)h(to)g(use)f(the)g(FITSIO)g +(routines)g(in)g(a)h(F)-8 b(ortran)33 b(program.)0 5001 +y(Refer)38 b(to)h(the)g(co)s(okb)s(o)s(ok.f)f(program)g(that)h(is)f +(included)f(with)h(the)h(FITSIO)e(distribution)g(for)h(examples)h(of)0 +5114 y(other)31 b(FITS)e(programs.)286 5375 y Fe(program)46 +b(writeimage)0 5601 y(C)238 b(Create)46 b(a)i(FITS)f(primary)e(array)i +(containing)e(a)i(2-D)g(image)p eop end +%%Page: 9 15 +TeXDict begin 9 14 bop 0 299 a Fg(2.5.)72 b(EXAMPLE)31 +b(PR)m(OGRAM)2664 b Fi(9)286 555 y Fe(integer)46 b +(status,unit,blocksize,bit)o(pix,)o(nax)o(is,n)o(axes)o(\(2\))286 +668 y(integer)g(i,j,group,fpixel,nelement)o(s,ar)o(ray)o(\(300)o(,200)o +(\))286 781 y(character)g(filename*80)286 894 y(logical)g +(simple,extend)286 1120 y(status=0)0 1233 y(C)238 b(Name)47 +b(of)g(the)g(FITS)g(file)f(to)i(be)f(created:)286 1346 +y(filename='ATESTFILE.FITS')0 1571 y(C)238 b(Get)47 b(an)g(unused)g +(Logical)e(Unit)i(Number)f(to)h(use)g(to)g(create)f(the)h(FITS)g(file) +286 1684 y(call)g(ftgiou\(unit,status\))0 1910 y(C)238 +b(create)46 b(the)h(new)g(empty)g(FITS)f(file)286 2023 +y(blocksize=1)286 2136 y(call)h(ftinit\(unit,filename,blo)o(cksi)o +(ze,s)o(tat)o(us\))0 2362 y(C)238 b(initialize)45 b(parameters)g(about) +i(the)g(FITS)f(image)h(\(300)f(x)i(200)f(16-bit)f(integers\))286 +2475 y(simple=.true.)286 2588 y(bitpix=16)286 2700 y(naxis=2)286 +2813 y(naxes\(1\)=300)286 2926 y(naxes\(2\)=200)286 3039 +y(extend=.true.)0 3265 y(C)238 b(write)47 b(the)g(required)e(header)h +(keywords)286 3378 y(call)h(ftphpr\(unit,simple,bitpi)o(x,na)o(xis,)o +(nax)o(es,0)o(,1,e)o(xte)o(nd,s)o(tatu)o(s\))0 3604 y(C)238 +b(initialize)45 b(the)i(values)f(in)i(the)e(image)h(with)f(a)i(linear)e +(ramp)h(function)286 3717 y(do)h(j=1,naxes\(2\))477 3830 +y(do)f(i=1,naxes\(1\))668 3942 y(array\(i,j\)=i+j)477 +4055 y(end)g(do)286 4168 y(end)g(do)0 4394 y(C)238 b(write)47 +b(the)g(array)f(to)h(the)g(FITS)g(file)286 4507 y(group=1)286 +4620 y(fpixel=1)286 4733 y(nelements=naxes\(1\)*naxes\(2)o(\))286 +4846 y(call)g(ftpprj\(unit,group,fpixel)o(,nel)o(emen)o(ts,)o(arra)o +(y,st)o(atu)o(s\))0 5072 y(C)238 b(write)47 b(another)f(optional)f +(keyword)h(to)h(the)g(header)286 5185 y(call)g +(ftpkyj\(unit,'EXPOSURE',1)o(500,)o('Tot)o(al)41 b(Exposure)46 +b(Time',status\))0 5410 y(C)238 b(close)47 b(the)g(file)f(and)h(free)g +(the)g(unit)f(number)286 5523 y(call)h(ftclos\(unit,)d(status\))286 +5636 y(call)j(ftfiou\(unit,)d(status\))p eop end +%%Page: 10 16 +TeXDict begin 10 15 bop 0 299 a Fi(10)1851 b Fg(CHAPTER)30 +b(2.)111 b(CREA)-8 b(TING)31 b(FITSIO/CFITSIO)286 555 +y Fe(end)0 948 y Fd(2.6)135 b(Legal)46 b(Stu\013)0 1210 +y Fi(Cop)m(yrigh)m(t)37 b(\(Unpublished{all)g(righ)m(ts)g(reserv)m(ed)g +(under)e(the)i(cop)m(yrigh)m(t)h(la)m(ws)f(of)g(the)g(United)g +(States\),)j(U.S.)0 1323 y(Go)m(v)m(ernmen)m(t)30 b(as)g(represen)m +(ted)e(b)m(y)h(the)g(Administrator)g(of)g(the)g(National)h(Aeronautics) +g(and)e(Space)h(Adminis-)0 1436 y(tration.)42 b(No)31 +b(cop)m(yrigh)m(t)g(is)g(claimed)g(in)f(the)h(United)f(States)h(under)e +(Title)j(17,)f(U.S.)f(Co)s(de.)0 1596 y(P)m(ermission)g(to)g(freely)f +(use,)h(cop)m(y)-8 b(,)31 b(mo)s(dify)-8 b(,)29 b(and)g(distribute)g +(this)g(soft)m(w)m(are)i(and)e(its)h(do)s(cumen)m(tation)g(without)0 +1709 y(fee)f(is)f(hereb)m(y)g(gran)m(ted,)i(pro)m(vided)e(that)h(this)f +(cop)m(yrigh)m(t)i(notice)f(and)f(disclaimer)h(of)f(w)m(arran)m(t)m(y)i +(app)s(ears)d(in)h(all)0 1822 y(copies.)0 1982 y(DISCLAIMER:)0 +2142 y(THE)33 b(SOFTW)-10 b(ARE)32 b(IS)g(PR)m(O)m(VIDED)i('AS)f(IS')g +(WITHOUT)f(ANY)i(W)-10 b(ARRANTY)33 b(OF)g(ANY)h(KIND,)f(EI-)0 +2255 y(THER)42 b(EXPRESSED,)f(IMPLIED,)i(OR)e(ST)-8 b(A)g(TUTOR)g(Y,)43 +b(INCLUDING,)f(BUT)h(NOT)e(LIMITED)h(TO,)0 2368 y(ANY)33 +b(W)-10 b(ARRANTY)33 b(THA)-8 b(T)32 b(THE)g(SOFTW)-10 +b(ARE)32 b(WILL)g(CONF)m(ORM)g(TO)g(SPECIFICA)-8 b(TIONS,)30 +b(ANY)0 2481 y(IMPLIED)38 b(W)-10 b(ARRANTIES)37 b(OF)h(MER)m(CHANT)-8 +b(ABILITY,)38 b(FITNESS)f(F)m(OR)h(A)g(P)-8 b(AR)g(TICULAR)38 +b(PUR-)0 2594 y(POSE,)24 b(AND)i(FREEDOM)f(FR)m(OM)h(INFRINGEMENT,)g +(AND)f(ANY)h(W)-10 b(ARRANTY)25 b(THA)-8 b(T)25 b(THE)g(DOC-)0 +2707 y(UMENT)-8 b(A)g(TION)31 b(WILL)f(CONF)m(ORM)h(TO)e(THE)h(SOFTW) +-10 b(ARE,)30 b(OR)g(ANY)h(W)-10 b(ARRANTY)31 b(THA)-8 +b(T)30 b(THE)0 2819 y(SOFTW)-10 b(ARE)31 b(WILL)h(BE)g(ERR)m(OR)g +(FREE.)g(IN)g(NO)g(EVENT)f(SHALL)g(NASA)h(BE)g(LIABLE)g(F)m(OR)g(ANY)0 +2932 y(D)m(AMA)m(GES,)26 b(INCLUDING,)e(BUT)f(NOT)g(LIMITED)h(TO,)f +(DIRECT,)g(INDIRECT,)g(SPECIAL)f(OR)h(CON-)0 3045 y(SEQUENTIAL)28 +b(D)m(AMA)m(GES,)k(ARISING)d(OUT)g(OF,)h(RESUL)-8 b(TING)29 +b(FR)m(OM,)h(OR)f(IN)h(ANY)g(W)-10 b(A)i(Y)30 b(CON-)0 +3158 y(NECTED)25 b(WITH)g(THIS)f(SOFTW)-10 b(ARE,)25 +b(WHETHER)g(OR)g(NOT)g(BASED)g(UPON)g(W)-10 b(ARRANTY,)26 +b(CON-)0 3271 y(TRA)m(CT,)d(TOR)-8 b(T)23 b(,)g(OR)g(OTHER)-10 +b(WISE,)22 b(WHETHER)i(OR)f(NOT)f(INJUR)-8 b(Y)24 b(W)-10 +b(AS)23 b(SUST)-8 b(AINED)23 b(BY)h(PER-)0 3384 y(SONS)h(OR)i(PR)m +(OPER)-8 b(TY)26 b(OR)g(OTHER)-10 b(WISE,)26 b(AND)h(WHETHER)g(OR)f +(NOT)g(LOSS)f(W)-10 b(AS)26 b(SUST)-8 b(AINED)0 3497 +y(FR)m(OM,)37 b(OR)e(AR)m(OSE)h(OUT)f(OF)h(THE)g(RESUL)-8 +b(TS)35 b(OF,)h(OR)f(USE)h(OF,)g(THE)g(SOFTW)-10 b(ARE)35 +b(OR)g(SER-)0 3610 y(VICES)29 b(PR)m(O)m(VIDED)j(HEREUNDER.")0 +4002 y Fd(2.7)135 b(Ac)l(kno)l(wledgmen)l(ts)0 4264 y +Fi(The)29 b(dev)m(elopmen)m(t)h(of)g(man)m(y)f(of)h(the)f(p)s(o)m(w)m +(erful)g(features)g(in)g(CFITSIO)f(w)m(as)i(made)f(p)s(ossible)g +(through)f(collab-)0 4377 y(orations)35 b(with)f(man)m(y)h(p)s(eople)f +(or)h(organizations)h(from)e(around)f(the)i(w)m(orld.)52 +b(The)34 b(follo)m(wing,)j(in)d(particular,)0 4490 y(ha)m(v)m(e)e(made) +e(esp)s(ecially)i(signi\014can)m(t)f(con)m(tributions:)0 +4650 y(Programmers)25 b(from)h(the)f(In)m(tegral)i(Science)g(Data)g +(Cen)m(ter,)g(Switzerland)f(\(namely)-8 b(,)28 b(Jurek)c(Bork)m(o)m +(wski,)29 b(Bruce)0 4763 y(O'Neel,)34 b(and)e(Don)h(Jennings\),)f +(designed)g(the)h(concept)g(for)f(the)h(plug-in)f(I/O)g(driv)m(ers)g +(that)h(w)m(as)g(in)m(tro)s(duced)0 4876 y(with)i(CFITSIO)e(2.0.)56 +b(The)34 b(use)h(of)g(`driv)m(ers')g(greatly)h(simpli\014ed)f(the)g(lo) +m(w-lev)m(el)j(I/O,)d(whic)m(h)f(in)h(turn)f(made)0 4989 +y(other)40 b(new)f(features)i(in)e(CFITSIO)f(\(e.g.,)45 +b(supp)s(ort)38 b(for)h(compressed)h(FITS)f(\014les)h(and)f(supp)s(ort) +f(for)i(IRAF)0 5102 y(format)32 b(image)g(\014les\))g(m)m(uc)m(h)f +(easier)i(to)f(implemen)m(t.)44 b(Jurek)31 b(Bork)m(o)m(wski)h(wrote)g +(the)g(Shared)e(Memory)i(driv)m(er,)0 5215 y(and)23 b(Bruce)i(O'Neel)g +(wrote)f(the)g(driv)m(ers)g(for)f(accessing)j(FITS)d(\014les)h(o)m(v)m +(er)h(the)f(net)m(w)m(ork)h(using)e(the)i(FTP)-8 b(,)24 +b(HTTP)-8 b(,)0 5328 y(and)30 b(R)m(OOT)g(proto)s(cols.)0 +5488 y(The)45 b(ISDC)g(also)h(pro)m(vided)f(the)h(template)h(parsing)e +(routines)g(\(written)h(b)m(y)f(Jurek)g(Bork)m(o)m(wski\))i(and)e(the)0 +5601 y(hierarc)m(hical)39 b(grouping)d(routines)h(\(written)h(b)m(y)f +(Don)h(Jennings\).)60 b(The)37 b(ISDC)f(D)m(AL)i(\(Data)h(Access)f(La)m +(y)m(er\))0 5714 y(routines)30 b(are)h(la)m(y)m(ered)h(on)e(top)h(of)f +(CFITSIO)f(and)h(mak)m(e)h(extensiv)m(e)h(use)e(of)h(these)g(features.) +p eop end +%%Page: 11 17 +TeXDict begin 11 16 bop 0 299 a Fg(2.7.)72 b(A)m(CKNO)m(WLEDGMENTS)2577 +b Fi(11)0 555 y(Uw)m(e)25 b(Lammers)e(\(XMM/ESA/ESTEC,)h(The)g +(Netherlands\))g(designed)g(the)g(high-p)s(erformance)f(lexical)j +(pars-)0 668 y(ing)42 b(algorithm)h(that)f(is)g(used)f(to)i(do)e +(on-the-\015y)h(\014ltering)g(of)g(FITS)f(tables.)76 +b(This)41 b(algorithm)i(essen)m(tially)0 781 y(pre-compiles)36 +b(the)g(user-supplied)e(selection)k(expression)d(in)m(to)i(a)f(form)g +(that)g(can)g(b)s(e)f(rapidly)g(ev)-5 b(aluated)37 b(for)0 +894 y(eac)m(h)31 b(ro)m(w.)40 b(P)m(eter)31 b(Wilson)f(\(RSTX,)f +(NASA/GSF)m(C\))i(then)e(wrote)h(the)g(parsing)f(routines)g(used)g(b)m +(y)g(CFITSIO)0 1007 y(based)i(on)f(Lammers')h(design,)g(com)m(bined)g +(with)g(other)g(tec)m(hniques)g(suc)m(h)g(as)g(the)g(CFITSIO)f +(iterator)i(routine)0 1120 y(to)g(further)e(enhance)h(the)h(data)g(pro) +s(cessing)f(throughput.)42 b(This)31 b(e\013ort)h(also)g(b)s +(ene\014ted)e(from)h(a)h(m)m(uc)m(h)f(earlier)0 1233 +y(lexical)25 b(parsing)f(routine)f(that)h(w)m(as)g(dev)m(elop)s(ed)g(b) +m(y)g(Ken)m(t)g(Blac)m(kburn)f(\(NASA/GSF)m(C\).)i(More)g(recen)m(tly) +-8 b(,)27 b(Craig)0 1346 y(Markw)m(ardt)i(\(NASA/GSF)m(C\))g(implemen)m +(ted)g(additional)g(functions)f(\(median,)h(a)m(v)m(erage,)j(stddev\))c +(and)g(other)0 1458 y(enhancemen)m(ts)j(to)g(the)g(lexical)h(parser.)0 +1619 y(The)40 b(CFITSIO)g(iterator)i(function)e(is)h(lo)s(osely)h +(based)f(on)f(similar)i(ideas)f(dev)m(elop)s(ed)g(for)g(the)g(XMM)g +(Data)0 1732 y(Access)31 b(La)m(y)m(er.)0 1892 y(P)m(eter)25 +b(Wilson)g(\(RSTX,)f(NASA/GSF)m(C\))h(wrote)g(the)f(complete)i(set)e +(of)h(F)-8 b(ortran-callable)27 b(wrapp)s(ers)22 b(for)i(all)h(the)0 +2005 y(CFITSIO)k(routines,)h(whic)m(h)g(in)g(turn)g(rely)g(on)h(the)f +(CF)m(OR)-8 b(TRAN)31 b(macro)g(dev)m(elop)s(ed)g(b)m(y)f(Burkhard)f +(Buro)m(w.)0 2165 y(The)h(syn)m(tax)i(used)e(b)m(y)h(CFITSIO)f(for)g +(\014ltering)i(or)f(binning)e(input)h(FITS)h(\014les)g(is)g(based)f(on) +h(ideas)h(dev)m(elop)s(ed)0 2278 y(for)41 b(the)g(AXAF)h(Science)g(Cen) +m(ter)g(Data)h(Mo)s(del)e(b)m(y)g(Jonathan)g(McDo)m(w)m(ell,)47 +b(An)m(tonella)c(F)-8 b(ruscione,)45 b(Aneta)0 2391 y(Siemigino)m(wsk) +-5 b(a)27 b(and)e(Bill)i(Jo)m(y)m(e.)41 b(See)26 b(h)m +(ttp://heasarc.gsfc.nasa.go)m(v/do)s(cs/journal/axa)q(f7.h)m(t)q(ml)32 +b(for)25 b(further)0 2503 y(description)30 b(of)h(the)g(AXAF)g(Data)h +(Mo)s(del.)0 2664 y(The)j(\014le)g(decompression)g(co)s(de)g(w)m(ere)h +(tak)m(en)g(directly)g(from)e(the)i(gzip)f(\(GNU)h(zip\))g(program)f +(dev)m(elop)s(ed)g(b)m(y)0 2777 y(Jean-loup)30 b(Gailly)i(and)e +(others.)0 2937 y(Doug)h(Mink,)g(SA)m(O,)f(pro)m(vided)g(the)h +(routines)f(for)g(con)m(v)m(erting)i(IRAF)f(format)g(images)g(in)m(to)g +(FITS)f(format.)0 3097 y(Martin)j(Reinec)m(k)m(e)i(\(Max)f(Planc)m(k)f +(Institute,)h(Garc)m(hing\)\))g(pro)m(vided)f(the)g(mo)s(di\014cations) +f(to)i(cfortran.h)e(that)0 3210 y(are)d(necessary)h(to)f(supp)s(ort)e +(64-bit)k(in)m(teger)f(v)-5 b(alues)29 b(when)f(calling)i(C)f(routines) +g(from)f(fortran)h(programs.)39 b(The)0 3323 y(cfortran.h)30 +b(macros)h(w)m(ere)g(originally)h(dev)m(elop)s(ed)e(b)m(y)h(Burkhard)e +(Buro)m(w)h(\(CERN\).)0 3483 y(Julian)f(T)-8 b(a)m(ylor)31 +b(\(ESO,)e(Garc)m(hing\))i(pro)m(vided)e(the)g(fast)h(b)m(yte-sw)m +(apping)g(algorithms)h(that)f(use)f(the)h(SSE2)f(and)0 +3596 y(SSSE3)g(mac)m(hine)i(instructions)f(a)m(v)-5 b(ailable)33 +b(on)d(x86)p 1784 3596 28 4 v 34 w(64)h(CPUs.)0 3756 +y(In)c(addition,)i(man)m(y)f(other)g(p)s(eople)g(ha)m(v)m(e)h(made)f(v) +-5 b(aluable)29 b(con)m(tributions)f(to)h(the)f(dev)m(elopmen)m(t)h(of) +f(CFITSIO.)0 3869 y(These)i(include)g(\(with)h(ap)s(ologies)h(to)f +(others)f(that)h(ma)m(y)g(ha)m(v)m(e)h(inadv)m(erten)m(tly)g(b)s(een)d +(omitted\):)0 4029 y(Stev)m(e)g(Allen,)g(Carl)f(Ak)m(erlof,)h(Keith)f +(Arnaud,)g(Morten)g(Krabb)s(e)e(Barfo)s(ed,)j(Ken)m(t)f(Blac)m(kburn,)h +(G)f(Bo)s(dammer,)0 4142 y(Romk)m(e)h(Bon)m(tek)m(o)s(e,)i(Lucio)d +(Chiapp)s(etti,)g(Keith)g(Costorf,)g(Robin)g(Corb)s(et,)g(John)e(Da)m +(vis,)k(Ric)m(hard)e(Fink,)h(Ning)0 4255 y(Gan,)g(Emily)e(Greene,)i(Jo) +s(e)f(Harrington,)h(Cheng)e(Ho,)i(Phil)e(Ho)s(dge,)i(Jim)f(Ingham,)g(Y) +-8 b(oshitak)j(a)29 b(Ishisaki,)f(Diab)0 4368 y(Jerius,)j(Mark)h +(Levine,)g(T)-8 b(o)s(dd)30 b(Karak)-5 b(askian,)32 b(Edw)m(ard)f +(King,)g(Scott)i(Ko)s(c)m(h,)e(Claire)h(Larkin,)f(Rob)h(Managan,)0 +4481 y(Eric)38 b(Mandel,)i(John)d(Matto)m(x,)43 b(Carsten)37 +b(Mey)m(er,)42 b(Emi)37 b(Miy)m(ata,)43 b(Stefan)38 b(Mo)s(c)m(hnac)m +(ki,)j(Mik)m(e)f(Noble,)g(Oliv)m(er)0 4594 y(Ob)s(erdorf,)c(Cliv)m(e)i +(P)m(age,)i(Arvind)35 b(P)m(armar,)j(Je\013)f(P)m(edelt)m(y)-8 +b(,)40 b(Tim)c(P)m(earson,)j(Maren)e(Purv)m(es,)h(Scott)f(Randall,)0 +4706 y(Chris)d(Rogers,)j(Arnold)e(Rots,)i(Barry)f(Sc)m(hlesinger,)h +(Robin)e(Stebbins,)g(Andrew)f(Szymk)m(o)m(wiak,)k(Allyn)e(T)-8 +b(en-)0 4819 y(nan)m(t,)31 b(P)m(eter)g(T)-8 b(eub)s(en,)30 +b(James)g(Theiler,)h(Doug)g(T)-8 b(o)s(dy)g(,)31 b(Shiro)e(Ueno,)j +(Stev)m(e)f(W)-8 b(alton,)33 b(Arc)m(hie)e(W)-8 b(arno)s(c)m(k,)32 +b(Alan)0 4932 y(W)-8 b(atson,)32 b(Dan)f(Whipple,)f(Wim)h(Wimmers,)g(P) +m(eter)g(Y)-8 b(oung,)31 b(Jianjun)e(Xu,)h(and)g(Nelson)h(Zarate.)p +eop end +%%Page: 12 18 +TeXDict begin 12 17 bop 0 299 a Fi(12)1851 b Fg(CHAPTER)30 +b(2.)111 b(CREA)-8 b(TING)31 b(FITSIO/CFITSIO)p eop end +%%Page: 13 19 +TeXDict begin 13 18 bop 0 1225 a Ff(Chapter)65 b(3)0 +1687 y Fl(A)78 b(FITS)f(Primer)0 2180 y Fi(This)23 b(section)j(giv)m +(es)f(a)g(brief)e(o)m(v)m(erview)j(of)e(the)h(structure)e(of)i(FITS)e +(\014les.)38 b(Users)24 b(should)g(refer)f(to)i(the)g(do)s(cumen-)0 +2293 y(tation)j(a)m(v)-5 b(ailable)30 b(from)d(the)g(NOST,)f(as)i +(describ)s(ed)e(in)h(the)g(in)m(tro)s(duction,)h(for)f(more)g(detailed) +i(information)e(on)0 2406 y(FITS)j(formats.)0 2566 y(FITS)37 +b(w)m(as)g(\014rst)g(dev)m(elop)s(ed)h(in)f(the)g(late)i(1970's)g(as)f +(a)f(standard)g(data)h(in)m(terc)m(hange)h(format)f(b)s(et)m(w)m(een)g +(v)-5 b(ar-)0 2679 y(ious)38 b(astronomical)h(observ)-5 +b(atories.)64 b(Since)37 b(then)g(FITS)g(has)h(b)s(ecome)g(the)g +(defacto)g(standard)f(data)i(format)0 2791 y(supp)s(orted)29 +b(b)m(y)h(most)h(astronomical)h(data)f(analysis)g(soft)m(w)m(are)g(pac) +m(k)-5 b(ages.)0 2952 y(A)34 b(FITS)f(\014le)g(consists)h(of)g(one)g +(or)g(more)g(Header)g(+)f(Data)i(Units)f(\(HDUs\),)i(where)d(the)h +(\014rst)f(HDU)h(is)g(called)0 3065 y(the)j(`Primary)f(HDU',)i(or)f +(`Primary)f(Arra)m(y'.)60 b(The)36 b(primary)g(arra)m(y)h(con)m(tains)h +(an)e(N-dimensional)i(arra)m(y)f(of)0 3177 y(pixels,)32 +b(suc)m(h)g(as)f(a)h(1-D)h(sp)s(ectrum,)e(a)h(2-D)h(image,)g(or)f(a)g +(3-D)g(data)h(cub)s(e.)43 b(Six)31 b(di\013eren)m(t)h(primary)f(datat)m +(yp)s(es)0 3290 y(are)g(supp)s(orted:)39 b(Unsigned)30 +b(8-bit)h(b)m(ytes,)g(16,)g(32,)h(and)e(64-bit)h(signed)g(in)m(tegers,) +h(and)d(32)j(and)d(64-bit)j(\015oating)0 3403 y(p)s(oin)m(t)d(reals.)41 +b(FITS)29 b(also)i(has)e(a)h(con)m(v)m(en)m(tion)h(for)e(storing)h +(unsigned)f(in)m(tegers)h(\(see)h(the)e(later)i(section)f(en)m(titled)0 +3516 y(`Unsigned)h(In)m(tegers')h(for)f(more)g(details\).)43 +b(The)31 b(primary)f(HDU)i(ma)m(y)f(also)h(consist)g(of)f(only)g(a)g +(header)g(with)g(a)0 3629 y(n)m(ull)f(arra)m(y)h(con)m(taining)h(no)e +(data)h(pixels.)0 3789 y(An)m(y)i(n)m(um)m(b)s(er)e(of)h(additional)i +(HDUs)f(ma)m(y)g(follo)m(w)h(the)e(primary)g(arra)m(y;)i(these)f +(additional)h(HDUs)f(are)g(called)0 3902 y(FITS)d(`extensions'.)41 +b(There)30 b(are)h(curren)m(tly)f(3)h(t)m(yp)s(es)g(of)f(extensions)h +(de\014ned)e(b)m(y)h(the)h(FITS)f(standard:)136 4171 +y Fc(\017)46 b Fi(Image)31 b(Extension)g(-)g(a)f(N-dimensional)h(arra)m +(y)g(of)g(pixels,)g(lik)m(e)g(in)f(a)h(primary)e(arra)m(y)136 +4368 y Fc(\017)46 b Fi(ASCI)s(I)29 b(T)-8 b(able)31 b(Extension)g(-)f +(ro)m(ws)h(and)e(columns)h(of)h(data)g(in)f(ASCI)s(I)f(c)m(haracter)j +(format)136 4564 y Fc(\017)46 b Fi(Binary)31 b(T)-8 b(able)31 +b(Extension)f(-)h(ro)m(ws)f(and)g(columns)g(of)h(data)g(in)f(binary)f +(represen)m(tation)0 4833 y(In)k(eac)m(h)i(case)g(the)f(HDU)h(consists) +g(of)f(an)g(ASCI)s(I)e(Header)i(Unit)h(follo)m(w)m(ed)g(b)m(y)f(an)g +(optional)h(Data)g(Unit.)52 b(F)-8 b(or)0 4946 y(historical)37 +b(reasons,)g(eac)m(h)f(Header)g(or)g(Data)h(unit)e(m)m(ust)g(b)s(e)g +(an)g(exact)i(m)m(ultiple)f(of)g(2880)h(8-bit)f(b)m(ytes)g(long.)0 +5059 y(An)m(y)30 b(un)m(used)g(space)g(is)h(padded)e(with)h(\014ll)g(c) +m(haracters)i(\(ASCI)s(I)d(blanks)h(or)h(zeros\).)0 5219 +y(Eac)m(h)i(Header)f(Unit)h(consists)g(of)f(an)m(y)g(n)m(um)m(b)s(er)f +(of)i(80-c)m(haracter)i(k)m(eyw)m(ord)d(records)g(or)g(`card)h(images') +g(whic)m(h)0 5332 y(ha)m(v)m(e)f(the)e(general)i(form:)95 +5601 y Fe(KEYNAME)46 b(=)i(value)e(/)i(comment)d(string)95 +5714 y(NULLKEY)h(=)334 b(/)48 b(comment:)d(This)i(keyword)f(has)g(no)i +(value)1905 5942 y Fi(13)p eop end +%%Page: 14 20 +TeXDict begin 14 19 bop 0 299 a Fi(14)2398 b Fg(CHAPTER)30 +b(3.)112 b(A)30 b(FITS)g(PRIMER)0 555 y Fi(The)35 b(k)m(eyw)m(ord)i +(names)f(ma)m(y)g(b)s(e)g(up)f(to)h(8)h(c)m(haracters)g(long)g(and)e +(can)h(only)h(con)m(tain)g(upp)s(ercase)e(letters,)k(the)0 +668 y(digits)25 b(0-9,)i(the)e(h)m(yphen,)g(and)f(the)h(underscore)e(c) +m(haracter.)41 b(The)24 b(k)m(eyw)m(ord)h(name)g(is)f(\(usually\))h +(follo)m(w)m(ed)i(b)m(y)d(an)0 781 y(equals)29 b(sign)g(and)f(a)g +(space)i(c)m(haracter)g(\(=)e(\))h(in)f(columns)h(9)g(-)f(10)i(of)f +(the)f(record,)h(follo)m(w)m(ed)i(b)m(y)d(the)h(v)-5 +b(alue)29 b(of)g(the)0 894 y(k)m(eyw)m(ord)34 b(whic)m(h)g(ma)m(y)g(b)s +(e)f(either)h(an)g(in)m(teger,)i(a)e(\015oating)g(p)s(oin)m(t)g(n)m(um) +m(b)s(er,)g(a)g(c)m(haracter)h(string)e(\(enclosed)i(in)0 +1007 y(single)28 b(quotes\),)i(or)e(a)g(b)s(o)s(olean)g(v)-5 +b(alue)28 b(\(the)g(letter)h(T)f(or)f(F\).)i(A)f(k)m(eyw)m(ord)g(ma)m +(y)h(also)f(ha)m(v)m(e)h(a)g(n)m(ull)e(or)h(unde\014ned)0 +1120 y(v)-5 b(alue)31 b(if)f(there)h(is)f(no)g(sp)s(eci\014ed)g(v)-5 +b(alue)31 b(string,)g(as)f(in)g(the)h(second)f(example.)0 +1280 y(The)42 b(last)h(k)m(eyw)m(ord)g(in)f(the)h(header)f(is)g(alw)m +(a)m(ys)i(the)f(`END')g(k)m(eyw)m(ord)g(whic)m(h)f(has)g(no)h(v)-5 +b(alue)42 b(or)h(commen)m(t)0 1393 y(\014elds.)d(There)30 +b(are)h(man)m(y)f(rules)g(go)m(v)m(erning)i(the)e(exact)i(format)f(of)f +(a)h(k)m(eyw)m(ord)f(record)h(\(see)g(the)f(NOST)g(FITS)0 +1506 y(Standard\))h(so)h(it)g(is)f(b)s(etter)h(to)g(rely)g(on)f +(standard)g(in)m(terface)i(soft)m(w)m(are)g(lik)m(e)g(FITSIO)d(to)j +(correctly)g(construct)0 1619 y(or)d(to)h(parse)g(the)f(k)m(eyw)m(ord)h +(records)f(rather)g(than)h(try)f(to)h(deal)g(directly)g(with)f(the)g +(ra)m(w)h(FITS)f(formats.)0 1779 y(Eac)m(h)37 b(Header)g(Unit)f(b)s +(egins)g(with)g(a)g(series)h(of)f(required)g(k)m(eyw)m(ords)g(whic)m(h) +g(dep)s(end)f(on)h(the)g(t)m(yp)s(e)h(of)f(HDU.)0 1892 +y(These)31 b(required)g(k)m(eyw)m(ords)h(sp)s(ecify)g(the)f(size)i(and) +e(format)h(of)g(the)g(follo)m(wing)h(Data)g(Unit.)45 +b(The)31 b(header)g(ma)m(y)0 2005 y(con)m(tain)h(other)f(optional)g(k)m +(eyw)m(ords)g(to)h(describ)s(e)e(other)g(asp)s(ects)h(of)g(the)g(data,) +g(suc)m(h)g(as)g(the)f(units)g(or)h(scaling)0 2118 y(v)-5 +b(alues.)44 b(Other)31 b(COMMENT)g(or)g(HISTOR)-8 b(Y)30 +b(k)m(eyw)m(ords)i(are)g(also)g(frequen)m(tly)g(added)e(to)i(further)e +(do)s(cumen)m(t)0 2230 y(the)h(data)g(\014le.)0 2391 +y(The)36 b(optional)h(Data)h(Unit)f(immediately)g(follo)m(ws)h(the)e +(last)h(2880-b)m(yte)i(blo)s(c)m(k)e(in)f(the)g(Header)h(Unit.)59 +b(Some)0 2503 y(HDUs)31 b(do)f(not)h(ha)m(v)m(e)g(a)g(Data)h(Unit)f +(and)f(only)g(consist)h(of)g(the)f(Header)h(Unit.)0 2664 +y(If)24 b(there)i(is)f(more)g(than)f(one)h(HDU)h(in)f(the)g(FITS)f +(\014le,)i(then)f(the)g(Header)h(Unit)f(of)g(the)g(next)g(HDU)h +(immediately)0 2777 y(follo)m(ws)g(the)e(last)i(2880-b)m(yte)h(blo)s(c) +m(k)e(of)g(the)f(previous)g(Data)j(Unit)d(\(or)h(Header)g(Unit)g(if)f +(there)h(is)g(no)f(Data)i(Unit\).)0 2937 y(The)k(main)g(required)g(k)m +(eyw)m(ords)g(in)g(FITS)g(primary)g(arra)m(ys)g(or)h(image)g +(extensions)g(are:)136 3172 y Fc(\017)46 b Fi(BITPIX)33 +b({)h(de\014nes)e(the)i(datat)m(yp)s(e)g(of)f(the)g(arra)m(y:)47 +b(8,)35 b(16,)g(32,)g(64,)g(-32,)g(-64)g(for)e(unsigned)f(8{bit)i(b)m +(yte,)227 3284 y(16{bit)27 b(signed)e(in)m(teger,)i(32{bit)g(signed)e +(in)m(teger,)j(64{bit)e(signed)f(in)m(teger,)j(32{bit)e(IEEE)f +(\015oating)h(p)s(oin)m(t,)227 3397 y(and)k(64{bit)i(IEEE)e(double)f +(precision)i(\015oating)g(p)s(oin)m(t,)g(resp)s(ectiv)m(ely)-8 +b(.)136 3585 y Fc(\017)46 b Fi(NAXIS)30 b({)h(the)g(n)m(um)m(b)s(er)e +(of)h(dimensions)g(in)g(the)h(arra)m(y)-8 b(,)31 b(usually)f(0,)h(1,)g +(2,)g(3,)g(or)g(4.)136 3773 y Fc(\017)46 b Fi(NAXISn)30 +b({)h(\(n)f(ranges)g(from)g(1)h(to)g(NAXIS\))g(de\014nes)e(the)i(size)g +(of)g(eac)m(h)g(dimension.)0 4008 y(FITS)e(tables)i(start)g(with)f(the) +g(k)m(eyw)m(ord)g(XTENSION)g(=)f(`T)-8 b(ABLE')31 b(\(for)f(ASCI)s(I)f +(tables\))i(or)f(XTENSION)f(=)0 4120 y(`BINT)-8 b(ABLE')32 +b(\(for)e(binary)g(tables\))h(and)f(ha)m(v)m(e)i(the)e(follo)m(wing)i +(main)e(k)m(eyw)m(ords:)136 4355 y Fc(\017)46 b Fi(TFIELDS)30 +b({)h(n)m(um)m(b)s(er)e(of)h(\014elds)g(or)h(columns)f(in)g(the)g +(table)136 4543 y Fc(\017)46 b Fi(NAXIS2)31 b({)g(n)m(um)m(b)s(er)e(of) +h(ro)m(ws)h(in)f(the)g(table)136 4731 y Fc(\017)46 b +Fi(TTYPEn)29 b({)i(for)f(eac)m(h)i(column)e(\(n)g(ranges)h(from)f(1)g +(to)h(TFIELDS\))g(giv)m(es)g(the)g(name)f(of)h(the)f(column)136 +4918 y Fc(\017)46 b Fi(TF)m(ORMn)31 b({)f(the)h(datat)m(yp)s(e)g(of)g +(the)f(column)136 5106 y Fc(\017)46 b Fi(TUNITn)30 b({)g(the)h(ph)m +(ysical)g(units)f(of)g(the)h(column)f(\(optional\))0 +5341 y(Users)k(should)f(refer)h(to)h(the)f(FITS)g(Supp)s(ort)e(O\016ce) +i(at)h Fe(http://fits.gsfc.nasa.gov)27 b Fi(for)34 b(further)f(infor-)0 +5454 y(mation)e(ab)s(out)f(the)h(FITS)e(format)i(and)f(related)h(soft)m +(w)m(are)h(pac)m(k)-5 b(ages.)p eop end +%%Page: 15 21 +TeXDict begin 15 20 bop 0 1225 a Ff(Chapter)65 b(4)0 +1687 y Fl(FITSIO)76 b(Con)-6 b(v)g(en)g(tions)76 b(and)h(Guidelines)0 +2216 y Fd(4.1)135 b(CFITSIO)44 b(Size)h(Limitations)0 +2524 y Fi(CFITSIO)31 b(places)i(few)g(restrictions)g(on)g(the)f(size)i +(of)e(FITS)g(\014les)h(that)g(it)g(reads)f(or)h(writes.)47 +b(There)32 b(are)h(a)g(few)0 2636 y(limits,)e(ho)m(w)m(ev)m(er,)h(whic) +m(h)e(ma)m(y)h(a\013ect)h(some)f(extreme)g(cases:)0 2797 +y(1.)43 b(The)31 b(maxim)m(um)g(n)m(um)m(b)s(er)f(of)h(FITS)f(\014les)h +(that)h(ma)m(y)g(b)s(e)e(sim)m(ultaneously)i(op)s(ened)f(b)m(y)g +(CFITSIO)e(is)i(set)h(b)m(y)0 2910 y(NMAXFILES)i(as)g(de\014ned)f(in)h +(\014tsio2.h.)52 b(It)34 b(is)g(curren)m(tly)g(set)h(=)f(300)h(b)m(y)f +(default.)52 b(CFITSIO)32 b(will)i(allo)s(cate)0 3022 +y(ab)s(out)f(80)g(*)h(NMAXFILES)f(b)m(ytes)g(of)g(memory)g(for)g(in)m +(ternal)g(use.)48 b(Note)34 b(that)g(the)f(underlying)f(C)g(compiler)0 +3135 y(or)39 b(op)s(erating)h(system,)j(ma)m(y)d(ha)m(v)m(e)g(a)g +(smaller)g(limit)h(on)e(the)h(n)m(um)m(b)s(er)e(of)i(op)s(ened)e +(\014les.)68 b(The)39 b(C)h(sym)m(b)s(olic)0 3248 y(constan)m(t)31 +b(F)m(OPEN)p 690 3248 28 4 v 34 w(MAX)f(is)g(in)m(tended)g(to)h +(de\014ne)e(the)i(maxim)m(um)f(n)m(um)m(b)s(er)e(of)j(\014les)f(that)g +(ma)m(y)h(op)s(en)e(at)i(once)0 3361 y(\(including)g(an)m(y)g(other)g +(text)h(or)f(binary)f(\014les)h(that)h(ma)m(y)f(b)s(e)g(op)s(en,)f(not) +h(just)g(FITS)f(\014les\).)43 b(On)30 b(some)h(systems)0 +3474 y(it)g(has)f(b)s(een)g(found)f(that)i(gcc)g(supp)s(orts)e(a)h +(maxim)m(um)h(of)f(255)i(op)s(ened)e(\014les.)0 3634 +y(2.)54 b(By)35 b(default,)h(CFITSIO)d(can)i(handle)g(FITS)f(\014les)g +(up)g(to)h(2.1)h(GB)g(in)e(size)i(\(2**31)h(b)m(ytes\).)54 +b(This)34 b(\014le)h(size)0 3747 y(limit)41 b(is)g(often)f(imp)s(osed)g +(b)m(y)g(32-bit)i(op)s(erating)e(systems.)71 b(More)41 +b(recen)m(tly)-8 b(,)45 b(as)c(64-bit)g(op)s(erating)g(systems)0 +3860 y(b)s(ecome)33 b(more)g(common,)g(an)g(industry-wide)e(standard)h +(\(at)i(least)f(on)g(Unix)f(systems\))h(has)g(b)s(een)f(dev)m(elop)s +(ed)0 3973 y(to)39 b(supp)s(ort)d(larger)i(sized)h(\014les)f(\(see)g(h) +m(ttp://ftp.sas.com/standards/large.\014le/\).)69 b(Starting)38 +b(with)f(v)m(ersion)0 4086 y(2.1)45 b(of)e(CFITSIO,)f(larger)i(FITS)f +(\014les)g(up)g(to)h(6)g(terab)m(ytes)h(in)e(size)h(ma)m(y)g(b)s(e)f +(read)g(and)g(written)h(on)f(sup-)0 4199 y(p)s(orted)f(platforms.)76 +b(In)42 b(order)g(to)h(supp)s(ort)e(these)h(larger)h(\014les,)j +(CFITSIO)41 b(m)m(ust)h(b)s(e)g(compiled)h(with)f(the)0 +4312 y('-D)p 129 4312 V 34 w(LAR)m(GEFILE)p 696 4312 +V 33 w(SOUR)m(CE')g(and)g(`-D)p 1491 4312 V 34 w(FILE)p +1736 4312 V 33 w(OFFSET)p 2137 4312 V 32 w(BITS=64')h(compiler)g +(\015ags.)78 b(Some)43 b(platforms)0 4425 y(ma)m(y)c(also)g(require)f +(the)g(`-D)p 1002 4425 V 34 w(LAR)m(GE)p 1358 4425 V +33 w(FILES')g(compiler)h(\015ag.)64 b(This)38 b(causes)g(the)h +(compiler)g(to)g(allo)s(cate)h(8-)0 4538 y(b)m(ytes)k(instead)g(of)g +(4-b)m(ytes)h(for)f(the)g(`o\013)p 1473 4538 V 33 w(t')g(datat)m(yp)s +(e)h(whic)m(h)f(is)f(used)g(to)i(store)f(\014le)g(o\013set)h(p)s +(ositions.)81 b(It)0 4650 y(app)s(ears)31 b(that)i(in)e(most)i(cases)g +(it)f(is)g(not)g(necessary)h(to)f(also)h(include)f(these)g(compiler)h +(\015ags)f(when)f(compiling)0 4763 y(programs)f(that)h(link)f(to)h(the) +g(CFITSIO)e(library)-8 b(.)0 4924 y(If)21 b(CFITSIO)e(is)i(compiled)h +(with)f(the)g(-D)p 1386 4924 V 33 w(LAR)m(GEFILE)p 1952 +4924 V 34 w(SOUR)m(CE)f(and)g(-D)p 2654 4924 V 34 w(FILE)p +2899 4924 V 33 w(OFFSET)p 3300 4924 V 32 w(BITS=64)h(\015ags)0 +5036 y(on)36 b(a)g(platform)g(that)g(supp)s(orts)e(large)j(\014les,)h +(then)d(it)i(can)f(read)g(and)f(write)h(FITS)f(\014les)h(that)g(con)m +(tain)h(up)e(to)0 5149 y(2**31)k(2880-b)m(yte)g(FITS)d(records,)j(or)d +(appro)m(ximately)i(6)f(terab)m(ytes)h(in)f(size.)60 +b(It)37 b(is)g(still)h(required)d(that)j(the)0 5262 y(v)-5 +b(alue)30 b(of)f(the)g(NAXISn)f(and)h(PCOUNT)f(k)m(eyw)m(ords)h(in)g +(eac)m(h)h(extension)g(b)s(e)e(within)h(the)g(range)h(of)f(a)g(signed)g +(4-)0 5375 y(b)m(yte)c(in)m(teger)h(\(max)f(v)-5 b(alue)26 +b(=)e(2,147,483,648\).)44 b(Th)m(us,)25 b(eac)m(h)h(dimension)e(of)h +(an)f(image)i(\(giv)m(en)g(b)m(y)f(the)g(NAXISn)0 5488 +y(k)m(eyw)m(ords\),)32 b(the)f(total)i(width)d(of)h(a)g(table)h +(\(NAXIS1)g(k)m(eyw)m(ord\),)g(the)f(n)m(um)m(b)s(er)f(of)h(ro)m(ws)g +(in)f(a)h(table)h(\(NAXIS2)0 5601 y(k)m(eyw)m(ord\),)c(and)d(the)h +(total)i(size)f(of)f(the)g(v)-5 b(ariable-length)28 b(arra)m(y)e(heap)g +(in)g(binary)f(tables)i(\(PCOUNT)e(k)m(eyw)m(ord\))0 +5714 y(m)m(ust)30 b(b)s(e)g(less)h(than)f(this)g(limit.)1905 +5942 y(15)p eop end +%%Page: 16 22 +TeXDict begin 16 21 bop 0 299 a Fi(16)1277 b Fg(CHAPTER)29 +b(4.)72 b(FITSIO)29 b(CONVENTIONS)g(AND)i(GUIDELINES)0 +555 y Fi(Curren)m(tly)-8 b(,)31 b(supp)s(ort)e(for)i(large)h(\014les)f +(within)f(CFITSIO)f(has)i(b)s(een)f(tested)i(on)f(the)g(Lin)m(ux,)g +(Solaris,)g(and)f(IBM)0 668 y(AIX)g(op)s(erating)h(systems.)0 +1133 y Fd(4.2)135 b(Multiple)46 b(Access)e(to)i(the)f(Same)g(FITS)f +(File)0 1409 y Fi(CFITSIO)35 b(supp)s(orts)g(sim)m(ultaneous)i(read)f +(and)g(write)h(access)g(to)h(m)m(ultiple)f(HDUs)g(in)f(the)h(same)g +(FITS)f(\014le.)0 1522 y(Th)m(us,)43 b(one)e(can)h(op)s(en)e(the)h +(same)h(FITS)e(\014le)h(t)m(wice)i(within)d(a)i(single)f(program)g(and) +g(mo)m(v)m(e)h(to)g(2)f(di\013eren)m(t)0 1635 y(HDUs)30 +b(in)f(the)h(\014le,)g(and)f(then)g(read)h(and)e(write)i(data)g(or)g(k) +m(eyw)m(ords)g(to)g(the)g(2)f(extensions)i(just)d(as)i(if)g(one)f(w)m +(ere)0 1748 y(accessing)f(2)f(completely)h(separate)f(FITS)f(\014les.) +39 b(Since)27 b(in)f(general)h(it)g(is)g(not)g(p)s(ossible)f(to)h(ph)m +(ysically)g(op)s(en)f(the)0 1861 y(same)36 b(\014le)g(t)m(wice)h(and)e +(then)g(exp)s(ect)h(to)g(b)s(e)f(able)h(to)h(sim)m(ultaneously)f(\(or)g +(in)f(alternating)i(succession\))g(write)0 1974 y(to)e(2)f(di\013eren)m +(t)h(lo)s(cations)h(in)d(the)i(\014le,)g(CFITSIO)e(recognizes)j(when)d +(the)h(\014le)g(to)h(b)s(e)f(op)s(ened)f(\(in)h(the)h(call)g(to)0 +2087 y(\014ts)p 127 2087 28 4 v 32 w(op)s(en)p 349 2087 +V 33 w(\014le\))29 b(has)f(already)h(b)s(een)f(op)s(ened)g(and)g +(instead)h(of)g(actually)h(op)s(ening)e(the)h(\014le)g(again,)h(just)e +(logically)0 2199 y(links)i(the)h(new)f(\014le)h(to)g(the)g(old)f +(\014le.)42 b(\(This)30 b(only)h(applies)f(if)h(the)g(\014le)f(is)h(op) +s(ened)f(more)g(than)g(once)i(within)e(the)0 2312 y(same)e(program,)g +(and)f(do)s(es)h(not)f(prev)m(en)m(t)i(the)f(same)g(\014le)f(from)g(b)s +(eing)h(sim)m(ultaneously)g(op)s(ened)f(b)m(y)g(more)h(than)0 +2425 y(one)h(program\).)40 b(Then)28 b(b)s(efore)g(CFITSIO)f(reads)h +(or)h(writes)g(to)g(either)g(\(logical\))j(\014le,)d(it)g(mak)m(es)h +(sure)d(that)j(an)m(y)0 2538 y(mo)s(di\014cations)i(made)f(to)h(the)g +(other)g(\014le)f(ha)m(v)m(e)i(b)s(een)e(completely)i(\015ushed)c(from) +i(the)h(in)m(ternal)g(bu\013ers)f(to)h(the)0 2651 y(\014le.)44 +b(Th)m(us,)30 b(in)h(principle,)h(one)f(could)g(op)s(en)g(a)h(\014le)f +(t)m(wice,)i(in)e(one)h(case)g(p)s(oin)m(ting)g(to)g(the)f(\014rst)g +(extension)h(and)0 2764 y(in)i(the)h(other)g(p)s(oin)m(ting)f(to)i(the) +e(2nd)g(extension)i(and)d(then)i(write)f(data)i(to)f(b)s(oth)f +(extensions,)i(in)e(an)m(y)h(order,)0 2877 y(without)25 +b(danger)h(of)f(corrupting)h(the)f(\014le,)i(There)e(ma)m(y)h(b)s(e)f +(some)h(e\016ciency)g(p)s(enalties)g(in)f(doing)h(this)f(ho)m(w)m(ev)m +(er,)0 2990 y(since)j(CFITSIO)f(has)h(to)h(\015ush)d(all)j(the)f(in)m +(ternal)h(bu\013ers)e(related)i(to)g(one)f(\014le)g(b)s(efore)g(switc)m +(hing)g(to)h(the)f(other,)0 3103 y(so)i(it)h(w)m(ould)f(still)h(b)s(e)f +(pruden)m(t)f(to)i(minimize)g(the)f(n)m(um)m(b)s(er)f(of)i(times)f(one) +h(switc)m(hes)g(bac)m(k)g(and)e(forth)h(b)s(et)m(w)m(een)0 +3216 y(doing)g(I/O)h(to)g(di\013eren)m(t)g(HDUs)g(in)f(the)g(same)h +(\014le.)0 3680 y Fd(4.3)135 b(Curren)l(t)46 b(Header)f(Data)h(Unit)g +(\(CHDU\))0 3957 y Fi(In)32 b(general,)j(a)f(FITS)e(\014le)i(can)f(con) +m(tain)h(m)m(ultiple)g(Header)g(Data)h(Units,)f(also)g(called)g +(extensions.)49 b(CFITSIO)0 4070 y(only)38 b(op)s(erates)h(within)f +(one)g(HDU)h(at)g(an)m(y)g(giv)m(en)g(time,)i(and)d(the)g(curren)m(tly) +g(selected)i(HDU)f(is)f(called)i(the)0 4183 y(Curren)m(t)f(Header)h +(Data)h(Unit)f(\(CHDU\).)h(When)f(a)g(FITS)f(\014le)h(is)f(\014rst)g +(created)i(or)f(op)s(ened)f(the)h(CHDU)g(is)0 4295 y(automatically)28 +b(de\014ned)23 b(to)j(b)s(e)e(the)h(\014rst)f(HDU)i(\(i.e.,)h(the)e +(primary)f(arra)m(y\).)40 b(CFITSIO)23 b(routines)i(are)g(pro)m(vided)0 +4408 y(to)36 b(mo)m(v)m(e)h(to)g(and)e(op)s(en)g(an)m(y)h(other)g +(existing)g(HDU)h(within)e(the)h(FITS)f(\014le)g(or)h(to)g(app)s(end)e +(or)i(insert)f(a)h(new)0 4521 y(HDU)31 b(in)f(the)h(FITS)e(\014le)i +(whic)m(h)f(then)g(b)s(ecomes)h(the)f(CHDU.)0 4986 y +Fd(4.4)135 b(Subroutine)45 b(Names)0 5262 y Fi(All)26 +b(FITSIO)f(subroutine)g(names)h(b)s(egin)f(with)h(the)g(letters)h('ft') +f(to)h(distinguish)e(them)h(from)f(other)h(subroutines)0 +5375 y(and)34 b(are)h(5)g(or)f(6)h(c)m(haracters)h(long.)54 +b(Users)34 b(should)g(not)g(name)h(their)g(o)m(wn)f(subroutines)f(b)s +(eginning)h(with)g('ft')0 5488 y(to)e(a)m(v)m(oid)i(con\015icts.)45 +b(\(The)32 b(SPP)f(in)m(terface)i(routines)e(all)i(b)s(egin)e(with)h +('fs'\).)45 b(Subroutines)30 b(whic)m(h)h(read)h(or)g(get)0 +5601 y(information)e(from)g(the)h(FITS)e(\014le)h(ha)m(v)m(e)i(names)e +(b)s(eginning)f(with)h('ftg...'.)43 b(Subroutines)28 +b(whic)m(h)i(write)g(or)h(put)0 5714 y(information)g(in)m(to)g(the)g +(FITS)e(\014le)i(ha)m(v)m(e)g(names)g(b)s(eginning)e(with)h('ftp...'.)p +eop end +%%Page: 17 23 +TeXDict begin 17 22 bop 0 299 a Fg(4.5.)72 b(SUBR)m(OUTINE)30 +b(F)-10 b(AMILIES)30 b(AND)h(D)m(A)-8 b(T)g(A)g(TYPES)1697 +b Fi(17)0 555 y Fd(4.5)135 b(Subroutine)45 b(F)-11 b(amilies)46 +b(and)f(Datat)l(yp)t(es)0 805 y Fi(Man)m(y)h(of)g(the)g(subroutines)e +(come)j(in)e(families)h(whic)m(h)g(di\013er)f(only)h(in)f(the)h(datat)m +(yp)s(e)g(of)g(the)f(asso)s(ciated)0 918 y(parameter\(s\))34 +b(.)47 b(The)32 b(datat)m(yp)s(e)i(of)f(these)g(subroutines)e(is)i +(indicated)g(b)m(y)g(the)g(last)g(letter)h(of)f(the)g(subroutine)0 +1031 y(name)d(\(e.g.,)j('j')d(in)g('ftpkyj'\))h(as)f(follo)m(ws:)382 +1284 y Fe(x)47 b(-)h(bit)382 1397 y(b)f(-)h(character*1)c(\(unsigned)i +(byte\))382 1510 y(i)h(-)h(short)e(integer)g(\(I*2\))382 +1623 y(j)h(-)h(integer)e(\(I*4,)g(32-bit)g(integer\))382 +1735 y(k)h(-)h(long)e(long)h(integer)f(\(I*8,)g(64-bit)g(integer\))382 +1848 y(e)h(-)h(real)e(exponential)f(floating)h(point)g(\(R*4\))382 +1961 y(f)h(-)h(real)e(fixed-format)f(floating)g(point)i(\(R*4\))382 +2074 y(d)g(-)h(double)e(precision)f(real)i(floating-point)d(\(R*8\))382 +2187 y(g)j(-)h(double)e(precision)f(fixed-format)g(floating)g(point)h +(\(R*8\))382 2300 y(c)h(-)h(complex)e(reals)g(\(pairs)g(of)h(R*4)g +(values\))382 2413 y(m)g(-)h(double)e(precision)f(complex)h(\(pairs)g +(of)h(R*8)g(values\))382 2526 y(l)g(-)h(logical)e(\(L*4\))382 +2639 y(s)h(-)h(character)d(string)0 2891 y Fi(When)23 +b(dealing)h(with)f(the)g(FITS)g(b)m(yte)g(datat)m(yp)s(e,)j(it)e(is)f +(imp)s(ortan)m(t)h(to)f(remem)m(b)s(er)g(that)h(the)f(ra)m(w)g(v)-5 +b(alues)24 b(\(b)s(efore)0 3004 y(an)m(y)h(scaling)g(b)m(y)f(the)h +(BSCALE)e(and)h(BZER)m(O,)g(or)h(TSCALn)d(and)i(TZER)m(On)f(k)m(eyw)m +(ord)i(v)-5 b(alues\))25 b(in)f(b)m(yte)h(arra)m(ys)0 +3117 y(\(BITPIX)37 b(=)f(8\))h(or)f(b)m(yte)i(columns)e(\(TF)m(ORMn)h +(=)f('B'\))h(are)g(in)m(terpreted)g(as)g(unsigned)e(b)m(ytes)i(with)g +(v)-5 b(alues)0 3230 y(ranging)40 b(from)f(0)i(to)f(255.)71 +b(Some)40 b(F)-8 b(ortran)40 b(compilers)h(supp)s(ort)d(a)i +(non-standard)f(b)m(yte)h(datat)m(yp)s(e)h(suc)m(h)f(as)0 +3343 y(INTEGER*1,)34 b(LOGICAL*1,)g(or)f(BYTE,)g(whic)m(h)f(can)h +(sometimes)h(b)s(e)e(used)g(instead)h(of)g(CHARA)m(CTER*1)0 +3456 y(v)-5 b(ariables.)39 b(Man)m(y)23 b(mac)m(hines)g(p)s(ermit)g +(passing)f(a)h(n)m(umeric)g(datat)m(yp)s(e)g(\(suc)m(h)g(as)g +(INTEGER*1\))h(to)f(the)g(FITSIO)0 3569 y(subroutines)41 +b(whic)m(h)i(are)g(exp)s(ecting)g(a)g(CHARA)m(CTER*1)h(datat)m(yp)s(e,) +j(but)42 b(this)g(tec)m(hnically)j(violates)g(the)0 3682 +y(F)-8 b(ortran-77)29 b(standard)d(and)g(is)h(not)g(supp)s(orted)e(on)i +(all)h(mac)m(hines)f(\(e.g.,)j(on)c(a)i(V)-10 b(AX/VMS)27 +b(mac)m(hine)h(one)f(m)m(ust)0 3795 y(use)j(the)h(V)-10 +b(AX-sp)s(eci\014c)31 b(\045DESCR)e(function\).)0 3955 +y(One)22 b(feature)h(of)g(the)g(CFITSIO)e(routines)i(is)f(that)i(they)f +(can)g(op)s(erate)g(on)f(a)h(`X')h(\(bit\))f(column)g(in)f(a)h(binary)f +(table)0 4068 y(as)35 b(though)e(it)i(w)m(ere)g(a)g(`B')g(\(b)m(yte\))g +(column.)53 b(F)-8 b(or)35 b(example)g(a)f(`11X')i(datat)m(yp)s(e)f +(column)f(can)h(b)s(e)f(in)m(terpreted)0 4181 y(the)28 +b(same)h(as)f(a)g(`2B')i(column)e(\(i.e.,)i(2)e(unsigned)f(8-bit)i(b)m +(ytes\).)41 b(In)27 b(some)i(instances,)g(it)f(can)h(b)s(e)e(more)h +(e\016cien)m(t)0 4294 y(to)j(read)f(and)g(write)h(whole)f(b)m(ytes)h +(at)g(a)g(time,)g(rather)g(than)f(reading)g(or)h(writing)f(eac)m(h)i +(individual)d(bit.)0 4454 y(The)41 b(double)h(precision)g(complex)g +(datat)m(yp)s(e)h(is)f(not)g(a)g(standard)f(F)-8 b(ortran-77)43 +b(datat)m(yp)s(e.)76 b(If)41 b(a)i(particular)0 4567 +y(F)-8 b(ortran)35 b(compiler)g(do)s(es)f(not)h(directly)g(supp)s(ort)e +(this)i(datat)m(yp)s(e,)h(then)f(one)f(ma)m(y)h(instead)g(pass)f(an)h +(arra)m(y)g(of)0 4680 y(pairs)d(of)h(double)f(precision)h(v)-5 +b(alues)32 b(to)i(these)f(subroutines.)45 b(The)33 b(\014rst)e(v)-5 +b(alue)33 b(in)f(eac)m(h)i(pair)e(is)h(the)g(real)g(part,)0 +4792 y(and)d(the)g(second)h(is)f(the)h(imaginary)g(part.)0 +5125 y Fd(4.6)135 b(Implicit)46 b(Data)g(T)l(yp)t(e)f(Con)l(v)l(ersion) +0 5375 y Fi(The)22 b(FITSIO)g(routines)h(that)h(read)e(and)h(write)g(n) +m(umerical)g(data)h(can)f(p)s(erform)f(implicit)i(data)f(t)m(yp)s(e)g +(con)m(v)m(ersion.)0 5488 y(This)i(means)g(that)h(the)g(data)g(t)m(yp)s +(e)g(of)g(the)g(v)-5 b(ariable)26 b(or)g(arra)m(y)g(in)f(the)h(program) +f(do)s(es)g(not)h(need)g(to)g(b)s(e)f(the)h(same)0 5601 +y(as)i(the)f(data)h(t)m(yp)s(e)g(of)f(the)h(v)-5 b(alue)28 +b(in)f(the)g(FITS)g(\014le.)40 b(Data)28 b(t)m(yp)s(e)g(con)m(v)m +(ersion)h(is)e(supp)s(orted)f(for)h(n)m(umerical)h(and)0 +5714 y(string)33 b(data)h(t)m(yp)s(es)f(\(if)h(the)g(string)f(con)m +(tains)h(a)g(v)-5 b(alid)33 b(n)m(um)m(b)s(er)f(enclosed)i(in)f +(quotes\))h(when)f(reading)g(a)h(FITS)p eop end +%%Page: 18 24 +TeXDict begin 18 23 bop 0 299 a Fi(18)1277 b Fg(CHAPTER)29 +b(4.)72 b(FITSIO)29 b(CONVENTIONS)g(AND)i(GUIDELINES)0 +555 y Fi(header)g(k)m(eyw)m(ord)g(v)-5 b(alue)31 b(and)g(for)f(n)m +(umeric)h(v)-5 b(alues)31 b(when)f(reading)h(or)g(writing)g(v)-5 +b(alues)31 b(in)f(the)h(primary)f(arra)m(y)0 668 y(or)40 +b(a)h(table)h(column.)70 b(CFITSIO)39 b(returns)h(status)g(=)h(NUM)p +2185 668 28 4 v 33 w(O)m(VERFLO)m(W)g(if)g(the)f(con)m(v)m(erted)i +(data)f(v)-5 b(alue)0 781 y(exceeds)33 b(the)g(range)g(of)g(the)f +(output)g(data)i(t)m(yp)s(e.)47 b(Implicit)33 b(data)g(t)m(yp)s(e)g +(con)m(v)m(ersion)h(is)e(not)h(supp)s(orted)d(within)0 +894 y(binary)g(tables)h(for)f(string,)g(logical,)k(complex,)d(or)f +(double)g(complex)h(data)g(t)m(yp)s(es.)0 1054 y(In)g(addition,)h(an)m +(y)f(table)h(column)f(ma)m(y)h(b)s(e)f(read)g(as)h(if)f(it)h(con)m +(tained)g(string)f(v)-5 b(alues.)44 b(In)31 b(the)g(case)i(of)e(n)m +(umeric)0 1167 y(columns)f(the)h(returned)e(string)h(will)h(b)s(e)f +(formatted)h(using)e(the)i(TDISPn)e(displa)m(y)i(format)f(if)h(it)g +(exists.)0 1496 y Fd(4.7)135 b(Data)46 b(Scaling)0 1746 +y Fi(When)38 b(reading)f(n)m(umerical)i(data)f(v)-5 b(alues)38 +b(in)f(the)h(primary)f(arra)m(y)h(or)g(a)g(table)h(column,)h(the)d(v)-5 +b(alues)38 b(will)h(b)s(e)0 1859 y(scaled)f(automatically)j(b)m(y)c +(the)h(BSCALE)f(and)g(BZER)m(O)h(\(or)g(TSCALn)d(and)i(TZER)m(On\))g +(header)g(k)m(eyw)m(ord)0 1972 y(v)-5 b(alues)33 b(if)f(they)g(are)h +(presen)m(t)g(in)f(the)g(header.)47 b(The)31 b(scaled)j(data)f(that)g +(is)f(returned)f(to)i(the)g(reading)f(program)0 2085 +y(will)f(ha)m(v)m(e)382 2316 y Fe(output)46 b(value)g(=)i(\(FITS)e +(value\))g(*)i(BSCALE)e(+)h(BZERO)0 2546 y Fi(\(a)30 +b(corresp)s(onding)e(form)m(ula)h(using)g(TSCALn)e(and)i(TZER)m(On)e +(is)i(used)g(when)f(reading)h(from)g(table)h(columns\).)0 +2659 y(In)h(the)i(case)g(of)f(in)m(teger)h(output)f(v)-5 +b(alues)32 b(the)h(\015oating)g(p)s(oin)m(t)f(scaled)g(v)-5 +b(alue)33 b(is)f(truncated)g(to)h(an)f(in)m(teger)h(\(not)0 +2772 y(rounded)38 b(to)i(the)g(nearest)g(in)m(teger\).)70 +b(The)39 b(ftpscl)g(and)g(fttscl)i(subroutines)d(ma)m(y)i(b)s(e)f(used) +g(to)h(o)m(v)m(erride)h(the)0 2885 y(scaling)30 b(parameters)f +(de\014ned)e(in)h(the)h(header)f(\(e.g.,)j(to)e(turn)f(o\013)h(the)f +(scaling)i(so)f(that)g(the)g(program)f(can)h(read)0 2998 +y(the)i(ra)m(w)f(unscaled)g(v)-5 b(alues)31 b(from)f(the)g(FITS)g +(\014le\).)0 3158 y(When)44 b(writing)h(n)m(umerical)g(data)g(to)g(the) +g(primary)f(arra)m(y)h(or)f(to)h(a)g(table)h(column)e(the)h(data)g(v)-5 +b(alues)45 b(will)0 3271 y(generally)29 b(b)s(e)f(automatically)j(in)m +(v)m(ersely)f(scaled)f(b)m(y)f(the)g(v)-5 b(alue)29 b(of)f(the)h +(BSCALE)e(and)h(BZER)m(O)g(\(or)h(TSCALn)0 3384 y(and)h(TZER)m(On\))g +(header)g(k)m(eyw)m(ord)h(v)-5 b(alues)31 b(if)g(they)g(they)g(exist)g +(in)f(the)h(header.)42 b(These)30 b(k)m(eyw)m(ords)h(m)m(ust)g(ha)m(v)m +(e)0 3497 y(b)s(een)f(written)h(to)h(the)g(header)e(b)s(efore)h(an)m(y) +h(data)f(is)g(written)h(for)e(them)i(to)f(ha)m(v)m(e)i(an)m(y)e +(e\013ect.)44 b(Otherwise,)32 b(one)0 3610 y(ma)m(y)i(use)f(the)g +(ftpscl)g(and)g(fttscl)h(subroutines)d(to)j(de\014ne)f(or)g(o)m(v)m +(erride)h(the)f(scaling)i(k)m(eyw)m(ords)e(in)g(the)g(header)0 +3723 y(\(e.g.,)h(to)f(turn)d(o\013)j(the)f(scaling)h(so)f(that)g(the)g +(program)g(can)g(write)g(the)g(ra)m(w)g(unscaled)g(v)-5 +b(alues)32 b(in)m(to)h(the)f(FITS)0 3836 y(\014le\).)43 +b(If)30 b(scaling)i(is)f(p)s(erformed,)e(the)i(in)m(v)m(erse)h(scaled)g +(output)e(v)-5 b(alue)32 b(that)f(is)g(written)g(in)m(to)h(the)f(FITS)f +(\014le)h(will)0 3949 y(ha)m(v)m(e)430 4179 y Fe(FITS)46 +b(value)h(=)g(\(\(input)f(value\))g(-)h(BZERO\))f(/)i(BSCALE)0 +4410 y Fi(\(a)39 b(corresp)s(onding)d(form)m(ula)i(using)g(TSCALn)e +(and)h(TZER)m(On)g(is)h(used)f(when)f(writing)i(to)h(table)g +(columns\).)0 4523 y(Rounding)19 b(to)i(the)g(nearest)g(in)m(teger,)i +(rather)e(than)f(truncation,)j(is)d(p)s(erformed)f(when)g(writing)h(in) +m(teger)i(datat)m(yp)s(es)0 4636 y(to)31 b(the)g(FITS)e(\014le.)0 +4965 y Fd(4.8)135 b(Error)46 b(Status)f(V)-11 b(alues)45 +b(and)g(the)g(Error)g(Message)h(Stac)l(k)0 5215 y Fi(The)33 +b(last)i(parameter)f(in)g(nearly)g(ev)m(ery)g(FITSIO)f(subroutine)g(is) +h(the)g(error)f(status)h(v)-5 b(alue)35 b(whic)m(h)e(is)h(b)s(oth)f(an) +0 5328 y(input)j(and)f(an)i(output)f(parameter.)60 b(A)36 +b(returned)f(p)s(ositiv)m(e)j(v)-5 b(alue)37 b(for)f(this)h(parameter)g +(indicates)g(an)f(error)0 5441 y(w)m(as)31 b(detected.)42 +b(A)30 b(listing)h(of)g(all)g(the)g(FITSIO)e(status)i(co)s(de)f(v)-5 +b(alues)31 b(is)f(giv)m(en)i(at)f(the)f(end)g(of)h(this)f(do)s(cumen)m +(t.)0 5601 y(The)22 b(FITSIO)g(library)g(uses)h(an)f(`inherited)h +(status')g(con)m(v)m(en)m(tion)i(for)e(the)g(status)g(parameter)g(whic) +m(h)g(means)f(that)0 5714 y(if)i(a)h(subroutine)f(is)g(called)i(with)e +(a)h(p)s(ositiv)m(e)g(input)f(v)-5 b(alue)25 b(of)g(the)f(status)h +(parameter,)h(then)f(the)f(subroutine)g(will)p eop end +%%Page: 19 25 +TeXDict begin 19 24 bop 0 299 a Fg(4.9.)72 b(V)-10 b(ARIABLE-LENGTH)31 +b(ARRA)-8 b(Y)31 b(F)-10 b(A)m(CILITY)30 b(IN)h(BINAR)-8 +b(Y)31 b(T)-8 b(ABLES)956 b Fi(19)0 555 y(exit)26 b(immediately)f +(without)g(c)m(hanging)h(the)e(v)-5 b(alue)25 b(of)g(the)g(status)g +(parameter.)39 b(Th)m(us,)25 b(if)g(one)f(passes)h(the)g(status)0 +668 y(v)-5 b(alue)31 b(returned)e(from)h(eac)m(h)i(FITSIO)d(routine)h +(as)h(input)f(to)h(the)f(next)h(FITSIO)e(subroutine,)h(then)g(whenev)m +(er)0 781 y(an)39 b(error)g(is)h(detected)g(all)h(further)d(FITSIO)g +(pro)s(cessing)h(will)h(cease.)69 b(This)39 b(con)m(v)m(en)m(tion)i +(can)f(simplify)f(the)0 894 y(error)30 b(c)m(hec)m(king)j(in)d +(application)i(programs)f(b)s(ecause)g(it)g(is)g(not)g(necessary)g(to)g +(c)m(hec)m(k)i(the)e(v)-5 b(alue)31 b(of)g(the)g(status)0 +1007 y(parameter)j(after)g(ev)m(ery)g(single)h(FITSIO)d(subroutine)g +(call.)52 b(If)33 b(a)h(program)f(con)m(tains)i(a)f(sequence)g(of)g +(sev)m(eral)0 1120 y(FITSIO)23 b(calls,)j(one)e(can)g(just)g(c)m(hec)m +(k)h(the)f(status)g(v)-5 b(alue)24 b(after)h(the)f(last)g(call.)40 +b(Since)24 b(the)g(returned)e(status)j(v)-5 b(alues)0 +1233 y(are)36 b(generally)h(distinctiv)m(e,)i(it)d(should)f(b)s(e)g(p)s +(ossible)g(to)h(determine)g(whic)m(h)f(subroutine)g(originally)i +(returned)0 1346 y(the)31 b(error)f(status.)0 1506 y(FITSIO)i(also)i +(main)m(tains)f(an)g(in)m(ternal)h(stac)m(k)g(of)f(error)g(messages)h +(\(80-c)m(haracter)i(maxim)m(um)d(length\))g(whic)m(h)0 +1619 y(in)j(man)m(y)g(cases)h(pro)m(vide)f(a)g(more)g(detailed)i +(explanation)f(of)f(the)g(cause)h(of)f(the)g(error)g(than)f(is)h(pro)m +(vided)g(b)m(y)0 1732 y(the)k(error)e(status)i(n)m(um)m(b)s(er)e +(alone.)69 b(It)39 b(is)h(recommended)f(that)g(the)h(error)f(message)h +(stac)m(k)h(b)s(e)e(prin)m(ted)g(out)0 1844 y(whenev)m(er)31 +b(a)h(program)g(detects)g(a)g(FITSIO)e(error.)44 b(T)-8 +b(o)32 b(do)f(this,)h(call)h(the)f(FTGMSG)g(routine)f(rep)s(eatedly)h +(to)0 1957 y(get)h(the)g(successiv)m(e)g(messages)h(on)e(the)g(stac)m +(k.)48 b(When)32 b(the)h(stac)m(k)g(is)g(empt)m(y)f(FTGMSG)h(will)g +(return)e(a)h(blank)0 2070 y(string.)41 b(Note)31 b(that)g(this)f(is)g +(a)g(`First)h(In)e({)i(First)f(Out')g(stac)m(k,)i(so)e(the)h(oldest)g +(error)e(message)j(is)e(returned)f(\014rst)0 2183 y(b)m(y)h(ftgmsg.)0 +2557 y Fd(4.9)135 b(V)-11 b(ariable-Length)46 b(Arra)l(y)f(F)-11 +b(acilit)l(y)46 b(in)f(Binary)g(T)-11 b(ables)0 2815 +y Fi(FITSIO)38 b(pro)m(vides)i(easy-to-use)h(supp)s(ort)d(for)h +(reading)g(and)g(writing)h(data)g(in)f(v)-5 b(ariable)40 +b(length)g(\014elds)f(of)h(a)0 2928 y(binary)35 b(table.)56 +b(The)35 b(v)-5 b(ariable)36 b(length)f(columns)g(ha)m(v)m(e)i(TF)m +(ORMn)e(k)m(eyw)m(ord)h(v)-5 b(alues)35 b(of)h(the)f(form)g +(`1Pt\(len\)')0 3041 y(or)30 b(`1Qt\(len\)')h(where)f(`t')g(is)g(the)g +(datat)m(yp)s(e)h(co)s(de)f(\(e.g.,)i(I,)e(J,)f(E,)h(D,)h(etc.\))42 +b(and)29 b(`len')h(is)g(an)g(in)m(teger)h(sp)s(ecifying)0 +3154 y(the)f(maxim)m(um)g(length)g(of)g(the)g(v)m(ector)h(in)f(the)g +(table.)41 b(If)30 b(the)g(v)-5 b(alue)30 b(of)g(`len')g(is)g(not)g(sp) +s(eci\014ed)f(when)g(the)h(table)0 3267 y(is)j(created)g(\(e.g.,)i(if)e +(the)f(TF)m(ORM)h(k)m(eyw)m(ord)g(v)-5 b(alue)33 b(is)g(simply)f(sp)s +(eci\014ed)g(as)h('1PE')g(instead)g(of)f('1PE\(400\))j(\),)0 +3380 y(then)28 b(FITSIO)f(will)h(automatically)k(scan)c(the)g(table)h +(when)f(it)g(is)h(closed)g(to)g(determine)f(the)g(maxim)m(um)h(length)0 +3493 y(of)i(the)f(v)m(ector)i(and)e(will)h(app)s(end)d(this)j(v)-5 +b(alue)30 b(to)i(the)e(TF)m(ORMn)g(v)-5 b(alue.)0 3653 +y(The)25 b(same)h(routines)g(whic)m(h)f(read)h(and)f(write)h(data)g(in) +f(an)h(ordinary)f(\014xed)g(length)h(binary)f(table)h(extension)h(are)0 +3766 y(also)k(used)e(for)h(v)-5 b(ariable)31 b(length)g(\014elds,)e(ho) +m(w)m(ev)m(er,)j(the)e(subroutine)f(parameters)i(tak)m(e)h(on)e(a)g +(sligh)m(tly)h(di\013eren)m(t)0 3878 y(in)m(terpretation)h(as)e +(describ)s(ed)g(b)s(elo)m(w.)0 4039 y(All)37 b(the)f(data)h(in)f(a)h(v) +-5 b(ariable)37 b(length)f(\014eld)g(is)g(written)h(in)m(to)g(an)f +(area)h(called)h(the)e(`heap')g(whic)m(h)g(follo)m(ws)i(the)0 +4152 y(main)26 b(\014xed-length)g(FITS)f(binary)h(table.)40 +b(The)25 b(size)i(of)g(the)f(heap,)h(in)f(b)m(ytes,)h(is)f(sp)s +(eci\014ed)g(with)f(the)i(PCOUNT)0 4264 y(k)m(eyw)m(ord)21 +b(in)f(the)h(FITS)f(header.)37 b(When)20 b(creating)i(a)f(new)f(binary) +g(table,)j(the)e(initial)h(v)-5 b(alue)21 b(of)f(PCOUNT)g(should)0 +4377 y(usually)31 b(b)s(e)f(set)i(to)g(zero.)44 b(FITSIO)30 +b(will)h(recompute)h(the)f(size)h(of)g(the)f(heap)g(as)g(the)h(data)g +(is)f(written)g(and)g(will)0 4490 y(automatically)d(up)s(date)c(the)i +(PCOUNT)e(k)m(eyw)m(ord)h(v)-5 b(alue)26 b(when)e(the)h(table)h(is)f +(closed.)40 b(When)25 b(writing)g(v)-5 b(ariable)0 4603 +y(length)34 b(data)g(to)g(a)g(table,)i(CFITSIO)c(will)h(automatically)k +(extend)c(the)h(size)g(of)g(the)g(heap)f(area)h(if)g(necessary)-8 +b(,)0 4716 y(so)31 b(that)g(an)m(y)f(follo)m(wing)i(HDUs)f(do)f(not)h +(get)h(o)m(v)m(erwritten.)0 4876 y(By)e(default)f(the)h(heap)f(data)i +(area)f(starts)g(immediately)h(after)f(the)f(last)i(ro)m(w)e(of)h(the)g +(\014xed-length)f(table.)42 b(This)0 4989 y(default)27 +b(starting)g(lo)s(cation)i(ma)m(y)e(b)s(e)f(o)m(v)m(erridden)h(b)m(y)g +(the)g(THEAP)f(k)m(eyw)m(ord,)i(but)f(this)f(is)h(not)g(recommended.)0 +5102 y(If)34 b(additional)h(ro)m(ws)f(of)g(data)h(are)g(added)e(to)i +(the)f(table,)j(CFITSIO)32 b(will)j(automatically)i(shift)c(the)i(the)f +(heap)0 5215 y(do)m(wn)g(to)i(mak)m(e)f(ro)s(om)g(for)f(the)h(new)f(ro) +m(ws,)i(but)e(it)i(is)e(ob)m(viously)i(b)s(e)e(more)h(e\016cien)m(t)h +(to)f(initially)h(create)h(the)0 5328 y(table)31 b(with)e(the)h +(necessary)g(n)m(um)m(b)s(er)f(of)h(blank)f(ro)m(ws,)h(so)g(that)g(the) +g(heap)g(do)s(es)f(not)h(needed)g(to)g(b)s(e)f(constan)m(tly)0 +5441 y(mo)m(v)m(ed.)0 5601 y(When)40 b(writing)h(to)g(a)g(v)-5 +b(ariable)41 b(length)g(\014eld,)i(the)e(en)m(tire)h(arra)m(y)f(of)f(v) +-5 b(alues)41 b(for)f(a)h(giv)m(en)h(ro)m(w)f(of)f(the)h(table)0 +5714 y(m)m(ust)36 b(b)s(e)g(written)g(with)g(a)g(single)h(call)h(to)f +(FTPCLx.)57 b(The)36 b(total)i(length)f(of)f(the)g(arra)m(y)h(is)f +(calculated)i(from)p eop end +%%Page: 20 26 +TeXDict begin 20 25 bop 0 299 a Fi(20)1277 b Fg(CHAPTER)29 +b(4.)72 b(FITSIO)29 b(CONVENTIONS)g(AND)i(GUIDELINES)0 +555 y Fi(\(NELEM+FELEM-1\).)44 b(One)30 b(cannot)i(app)s(end)d(more)i +(elemen)m(ts)h(to)g(an)e(existing)i(\014eld)f(at)g(a)h(later)g(time;)g +(an)m(y)0 668 y(attempt)j(to)f(do)g(so)g(will)g(simply)f(o)m(v)m +(erwrite)j(all)e(the)g(data)h(whic)m(h)e(w)m(as)h(previously)g +(written.)51 b(Note)35 b(also)f(that)0 781 y(the)g(new)g(data)g(will)h +(b)s(e)e(written)h(to)h(a)f(new)g(area)g(of)g(the)h(heap)e(and)h(the)g +(heap)g(space)g(used)f(b)m(y)h(the)g(previous)0 894 y(write)j(cannot)h +(b)s(e)e(reclaimed.)62 b(F)-8 b(or)38 b(this)f(reason)g(it)h(is)f +(advised)g(that)h(eac)m(h)g(ro)m(w)f(of)h(a)f(v)-5 b(ariable)38 +b(length)f(\014eld)0 1007 y(only)c(b)s(e)g(written)g(once.)50 +b(An)33 b(exception)h(to)g(this)f(general)h(rule)f(o)s(ccurs)g(when)f +(setting)i(elemen)m(ts)h(of)e(an)g(arra)m(y)0 1120 y(as)38 +b(unde\014ned.)63 b(One)37 b(m)m(ust)i(\014rst)e(write)h(a)h(dumm)m(y)e +(v)-5 b(alue)39 b(in)m(to)g(the)g(arra)m(y)f(with)g(FTPCLx,)i(and)e +(then)g(call)0 1233 y(FTPCLU)33 b(to)i(\015ag)f(the)f(desired)h(elemen) +m(ts)h(as)e(unde\014ned.)49 b(\(Do)35 b(not)f(use)f(the)h(FTPCNx)f +(family)h(of)g(routines)0 1346 y(with)28 b(v)-5 b(ariable)30 +b(length)f(\014elds\).)40 b(Note)30 b(that)f(the)g(ro)m(ws)g(of)g(a)g +(table,)h(whether)e(\014xed)g(or)h(v)-5 b(ariable)29 +b(length,)h(do)f(not)0 1458 y(ha)m(v)m(e)j(to)f(b)s(e)e(written)i +(consecutiv)m(ely)h(and)e(ma)m(y)h(b)s(e)f(written)g(in)g(an)m(y)h +(order.)0 1619 y(When)40 b(writing)h(to)g(a)g(v)-5 b(ariable)41 +b(length)g(ASCI)s(I)e(c)m(haracter)j(\014eld)e(\(e.g.,)45 +b(TF)m(ORM)c(=)f('1P)-8 b(A'\))43 b(only)d(a)h(single)0 +1732 y(c)m(haracter)33 b(string)f(written.)44 b(FTPCLS)30 +b(writes)i(the)g(whole)f(length)h(of)g(the)g(input)e(string)i(\(min)m +(us)f(an)m(y)h(trailing)0 1844 y(blank)37 b(c)m(haracters\),)42 +b(th)m(us)37 b(the)h(NELEM)f(and)g(FELEM)h(parameters)g(are)g(ignored.) +62 b(If)37 b(the)h(input)e(string)i(is)0 1957 y(completely)28 +b(blank)f(then)f(FITSIO)g(will)h(write)g(one)g(blank)f(c)m(haracter)j +(to)e(the)g(FITS)f(\014le.)40 b(Similarly)-8 b(,)28 b(FTGCVS)0 +2070 y(and)35 b(FTGCFS)g(read)g(the)h(en)m(tire)g(string)g(\(truncated) +f(to)i(the)e(width)g(of)g(the)h(c)m(haracter)h(string)e(argumen)m(t)h +(in)0 2183 y(the)31 b(subroutine)e(call\))j(and)e(also)h(ignore)g(the)f +(NELEM)h(and)f(FELEM)g(parameters.)0 2343 y(The)35 b(FTPDES)h +(subroutine)e(is)i(useful)f(in)g(situations)i(where)e(m)m(ultiple)i(ro) +m(ws)e(of)h(a)g(v)-5 b(ariable)37 b(length)f(column)0 +2456 y(ha)m(v)m(e)c(the)e(iden)m(tical)i(arra)m(y)f(of)g(v)-5 +b(alues.)41 b(One)30 b(can)g(simply)g(write)h(the)f(arra)m(y)h(once)g +(for)g(the)f(\014rst)g(ro)m(w,)g(and)g(then)0 2569 y(use)36 +b(FTPDES)g(to)h(write)g(the)f(same)h(descriptor)g(v)-5 +b(alues)36 b(in)m(to)i(the)e(other)h(ro)m(ws)f(\(use)h(the)f(FTGDES)h +(routine)0 2682 y(to)f(read)f(the)h(\014rst)f(descriptor)g(v)-5 +b(alue\);)39 b(all)d(the)g(ro)m(ws)f(will)h(then)f(p)s(oin)m(t)g(to)h +(the)g(same)f(storage)i(lo)s(cation)g(th)m(us)0 2795 +y(sa)m(ving)31 b(disk)f(space.)0 2955 y(When)35 b(reading)g(from)f(a)i +(v)-5 b(ariable)35 b(length)h(arra)m(y)f(\014eld)g(one)g(can)g(only)h +(read)e(as)i(man)m(y)f(elemen)m(ts)h(as)f(actually)0 +3068 y(exist)i(in)e(that)i(ro)m(w)e(of)h(the)g(table;)k(reading)c(do)s +(es)g(not)g(automatically)i(con)m(tin)m(ue)f(with)f(the)g(next)g(ro)m +(w)g(of)g(the)0 3181 y(table)29 b(as)f(o)s(ccurs)g(when)f(reading)h(an) +g(ordinary)g(\014xed)f(length)h(table)h(\014eld.)40 b(A)m(ttempts)29 +b(to)g(read)f(more)g(than)g(this)0 3294 y(will)k(cause)h(an)e(error)h +(status)g(to)g(b)s(e)f(returned.)44 b(One)32 b(can)g(determine)g(the)g +(n)m(um)m(b)s(er)e(of)i(elemen)m(ts)h(in)f(eac)m(h)h(ro)m(w)0 +3407 y(of)e(a)f(v)-5 b(ariable)31 b(column)g(with)f(the)g(FTGDES)h +(subroutine.)0 3859 y Fd(4.10)136 b(Supp)t(ort)44 b(for)h(IEEE)g(Sp)t +(ecial)h(V)-11 b(alues)0 4133 y Fi(The)26 b(ANSI/IEEE-754)h +(\015oating-p)s(oin)m(t)h(n)m(um)m(b)s(er)d(standard)g(de\014nes)h +(certain)h(sp)s(ecial)g(v)-5 b(alues)26 b(that)h(are)g(used)e(to)0 +4246 y(represen)m(t)j(suc)m(h)g(quan)m(tities)h(as)f(Not-a-Num)m(b)s +(er)h(\(NaN\),)h(denormalized,)f(under\015o)m(w,)e(o)m(v)m(er\015o)m +(w,)j(and)d(in\014nit)m(y)-8 b(.)0 4359 y(\(See)29 b(the)f(App)s(endix) +e(in)i(the)g(NOST)g(FITS)f(standard)g(or)h(the)g(NOST)g(FITS)f(User's)h +(Guide)g(for)g(a)g(list)h(of)f(these)0 4472 y(v)-5 b(alues\).)41 +b(The)30 b(FITSIO)f(subroutines)g(that)i(read)f(\015oating)i(p)s(oin)m +(t)e(data)h(in)f(FITS)f(\014les)i(recognize)h(these)f(IEEE)0 +4585 y(sp)s(ecial)40 b(v)-5 b(alues)39 b(and)g(b)m(y)g(default)g(in)m +(terpret)h(the)f(o)m(v)m(er\015o)m(w)i(and)d(in\014nit)m(y)h(v)-5 +b(alues)40 b(as)f(b)s(eing)g(equiv)-5 b(alen)m(t)41 b(to)f(a)0 +4698 y(NaN,)35 b(and)e(con)m(v)m(ert)i(the)f(under\015o)m(w)e(and)h +(denormalized)h(v)-5 b(alues)34 b(in)m(to)h(zeros.)51 +b(In)33 b(some)h(cases)h(programmers)0 4811 y(ma)m(y)d(w)m(an)m(t)g +(access)g(to)g(the)g(ra)m(w)f(IEEE)g(v)-5 b(alues,)32 +b(without)f(an)m(y)h(mo)s(di\014cation)f(b)m(y)g(FITSIO.)g(This)f(can)i +(b)s(e)e(done)0 4924 y(b)m(y)k(calling)j(the)d(FTGPVx)h(or)g(FTGCVx)g +(routines)f(while)h(sp)s(ecifying)f(0.0)i(as)f(the)f(v)-5 +b(alue)35 b(of)g(the)g(NULL)-10 b(V)g(AL)0 5036 y(parameter.)72 +b(This)39 b(will)i(force)g(FITSIO)e(to)j(simply)e(pass)g(the)g(IEEE)g +(v)-5 b(alues)41 b(through)f(to)h(the)g(application)0 +5149 y(program,)30 b(without)g(an)m(y)g(mo)s(di\014cation.)41 +b(This)30 b(do)s(es)f(not)h(w)m(ork)g(for)g(double)g(precision)g(v)-5 +b(alues)30 b(on)g(V)-10 b(AX/VMS)0 5262 y(mac)m(hines,)38 +b(ho)m(w)m(ev)m(er,)h(where)d(there)g(is)g(no)g(easy)g(w)m(a)m(y)h(to)g +(b)m(ypass)f(the)g(default)g(in)m(terpretation)h(of)f(the)h(IEEE)0 +5375 y(sp)s(ecial)45 b(v)-5 b(alues.)83 b(This)44 b(is)h(also)g(not)g +(supp)s(orted)d(when)i(reading)h(\015oating-p)s(oin)m(t)g(images)h +(that)f(ha)m(v)m(e)h(b)s(een)0 5488 y(compressed)29 b(with)h(the)f +(FITS)g(tiled)i(image)f(compression)g(con)m(v)m(en)m(tion)i(that)e(is)g +(discussed)e(in)i(section)h(5.6;)g(the)0 5601 y(pixels)k(v)-5 +b(alues)35 b(in)g(tile)g(compressed)g(images)h(are)f(represen)m(ted)g +(b)m(y)f(scaled)i(in)m(tegers,)h(and)d(a)i(reserv)m(ed)f(in)m(teger)0 +5714 y(v)-5 b(alue)31 b(\(not)g(a)g(NaN\))g(is)f(used)g(to)h(represen)m +(t)g(unde\014ned)d(pixels.)p eop end +%%Page: 21 27 +TeXDict begin 21 26 bop 0 299 a Fg(4.11.)73 b(WHEN)31 +b(THE)f(FINAL)g(SIZE)f(OF)i(THE)f(FITS)f(HDU)i(IS)f(UNKNO)m(WN)978 +b Fi(21)0 555 y Fd(4.11)136 b(When)44 b(the)h(Final)h(Size)f(of)g(the)g +(FITS)f(HDU)h(is)g(Unkno)l(wn)0 805 y Fi(It)27 b(is)h(not)f(required)f +(to)i(kno)m(w)f(the)h(total)h(size)f(of)f(a)h(FITS)e(data)i(arra)m(y)g +(or)f(table)h(b)s(efore)f(b)s(eginning)f(to)i(write)g(the)0 +918 y(data)k(to)f(the)g(FITS)f(\014le.)43 b(In)30 b(the)h(case)h(of)f +(the)g(primary)f(arra)m(y)h(or)g(an)f(image)j(extension,)e(one)h +(should)d(initially)0 1031 y(create)i(the)e(arra)m(y)h(with)e(the)i +(size)g(of)f(the)g(highest)g(dimension)g(\(largest)i(NAXISn)d(k)m(eyw)m +(ord\))i(set)g(to)g(a)f(dumm)m(y)0 1144 y(v)-5 b(alue,)26 +b(suc)m(h)e(as)g(1.)39 b(Then)23 b(after)i(all)g(the)g(data)f(ha)m(v)m +(e)i(b)s(een)d(written)h(and)g(the)g(true)g(dimensions)g(are)g(kno)m +(wn,)h(then)0 1257 y(the)31 b(NAXISn)e(v)-5 b(alue)31 +b(should)f(b)s(e)g(up)s(dated)f(using)h(the)h(\014ts)p +2051 1257 28 4 v 62 w(up)s(date)p 2389 1257 V 32 w(k)m(ey)h(routine)e +(b)s(efore)g(mo)m(ving)i(to)f(another)0 1370 y(extension)g(or)f +(closing)i(the)e(FITS)g(\014le.)0 1530 y(When)f(writing)g(to)g(FITS)g +(tables,)h(CFITSIO)d(automatically)32 b(k)m(eeps)e(trac)m(k)g(of)f(the) +g(highest)h(ro)m(w)f(n)m(um)m(b)s(er)e(that)0 1643 y(is)32 +b(written)g(to,)h(and)e(will)h(increase)h(the)f(size)h(of)f(the)g +(table)g(if)g(necessary)-8 b(.)46 b(CFITSIO)30 b(will)i(also)h +(automatically)0 1756 y(insert)j(space)h(in)f(the)g(FITS)f(\014le)i(if) +f(necessary)-8 b(,)39 b(to)e(ensure)e(that)i(the)f(data)h('heap',)h(if) +e(it)h(exists,)h(and/or)f(an)m(y)0 1869 y(additional)29 +b(HDUs)g(that)g(follo)m(w)g(the)g(table)g(do)f(not)h(get)g(o)m(v)m +(erwritten)h(as)e(new)g(ro)m(ws)g(are)h(written)f(to)h(the)g(table.)0 +2029 y(As)37 b(a)h(general)g(rule)f(it)h(is)f(b)s(est)g(to)h(sp)s +(ecify)f(the)h(initial)g(n)m(um)m(b)s(er)e(of)i(ro)m(ws)f(=)g(0)g(when) +g(the)g(table)h(is)g(created,)0 2142 y(then)g(let)h(CFITSIO)e(k)m(eep)i +(trac)m(k)g(of)g(the)f(n)m(um)m(b)s(er)f(of)i(ro)m(ws)f(that)h(are)f +(actually)i(written.)65 b(The)38 b(application)0 2255 +y(program)e(should)f(not)i(man)m(ually)g(up)s(date)e(the)i(n)m(um)m(b)s +(er)e(of)h(ro)m(ws)g(in)g(the)h(table)g(\(as)g(giv)m(en)g(b)m(y)f(the)h +(NAXIS2)0 2368 y(k)m(eyw)m(ord\))j(since)f(CFITSIO)e(do)s(es)i(this)g +(automatically)-8 b(.)69 b(If)38 b(a)i(table)f(is)g(initially)i +(created)f(with)e(more)h(than)0 2481 y(zero)i(ro)m(ws,)j(then)c(this)h +(will)f(usually)h(b)s(e)f(considered)g(as)h(the)g(minim)m(um)f(size)h +(of)g(the)g(table,)j(ev)m(en)d(if)g(few)m(er)0 2594 y(ro)m(ws)30 +b(are)g(actually)h(written)f(to)h(the)f(table.)41 b(Th)m(us,)30 +b(if)f(a)i(table)f(is)g(initially)h(created)g(with)f(NAXIS2)g(=)g(20,)h +(and)0 2706 y(CFITSIO)g(only)i(writes)f(10)i(ro)m(ws)e(of)h(data)g(b)s +(efore)f(closing)i(the)f(table,)h(then)e(NAXIS2)h(will)g(remain)f +(equal)h(to)0 2819 y(20.)50 b(If)33 b(ho)m(w)m(ev)m(er,)i(30)g(ro)m(ws) +e(of)g(data)h(are)g(written)f(to)h(this)f(table,)i(then)e(NAXIS2)h +(will)f(b)s(e)g(increased)g(from)g(20)0 2932 y(to)f(30.)44 +b(The)31 b(one)g(exception)i(to)f(this)f(automatic)i(up)s(dating)d(of)h +(the)h(NAXIS2)f(k)m(eyw)m(ord)h(is)f(if)g(the)h(application)0 +3045 y(program)c(directly)g(mo)s(di\014es)f(the)i(v)-5 +b(alue)28 b(of)g(NAXIS2)g(\(up)f(or)h(do)m(wn\))g(itself)h(just)e(b)s +(efore)h(closing)h(the)f(table.)41 b(In)0 3158 y(this)28 +b(case,)i(CFITSIO)d(do)s(es)h(not)h(up)s(date)e(NAXIS2)i(again,)h +(since)f(it)g(assumes)f(that)h(the)f(application)i(program)0 +3271 y(m)m(ust)i(ha)m(v)m(e)h(had)f(a)g(go)s(o)s(d)g(reason)h(for)f(c)m +(hanging)h(the)f(v)-5 b(alue)33 b(directly)-8 b(.)47 +b(This)31 b(is)h(not)h(recommended,)f(ho)m(w)m(ev)m(er,)0 +3384 y(and)j(is)h(only)g(pro)m(vided)g(for)f(bac)m(kw)m(ard)h +(compatibilit)m(y)i(with)e(soft)m(w)m(are)h(that)g(initially)g(creates) +g(a)f(table)h(with)0 3497 y(a)d(large)h(n)m(um)m(b)s(er)e(of)h(ro)m +(ws,)h(than)f(decreases)g(the)h(NAXIS2)f(v)-5 b(alue)34 +b(to)h(the)f(actual)h(smaller)g(v)-5 b(alue)34 b(just)f(b)s(efore)0 +3610 y(closing)e(the)g(table.)0 3941 y Fd(4.12)136 b(Lo)t(cal)45 +b(FITS)e(Con)l(v)l(en)l(tions)k(supp)t(orted)d(b)l(y)h(FITSIO)0 +4191 y Fi(CFITSIO)25 b(supp)s(orts)g(sev)m(eral)j(lo)s(cal)f(FITS)f +(con)m(v)m(en)m(tions)j(whic)m(h)d(are)h(not)g(de\014ned)e(in)h(the)h +(o\016cial)h(NOST)e(FITS)0 4304 y(standard)k(and)g(whic)m(h)h(are)g +(not)g(necessarily)g(recognized)h(or)f(supp)s(orted)e(b)m(y)i(other)g +(FITS)f(soft)m(w)m(are)i(pac)m(k)-5 b(ages.)0 4417 y(Programmers)36 +b(should)f(b)s(e)g(cautious)i(ab)s(out)e(using)h(these)g(features,)i +(esp)s(ecially)f(if)f(the)g(FITS)f(\014les)h(that)h(are)0 +4530 y(pro)s(duced)31 b(are)i(exp)s(ected)g(to)g(b)s(e)f(pro)s(cessed)g +(b)m(y)h(other)f(soft)m(w)m(are)i(systems)f(whic)m(h)f(do)h(not)f(use)h +(the)f(CFITSIO)0 4642 y(in)m(terface.)0 4930 y Fb(4.12.1)113 +b(Supp)s(ort)37 b(for)h(Long)g(String)f(Keyw)m(ord)h(V)-9 +b(alues.)0 5149 y Fi(The)23 b(length)i(of)f(a)g(standard)f(FITS)g +(string)h(k)m(eyw)m(ord)g(is)g(limited)h(to)f(68)h(c)m(haracters)g(b)s +(ecause)f(it)g(m)m(ust)g(\014t)g(en)m(tirely)0 5262 y(within)35 +b(a)h(single)h(FITS)e(header)h(k)m(eyw)m(ord)g(record.)57 +b(In)35 b(some)h(instances)g(it)h(is)e(necessary)i(to)f(enco)s(de)g +(strings)0 5375 y(longer)27 b(than)f(this)g(limit,)i(so)e(FITSIO)f +(supp)s(orts)f(a)j(lo)s(cal)g(con)m(v)m(en)m(tion)h(in)e(whic)m(h)g +(the)g(string)g(v)-5 b(alue)27 b(is)f(con)m(tin)m(ued)0 +5488 y(o)m(v)m(er)34 b(m)m(ultiple)g(k)m(eyw)m(ords.)49 +b(This)32 b(con)m(tin)m(uation)j(con)m(v)m(en)m(tion)h(uses)c(an)h(amp) +s(ersand)f(c)m(haracter)i(at)g(the)f(end)g(of)0 5601 +y(eac)m(h)c(substring)d(to)i(indicate)h(that)f(it)g(is)g(con)m(tin)m +(ued)g(on)f(the)h(next)g(k)m(eyw)m(ord,)h(and)d(the)i(con)m(tin)m +(uation)i(k)m(eyw)m(ords)0 5714 y(all)44 b(ha)m(v)m(e)h(the)f(name)f +(CONTINUE)g(without)g(an)h(equal)g(sign)f(in)g(column)h(9.)80 +b(The)43 b(string)h(v)-5 b(alue)43 b(ma)m(y)i(b)s(e)p +eop end +%%Page: 22 28 +TeXDict begin 22 27 bop 0 299 a Fi(22)1277 b Fg(CHAPTER)29 +b(4.)72 b(FITSIO)29 b(CONVENTIONS)g(AND)i(GUIDELINES)0 +555 y Fi(con)m(tin)m(ued)e(in)f(this)h(w)m(a)m(y)g(o)m(v)m(er)h(as)e +(man)m(y)h(additional)g(CONTINUE)f(k)m(eyw)m(ords)g(as)h(is)f +(required.)40 b(The)27 b(follo)m(wing)0 668 y(lines)k(illustrate)g +(this)f(con)m(tin)m(uation)j(con)m(v)m(en)m(tion)f(whic)m(h)e(is)h +(used)e(in)h(the)h(v)-5 b(alue)31 b(of)f(the)h(STRKEY)e(k)m(eyw)m(ord:) +0 920 y Fe(LONGSTRN=)45 b('OGIP)i(1.0')524 b(/)47 b(The)g(OGIP)g(Long)f +(String)g(Convention)f(may)i(be)g(used.)0 1033 y(STRKEY)94 +b(=)47 b('This)g(is)g(a)g(very)g(long)g(string)f(keyword&')93 +b(/)47 b(Optional)f(Comment)0 1146 y(CONTINUE)93 b(')48 +b(value)e(that)h(is)g(continued)e(over)i(3)g(keywords)f(in)h(the)g(&)95 +b(')0 1259 y(CONTINUE)e('FITS)47 b(header.')e(/)j(This)e(is)h(another)f +(optional)g(comment.)0 1511 y Fi(It)29 b(is)g(recommended)f(that)h(the) +g(LONGSTRN)f(k)m(eyw)m(ord,)i(as)f(sho)m(wn)f(here,)h(alw)m(a)m(ys)i(b) +s(e)d(included)g(in)g(an)m(y)h(HDU)0 1624 y(that)f(uses)e(this)h +(longstring)h(con)m(v)m(en)m(tion.)42 b(A)27 b(subroutine)f(called)i +(FTPLSW)f(has)g(b)s(een)f(pro)m(vided)h(in)f(CFITSIO)0 +1737 y(to)31 b(write)g(this)f(k)m(eyw)m(ord)h(if)f(it)h(do)s(es)f(not)h +(already)g(exist.)0 1897 y(This)23 b(long)i(string)g(con)m(v)m(en)m +(tion)h(is)e(supp)s(orted)f(b)m(y)h(the)g(follo)m(wing)i(FITSIO)d +(subroutines)g(that)i(deal)g(with)f(string-)0 2010 y(v)-5 +b(alued)30 b(k)m(eyw)m(ords:)286 2262 y Fe(ftgkys)46 +b(-)i(read)f(a)g(string)f(keyword)286 2375 y(ftpkls)g(-)i(write)e +(\(append\))g(a)h(string)f(keyword)286 2488 y(ftikls)g(-)i(insert)e(a)h +(string)g(keyword)286 2601 y(ftmkls)f(-)i(modify)e(the)h(value)f(of)h +(an)h(existing)d(string)h(keyword)286 2714 y(ftukls)g(-)i(update)e(an)h +(existing)f(keyword,)f(or)i(write)g(a)g(new)g(keyword)286 +2827 y(ftdkey)f(-)i(delete)e(a)h(keyword)0 3079 y Fi(These)41 +b(routines)f(will)h(transparen)m(tly)g(read,)j(write,)g(or)d(delete)h +(a)f(long)g(string)g(v)-5 b(alue)41 b(in)g(the)g(FITS)f(\014le,)k(so)0 +3192 y(programmers)36 b(in)g(general)h(do)f(not)h(ha)m(v)m(e)g(to)g(b)s +(e)f(concerned)g(ab)s(out)g(the)g(details)i(of)e(the)h(con)m(v)m(en)m +(tion)h(that)f(is)0 3304 y(used)32 b(to)i(enco)s(de)f(the)g(long)g +(string)g(in)g(the)g(FITS)f(header.)48 b(When)33 b(reading)g(a)g(long)h +(string,)g(one)f(m)m(ust)g(ensure)0 3417 y(that)h(the)f(c)m(haracter)i +(string)f(parameter)g(used)e(in)h(these)h(subroutine)e(calls)j(has)e(b) +s(een)f(declared)i(long)g(enough)0 3530 y(to)d(hold)f(the)h(en)m(tire)g +(string,)g(otherwise)f(the)h(returned)e(string)h(v)-5 +b(alue)31 b(will)g(b)s(e)f(truncated.)0 3690 y(Note)d(that)e(the)h +(more)f(commonly)h(used)e(FITSIO)g(subroutine)h(to)h(write)f(string)g +(v)-5 b(alued)25 b(k)m(eyw)m(ords)h(\(FTPKYS\))0 3803 +y(do)s(es)38 b(NOT)g(supp)s(ort)f(this)h(long)h(string)g(con)m(v)m(en)m +(tion)h(and)e(only)h(supp)s(orts)d(strings)i(up)g(to)h(68)g(c)m +(haracters)h(in)0 3916 y(length.)i(This)30 b(has)g(b)s(een)g(done)h +(delib)s(erately)g(to)h(prev)m(en)m(t)f(programs)g(from)f(inadv)m +(erten)m(tly)i(writing)f(k)m(eyw)m(ords)0 4029 y(using)38 +b(this)h(non-standard)e(con)m(v)m(en)m(tion)k(without)e(the)f(explicit) +i(in)m(ten)m(t)g(of)f(the)g(programmer)f(or)h(user.)64 +b(The)0 4142 y(FTPKLS)28 b(subroutine)g(m)m(ust)h(b)s(e)g(called)h +(instead)g(to)g(write)f(long)h(strings.)40 b(This)28 +b(routine)i(can)f(also)h(b)s(e)f(used)f(to)0 4255 y(write)j(ordinary)e +(string)i(v)-5 b(alues)30 b(less)h(than)f(68)h(c)m(haracters)h(in)e +(length.)0 4544 y Fb(4.12.2)113 b(Arra)m(ys)37 b(of)g(Fixed-Length)j +(Strings)e(in)f(Binary)h(T)-9 b(ables)0 4763 y Fi(CFITSIO)25 +b(supp)s(orts)g(2)i(w)m(a)m(ys)g(to)g(sp)s(ecify)f(that)i(a)f(c)m +(haracter)h(column)e(in)g(a)h(binary)f(table)i(con)m(tains)f(an)g(arra) +m(y)g(of)0 4876 y(\014xed-length)32 b(strings.)46 b(The)32 +b(\014rst)f(w)m(a)m(y)-8 b(,)34 b(whic)m(h)e(is)g(o\016cially)i(supp)s +(orted)c(b)m(y)i(the)h(FITS)e(Standard)g(do)s(cumen)m(t,)0 +4989 y(uses)38 b(the)g(TDIMn)g(k)m(eyw)m(ord.)65 b(F)-8 +b(or)39 b(example,)i(if)d(TF)m(ORMn)g(=)g('60A')h(and)f(TDIMn)g(=)g +('\(12,5\)')i(then)e(that)0 5102 y(column)30 b(will)h(b)s(e)f(in)m +(terpreted)g(as)h(con)m(taining)h(an)e(arra)m(y)h(of)g(5)f(strings,)h +(eac)m(h)g(12)g(c)m(haracters)h(long.)0 5262 y(FITSIO)40 +b(also)i(supp)s(orts)d(a)i(lo)s(cal)h(con)m(v)m(en)m(tion)h(for)e(the)g +(format)h(of)f(the)g(TF)m(ORMn)g(k)m(eyw)m(ord)g(v)-5 +b(alue)42 b(of)f(the)0 5375 y(form)h('rAw')g(where)g('r')g(is)h(an)f +(in)m(teger)i(sp)s(ecifying)e(the)g(total)j(width)c(in)h(c)m(haracters) +i(of)f(the)f(column,)k(and)0 5488 y('w')36 b(is)g(an)g(in)m(teger)h(sp) +s(ecifying)f(the)g(\(\014xed\))g(length)h(of)f(an)g(individual)f(unit)h +(string)f(within)h(the)g(v)m(ector.)59 b(F)-8 b(or)0 +5601 y(example,)47 b(TF)m(ORM1)d(=)f('120A10')j(w)m(ould)d(indicate)h +(that)f(the)h(binary)e(table)i(column)f(is)g(120)h(c)m(haracters)0 +5714 y(wide)32 b(and)g(consists)h(of)g(12)g(10-c)m(haracter)i(length)e +(strings.)47 b(This)31 b(con)m(v)m(en)m(tion)k(is)d(recognized)i(b)m(y) +e(the)h(FITSIO)p eop end +%%Page: 23 29 +TeXDict begin 23 28 bop 0 299 a Fg(4.12.)73 b(LOCAL)29 +b(FITS)h(CONVENTIONS)f(SUPPOR)-8 b(TED)29 b(BY)i(FITSIO)1168 +b Fi(23)0 555 y(subroutines)40 b(that)h(read)g(or)g(write)g(strings)f +(in)h(binary)f(tables.)73 b(The)40 b(Binary)h(T)-8 b(able)42 +b(de\014nition)e(do)s(cumen)m(t)0 668 y(sp)s(eci\014es)31 +b(that)i(other)e(optional)i(c)m(haracters)g(ma)m(y)g(follo)m(w)g(the)e +(datat)m(yp)s(e)i(co)s(de)f(in)f(the)h(TF)m(ORM)g(k)m(eyw)m(ord,)h(so)0 +781 y(this)j(lo)s(cal)i(con)m(v)m(en)m(tion)h(is)e(in)f(compliance)i +(with)e(the)h(FITS)f(standard,)h(although)g(other)g(FITS)f(readers)h +(are)0 894 y(not)31 b(required)e(to)i(recognize)h(this)f(con)m(v)m(en)m +(tion.)0 1054 y(The)25 b(Binary)h(T)-8 b(able)27 b(de\014nition)e(do)s +(cumen)m(t)h(that)h(w)m(as)f(appro)m(v)m(ed)g(b)m(y)g(the)g(IA)m(U)g +(in)g(1994)i(con)m(tains)f(an)e(app)s(endix)0 1167 y(describing)d(an)h +(alternate)h(con)m(v)m(en)m(tion)h(for)e(sp)s(ecifying)f(arra)m(ys)h +(of)g(\014xed)f(or)h(v)-5 b(ariable)24 b(length)f(strings)f(in)h(a)g +(binary)0 1280 y(table)35 b(c)m(haracter)g(column)f(\(with)g(the)h +(form)e('rA:SSTRw/nnn\)'.)50 b(This)33 b(app)s(endix)f(w)m(as)j(not)f +(o\016cially)i(v)m(oted)0 1393 y(on)30 b(b)m(y)h(the)f(IA)m(U)h(and)f +(hence)g(is)h(still)g(pro)m(visional.)42 b(FITSIO)29 +b(do)s(es)h(not)h(curren)m(tly)f(supp)s(ort)f(this)h(prop)s(osal.)0 +1687 y Fb(4.12.3)113 b(Keyw)m(ord)37 b(Units)h(Strings)0 +1907 y Fi(One)f(de\014ciency)h(of)g(the)g(curren)m(t)g(FITS)f(Standard) +f(is)i(that)h(it)f(do)s(es)f(not)h(de\014ne)f(a)i(sp)s(eci\014c)e(con)m +(v)m(en)m(tion)j(for)0 2020 y(recording)30 b(the)g(ph)m(ysical)h(units) +f(of)g(a)g(k)m(eyw)m(ord)h(v)-5 b(alue.)41 b(The)30 b(TUNITn)f(k)m(eyw) +m(ord)h(can)g(b)s(e)g(used)f(to)i(sp)s(ecify)f(the)0 +2133 y(ph)m(ysical)36 b(units)f(of)g(the)h(v)-5 b(alues)36 +b(in)f(a)g(table)i(column,)f(but)f(there)g(is)h(no)f(analogous)i(con)m +(v)m(en)m(tion)g(for)e(k)m(eyw)m(ord)0 2246 y(v)-5 b(alues.)42 +b(The)30 b(commen)m(t)h(\014eld)g(of)f(the)h(k)m(eyw)m(ord)g(is)g +(often)g(used)f(for)g(this)g(purp)s(ose,)g(but)f(the)i(units)f(are)h +(usually)0 2359 y(not)g(sp)s(eci\014ed)e(in)h(a)h(w)m(ell)g(de\014ned)f +(format)g(that)h(FITS)f(readers)g(can)h(easily)g(recognize)h(and)e +(extract.)0 2519 y(T)-8 b(o)28 b(solv)m(e)h(this)e(de\014ciency)-8 +b(,)30 b(FITSIO)c(uses)h(a)h(lo)s(cal)h(con)m(v)m(en)m(tion)h(in)d +(whic)m(h)g(the)h(k)m(eyw)m(ord)g(units)f(are)h(enclosed)g(in)0 +2632 y(square)20 b(brac)m(k)m(ets)j(as)e(the)f(\014rst)g(tok)m(en)i(in) +f(the)f(k)m(eyw)m(ord)i(commen)m(t)f(\014eld;)j(more)d(sp)s +(eci\014cally)-8 b(,)24 b(the)d(op)s(ening)f(square)0 +2745 y(brac)m(k)m(et)28 b(immediately)g(follo)m(ws)f(the)g(slash)f('/') +h(commen)m(t)h(\014eld)e(delimiter)h(and)f(a)g(single)h(space)g(c)m +(haracter.)41 b(The)0 2858 y(follo)m(wing)32 b(examples)f(illustrate)g +(k)m(eyw)m(ords)g(that)g(use)f(this)g(con)m(v)m(en)m(tion:)0 +3121 y Fe(EXPOSURE=)713 b(1800.0)47 b(/)g([s])g(elapsed)f(exposure)f +(time)0 3234 y(V_HELIO)h(=)763 b(16.23)47 b(/)g([km)g(s**\(-1\)])e +(heliocentric)g(velocity)0 3347 y(LAMBDA)94 b(=)763 b(5400.)47 +b(/)g([angstrom])e(central)h(wavelength)0 3460 y(FLUX)190 +b(=)47 b(4.9033487787637465E-30)42 b(/)47 b([J/cm**2/s])e(average)h +(flux)0 3723 y Fi(In)28 b(general,)h(the)g(units)e(named)h(in)g(the)h +(IA)m(U\(1988\))i(St)m(yle)e(Guide)f(are)h(recommended,)f(with)g(the)h +(main)f(excep-)0 3836 y(tion)j(that)g(the)f(preferred)g(unit)f(for)i +(angle)g(is)f('deg')i(for)e(degrees.)0 3996 y(The)24 +b(FTPUNT)g(and)g(FTGUNT)h(subroutines)f(in)g(FITSIO)f(write)i(and)f +(read,)i(resp)s(ectiv)m(ely)-8 b(,)28 b(the)c(k)m(eyw)m(ord)h(unit)0 +4109 y(strings)30 b(in)g(an)h(existing)g(k)m(eyw)m(ord.)0 +4403 y Fb(4.12.4)113 b(HIERAR)m(CH)34 b(Con)m(v)m(en)m(tion)k(for)f +(Extended)h(Keyw)m(ord)f(Names)0 4623 y Fi(CFITSIO)k(supp)s(orts)g(the) +i(HIERAR)m(CH)g(k)m(eyw)m(ord)g(con)m(v)m(en)m(tion)i(whic)m(h)e(allo)m +(ws)h(k)m(eyw)m(ord)f(names)g(that)h(are)0 4736 y(longer)34 +b(then)e(8)i(c)m(haracters)g(and)f(ma)m(y)h(con)m(tain)g(the)f(full)g +(range)g(of)h(prin)m(table)f(ASCI)s(I)e(text)j(c)m(haracters.)51 +b(This)0 4849 y(con)m(v)m(en)m(tion)39 b(w)m(as)f(dev)m(elop)s(ed)f(at) +h(the)f(Europ)s(ean)f(Southern)g(Observ)-5 b(atory)37 +b(\(ESO\))f(to)i(supp)s(ort)d(hierarc)m(hical)0 4962 +y(FITS)30 b(k)m(eyw)m(ord)g(suc)m(h)h(as:)0 5225 y Fe(HIERARCH)46 +b(ESO)g(INS)h(FOCU)g(POS)g(=)g(-0.00002500)e(/)j(Focus)e(position)0 +5488 y Fi(Basically)-8 b(,)55 b(this)47 b(con)m(v)m(en)m(tion)j(uses)d +(the)h(FITS)f(k)m(eyw)m(ord)h('HIERAR)m(CH')h(to)f(indicate)h(that)f +(this)f(con)m(v)m(en-)0 5601 y(tion)e(is)f(b)s(eing)g(used,)j(then)d +(the)g(actual)i(k)m(eyw)m(ord)e(name)h(\()p Fe('ESO)i(INS)f(FOCU)h +(POS')c Fi(in)h(this)g(example\))h(b)s(e-)0 5714 y(gins)40 +b(in)f(column)g(10)i(and)e(can)h(con)m(tain)g(an)m(y)g(prin)m(table)g +(ASCI)s(I)e(text)j(c)m(haracters,)i(including)d(spaces.)68 +b(The)p eop end +%%Page: 24 30 +TeXDict begin 24 29 bop 0 299 a Fi(24)1277 b Fg(CHAPTER)29 +b(4.)72 b(FITSIO)29 b(CONVENTIONS)g(AND)i(GUIDELINES)0 +555 y Fi(equals)44 b(sign)h(marks)e(the)h(end)g(of)g(the)g(k)m(eyw)m +(ord)h(name)f(and)f(is)i(follo)m(w)m(ed)g(b)m(y)f(the)g(usual)g(v)-5 +b(alue)45 b(and)e(com-)0 668 y(men)m(t)31 b(\014elds)f(just)g(as)h(in)f +(standard)g(FITS)g(k)m(eyw)m(ords.)41 b(F)-8 b(urther)30 +b(details)i(of)f(this)f(con)m(v)m(en)m(tion)j(are)e(describ)s(ed)e(at)0 +781 y(h)m(ttp://arcdev.hq.eso.org/dicb/dicd/dic-1-1.4.)q(h)m(tml)36 +b(\(searc)m(h)c(for)e(HIERAR)m(CH\).)0 941 y(This)43 +b(con)m(v)m(en)m(tion)k(allo)m(ws)f(a)e(m)m(uc)m(h)h(broader)e(range)i +(of)f(k)m(eyw)m(ord)h(names)f(than)h(is)f(allo)m(w)m(ed)i(b)m(y)e(the)h +(FITS)0 1054 y(Standard.)40 b(Here)30 b(are)h(more)g(examples)g(of)f +(suc)m(h)g(k)m(eyw)m(ords:)0 1296 y Fe(HIERARCH)46 b(LongKeyword)e(=)k +(47.5)e(/)i(Keyword)e(has)h(>)g(8)g(characters,)e(and)i(mixed)f(case)0 +1409 y(HIERARCH)g(XTE$TEMP)f(=)j(98.6)e(/)i(Keyword)d(contains)h(the)h +('$')g(character)0 1522 y(HIERARCH)f(Earth)g(is)h(a)h(star)e(=)i(F)f(/) +h(Keyword)d(contains)h(embedded)f(spaces)0 1764 y Fi(CFITSIO)40 +b(will)i(transparen)m(tly)g(read)g(and)f(write)g(these)i(k)m(eyw)m +(ords,)i(so)d(application)h(programs)e(do)g(not)h(in)0 +1877 y(general)33 b(need)f(to)h(kno)m(w)f(an)m(ything)h(ab)s(out)f(the) +g(sp)s(eci\014c)g(implemen)m(tation)i(details)f(of)g(the)f(HIERAR)m(CH) +g(con-)0 1990 y(v)m(en)m(tion.)50 b(In)32 b(particular,)j(application)f +(programs)e(do)h(not)h(need)e(to)i(sp)s(ecify)f(the)g(`HIERAR)m(CH')h +(part)f(of)g(the)0 2103 y(k)m(eyw)m(ord)g(name)f(when)g(reading)g(or)g +(writing)h(k)m(eyw)m(ords)f(\(although)h(it)g(ma)m(y)g(b)s(e)f +(included)f(if)i(desired\).)46 b(When)0 2216 y(writing)35 +b(a)g(k)m(eyw)m(ord,)h(CFITSIO)d(\014rst)h(c)m(hec)m(ks)i(to)f(see)g +(if)g(the)g(k)m(eyw)m(ord)g(name)f(is)h(legal)h(as)f(a)g(standard)f +(FITS)0 2328 y(k)m(eyw)m(ord)k(\(no)g(more)f(than)h(8)g(c)m(haracters)h +(long)f(and)f(con)m(taining)i(only)e(letters,)k(digits,)f(or)e(a)g(min) +m(us)e(sign)i(or)0 2441 y(underscore\).)68 b(If)39 b(so)h(it)g(writes)g +(it)g(as)f(a)h(standard)f(FITS)g(k)m(eyw)m(ord,)k(otherwise)d(it)g +(uses)f(the)h(hierarc)m(h)f(con-)0 2554 y(v)m(en)m(tion)34 +b(to)f(write)g(the)f(k)m(eyw)m(ord.)48 b(The)32 b(maxim)m(um)g(k)m(eyw) +m(ord)h(name)f(length)h(is)g(67)g(c)m(haracters,)i(whic)m(h)d(lea)m(v)m +(es)0 2667 y(only)c(1)h(space)g(for)f(the)h(v)-5 b(alue)29 +b(\014eld.)39 b(A)29 b(more)f(practical)i(limit)f(is)g(ab)s(out)f(40)h +(c)m(haracters,)i(whic)m(h)d(lea)m(v)m(es)i(enough)0 +2780 y(ro)s(om)e(for)h(most)f(k)m(eyw)m(ord)h(v)-5 b(alues.)41 +b(CFITSIO)27 b(returns)g(an)h(error)h(if)f(there)h(is)f(not)h(enough)f +(ro)s(om)h(for)f(b)s(oth)g(the)0 2893 y(k)m(eyw)m(ord)k(name)f(and)f +(the)i(k)m(eyw)m(ord)f(v)-5 b(alue)32 b(on)f(the)h(80-c)m(haracter)h +(card,)f(except)g(for)f(string-v)-5 b(alued)32 b(k)m(eyw)m(ords)0 +3006 y(whic)m(h)h(are)g(simply)f(truncated)h(so)g(that)h(the)f(closing) +h(quote)g(c)m(haracter)g(falls)f(in)g(column)g(80.)49 +b(In)32 b(the)h(curren)m(t)0 3119 y(implemen)m(tation,)e(CFITSIO)c +(preserv)m(es)i(the)g(case)h(of)f(the)g(letters)h(when)e(writing)h(the) +g(k)m(eyw)m(ord)g(name,)g(but)f(it)0 3232 y(is)d(case-insensitiv)m(e)i +(when)d(reading)h(or)g(searc)m(hing)h(for)f(a)g(k)m(eyw)m(ord.)40 +b(The)24 b(curren)m(t)h(implemen)m(tation)h(allo)m(ws)h(an)m(y)0 +3345 y(ASCI)s(I)i(text)j(c)m(haracter)h(\(ASCI)s(I)c(32)j(to)f(ASCI)s +(I)f(126\))i(in)f(the)g(k)m(eyw)m(ord)g(name)g(except)h(for)e(the)h +('=')g(c)m(haracter.)0 3458 y(A)f(space)h(is)g(also)g(required)f(on)g +(either)h(side)f(of)h(the)f(equal)h(sign.)0 3788 y Fd(4.13)136 +b(Optimizing)45 b(Co)t(de)g(for)h(Maxim)l(um)f(Pro)t(cessing)g(Sp)t +(eed)0 4039 y Fi(CFITSIO)22 b(has)h(b)s(een)f(carefully)i(designed)f +(to)h(obtain)g(the)f(highest)h(p)s(ossible)e(sp)s(eed)h(when)f(reading) +h(and)g(writing)0 4152 y(FITS)33 b(\014les.)51 b(In)33 +b(order)h(to)g(ac)m(hiev)m(e)i(the)e(b)s(est)g(p)s(erformance,)g(ho)m +(w)m(ev)m(er,)i(application)g(programmers)d(m)m(ust)h(b)s(e)0 +4264 y(careful)24 b(to)h(call)g(the)f(CFITSIO)f(routines)g +(appropriately)i(and)e(in)h(an)f(e\016cien)m(t)j(sequence;)h +(inappropriate)c(usage)0 4377 y(of)31 b(CFITSIO)d(routines)j(can)f +(greatly)i(slo)m(w)f(do)m(wn)f(the)h(execution)g(sp)s(eed)f(of)g(a)h +(program.)0 4538 y(The)f(maxim)m(um)h(p)s(ossible)f(I/O)h(sp)s(eed)f +(of)h(CFITSIO)e(dep)s(ends)g(of)i(course)g(on)f(the)h(t)m(yp)s(e)g(of)g +(computer)g(system)0 4650 y(that)g(it)f(is)g(running)e(on.)41 +b(As)30 b(a)g(rough)g(guide,)g(the)g(curren)m(t)g(generation)h(of)f(w)m +(orkstations)h(can)g(ac)m(hiev)m(e)h(sp)s(eeds)0 4763 +y(of)j(2)g({)g(10)g(MB/s)h(when)e(reading)h(or)f(writing)h(FITS)f +(images)i(and)e(similar,)i(or)f(sligh)m(tly)h(slo)m(w)m(er)g(sp)s(eeds) +d(with)0 4876 y(FITS)c(binary)h(tables.)41 b(Reading)31 +b(of)f(FITS)g(\014les)g(can)h(o)s(ccur)f(at)h(ev)m(en)f(higher)g(rates) +h(\(30MB/s)i(or)d(more\))h(if)f(the)0 4989 y(FITS)c(\014le)h(is)f +(still)i(cac)m(hed)g(in)e(system)h(memory)f(follo)m(wing)j(a)e +(previous)f(read)g(or)h(write)g(op)s(eration)g(on)g(the)g(same)0 +5102 y(\014le.)44 b(T)-8 b(o)32 b(more)g(accurately)h(predict)e(the)h +(b)s(est)f(p)s(erformance)g(that)h(is)f(p)s(ossible)g(on)h(an)m(y)g +(particular)f(system,)i(a)0 5215 y(diagnostic)h(program)f(called)h +(\\sp)s(eed.c")f(is)g(included)f(with)h(the)g(CFITSIO)e(distribution)h +(whic)m(h)h(can)g(b)s(e)f(run)0 5328 y(to)f(appro)m(ximately)h(measure) +e(the)h(maxim)m(um)f(p)s(ossible)g(sp)s(eed)f(of)i(writing)f(and)g +(reading)h(a)f(test)i(FITS)d(\014le.)0 5488 y(The)k(follo)m(wing)h(2)g +(sections)g(pro)m(vide)g(some)f(bac)m(kground)g(on)h(ho)m(w)f(CFITSIO)f +(in)m(ternally)i(manages)g(the)f(data)0 5601 y(I/O)g(and)g(describ)s +(es)f(some)i(strategies)h(that)f(ma)m(y)g(b)s(e)e(used)h(to)h(optimize) +g(the)g(pro)s(cessing)f(sp)s(eed)f(of)h(soft)m(w)m(are)0 +5714 y(that)e(uses)f(CFITSIO.)p eop end +%%Page: 25 31 +TeXDict begin 25 30 bop 0 299 a Fg(4.13.)73 b(OPTIMIZING)29 +b(CODE)h(F)m(OR)h(MAXIMUM)g(PR)m(OCESSING)f(SPEED)971 +b Fi(25)0 555 y Fb(4.13.1)113 b(Bac)m(kground)38 b(Information:)50 +b(Ho)m(w)37 b(CFITSIO)h(Manages)h(Data)f(I/O)0 776 y +Fi(Man)m(y)22 b(CFITSIO)e(op)s(erations)i(in)m(v)m(olv)m(e)i +(transferring)d(only)h(a)g(small)g(n)m(um)m(b)s(er)f(of)h(b)m(ytes)g +(to)g(or)g(from)f(the)h(FITS)f(\014le)0 889 y(\(e.g,)31 +b(reading)e(a)g(k)m(eyw)m(ord,)h(or)f(writing)g(a)g(ro)m(w)g(in)f(a)h +(table\);)i(it)f(w)m(ould)e(b)s(e)g(v)m(ery)i(ine\016cien)m(t)g(to)f +(ph)m(ysically)h(read)0 1002 y(or)i(write)h(suc)m(h)f(small)g(blo)s(c)m +(ks)h(of)f(data)h(directly)g(in)f(the)g(FITS)g(\014le)g(on)g(disk,)h +(therefore)f(CFITSIO)f(main)m(tains)0 1115 y(a)38 b(set)g(of)g(in)m +(ternal)h(Input{Output)c(\(IO\))j(bu\013ers)f(in)g(RAM)h(memory)g(that) +g(eac)m(h)h(con)m(tain)g(one)f(FITS)f(blo)s(c)m(k)0 1228 +y(\(2880)27 b(b)m(ytes\))f(of)f(data.)40 b(Whenev)m(er)25 +b(CFITSIO)f(needs)g(to)i(access)g(data)g(in)f(the)g(FITS)f(\014le,)j +(it)e(\014rst)f(transfers)h(the)0 1341 y(FITS)30 b(blo)s(c)m(k)h(con)m +(taining)h(those)f(b)m(ytes)g(in)m(to)g(one)g(of)f(the)h(IO)f +(bu\013ers)f(in)h(memory)-8 b(.)42 b(The)30 b(next)g(time)h(CFITSIO)0 +1454 y(needs)36 b(to)g(access)i(b)m(ytes)e(in)g(the)g(same)h(blo)s(c)m +(k)f(it)h(can)f(then)g(go)h(to)f(the)h(fast)f(IO)f(bu\013er)g(rather)h +(than)g(using)g(a)0 1567 y(m)m(uc)m(h)c(slo)m(w)m(er)i(system)e(disk)g +(access)h(routine.)46 b(The)32 b(n)m(um)m(b)s(er)f(of)h(a)m(v)-5 +b(ailable)35 b(IO)d(bu\013ers)f(is)h(determined)g(b)m(y)g(the)0 +1680 y(NIOBUF)f(parameter)g(\(in)f(\014tsio2.h\))h(and)f(is)h(curren)m +(tly)f(set)h(to)g(40.)0 1840 y(Whenev)m(er)24 b(CFITSIO)f(reads)g(or)h +(writes)g(data)g(it)h(\014rst)e(c)m(hec)m(ks)i(to)g(see)f(if)g(that)g +(blo)s(c)m(k)h(of)f(the)g(FITS)f(\014le)g(is)h(already)0 +1953 y(loaded)33 b(in)m(to)g(one)f(of)g(the)g(IO)g(bu\013ers.)44 +b(If)32 b(not,)h(and)e(if)h(there)g(is)g(an)g(empt)m(y)h(IO)e(bu\013er) +g(a)m(v)-5 b(ailable,)35 b(then)d(it)h(will)0 2066 y(load)g(that)h(blo) +s(c)m(k)f(in)m(to)g(the)g(IO)g(bu\013er)e(\(when)h(reading)h(a)g(FITS)f +(\014le\))h(or)g(will)g(initialize)i(a)e(new)f(blo)s(c)m(k)i(\(when)0 +2179 y(writing)j(to)h(a)g(FITS)f(\014le\).)62 b(If)37 +b(all)h(the)g(IO)e(bu\013ers)h(are)g(already)h(full,)h(it)f(m)m(ust)g +(decide)f(whic)m(h)g(one)h(to)g(reuse)0 2291 y(\(generally)c(the)f(one) +g(that)g(has)f(b)s(een)g(accessed)i(least)f(recen)m(tly\),)i(and)d +(\015ush)f(the)i(con)m(ten)m(ts)h(bac)m(k)g(to)f(disk)f(if)g(it)0 +2404 y(has)e(b)s(een)g(mo)s(di\014ed)f(b)s(efore)h(loading)h(the)g(new) +f(blo)s(c)m(k.)0 2565 y(The)g(one)g(ma)5 b(jor)30 b(exception)i(to)f +(the)f(ab)s(o)m(v)m(e)h(pro)s(cess)f(o)s(ccurs)g(whenev)m(er)g(a)g +(large)i(con)m(tiguous)f(set)g(of)f(b)m(ytes)h(are)0 +2677 y(accessed,)37 b(as)d(migh)m(t)i(o)s(ccur)e(when)f(reading)i(or)f +(writing)g(a)h(FITS)f(image.)54 b(In)34 b(this)g(case)h(CFITSIO)e(b)m +(ypasses)0 2790 y(the)i(in)m(ternal)h(IO)f(bu\013ers)f(and)g(simply)h +(reads)g(or)g(writes)h(the)f(desired)g(b)m(ytes)g(directly)h(in)f(the)g +(disk)g(\014le)g(with)0 2903 y(a)i(single)g(call)g(to)g(a)g(lo)m(w-lev) +m(el)i(\014le)d(read)g(or)h(write)f(routine.)58 b(The)36 +b(minim)m(um)g(threshold)f(for)h(the)h(n)m(um)m(b)s(er)e(of)0 +3016 y(b)m(ytes)40 b(to)g(read)f(or)g(write)g(this)h(w)m(a)m(y)g(is)f +(set)h(b)m(y)f(the)g(MINDIRECT)g(parameter)h(and)e(is)i(curren)m(tly)f +(set)h(to)g(3)0 3129 y(FITS)28 b(blo)s(c)m(ks)g(=)g(8640)i(b)m(ytes.)41 +b(This)28 b(is)g(the)g(most)h(e\016cien)m(t)h(w)m(a)m(y)f(to)g(read)g +(or)f(write)h(large)g(c)m(h)m(unks)f(of)g(data)i(and)0 +3242 y(can)37 b(ac)m(hiev)m(e)i(IO)d(transfer)g(rates)h(of)g(5)g({)g +(10MB/s)i(or)d(greater.)61 b(Note)38 b(that)f(this)g(fast)g(direct)g +(IO)f(pro)s(cess)g(is)0 3355 y(not)29 b(applicable)g(when)e(accessing)j +(columns)f(of)f(data)h(in)f(a)h(FITS)f(table)h(b)s(ecause)g(the)f(b)m +(ytes)h(are)g(generally)h(not)0 3468 y(con)m(tiguous)g(since)f(they)g +(are)h(in)m(terlea)m(v)m(ed)h(b)m(y)e(the)g(other)g(columns)g(of)g +(data)g(in)g(the)g(table.)41 b(This)28 b(explains)h(wh)m(y)0 +3581 y(the)i(sp)s(eed)e(for)h(accessing)i(FITS)e(tables)h(is)f +(generally)i(slo)m(w)m(er)f(than)g(accessing)g(FITS)f(images.)0 +3741 y(Giv)m(en)i(this)g(bac)m(kground)f(information,)h(the)g(general)g +(strategy)h(for)e(e\016cien)m(tly)i(accessing)g(FITS)e(\014les)g +(should)0 3854 y(no)m(w)36 b(b)s(e)g(apparen)m(t:)52 +b(when)35 b(dealing)i(with)f(FITS)g(images,)j(read)d(or)g(write)g +(large)i(c)m(h)m(unks)e(of)g(data)h(at)g(a)f(time)0 3967 +y(so)30 b(that)h(the)f(direct)h(IO)e(mec)m(hanism)h(will)h(b)s(e)e(in)m +(v)m(ok)m(ed;)j(when)d(accessing)j(FITS)d(headers)h(or)g(FITS)f +(tables,)i(on)0 4080 y(the)k(other)g(hand,)g(once)g(a)g(particular)h +(FITS)e(blo)s(c)m(k)h(has)f(b)s(een)g(loading)i(in)m(to)g(one)f(of)g +(the)f(IO)h(bu\013ers,)g(try)f(to)0 4193 y(access)39 +b(all)f(the)f(needed)g(information)h(in)f(that)h(blo)s(c)m(k)g(b)s +(efore)f(it)h(gets)g(\015ushed)d(out)j(of)g(the)f(IO)g(bu\013er.)60 +b(It)38 b(is)0 4305 y(imp)s(ortan)m(t)31 b(to)h(a)m(v)m(oid)g(the)f +(situation)h(where)f(the)g(same)g(FITS)f(blo)s(c)m(k)i(is)f(b)s(eing)f +(read)h(then)g(\015ushed)e(from)h(a)h(IO)0 4418 y(bu\013er)e(m)m +(ultiple)i(times.)0 4578 y(The)f(follo)m(wing)i(section)f(giv)m(es)h +(more)e(sp)s(eci\014c)h(suggestions)g(for)f(optimizing)i(the)e(use)g +(of)h(CFITSIO.)0 4881 y Fb(4.13.2)113 b(Optimization)38 +b(Strategies)0 5102 y Fi(1.)43 b(Because)32 b(the)f(data)g(in)g(FITS)f +(\014les)h(is)g(alw)m(a)m(ys)h(stored)f(in)g("big-endian")h(b)m(yte)f +(order,)g(where)f(the)h(\014rst)f(b)m(yte)0 5215 y(of)g(n)m(umeric)h(v) +-5 b(alues)30 b(con)m(tains)i(the)e(most)h(signi\014can)m(t)g(bits)f +(and)g(the)g(last)i(b)m(yte)e(con)m(tains)i(the)e(least)i(signi\014can) +m(t)0 5328 y(bits,)e(CFITSIO)f(m)m(ust)h(sw)m(ap)g(the)g(order)f(of)h +(the)h(b)m(ytes)f(when)f(reading)h(or)g(writing)g(FITS)g(\014les)g +(when)f(running)0 5441 y(on)k(little-endian)i(mac)m(hines)f(\(e.g.,)i +(Lin)m(ux)d(and)g(Microsoft)i(Windo)m(ws)e(op)s(erating)h(systems)g +(running)d(on)j(PCs)0 5554 y(with)c(x86)h(CPUs\).)0 5714 +y(On)i(fairly)h(new)f(CPUs)g(that)i(supp)s(ort)d("SSSE3")h(mac)m(hine)h +(instructions)g(\(e.g.,)i(starting)f(with)e(In)m(tel)i(Core)f(2)p +eop end +%%Page: 26 32 +TeXDict begin 26 31 bop 0 299 a Fi(26)1277 b Fg(CHAPTER)29 +b(4.)72 b(FITSIO)29 b(CONVENTIONS)g(AND)i(GUIDELINES)0 +555 y Fi(CPUs)21 b(in)h(2007,)j(and)d(in)f(AMD)i(CPUs)e(b)s(eginning)g +(in)h(2011\))i(signi\014can)m(tly)f(faster)f(4-b)m(yte)h(and)e(8-b)m +(yte)i(sw)m(apping)0 668 y(algorithms)k(are)g(a)m(v)-5 +b(ailable.)42 b(These)26 b(faster)h(b)m(yte)g(sw)m(apping)f(functions)g +(are)h(not)g(used)e(b)m(y)i(default)f(in)g(CFITSIO)0 +781 y(\(b)s(ecause)c(of)f(the)h(p)s(oten)m(tial)g(co)s(de)g(p)s +(ortablilit)m(y)g(issues\),)i(but)c(users)h(can)g(enable)h(them)f(on)h +(supp)s(orted)d(platforms)0 894 y(b)m(y)38 b(adding)f(the)h +(appropriate)f(compiler)i(\015ags)e(\(-mssse3)i(with)e(gcc)i(or)f(icc)g +(on)g(lin)m(ux\))g(when)e(compiling)j(the)0 1007 y(sw)m(appro)s(c.c)30 +b(source)g(\014le,)g(whic)m(h)g(will)g(allo)m(w)i(the)e(compiler)g(to)h +(generate)h(co)s(de)e(using)f(the)h(SSSE3)f(instruction)0 +1120 y(set.)41 b(A)28 b(con)m(v)m(enien)m(t)i(w)m(a)m(y)f(to)g(do)g +(this)f(is)g(to)h(con\014gure)f(the)g(CFITSIO)f(library)h(with)g(the)g +(follo)m(wing)i(command:)95 1386 y Fe(>)96 b(./configure)44 +b(--enable-ssse3)0 1652 y Fi(Note,)37 b(ho)m(w)m(ev)m(er,)h(that)d(a)g +(binary)f(executable)j(\014le)e(that)g(is)g(created)h(using)e(these)h +(faster)g(functions)g(will)g(only)0 1765 y(run)c(on)h(mac)m(hines)g +(that)h(supp)s(ort)d(the)i(SSSE3)f(mac)m(hine)i(instructions.)45 +b(It)33 b(will)f(crash)g(on)g(mac)m(hines)g(that)h(do)0 +1878 y(not)e(supp)s(ort)d(them.)0 2038 y(F)-8 b(or)36 +b(faster)f(2-b)m(yte)i(sw)m(aps)e(on)g(virtually)g(all)h(x86-64)h(CPUs) +e(\(ev)m(en)h(those)g(that)f(do)g(not)h(supp)s(ort)d(SSSE3\),)j(a)0 +2151 y(v)-5 b(arian)m(t)26 b(using)e(only)g(SSE2)g(instructions)h +(exists.)39 b(SSE2)24 b(is)h(enabled)f(b)m(y)h(default)g(on)f(x86)p +3066 2151 28 4 v 34 w(64)h(CPUs)f(with)h(64-bit)0 2264 +y(op)s(erating)30 b(systems)f(\(and)g(is)g(also)i(automatically)h +(enabled)d(b)m(y)g(the)g({enable-ssse3)i(\015ag\).)41 +b(When)30 b(running)d(on)0 2377 y(x86)p 143 2377 V 34 +w(64)k(CPUs)g(with)f(32-bit)i(op)s(erating)g(systems,)f(these)g(faster) +h(2-b)m(yte)g(sw)m(apping)f(algorithms)g(are)h(not)f(used)0 +2490 y(b)m(y)f(default)h(in)f(CFITSIO,)f(but)h(can)g(b)s(e)g(enabled)g +(explicitly)i(with:)0 2756 y Fe(./configure)45 b(--enable-sse2)0 +3022 y Fi(Preliminary)f(testing)h(indicates)g(that)g(these)f(SSSE3)f +(and)g(SSE2)g(based)h(b)m(yte-sw)m(apping)h(algorithms)g(can)0 +3135 y(b)s(o)s(ost)31 b(the)h(CFITSIO)e(p)s(erformance)h(when)f +(reading)i(or)f(writing)h(FITS)f(images)h(b)m(y)g(20\045)g(-)g(30\045)g +(or)f(more.)45 b(It)0 3248 y(is)36 b(imp)s(ortan)m(t)g(to)g(note,)i(ho) +m(w)m(ev)m(er,)h(that)d(compiler)g(optimization)i(m)m(ust)e(b)s(e)f +(turned)f(on)i(\(e.g.,)j(b)m(y)d(using)f(the)0 3361 y(-O1)f(or)g(-O2)g +(\015ags)g(in)g(gcc\))h(when)e(building)g(programs)h(that)g(use)g +(these)g(fast)g(b)m(yte-sw)m(apping)h(algorithms)f(in)0 +3474 y(order)d(to)h(reap)f(the)h(full)f(b)s(ene\014t)g(of)g(the)h +(SSSE3)e(and)h(SSE2)g(instructions;)h(without)f(optimization,)j(the)e +(co)s(de)0 3587 y(ma)m(y)f(actually)h(run)d(slo)m(w)m(er)i(than)f(when) +g(using)g(more)g(traditional)i(b)m(yte-sw)m(apping)f(tec)m(hniques.)0 +3747 y(2.)54 b(When)34 b(dealing)h(with)g(a)g(FITS)e(primary)h(arra)m +(y)h(or)g(IMA)m(GE)g(extension,)i(it)e(is)f(more)h(e\016cien)m(t)h(to)f +(read)g(or)0 3860 y(write)c(large)g(c)m(h)m(unks)f(of)g(the)h(image)g +(at)h(a)e(time)h(\(at)h(least)f(3)g(FITS)f(blo)s(c)m(ks)g(=)g(8640)i(b) +m(ytes\))f(so)g(that)g(the)f(direct)0 3973 y(IO)j(mec)m(hanism)h(will)f +(b)s(e)g(used)g(as)g(describ)s(ed)g(in)g(the)g(previous)g(section.)51 +b(Smaller)34 b(c)m(h)m(unks)f(of)g(data)h(are)g(read)0 +4086 y(or)d(written)g(via)h(the)f(IO)f(bu\013ers,)g(whic)m(h)h(is)g +(somewhat)g(less)g(e\016cien)m(t)i(b)s(ecause)e(of)g(the)g(extra)h(cop) +m(y)f(op)s(eration)0 4199 y(and)26 b(additional)h(b)s(o)s(okk)m(eeping) +g(steps)g(that)g(are)g(required.)39 b(In)26 b(principle)g(it)h(is)g +(more)f(e\016cien)m(t)i(to)g(read)e(or)h(write)0 4312 +y(as)i(big)g(an)g(arra)m(y)h(of)f(image)h(pixels)f(at)h(one)f(time)g +(as)h(p)s(ossible,)f(ho)m(w)m(ev)m(er,)h(if)f(the)h(arra)m(y)f(b)s +(ecomes)g(so)g(large)h(that)0 4425 y(the)i(op)s(erating)g(system)f +(cannot)h(store)g(it)g(all)h(in)e(RAM,)h(then)f(the)h(p)s(erformance)f +(ma)m(y)h(b)s(e)f(degraded)g(b)s(ecause)0 4538 y(of)g(the)f(increased)h +(sw)m(apping)f(of)g(virtual)h(memory)f(to)h(disk.)0 4698 +y(3.)51 b(When)33 b(dealing)i(with)e(FITS)g(tables,)j(the)e(most)g(imp) +s(ortan)m(t)g(e\016ciency)g(factor)h(in)e(the)h(soft)m(w)m(are)h +(design)f(is)0 4811 y(to)j(read)f(or)g(write)g(the)g(data)h(in)f(the)g +(FITS)g(\014le)g(in)g(a)g(single)h(pass)f(through)f(the)h(\014le.)58 +b(An)36 b(example)h(of)f(p)s(o)s(or)0 4924 y(program)g(design)h(w)m +(ould)f(b)s(e)g(to)h(read)g(a)f(large,)k(3-column)d(table)g(b)m(y)g +(sequen)m(tially)h(reading)f(the)f(en)m(tire)i(\014rst)0 +5036 y(column,)25 b(then)f(going)h(bac)m(k)f(to)h(read)e(the)h(2nd)g +(column,)h(and)e(\014nally)h(the)g(3rd)f(column;)j(this)e(ob)m(viously) +g(requires)0 5149 y(3)h(passes)f(through)f(the)i(\014le)f(whic)m(h)g +(could)h(triple)f(the)h(execution)g(time)g(of)g(an)f(I/O)g(limited)h +(program.)38 b(F)-8 b(or)25 b(small)0 5262 y(tables)31 +b(this)f(is)h(not)f(imp)s(ortan)m(t,)h(but)f(when)f(reading)h(m)m +(ulti-megab)m(yte)j(sized)e(tables)g(these)g(ine\016ciencies)h(can)0 +5375 y(b)s(ecome)d(signi\014can)m(t.)41 b(The)28 b(more)h(e\016cien)m +(t)h(pro)s(cedure)d(in)i(this)f(case)i(is)e(to)i(read)e(or)h(write)g +(only)f(as)h(man)m(y)g(ro)m(ws)0 5488 y(of)g(the)g(table)h(as)f(will)h +(\014t)e(in)m(to)i(the)g(a)m(v)-5 b(ailable)31 b(in)m(ternal)f(I/O)f +(bu\013ers,)f(then)h(access)h(all)g(the)f(necessary)g(columns)0 +5601 y(of)i(data)h(within)e(that)i(range)f(of)g(ro)m(ws.)43 +b(Then)29 b(after)j(the)f(program)g(is)g(completely)h(\014nished)e +(with)g(the)i(data)f(in)0 5714 y(those)i(ro)m(ws)e(it)i(can)f(mo)m(v)m +(e)i(on)e(to)g(the)h(next)f(range)g(of)g(ro)m(ws)g(that)h(will)f(\014t) +g(in)g(the)g(bu\013ers,)f(con)m(tin)m(uing)i(in)f(this)p +eop end +%%Page: 27 33 +TeXDict begin 27 32 bop 0 299 a Fg(4.13.)73 b(OPTIMIZING)29 +b(CODE)h(F)m(OR)h(MAXIMUM)g(PR)m(OCESSING)f(SPEED)971 +b Fi(27)0 555 y(w)m(a)m(y)28 b(un)m(til)f(the)f(en)m(tire)i(\014le)f +(has)f(b)s(een)g(pro)s(cessed.)39 b(By)27 b(using)f(this)h(pro)s +(cedure)e(of)i(accessing)h(all)g(the)e(columns)h(of)0 +668 y(a)j(table)g(in)f(parallel)h(rather)f(than)g(sequen)m(tially)-8 +b(,)32 b(eac)m(h)e(blo)s(c)m(k)g(of)g(the)f(FITS)g(\014le)g(will)g +(only)h(b)s(e)e(read)i(or)f(written)0 781 y(once.)0 941 +y(The)g(optimal)h(n)m(um)m(b)s(er)e(of)i(ro)m(ws)f(to)i(read)e(or)g +(write)h(at)g(one)g(time)g(in)f(a)h(giv)m(en)g(table)h(dep)s(ends)c(on) +j(the)f(width)g(of)0 1054 y(the)j(table)h(ro)m(w,)g(on)f(the)g(n)m(um)m +(b)s(er)f(of)h(I/O)g(bu\013ers)f(that)i(ha)m(v)m(e)g(b)s(een)e(allo)s +(cated)j(in)e(FITSIO,)f(and)h(also)h(on)f(the)0 1167 +y(n)m(um)m(b)s(er)27 b(of)i(other)f(FITS)g(\014les)g(that)h(are)g(op)s +(en)f(at)h(the)g(same)g(time)g(\(since)g(one)g(I/O)f(bu\013er)f(is)i +(alw)m(a)m(ys)h(reserv)m(ed)0 1280 y(for)k(eac)m(h)h(op)s(en)f(FITS)f +(\014le\).)53 b(F)-8 b(ortunately)g(,)37 b(a)e(FITSIO)e(routine)h(is)h +(a)m(v)-5 b(ailable)36 b(that)f(will)f(return)g(the)g(optimal)0 +1393 y(n)m(um)m(b)s(er)e(of)i(ro)m(ws)g(for)g(a)g(giv)m(en)g(table:)49 +b(call)35 b(ftgrsz\(unit,)g(nro)m(ws,)f(status\).)52 +b(It)34 b(is)g(not)g(critical)h(to)g(use)e(exactly)0 +1506 y(the)f(v)-5 b(alue)32 b(of)f(nro)m(ws)g(returned)g(b)m(y)g(this)g +(routine,)h(as)g(long)g(as)g(one)g(do)s(es)f(not)h(exceed)g(it.)45 +b(Using)32 b(a)g(v)m(ery)f(small)0 1619 y(v)-5 b(alue)32 +b(ho)m(w)m(ev)m(er)i(can)e(also)h(lead)f(to)h(p)s(o)s(or)e(p)s +(erformance)g(b)s(ecause)h(of)g(the)g(o)m(v)m(erhead)h(from)f(the)g +(larger)g(n)m(um)m(b)s(er)0 1732 y(of)f(subroutine)e(calls.)0 +1892 y(The)36 b(optimal)h(n)m(um)m(b)s(er)f(of)g(ro)m(ws)h(returned)e +(b)m(y)h(ftgrsz)h(is)g(v)-5 b(alid)37 b(only)f(as)h(long)g(as)g(the)f +(application)i(program)0 2005 y(is)c(only)h(reading)g(or)f(writing)g +(data)h(in)g(the)f(sp)s(eci\014ed)g(table.)54 b(An)m(y)34 +b(other)h(calls)g(to)g(access)h(data)f(in)f(the)h(table)0 +2118 y(header)26 b(w)m(ould)f(cause)i(additional)f(blo)s(c)m(ks)h(of)f +(data)g(to)h(b)s(e)e(loaded)h(in)m(to)h(the)f(I/O)g(bu\013ers)f +(displacing)h(data)g(from)0 2230 y(the)j(original)h(table,)g(and)e +(should)f(b)s(e)h(a)m(v)m(oided)i(during)e(the)h(critical)h(p)s(erio)s +(d)e(while)g(the)h(table)h(is)e(b)s(eing)g(read)h(or)0 +2343 y(written.)0 2503 y(4.)39 b(Use)24 b(binary)f(table)h(extensions)g +(rather)f(than)h(ASCI)s(I)e(table)i(extensions)g(for)f(b)s(etter)h +(e\016ciency)h(when)d(dealing)0 2616 y(with)37 b(tabular)h(data.)62 +b(The)37 b(I/O)g(to)h(ASCI)s(I)e(tables)i(is)g(slo)m(w)m(er)g(b)s +(ecause)g(of)f(the)h(o)m(v)m(erhead)h(in)e(formatting)h(or)0 +2729 y(parsing)30 b(the)h(ASCI)s(I)f(data)h(\014elds,)g(and)f(b)s +(ecause)h(ASCI)s(I)e(tables)i(are)g(ab)s(out)g(t)m(wice)h(as)f(large)h +(as)f(binary)f(tables)0 2842 y(with)g(the)h(same)f(information)h(con)m +(ten)m(t.)0 3002 y(5.)64 b(Design)39 b(soft)m(w)m(are)g(so)g(that)f(it) +h(reads)f(the)g(FITS)f(header)h(k)m(eyw)m(ords)g(in)g(the)g(same)h +(order)e(in)h(whic)m(h)g(they)0 3115 y(o)s(ccur)33 b(in)g(the)g +(\014le.)49 b(When)32 b(reading)i(k)m(eyw)m(ords,)g(FITSIO)e(searc)m +(hes)i(forw)m(ard)e(starting)i(from)e(the)i(p)s(osition)f(of)0 +3228 y(the)c(last)i(k)m(eyw)m(ord)e(that)h(w)m(as)g(read.)40 +b(If)29 b(it)g(reac)m(hes)i(the)e(end)g(of)g(the)h(header)f(without)g +(\014nding)f(the)h(k)m(eyw)m(ord,)h(it)0 3341 y(then)j(go)s(es)h(bac)m +(k)g(to)h(the)e(start)h(of)g(the)g(header)f(and)g(con)m(tin)m(ues)h +(the)g(searc)m(h)g(do)m(wn)f(to)h(the)g(p)s(osition)f(where)g(it)0 +3454 y(started.)41 b(In)30 b(practice,)i(as)e(long)h(as)g(the)f(en)m +(tire)i(FITS)d(header)h(can)h(\014t)f(at)h(one)g(time)g(in)f(the)g(a)m +(v)-5 b(ailable)33 b(in)m(ternal)0 3567 y(I/O)g(bu\013ers,)h(then)f +(the)h(header)f(k)m(eyw)m(ord)h(access)h(will)e(b)s(e)g(v)m(ery)h(fast) +g(and)f(it)h(mak)m(es)g(little)h(di\013erence)f(whic)m(h)0 +3680 y(order)c(they)g(are)h(accessed.)0 3840 y(6.)40 +b(Av)m(oid)29 b(the)e(use)h(of)f(scaling)i(\(b)m(y)f(using)f(the)h +(BSCALE)e(and)h(BZER)m(O)h(or)f(TSCAL)g(and)g(TZER)m(O)f(k)m(eyw)m +(ords\))0 3953 y(in)35 b(FITS)f(\014les)g(since)i(the)f(scaling)h(op)s +(erations)f(add)f(to)i(the)f(pro)s(cessing)f(time)i(needed)e(to)i(read) +f(or)g(write)g(the)0 4066 y(data.)60 b(In)36 b(some)i(cases)f(it)g(ma)m +(y)h(b)s(e)e(more)h(e\016cien)m(t)h(to)f(temp)s(orarily)g(turn)f(o\013) +h(the)g(scaling)h(\(using)e(ftpscl)h(or)0 4179 y(fttscl\))32 +b(and)d(then)h(read)h(or)f(write)h(the)f(ra)m(w)h(unscaled)f(v)-5 +b(alues)31 b(in)f(the)g(FITS)g(\014le.)0 4339 y(7.)40 +b(Av)m(oid)27 b(using)g(the)g('implicit)h(datat)m(yp)s(e)f(con)m(v)m +(ersion')h(capabilit)m(y)h(in)d(FITSIO.)g(F)-8 b(or)28 +b(instance,)g(when)e(reading)0 4452 y(a)f(FITS)e(image)j(with)e(BITPIX) +g(=)g(-32)i(\(32-bit)g(\015oating)f(p)s(oin)m(t)f(pixels\),)j(read)d +(the)h(data)g(in)m(to)g(a)g(single)g(precision)0 4565 +y(\015oating)e(p)s(oin)m(t)e(data)i(arra)m(y)f(in)g(the)g(program.)37 +b(F)-8 b(orcing)23 b(FITSIO)e(to)h(con)m(v)m(ert)i(the)e(data)g(to)h(a) +f(di\013eren)m(t)g(datat)m(yp)s(e)0 4678 y(can)31 b(signi\014can)m(tly) +g(slo)m(w)g(the)g(program.)0 4838 y(8.)57 b(Where)36 +b(feasible,)i(design)e(FITS)f(binary)g(tables)h(using)f(v)m(ector)j +(column)d(elemen)m(ts)i(so)f(that)g(the)g(data)h(are)0 +4951 y(written)30 b(as)g(a)g(con)m(tiguous)h(set)f(of)g(b)m(ytes,)g +(rather)g(than)f(as)h(single)g(elemen)m(ts)h(in)f(m)m(ultiple)g(ro)m +(ws.)41 b(F)-8 b(or)30 b(example,)0 5064 y(it)36 b(is)g(faster)g(to)g +(access)h(the)f(data)h(in)e(a)h(table)h(that)f(con)m(tains)h(a)f +(single)g(ro)m(w)g(and)f(2)h(columns)f(with)h(TF)m(ORM)0 +5176 y(k)m(eyw)m(ords)d(equal)h(to)g('10000E')h(and)e('10000J',)j(than) +d(it)g(is)g(to)h(access)g(the)g(same)f(amoun)m(t)h(of)f(data)h(in)f(a)g +(table)0 5289 y(with)40 b(10000)j(ro)m(ws)d(whic)m(h)h(has)f(columns)g +(with)g(the)h(TF)m(ORM)g(k)m(eyw)m(ords)g(equal)g(to)g('1E')h(and)e +('1J'.)h(In)f(the)0 5402 y(former)27 b(case)i(the)f(10000)i(\015oating) +f(p)s(oin)m(t)f(v)-5 b(alues)28 b(in)g(the)g(\014rst)f(column)h(are)g +(all)h(written)f(in)f(a)h(con)m(tiguous)h(blo)s(c)m(k)0 +5515 y(of)d(the)f(\014le)h(whic)m(h)f(can)h(b)s(e)f(read)g(or)g +(written)h(quic)m(kly)-8 b(,)28 b(whereas)d(in)g(the)h(second)f(case)i +(eac)m(h)g(\015oating)f(p)s(oin)m(t)f(v)-5 b(alue)0 5628 +y(in)34 b(the)g(\014rst)f(column)g(is)h(in)m(terlea)m(v)m(ed)j(with)c +(the)h(in)m(teger)i(v)-5 b(alue)34 b(in)g(the)g(second)g(column)f(of)h +(the)g(same)h(ro)m(w)f(so)p eop end +%%Page: 28 34 +TeXDict begin 28 33 bop 0 299 a Fi(28)1277 b Fg(CHAPTER)29 +b(4.)72 b(FITSIO)29 b(CONVENTIONS)g(AND)i(GUIDELINES)0 +555 y Fi(CFITSIO)e(has)h(to)h(explicitly)h(mo)m(v)m(e)g(to)f(the)g(p)s +(osition)f(of)h(eac)m(h)g(elemen)m(t)h(to)f(b)s(e)f(read)g(or)g +(written.)0 715 y(9.)52 b(Av)m(oid)35 b(the)g(use)e(of)i(v)-5 +b(ariable)34 b(length)h(v)m(ector)h(columns)d(in)h(binary)g(tables,)i +(since)e(an)m(y)h(reading)f(or)g(writing)0 828 y(of)f(these)g(data)g +(requires)f(that)h(CFITSIO)f(\014rst)f(lo)s(ok)j(up)d(or)i(compute)g +(the)f(starting)i(address)e(of)g(eac)m(h)i(ro)m(w)f(of)0 +941 y(data)e(in)f(the)h(heap.)40 b(In)30 b(practice,)i(this)e(is)g +(probably)g(not)h(a)f(signi\014can)m(t)i(e\016ciency)f(issue.)0 +1101 y(10.)39 b(When)24 b(cop)m(ying)h(data)f(from)f(one)i(FITS)e +(table)h(to)h(another,)g(it)g(is)f(faster)g(to)g(transfer)f(the)h(ra)m +(w)g(b)m(ytes)h(instead)0 1214 y(of)h(reading)g(then)g(writing)g(eac)m +(h)h(column)e(of)i(the)f(table.)40 b(The)25 b(FITSIO)g(subroutines)g +(FTGTBS)g(and)h(FTPTBS)0 1327 y(\(for)i(ASCI)s(I)f(tables\),)j(and)d +(FTGTBB)i(and)e(FTPTBB)i(\(for)f(binary)f(tables\))i(will)g(p)s(erform) +d(lo)m(w-lev)m(el)31 b(reads)d(or)0 1440 y(writes)34 +b(of)h(an)m(y)f(con)m(tiguous)i(range)f(of)f(b)m(ytes)h(in)f(a)h(table) +g(extension.)53 b(These)34 b(routines)g(can)h(b)s(e)e(used)h(to)h(read) +0 1553 y(or)29 b(write)g(a)g(whole)g(ro)m(w)f(\(or)i(m)m(ultiple)f(ro)m +(ws\))g(of)g(a)g(table)h(with)e(a)h(single)g(subroutine)f(call.)41 +b(These)29 b(routines)g(are)0 1666 y(fast)38 b(b)s(ecause)f(they)h(b)m +(ypass)f(all)h(the)g(usual)f(data)h(scaling,)j(error)c(c)m(hec)m(king)i +(and)e(mac)m(hine)h(dep)s(enden)m(t)e(data)0 1779 y(con)m(v)m(ersion)41 +b(that)g(is)e(normally)i(done)e(b)m(y)h(FITSIO,)f(and)g(they)h(allo)m +(w)h(the)f(program)g(to)h(write)f(the)g(data)g(to)0 1892 +y(the)34 b(output)g(\014le)g(in)g(exactly)i(the)e(same)h(b)m(yte)g +(order.)51 b(F)-8 b(or)35 b(these)g(same)f(reasons,)i(use)e(of)g(these) +h(routines)f(can)0 2005 y(b)s(e)f(somewhat)h(risky)f(b)s(ecause)g(no)g +(v)-5 b(alidation)35 b(or)f(mac)m(hine)g(dep)s(enden)m(t)e(con)m(v)m +(ersion)j(is)e(p)s(erformed)f(b)m(y)h(these)0 2118 y(routines.)40 +b(In)27 b(general)h(these)h(routines)e(are)h(only)g(recommended)f(for)h +(optimizing)h(critical)g(pieces)g(of)f(co)s(de)g(and)0 +2230 y(should)e(only)i(b)s(e)f(used)f(b)m(y)i(programmers)e(who)h +(thoroughly)h(understand)d(the)j(in)m(ternal)g(b)m(yte)g(structure)f +(of)h(the)0 2343 y(FITS)i(tables)h(they)f(are)h(reading)g(or)f +(writing.)0 2503 y(11.)41 b(Another)30 b(strategy)g(for)g(impro)m(ving) +f(the)h(sp)s(eed)e(of)i(writing)g(a)f(FITS)g(table,)i(similar)f(to)g +(the)f(previous)g(one,)0 2616 y(is)j(to)g(directly)h(construct)f(the)f +(en)m(tire)i(b)m(yte)f(stream)g(for)g(a)g(whole)g(table)g(ro)m(w)g +(\(or)g(m)m(ultiple)h(ro)m(ws\))f(within)f(the)0 2729 +y(application)k(program)f(and)g(then)f(write)i(it)f(to)h(the)f(FITS)f +(\014le)i(with)e(ftptbb.)51 b(This)33 b(a)m(v)m(oids)j(all)f(the)f(o)m +(v)m(erhead)0 2842 y(normally)g(presen)m(t)g(in)f(the)h(column-orien)m +(ted)h(CFITSIO)d(write)i(routines.)51 b(This)33 b(tec)m(hnique)h +(should)f(only)h(b)s(e)0 2955 y(used)26 b(for)h(critical)i +(applications,)g(b)s(ecause)d(it)i(mak)m(es)g(the)f(co)s(de)g(more)g +(di\016cult)g(to)g(understand)e(and)i(main)m(tain,)0 +3068 y(and)d(it)h(mak)m(es)h(the)f(co)s(de)g(more)f(system)h(dep)s +(enden)m(t)f(\(e.g.,)k(do)c(the)h(b)m(ytes)g(need)g(to)g(b)s(e)f(sw)m +(app)s(ed)g(b)s(efore)g(writing)0 3181 y(to)31 b(the)g(FITS)e +(\014le?\).)0 3341 y(12.)53 b(Finally)-8 b(,)37 b(external)e(factors)h +(suc)m(h)e(as)g(the)h(t)m(yp)s(e)f(of)h(magnetic)g(disk)f(con)m +(troller)i(\(SCSI)d(or)i(IDE\),)g(the)f(size)0 3454 y(of)h(the)g(disk)g +(cac)m(he,)j(the)d(a)m(v)m(erage)i(seek)f(sp)s(eed)e(of)h(the)g(disk,)h +(the)f(amoun)m(t)h(of)f(disk)f(fragmen)m(tation,)k(and)d(the)0 +3567 y(amoun)m(t)29 b(of)g(RAM)f(a)m(v)-5 b(ailable)31 +b(on)e(the)f(system)h(can)g(all)g(ha)m(v)m(e)h(a)f(signi\014can)m(t)g +(impact)h(on)e(o)m(v)m(erall)j(I/O)d(e\016ciency)-8 b(.)0 +3680 y(F)g(or)36 b(critical)h(applications,)g(a)f(system)f +(administrator)g(should)f(review)h(the)h(prop)s(osed)d(system)j(hardw)m +(are)e(to)0 3793 y(iden)m(tify)d(an)m(y)g(p)s(oten)m(tial)g(I/O)g(b)s +(ottlenec)m(ks.)p eop end +%%Page: 29 35 +TeXDict begin 29 34 bop 0 1225 a Ff(Chapter)65 b(5)0 +1687 y Fl(Basic)77 b(In)-6 b(terface)77 b(Routines)0 +2180 y Fi(This)27 b(section)h(de\014nes)f(a)h(basic)g(set)g(of)g +(subroutines)e(that)i(can)g(b)s(e)f(used)g(to)h(p)s(erform)e(the)i +(most)g(common)g(t)m(yp)s(es)0 2293 y(of)d(read)g(and)f(write)h(op)s +(erations)g(on)g(FITS)f(\014les.)39 b(New)25 b(users)f(should)g(start)h +(with)g(these)g(subroutines)f(and)g(then,)0 2406 y(as)33 +b(needed,)h(explore)f(the)h(more)f(adv)-5 b(ance)33 b(routines)g +(describ)s(ed)f(in)h(the)g(follo)m(wing)i(c)m(hapter)e(to)h(p)s(erform) +e(more)0 2518 y(complex)f(or)f(sp)s(ecialized)i(op)s(erations.)0 +2679 y(A)e(righ)m(t)g(arro)m(w)g(sym)m(b)s(ol)f(\()p +Fa(>)p Fi(\))h(is)g(used)f(to)h(separate)h(the)e(input)g(parameters)h +(from)f(the)h(output)f(parameters)h(in)0 2791 y(the)i(de\014nition)f +(of)g(eac)m(h)i(routine.)44 b(This)30 b(sym)m(b)s(ol)i(is)f(not)h +(actually)h(part)e(of)h(the)f(calling)i(sequence.)45 +b(Note)32 b(that)0 2904 y(the)f(status)h(parameter)g(is)f(b)s(oth)g(an) +g(input)f(and)h(an)g(output)g(parameter)h(and)e(m)m(ust)h(b)s(e)g +(initialized)i(=)e(0)h(prior)0 3017 y(to)f(calling)h(the)f(FITSIO)e +(subroutines.)0 3177 y(Refer)h(to)i(Chapter)d(9)i(for)f(the)h +(de\014nition)f(of)g(all)i(the)e(parameters)h(used)e(b)m(y)i(these)g +(in)m(terface)g(routines.)0 3525 y Fd(5.1)135 b(FITSIO)44 +b(Error)h(Status)h(Routines)0 3773 y Fh(1)81 b Fi(Return)24 +b(the)i(curren)m(t)f(v)m(ersion)h(n)m(um)m(b)s(er)e(of)i(the)f +(\014tsio)h(library)-8 b(.)39 b(The)25 b(v)m(ersion)h(n)m(um)m(b)s(er)e +(will)i(b)s(e)e(incremen)m(ted)227 3886 y(with)30 b(eac)m(h)i(new)e +(release)h(of)g(CFITSIO.)382 4157 y Fe(FTVERS\()46 b(>)h(version\))0 +4429 y Fh(2)81 b Fi(Return)45 b(the)i(descriptiv)m(e)g(text)g(string)g +(corresp)s(onding)e(to)i(a)g(FITSIO)e(error)h(status)h(co)s(de.)89 +b(The)46 b(30-)227 4541 y(c)m(haracter)32 b(length)f(string)f(con)m +(tains)i(a)f(brief)f(description)g(of)g(the)h(cause)g(of)f(the)h +(error.)382 4813 y Fe(FTGERR\(status,)44 b(>)j(errtext\))0 +5084 y Fh(3)81 b Fi(Return)40 b(the)h(top)g(\(oldest\))h(80-c)m +(haracter)i(error)c(message)i(from)f(the)g(in)m(ternal)g(FITSIO)f(stac) +m(k)i(of)f(error)227 5197 y(messages)29 b(and)f(shift)g(an)m(y)g +(remaining)g(messages)h(on)f(the)g(stac)m(k)i(up)d(one)h(lev)m(el.)42 +b(An)m(y)28 b(FITSIO)f(error)h(will)227 5310 y(generate)h(one)e(or)g +(more)h(messages)g(on)f(the)g(stac)m(k.)41 b(Call)28 +b(this)f(routine)g(rep)s(eatedly)g(to)h(get)h(eac)m(h)f(message)227 +5422 y(in)i(sequence.)41 b(The)30 b(error)g(stac)m(k)i(is)f(empt)m(y)f +(when)g(a)g(blank)g(string)h(is)f(returned.)382 5694 +y Fe(FTGMSG\()46 b(>)h(errmsg\))1905 5942 y Fi(29)p eop +end +%%Page: 30 36 +TeXDict begin 30 35 bop 0 299 a Fi(30)1747 b Fg(CHAPTER)30 +b(5.)111 b(BASIC)30 b(INTERF)-10 b(A)m(CE)30 b(R)m(OUTINES)0 +555 y Fh(4)81 b Fi(The)33 b(FTPMRK)h(routine)g(puts)g(an)g(in)m +(visible)h(mark)m(er)f(on)g(the)h(CFITSIO)d(error)i(stac)m(k.)54 +b(The)33 b(FTCMRK)227 668 y(routine)41 b(can)g(then)g(b)s(e)f(used)g +(to)h(delete)h(an)m(y)f(more)g(recen)m(t)h(error)e(messages)i(on)f(the) +g(stac)m(k,)k(bac)m(k)c(to)227 781 y(the)32 b(p)s(osition)f(of)g(the)g +(mark)m(er.)43 b(This)31 b(preserv)m(es)g(an)m(y)g(older)h(error)e +(messages)i(on)f(the)h(stac)m(k.)44 b(FTCMSG)227 894 +y(simply)23 b(clears)g(the)g(en)m(tire)h(error)e(message)i(stac)m(k.)40 +b(These)23 b(routines)f(are)h(called)h(without)f(an)m(y)g(argumen)m +(ts.)382 1152 y Fe(FTPMRK)382 1265 y(FTCMRK)382 1378 +y(FTCMSG)0 1637 y Fh(5)81 b Fi(Prin)m(t)30 b(out)h(the)g(error)f +(message)i(corresp)s(onding)e(to)h(the)g(input)f(status)h(v)-5 +b(alue)31 b(and)f(all)i(the)f(error)f(messages)227 1750 +y(on)g(the)h(FITSIO)e(stac)m(k)i(to)g(the)g(sp)s(eci\014ed)e(\014le)i +(stream)f(\(stream)h(can)g(b)s(e)e(either)i(the)f(string)g('STDOUT')227 +1863 y(or)h('STDERR'\).)f(If)g(the)h(input)e(status)i(v)-5 +b(alue)31 b(=)f(0)h(then)f(this)g(routine)g(do)s(es)g(nothing.)334 +2121 y Fe(FTRPRT)46 b(\(stream,)g(>)h(status\))0 2380 +y Fh(6)81 b Fi(W)-8 b(rite)39 b(an)f(80-c)m(haracter)j(message)e(to)g +(the)f(FITSIO)f(error)h(stac)m(k.)65 b(Application)39 +b(programs)f(should)f(not)227 2493 y(normally)31 b(write)f(to)i(the)e +(stac)m(k,)i(but)e(there)g(ma)m(y)h(b)s(e)f(some)h(situations)g(where)f +(this)g(is)h(desirable.)382 2751 y Fe(FTPMSG\(errmsg\))0 +3085 y Fd(5.2)135 b(File)46 b(I/O)f(Routines)0 3325 y +Fh(1)81 b Fi(Op)s(en)34 b(an)h(existing)i(FITS)d(\014le)i(with)f +(readonly)h(or)f(readwrite)h(access.)58 b(This)34 b(routine)i(alw)m(a)m +(ys)h(op)s(ens)e(the)227 3438 y(primary)30 b(arra)m(y)i(\(the)f +(\014rst)f(HDU\))i(of)f(the)h(\014le,)f(and)f(do)s(es)h(not)g(mo)m(v)m +(e)h(to)g(a)f(follo)m(wing)i(extension,)f(if)f(one)227 +3551 y(w)m(as)c(sp)s(eci\014ed)f(as)g(part)h(of)f(the)h(\014lename.)39 +b(Use)27 b(the)g(FTNOPN)f(routine)g(to)h(automatically)j(mo)m(v)m(e)e +(to)f(the)227 3664 y(extension.)44 b(This)31 b(routine)g(will)h(also)g +(op)s(en)f(IRAF)g(images)i(\(.imh)e(format)h(\014les\))g(and)e(ra)m(w)i +(binary)e(data)227 3776 y(arra)m(ys)e(with)f(READONL)-8 +b(Y)28 b(access)h(b)m(y)e(\014rst)g(con)m(v)m(erting)i(them)e(on)g(the) +h(\015y)f(in)m(to)h(virtual)g(FITS)f(images.)227 3889 +y(See)39 b(the)f(`Extended)g(File)h(Name)g(Syn)m(tax')f(c)m(hapter)h +(for)f(more)g(details.)65 b(The)38 b(FTDK)m(OPEN)g(routine)227 +4002 y(simply)f(op)s(ens)g(the)h(sp)s(eci\014ed)f(\014le)h(without)f +(trying)h(to)g(in)m(terpret)g(the)g(\014lename)g(using)f(the)h +(extended)227 4115 y(\014lename)31 b(syn)m(tax.)382 4374 +y Fe(FTOPEN\(unit,filename,rwm)o(ode)o(,)42 b(>)47 b +(blocksize,status\))382 4487 y(FTDKOPEN\(unit,filename,r)o(wmo)o(de,)41 +b(>)48 b(blocksize,status\))0 4745 y Fh(2)81 b Fi(Op)s(en)24 +b(an)i(existing)h(FITS)e(\014le)h(with)f(readonly)h(or)g(readwrite)g +(access)h(and)f(mo)m(v)m(e)h(to)f(a)h(follo)m(wing)g(extension,)227 +4858 y(if)38 b(one)g(w)m(as)g(sp)s(eci\014ed)g(as)g(part)f(of)h(the)h +(\014lename.)63 b(\(e.g.,)42 b('\014lename.\014ts+2')c(or)g +('\014lename.\014ts[2]')i(will)227 4971 y(mo)m(v)m(e)e(to)g(the)e(3rd)g +(HDU)i(in)e(the)h(\014le\).)60 b(Note)37 b(that)h(this)e(routine)h +(di\013ers)f(from)g(FTOPEN)g(in)g(that)h(it)227 5084 +y(do)s(es)30 b(not)h(ha)m(v)m(e)h(the)e(redundan)m(t)f(blo)s(c)m(ksize) +j(argumen)m(t.)382 5342 y Fe(FTNOPN\(unit,filename,rwm)o(ode)o(,)42 +b(>)47 b(status\))0 5601 y Fh(3)81 b Fi(Op)s(en)31 b(an)h(existing)h +(FITS)f(\014le)g(with)g(readonly)h(or)f(readwrite)h(access)g(and)f +(then)g(mo)m(v)m(e)i(to)f(the)g(\014rst)e(HDU)227 5714 +y(con)m(taining)c(signi\014can)m(t)g(data,)g(if)e(a\))i(an)e(HDU)h +(name)g(or)f(n)m(um)m(b)s(er)f(to)i(op)s(en)f(w)m(as)h(not)g +(explicitly)h(sp)s(eci\014ed)p eop end +%%Page: 31 37 +TeXDict begin 31 36 bop 0 299 a Fg(5.2.)72 b(FILE)30 +b(I/O)h(R)m(OUTINES)2693 b Fi(31)227 555 y(as)31 b(part)g(of)g(the)g +(\014lename,)h(and)e(b\))h(if)g(the)g(FITS)f(\014le)h(con)m(tains)h(a)g +(n)m(ull)e(primary)g(arra)m(y)i(\(i.e.,)h(NAXIS)d(=)227 +668 y(0\).)41 b(In)26 b(this)i(case,)h(it)f(will)g(lo)s(ok)g(for)f(the) +h(\014rst)e(IMA)m(GE)j(HDU)f(with)f(NAXIS)g(>)h(0,)g(or)g(the)f +(\014rst)g(table)h(that)227 781 y(do)s(es)g(not)g(con)m(tain)g(the)g +(strings)g(`GTI')g(\(Go)s(o)s(d)f(Time)h(In)m(terv)-5 +b(al\))29 b(or)f(`OBST)-8 b(ABLE')28 b(in)f(the)h(EXTNAME)227 +894 y(k)m(eyw)m(ord)37 b(v)-5 b(alue.)61 b(FTTOPN)36 +b(is)g(similar,)j(except)f(it)f(will)g(mo)m(v)m(e)i(to)e(the)g(\014rst) +f(signi\014can)m(t)i(table)f(HDU)227 1007 y(\(skipping)26 +b(o)m(v)m(er)g(an)m(y)g(image)h(HDUs\))g(in)e(the)h(\014le)g(if)f(a)h +(sp)s(eci\014c)g(HDU)g(name)g(or)g(n)m(um)m(b)s(er)e(is)i(not)f(sp)s +(eci\014ed.)227 1120 y(FTIOPN)30 b(will)h(mo)m(v)m(e)h(to)f(the)f +(\014rst)g(non-n)m(ull)g(image)i(HDU,)f(skipping)f(o)m(v)m(er)h(an)m(y) +g(tables.)382 1376 y Fe(FTDOPN\(unit,filename,rwm)o(ode)o(,)42 +b(>)47 b(status\))382 1489 y(FTTOPN\(unit,filename,rwm)o(ode)o(,)42 +b(>)47 b(status\))382 1602 y(FTIOPN\(unit,filename,rwm)o(ode)o(,)42 +b(>)47 b(status\))0 1858 y Fh(4)81 b Fi(Op)s(en)38 b(and)i(initialize)j +(a)e(new)e(empt)m(y)i(FITS)f(\014le.)70 b(A)41 b(template)h(\014le)e +(ma)m(y)h(also)g(b)s(e)f(sp)s(eci\014ed)g(to)h(de\014ne)227 +1971 y(the)34 b(structure)e(of)h(the)h(new)e(\014le)h(\(see)h(section)g +(4.2.4\).)51 b(The)33 b(FTDKINIT)g(routine)g(simply)f(creates)j(the)227 +2084 y(sp)s(eci\014ed)30 b(\014le)g(without)h(trying)f(to)h(in)m +(terpret)g(the)g(\014lename)f(using)g(the)h(extended)f(\014lename)h +(syn)m(tax.)382 2340 y Fe(FTINIT\(unit,filename,blo)o(cks)o(ize,)41 +b(>)48 b(status\))382 2453 y(FTDKINIT\(unit,filename,b)o(loc)o(ksiz)o +(e,)42 b(>)47 b(status\))0 2709 y Fh(5)81 b Fi(Close)31 +b(a)f(FITS)g(\014le)g(previously)g(op)s(ened)g(with)g(ftop)s(en)g(or)g +(ftinit)382 2965 y Fe(FTCLOS\(unit,)44 b(>)k(status\))0 +3221 y Fh(6)81 b Fi(Mo)m(v)m(e)32 b(to)f(a)g(sp)s(eci\014ed)f +(\(absolute\))h(HDU)g(in)g(the)f(FITS)g(\014le)g(\(nhdu)f(=)h(1)h(for)f +(the)g(FITS)g(primary)f(arra)m(y\))382 3478 y Fe(FTMAHD\(unit,nhdu,)43 +b(>)k(hdutype,status\))0 3734 y Fh(7)81 b Fi(Create)30 +b(a)f(primary)f(arra)m(y)i(\(if)g(none)f(already)g(exists\),)i(or)e +(insert)g(a)h(new)f(IMA)m(GE)h(extension)g(immediately)227 +3847 y(follo)m(wing)25 b(the)e(CHDU,)g(or)g(insert)g(a)g(new)g(Primary) +f(Arra)m(y)h(at)h(the)f(b)s(eginning)f(of)h(the)g(\014le.)38 +b(An)m(y)23 b(follo)m(wing)227 3960 y(extensions)29 b(in)g(the)g +(\014le)f(will)h(b)s(e)f(shifted)h(do)m(wn)f(to)h(mak)m(e)h(ro)s(om)e +(for)h(the)g(new)f(extension.)40 b(If)29 b(the)g(CHDU)227 +4072 y(is)h(the)g(last)g(HDU)g(in)g(the)f(\014le)h(then)f(the)h(new)f +(image)i(extension)f(will)g(simply)f(b)s(e)g(app)s(ended)f(to)i(the)g +(end)227 4185 y(of)k(the)h(\014le.)52 b(One)33 b(can)h(force)h(a)g(new) +e(primary)g(arra)m(y)i(to)g(b)s(e)e(inserted)h(at)h(the)f(b)s(eginning) +f(of)h(the)h(FITS)227 4298 y(\014le)29 b(b)m(y)f(setting)i(status)e(=)h +(-9)g(prior)e(to)j(calling)g(the)e(routine.)40 b(In)28 +b(this)g(case)i(the)e(existing)i(primary)d(arra)m(y)227 +4411 y(will)f(b)s(e)f(con)m(v)m(erted)i(to)g(an)e(IMA)m(GE)h +(extension.)40 b(The)25 b(new)g(extension)i(\(or)f(primary)e(arra)m +(y\))j(will)f(b)s(ecome)227 4524 y(the)32 b(CHDU.)f(The)g(FTI)s(IMGLL)f +(routine)i(is)f(iden)m(tical)i(to)e(the)h(FTI)s(IMG)f(routine)g(except) +h(that)f(the)h(4th)227 4637 y(parameter)25 b(\(the)g(length)g(of)f(eac) +m(h)h(axis\))g(is)g(an)f(arra)m(y)h(of)f(64-bit)i(in)m(tegers)f(rather) +f(than)g(an)g(arra)m(y)h(of)g(32-bit)227 4750 y(in)m(tegers.)382 +5006 y Fe(FTIIMG\(unit,bitpix,naxis)o(,na)o(xes,)41 b(>)48 +b(status\))382 5119 y(FTIIMGLL\(unit,bitpix,nax)o(is,)o(naxe)o(sll,)41 +b(>)47 b(status\))0 5375 y Fh(8)81 b Fi(Insert)30 b(a)i(new)f(ASCI)s(I) +f(T)-8 b(ABLE)31 b(extension)h(immediately)h(follo)m(wing)f(the)g +(CHDU.)g(An)m(y)f(follo)m(wing)i(exten-)227 5488 y(sions)26 +b(will)g(b)s(e)f(shifted)g(do)m(wn)g(to)h(mak)m(e)h(ro)s(om)e(for)h +(the)f(new)g(extension.)40 b(If)25 b(there)h(are)g(no)g(other)f(follo)m +(wing)227 5601 y(extensions)32 b(then)f(the)h(new)f(table)h(extension)g +(will)g(simply)f(b)s(e)g(app)s(ended)f(to)i(the)f(end)g(of)h(the)f +(\014le.)44 b(The)227 5714 y(new)33 b(extension)h(will)f(b)s(ecome)h +(the)f(CHDU.)h(The)f(FTIT)-8 b(ABLL)33 b(routine)g(is)g(iden)m(tical)i +(to)f(the)g(FTIT)-8 b(AB)p eop end +%%Page: 32 38 +TeXDict begin 32 37 bop 0 299 a Fi(32)1747 b Fg(CHAPTER)30 +b(5.)111 b(BASIC)30 b(INTERF)-10 b(A)m(CE)30 b(R)m(OUTINES)227 +555 y Fi(routine)36 b(except)g(that)g(the)f(2nd)g(and)g(3rd)g +(parameters)g(\(that)i(giv)m(e)g(the)e(size)h(of)g(the)f(table\))i(are) +f(64-bit)227 668 y(in)m(tegers)43 b(rather)f(than)f(32-bit)i(in)m +(tegers.)76 b(Under)41 b(normal)g(circumstances,)46 b(the)c(nro)m(ws)f +(and)g(nro)m(wsll)227 781 y(paramen)m(ters)f(should)e(ha)m(v)m(e)j(a)f +(v)-5 b(alue)40 b(of)f(0;)45 b(CFITSIO)38 b(will)h(automatically)k(up)s +(date)38 b(the)i(n)m(um)m(b)s(er)e(of)227 894 y(ro)m(ws)31 +b(as)f(data)h(is)g(written)f(to)h(the)g(table.)382 1158 +y Fe(FTITAB\(unit,rowlen,nrows)o(,tf)o(ield)o(s,tt)o(ype)o(,tbc)o(ol,t) +o(for)o(m,tu)o(nit,)o(ext)o(name)o(,)42 b(>)716 1271 +y(status\))382 1384 y(FTITABLL\(unit,rowlenll,n)o(row)o(sll,)o(tfie)o +(lds)o(,tty)o(pe,t)o(bco)o(l,tf)o(orm,)o(tun)o(it,e)o(xtna)o(me,)f(>) +716 1497 y(status\))0 1761 y Fh(9)81 b Fi(Insert)26 b(a)h(new)g(binary) +f(table)h(extension)h(immediately)g(follo)m(wing)h(the)e(CHDU.)g(An)m +(y)g(follo)m(wing)h(extensions)227 1874 y(will)39 b(b)s(e)f(shifted)g +(do)m(wn)g(to)h(mak)m(e)g(ro)s(om)g(for)f(the)g(new)g(extension.)66 +b(If)38 b(there)h(are)f(no)h(other)f(follo)m(wing)227 +1987 y(extensions)f(then)g(the)f(new)g(bin)m(table)h(extension)h(will)f +(simply)f(b)s(e)g(app)s(ended)e(to)k(the)e(end)g(of)h(the)g(\014le.)227 +2100 y(The)23 b(new)g(extension)h(will)f(b)s(ecome)h(the)f(CHDU.)h(The) +f(FTIBINLL)g(routine)g(is)g(iden)m(tical)i(to)f(the)g(FTIBIN)227 +2213 y(routine)30 b(except)i(that)e(the)h(2nd)e(parameter)i(\(that)g +(giv)m(es)g(the)g(length)f(of)h(the)f(table\))h(is)g(a)f(64-bit)i(in)m +(teger)227 2325 y(rather)24 b(than)g(a)g(32-bit)h(in)m(teger.)40 +b(Under)23 b(normal)h(circumstances,)i(the)e(nro)m(ws)f(and)h(nro)m +(wsll)g(paramen)m(ters)227 2438 y(should)31 b(ha)m(v)m(e)i(a)f(v)-5 +b(alue)32 b(of)f(0;)i(CFITSIO)d(will)i(automatically)j(up)s(date)30 +b(the)i(n)m(um)m(b)s(er)e(of)i(ro)m(ws)g(as)g(data)g(is)227 +2551 y(written)f(to)g(the)f(table.)382 2815 y Fe +(FTIBIN\(unit,nrows,tfield)o(s,t)o(type)o(,tfo)o(rm,)o(tuni)o(t,ex)o +(tna)o(me,v)o(arid)o(at)41 b(>)48 b(status\))382 2928 +y(FTIBINLL\(unit,nrowsll,tf)o(iel)o(ds,t)o(type)o(,tf)o(orm,)o(tuni)o +(t,e)o(xtna)o(me,v)o(ari)o(dat)41 b(>)48 b(status\))0 +3380 y Fd(5.3)135 b(Keyw)l(ord)46 b(I/O)f(Routines)0 +3624 y Fh(1)81 b Fi(Put)30 b(\(app)s(end\))f(an)h(80-c)m(haracter)j +(record)e(in)m(to)g(the)g(CHU.)382 3888 y Fe(FTPREC\(unit,card,)43 +b(>)k(status\))0 4152 y Fh(2)81 b Fi(Put)28 b(\(app)s(end\))g(a)h(new)g +(k)m(eyw)m(ord)g(of)g(the)g(appropriate)g(datat)m(yp)s(e)h(in)m(to)g +(the)f(CHU.)g(The)f(E)h(and)f(D)i(v)m(ersions)227 4265 +y(of)24 b(this)f(routine)g(ha)m(v)m(e)h(the)g(added)e(feature)i(that)g +(if)f(the)g('decimals')i(parameter)f(is)f(negativ)m(e,)k(then)c(the)g +('G')227 4378 y(displa)m(y)30 b(format)g(rather)f(then)g(the)h('E')f +(format)h(will)g(b)s(e)f(used)f(when)h(constructing)h(the)f(k)m(eyw)m +(ord)h(v)-5 b(alue,)227 4490 y(taking)27 b(the)g(absolute)g(v)-5 +b(alue)26 b(of)h('decimals')g(for)f(the)h(precision.)39 +b(This)26 b(will)g(suppress)e(trailing)k(zeros,)g(and)227 +4603 y(will)37 b(use)g(a)g(\014xed)f(format)h(rather)g(than)f(an)h(exp) +s(onen)m(tial)g(format,)i(dep)s(ending)c(on)i(the)g(magnitude)g(of)227 +4716 y(the)31 b(v)-5 b(alue.)382 4980 y Fe(FTPKY[JKLS]\(unit,keyword)o +(,ke)o(yval)o(,com)o(men)o(t,)42 b(>)47 b(status\))382 +5093 y(FTPKY[EDFG]\(unit,keyword)o(,ke)o(yval)o(,dec)o(ima)o(ls,c)o +(omme)o(nt,)41 b(>)48 b(status\))0 5357 y Fh(3)81 b Fi(Get)37 +b(the)f(n)m(th)f(80-c)m(haracter)k(header)d(record)g(from)f(the)h(CHU.) +h(The)e(\014rst)g(k)m(eyw)m(ord)i(in)e(the)h(header)g(is)g(at)227 +5470 y(k)m(ey)p 365 5470 28 4 v 34 w(no)42 b(=)f(1;)49 +b(if)42 b(k)m(ey)p 996 5470 V 34 w(no)g(=)f(0)i(then)e(this)h +(subroutine)f(simple)h(mo)m(v)m(es)i(the)e(in)m(ternal)h(p)s(oin)m(ter) +f(to)h(the)227 5583 y(b)s(eginning)35 b(of)h(the)g(header)f(so)h(that)g +(subsequen)m(t)f(k)m(eyw)m(ord)h(op)s(erations)g(will)g(start)g(at)g +(the)g(top)g(of)g(the)227 5696 y(header;)31 b(it)g(also)g(returns)e(a)i +(blank)f(card)g(v)-5 b(alue)31 b(in)f(this)g(case.)p +eop end +%%Page: 33 39 +TeXDict begin 33 38 bop 0 299 a Fg(5.4.)72 b(D)m(A)-8 +b(T)g(A)32 b(I/O)f(R)m(OUTINES)2650 b Fi(33)382 555 y +Fe(FTGREC\(unit,key_no,)42 b(>)48 b(card,status\))0 804 +y Fh(4)81 b Fi(Get)31 b(a)g(k)m(eyw)m(ord)g(v)-5 b(alue)30 +b(\(with)h(the)f(appropriate)h(datat)m(yp)s(e\))g(and)f(commen)m(t)i +(from)e(the)g(CHU)382 1052 y Fe(FTGKY[EDJKLS]\(unit,keywo)o(rd,)41 +b(>)48 b(keyval,comment,status\))0 1301 y Fh(5)81 b Fi(Delete)32 +b(an)e(existing)i(k)m(eyw)m(ord)f(record.)382 1550 y +Fe(FTDKEY\(unit,keyword,)42 b(>)48 b(status\))0 1881 +y Fd(5.4)135 b(Data)46 b(I/O)g(Routines)0 2132 y Fi(The)32 +b(follo)m(wing)i(routines)e(read)h(or)f(write)h(data)g(v)-5 +b(alues)33 b(in)f(the)h(curren)m(t)f(HDU)i(of)e(the)h(FITS)f(\014le.)47 +b(Automatic)0 2245 y(datat)m(yp)s(e)28 b(con)m(v)m(ersion)h(will)e(b)s +(e)g(attempted)h(for)g(n)m(umerical)f(datat)m(yp)s(es)i(if)e(the)g(sp)s +(eci\014ed)g(datat)m(yp)s(e)h(is)f(di\013eren)m(t)0 2357 +y(from)j(the)g(actual)i(datat)m(yp)s(e)g(of)e(the)h(FITS)e(arra)m(y)i +(or)f(table)i(column.)0 2606 y Fh(1)81 b Fi(W)-8 b(rite)31 +b(elemen)m(ts)h(in)m(to)f(the)g(primary)e(data)j(arra)m(y)e(or)h(image) +g(extension.)382 2855 y Fe(FTPPR[BIJKED]\(unit,group)o(,fp)o(ixel)o +(,nel)o(eme)o(nts,)o(valu)o(es,)41 b(>)48 b(status\))0 +3103 y Fh(2)81 b Fi(Read)30 b(elemen)m(ts)j(from)d(the)h(primary)e +(data)j(arra)m(y)f(or)g(image)h(extension.)42 b(Unde\014ned)29 +b(arra)m(y)j(elemen)m(ts)g(will)227 3216 y(b)s(e)f(returned)f(with)h(a) +h(v)-5 b(alue)31 b(=)g(n)m(ullv)-5 b(al,)33 b(unless)d(n)m(ullv)-5 +b(al)32 b(=)f(0)h(in)f(whic)m(h)g(case)h(no)f(c)m(hec)m(ks)i(for)e +(unde\014ned)227 3329 y(pixels)h(will)f(b)s(e)f(p)s(erformed.)42 +b(The)30 b(an)m(yf)i(parameter)f(is)g(set)h(to)g(true)f(\(=)g(.true.\)) +43 b(if)31 b(an)m(y)h(of)f(the)g(returned)227 3442 y(elemen)m(ts)h(w)m +(ere)f(unde\014ned.)382 3691 y Fe(FTGPV[BIJKED]\(unit,group)o(,fp)o +(ixel)o(,nel)o(eme)o(nts,)o(null)o(val)o(,)42 b(>)47 +b(values,anyf,status\))0 3939 y Fh(3)81 b Fi(W)-8 b(rite)36 +b(elemen)m(ts)h(in)m(to)f(an)f(ASCI)s(I)e(or)i(binary)g(table)h +(column.)54 b(The)35 b(`felem')h(parameter)g(applies)f(only)g(to)227 +4052 y(v)m(ector)d(columns)e(in)g(binary)g(tables)h(and)f(is)g(ignored) +h(when)e(writing)i(to)g(ASCI)s(I)d(tables.)382 4301 y +Fe(FTPCL[SLBIJKEDCM]\(unit,c)o(oln)o(um,f)o(row,)o(fel)o(em,n)o(elem)o +(ent)o(s,va)o(lues)o(,)42 b(>)47 b(status\))0 4549 y +Fh(4)81 b Fi(Read)22 b(elemen)m(ts)h(from)e(an)g(ASCI)s(I)g(or)g +(binary)g(table)i(column.)38 b(Unde\014ned)20 b(arra)m(y)i(elemen)m(ts) +h(will)f(b)s(e)f(returned)227 4662 y(with)32 b(a)h(v)-5 +b(alue)33 b(=)f(n)m(ullv)-5 b(al,)34 b(unless)e(n)m(ullv)-5 +b(al)32 b(=)h(0)f(\(or)h(=)f(')h(')f(for)g(ftgcvs\))i(in)e(whic)m(h)g +(case)i(no)e(c)m(hec)m(king)i(for)227 4775 y(unde\014ned)23 +b(v)-5 b(alues)25 b(will)g(b)s(e)g(p)s(erformed.)37 b(The)24 +b(ANYF)i(parameter)f(is)g(set)h(to)f(true)g(if)g(an)m(y)g(of)g(the)g +(returned)227 4888 y(elemen)m(ts)32 b(are)f(unde\014ned.)227 +5036 y(An)m(y)d(column,)h(regardless)f(of)g(it's)h(in)m(trinsic)f +(datat)m(yp)s(e,)i(ma)m(y)e(b)s(e)f(read)h(as)g(a)h(string.)40 +b(It)28 b(should)e(b)s(e)i(noted)227 5149 y(ho)m(w)m(ev)m(er)k(that)f +(reading)f(a)h(n)m(umeric)f(column)g(as)h(a)g(string)f(is)g(10)i(-)e +(100)i(times)f(slo)m(w)m(er)g(than)f(reading)h(the)227 +5262 y(same)36 b(column)f(as)h(a)g(n)m(um)m(b)s(er)e(due)g(to)j(the)e +(large)i(o)m(v)m(erhead)f(in)f(constructing)h(the)g(formatted)g +(strings.)227 5375 y(The)i(displa)m(y)g(format)g(of)g(the)g(returned)f +(strings)g(will)h(b)s(e)g(determined)f(b)m(y)h(the)g(TDISPn)f(k)m(eyw)m +(ord,)j(if)227 5488 y(it)d(exists,)h(otherwise)f(b)m(y)f(the)g(datat)m +(yp)s(e)h(of)f(the)h(column.)57 b(The)36 b(length)g(of)h(the)f +(returned)f(strings)h(can)227 5601 y(b)s(e)29 b(determined)f(with)h +(the)g(ftgcdw)g(routine.)40 b(The)28 b(follo)m(wing)j(TDISPn)c(displa)m +(y)j(formats)f(are)g(curren)m(tly)227 5714 y(supp)s(orted:)p +eop end +%%Page: 34 40 +TeXDict begin 34 39 bop 0 299 a Fi(34)1747 b Fg(CHAPTER)30 +b(5.)111 b(BASIC)30 b(INTERF)-10 b(A)m(CE)30 b(R)m(OUTINES)418 +555 y Fe(Iw.m)142 b(Integer)418 668 y(Ow.m)g(Octal)47 +b(integer)418 781 y(Zw.m)142 b(Hexadecimal)45 b(integer)418 +894 y(Fw.d)142 b(Fixed)47 b(floating)e(point)418 1007 +y(Ew.d)142 b(Exponential)45 b(floating)h(point)418 1120 +y(Dw.d)142 b(Exponential)45 b(floating)h(point)418 1233 +y(Gw.d)142 b(General;)46 b(uses)g(Fw.d)h(if)g(significance)e(not)i +(lost,)f(else)h(Ew.d)227 1483 y Fi(where)24 b(w)h(is)f(the)h(width)f +(in)g(c)m(haracters)i(of)f(the)g(displa)m(y)m(ed)g(v)-5 +b(alues,)27 b(m)d(is)h(the)f(minim)m(um)g(n)m(um)m(b)s(er)g(of)g +(digits)227 1595 y(displa)m(y)m(ed,)31 b(and)e(d)h(is)f(the)i(n)m(um)m +(b)s(er)d(of)i(digits)g(to)h(the)f(righ)m(t)h(of)f(the)g(decimal.)41 +b(The)29 b(.m)h(\014eld)g(is)g(optional.)382 1855 y Fe +(FTGCV[SBIJKEDCM]\(unit,co)o(lnu)o(m,fr)o(ow,f)o(ele)o(m,ne)o(leme)o +(nts)o(,nul)o(lval)o(,)42 b(>)1098 1968 y(values,anyf,status\))0 +2228 y Fh(5)81 b Fi(Get)42 b(the)g(table)h(column)e(n)m(um)m(b)s(er)g +(and)g(full)g(name)h(of)g(the)f(column)h(whose)f(name)h(matc)m(hes)h +(the)f(input)227 2341 y(template)35 b(string.)48 b(See)33 +b(the)h(`Adv)-5 b(anced)33 b(In)m(terface)h(Routines')f(c)m(hapter)h +(for)f(a)g(full)g(description)g(of)g(this)227 2454 y(routine.)382 +2714 y Fe(FTGCNN\(unit,casesen,colt)o(emp)o(late)o(,)42 +b(>)47 b(colname,colnum,status\))p eop end +%%Page: 35 41 +TeXDict begin 35 40 bop 0 1225 a Ff(Chapter)65 b(6)0 +1687 y Fl(Adv)-13 b(anced)78 b(In)-6 b(terface)77 b(Subroutines)0 +2180 y Fi(This)31 b(c)m(hapter)h(de\014nes)f(all)h(the)g(a)m(v)-5 +b(ailable)34 b(subroutines)d(in)g(the)h(FITSIO)e(user)h(in)m(terface.) +46 b(F)-8 b(or)33 b(completeness,)0 2293 y(the)43 b(basic)g +(subroutines)e(describ)s(ed)g(in)i(the)f(previous)h(c)m(hapter)g(are)g +(also)g(rep)s(eated)g(here.)77 b(A)43 b(righ)m(t)g(arro)m(w)0 +2406 y(sym)m(b)s(ol)29 b(is)f(used)g(here)h(to)g(separate)h(the)f +(input)f(parameters)h(from)f(the)h(output)g(parameters)g(in)f(the)h +(de\014nition)0 2518 y(of)k(eac)m(h)h(subroutine.)47 +b(This)32 b(sym)m(b)s(ol)h(is)g(not)g(actually)i(part)d(of)h(the)h +(calling)g(sequence.)49 b(An)32 b(alphab)s(etical)i(list)0 +2631 y(and)c(de\014nition)g(of)g(all)i(the)e(parameters)h(is)f(giv)m +(en)i(at)f(the)f(end)g(of)h(this)f(section.)0 2961 y +Fd(6.1)135 b(FITS)44 b(File)i(Op)t(en)e(and)h(Close)h(Subroutines:)0 +3197 y Fh(1)81 b Fi(Op)s(en)25 b(an)i(existing)h(FITS)f(\014le)g(with)g +(readonly)g(or)g(readwrite)g(access.)41 b(The)27 b(FTDK)m(OPEN)g +(routine)g(simply)227 3310 y(op)s(ens)32 b(the)g(sp)s(eci\014ed)g +(\014le)h(without)f(trying)h(to)g(in)m(terpret)g(the)f(\014lename)h +(using)f(the)g(extended)h(\014lename)227 3423 y(syn)m(tax.)41 +b(FTDOPN)28 b(op)s(ens)e(the)i(\014le)g(and)f(also)i(mo)m(v)m(es)g(to)g +(the)f(\014rst)f(HDU)h(con)m(taining)h(signi\014can)m(t)g(data,)227 +3536 y(if)35 b(no)h(sp)s(eci\014c)f(HDU)h(is)f(sp)s(eci\014ed)f(as)i +(part)f(of)g(the)h(\014lename.)55 b(FTTOPN)35 b(and)f(FTIOPN)h(are)h +(similar)227 3649 y(except)26 b(that)f(they)g(will)g(mo)m(v)m(e)h(to)g +(the)f(\014rst)f(table)h(HDU)h(or)e(image)i(HDU,)g(resp)s(ectiv)m(ely) +-8 b(,)28 b(if)c(a)h(HDU)h(name)227 3762 y(or)31 b(n)m(um)m(b)s(er)e +(is)h(not)h(sp)s(eci\014ed)e(as)i(part)f(of)h(the)f(\014lename.)382 +3996 y Fe(FTOPEN\(unit,filename,rwm)o(ode)o(,)42 b(>)47 +b(blocksize,status\))382 4108 y(FTDKOPEN\(unit,filename,r)o(wmo)o(de,) +41 b(>)48 b(blocksize,status\))382 4334 y(FTDOPN\(unit,filename,rwm)o +(ode)o(,)42 b(>)47 b(status\))382 4447 y(FTTOPN\(unit,filename,rwm)o +(ode)o(,)42 b(>)47 b(status\))382 4560 y(FTIOPN\(unit,filename,rwm)o +(ode)o(,)42 b(>)47 b(status\))0 4794 y Fh(2)81 b Fi(Op)s(en)24 +b(an)i(existing)h(FITS)e(\014le)h(with)f(readonly)h(or)g(readwrite)g +(access)h(and)f(mo)m(v)m(e)h(to)f(a)h(follo)m(wing)g(extension,)227 +4907 y(if)38 b(one)g(w)m(as)g(sp)s(eci\014ed)g(as)g(part)f(of)h(the)h +(\014lename.)63 b(\(e.g.,)42 b('\014lename.\014ts+2')c(or)g +('\014lename.\014ts[2]')i(will)227 5020 y(mo)m(v)m(e)e(to)g(the)e(3rd)g +(HDU)i(in)e(the)h(\014le\).)60 b(Note)37 b(that)h(this)e(routine)h +(di\013ers)f(from)g(FTOPEN)g(in)g(that)h(it)227 5133 +y(do)s(es)30 b(not)h(ha)m(v)m(e)h(the)e(redundan)m(t)f(blo)s(c)m(ksize) +j(argumen)m(t.)382 5367 y Fe(FTNOPN\(unit,filename,rwm)o(ode)o(,)42 +b(>)47 b(status\))0 5601 y Fh(3)81 b Fi(Reop)s(en)38 +b(a)i(FITS)e(\014le)i(that)f(w)m(as)h(previously)f(op)s(ened)f(with)h +(FTOPEN,)g(FTNOPN,)g(or)h(FTINIT.)e(The)227 5714 y(newunit)f(n)m(um)m +(b)s(er)f(ma)m(y)j(then)e(b)s(e)g(treated)i(as)f(a)g(separate)g +(\014le,)i(and)d(one)h(ma)m(y)h(sim)m(ultaneously)f(read)1905 +5942 y(35)p eop end +%%Page: 36 42 +TeXDict begin 36 41 bop 0 299 a Fi(36)1319 b Fg(CHAPTER)29 +b(6.)112 b(AD)m(V)-10 b(ANCED)32 b(INTERF)-10 b(A)m(CE)30 +b(SUBR)m(OUTINES)227 555 y Fi(or)36 b(write)g(to)g(2)g(\(or)g(more\))g +(di\013eren)m(t)g(extensions)g(in)f(the)h(same)g(\014le.)56 +b(The)35 b(FTOPEN)g(and)g(FTNOPN)227 668 y(routines)f(\(ab)s(o)m(v)m +(e\))h(automatically)h(detects)f(cases)g(where)e(a)g(previously)h(op)s +(ened)e(\014le)i(is)g(b)s(eing)f(op)s(ened)227 781 y(again,)c(and)e +(then)g(in)m(ternally)h(call)g(FTREOPEN,)f(so)h(programs)e(should)h +(rarely)g(need)g(to)h(explicitly)h(call)227 894 y(this)i(routine.)334 +1136 y Fe(FTREOPEN\(unit,)44 b(>)j(newunit,)f(status\))0 +1377 y Fh(4)81 b Fi(Op)s(en)24 b(and)g(initialize)k(a)e(new)f(empt)m(y) +g(FITS)g(\014le.)39 b(The)25 b(FTDKINIT)g(routine)g(simply)g(creates)i +(the)f(sp)s(eci\014ed)227 1490 y(\014le)31 b(without)f(trying)h(to)g +(in)m(terpret)f(the)h(\014lename)g(using)e(the)i(extended)f(\014lename) +h(syn)m(tax.)334 1732 y Fe(FTINIT\(unit,filename,bloc)o(ksi)o(ze,)41 +b(>)48 b(status\))334 1845 y(FTDKINIT\(unit,filename,bl)o(ock)o(size)o +(,)42 b(>)47 b(status\))0 2087 y Fh(5)81 b Fi(Create)24 +b(a)g(new)f(FITS)g(\014le,)i(using)e(a)h(template)h(\014le)e(to)i +(de\014ne)d(its)i(initial)h(size)g(and)e(structure.)37 +b(The)24 b(template)227 2199 y(ma)m(y)39 b(b)s(e)f(another)h(FITS)e +(HDU)i(or)g(an)f(ASCI)s(I)f(template)j(\014le.)64 b(If)38 +b(the)h(input)e(template)j(\014le)e(name)h(is)227 2312 +y(blank,)28 b(then)f(this)g(routine)g(b)s(eha)m(v)m(es)h(the)f(same)h +(as)f(FTINIT.)g(The)f(curren)m(tly)i(supp)s(orted)d(format)i(of)h(the) +227 2425 y(ASCI)s(I)c(template)j(\014le)f(is)f(describ)s(ed)g(under)f +(the)i(\014ts)p 2037 2425 28 4 v 32 w(parse)p 2277 2425 +V 33 w(template)g(routine)g(\(in)g(the)f(general)i(Utilities)227 +2538 y(section\),)32 b(but)e(this)g(ma)m(y)h(c)m(hange)h(sligh)m(tly)g +(later)f(releases)g(of)g(CFITSIO.)334 2780 y Fe(FTTPLT\(unit,)45 +b(filename,)g(tplfilename,)f(>)k(status\))0 3022 y Fh(6)81 +b Fi(Flush)33 b(in)m(ternal)h(bu\013ers)f(of)h(data)g(to)g(the)g +(output)g(FITS)f(\014le)h(previously)f(op)s(ened)g(with)g(ftop)s(en)h +(or)f(ftinit.)227 3135 y(The)j(routine)h(usually)f(nev)m(er)h(needs)f +(to)i(b)s(e)e(called,)j(but)d(doing)h(so)g(will)g(ensure)f(that)h(if)f +(the)h(program)227 3247 y(subsequen)m(tly)30 b(ab)s(orts,)g(then)h(the) +f(FITS)g(\014le)g(will)h(ha)m(v)m(e)h(at)f(least)g(b)s(een)f(closed)h +(prop)s(erly)-8 b(.)382 3489 y Fe(FTFLUS\(unit,)44 b(>)k(status\))0 +3731 y Fh(7)81 b Fi(Close)31 b(a)f(FITS)g(\014le)g(previously)g(op)s +(ened)g(with)g(ftop)s(en)g(or)g(ftinit)382 3973 y Fe(FTCLOS\(unit,)44 +b(>)k(status\))0 4214 y Fh(8)81 b Fi(Close)34 b(and)f(DELETE)g(a)h +(FITS)f(\014le)h(previously)f(op)s(ened)g(with)g(ftop)s(en)g(or)h +(ftinit.)51 b(This)33 b(routine)g(ma)m(y)i(b)s(e)227 +4327 y(useful)29 b(in)h(cases)g(where)g(a)g(FITS)f(\014le)g(is)h +(created,)h(but)e(an)h(error)f(o)s(ccurs)h(whic)m(h)f(prev)m(en)m(ts)i +(the)e(complete)227 4440 y(\014le)i(from)f(b)s(eing)g(written.)382 +4682 y Fe(FTDELT\(unit,)44 b(>)k(status\))0 4924 y Fh(9)81 +b Fi(Get)31 b(the)g(v)-5 b(alue)31 b(of)f(an)g(un)m(used)g(I/O)g(unit)g +(n)m(um)m(b)s(er)f(whic)m(h)h(ma)m(y)h(then)f(b)s(e)g(used)g(as)g +(input)g(to)h(FTOPEN)f(or)227 5036 y(FTINIT.)36 b(This)f(routine)h +(searc)m(hes)h(for)f(the)g(\014rst)f(un)m(used)g(unit)g(n)m(um)m(b)s +(er)g(in)g(the)i(range)f(from)f(with)h(99)227 5149 y(do)m(wn)d(to)h +(50.)50 b(This)32 b(routine)h(just)g(k)m(eeps)h(an)f(in)m(ternal)h +(list)f(of)h(the)f(allo)s(cated)i(unit)e(n)m(um)m(b)s(ers)f(and)g(do)s +(es)227 5262 y(not)26 b(ph)m(ysically)g(c)m(hec)m(k)g(that)g(the)g(F)-8 +b(ortran)25 b(unit)g(is)g(a)m(v)-5 b(ailable)28 b(\(to)e(b)s(e)f +(compatible)h(with)f(the)g(SPP)f(v)m(ersion)227 5375 +y(of)35 b(FITSIO\).)g(Th)m(us)f(users)g(m)m(ust)h(not)g(indep)s(enden)m +(tly)f(allo)s(cate)j(an)m(y)f(unit)e(n)m(um)m(b)s(ers)g(in)h(the)g +(range)g(50)227 5488 y(-)42 b(99)g(if)f(this)g(routine)g(is)g(also)h +(to)g(b)s(e)f(used)f(in)h(the)g(same)h(program.)73 b(This)40 +b(routine)h(is)g(pro)m(vided)g(for)227 5601 y(con)m(v)m(enience)34 +b(only)-8 b(,)32 b(and)e(it)i(is)f(not)h(required)e(that)i(the)f(unit)g +(n)m(um)m(b)s(ers)f(used)g(b)m(y)h(FITSIO)f(b)s(e)h(allo)s(cated)227 +5714 y(b)m(y)g(this)f(routine.)p eop end +%%Page: 37 43 +TeXDict begin 37 42 bop 0 299 a Fg(6.1.)72 b(FITS)30 +b(FILE)g(OPEN)g(AND)h(CLOSE)e(SUBR)m(OUTINES:)1561 b +Fi(37)382 555 y Fe(FTGIOU\()46 b(>)h(iounit,)f(status\))0 +807 y Fh(10)g Fi(F)-8 b(ree)34 b(\(deallo)s(cate\))i(an)d(I/O)g(unit)f +(n)m(um)m(b)s(er)g(whic)m(h)g(w)m(as)h(previously)g(allo)s(cated)i +(with)e(FTGIOU.)g(All)g(pre-)227 919 y(viously)28 b(allo)s(cated)i +(unit)d(n)m(um)m(b)s(ers)f(ma)m(y)i(b)s(e)f(deallo)s(cated)j(at)e(once) +h(b)m(y)e(calling)i(FTFIOU)f(with)f(iounit)h(=)227 1032 +y(-1.)382 1284 y Fe(FTFIOU\(iounit,)44 b(>)j(status\))0 +1535 y Fh(11)f Fi(Return)30 b(the)h(F)-8 b(ortran)31 +b(unit)g(n)m(um)m(b)s(er)e(that)i(corresp)s(onds)f(to)h(the)g(C)g +(\014ts\014le)f(p)s(oin)m(ter)h(v)-5 b(alue,)32 b(or)e(vice)i(v)m +(ersa.)227 1648 y(These)37 b(2)h(C)f(routines)g(ma)m(y)g(b)s(e)g +(useful)f(in)h(mixed)g(language)i(programs)e(where)f(b)s(oth)h(C)g(and) +f(F)-8 b(ortran)227 1761 y(subroutines)25 b(need)g(to)i(access)g(the)f +(same)g(\014le.)40 b(F)-8 b(or)26 b(example,)i(if)e(a)g(FITS)f(\014le)h +(is)g(op)s(ened)f(with)g(unit)g(12)i(b)m(y)227 1874 y(a)k(F)-8 +b(ortran)31 b(subroutine,)f(then)g(a)h(C)f(routine)h(within)f(the)g +(same)h(program)g(could)f(get)i(the)e(\014t\014le)h(p)s(oin)m(ter)227 +1987 y(v)-5 b(alue)39 b(to)f(access)h(the)f(same)h(\014le)f(b)m(y)f +(calling)j('fptr)d(=)h(CUnit2FITS\(12\)'.)64 b(These)38 +b(routines)g(return)f(a)227 2100 y(v)-5 b(alue)31 b(of)g(zero)g(if)f +(an)g(error)g(o)s(ccurs.)286 2351 y Fe(int)334 b(CFITS2Unit\(fitsfile) +42 b(*ptr\);)286 2464 y(fitsfile*)k(CUnit2FITS\(int)e(unit\);)0 +2715 y Fh(11)i Fi(P)m(arse)32 b(the)g(input)e(\014lename)i(and)f +(return)f(the)i(HDU)g(n)m(um)m(b)s(er)e(that)i(w)m(ould)f(b)s(e)g(mo)m +(v)m(ed)i(to)f(if)f(the)h(\014le)f(w)m(ere)227 2828 y(op)s(ened)i(with) +g(FTNOPN.)g(The)f(returned)g(HDU)i(n)m(um)m(b)s(er)e(b)s(egins)h(with)g +(1)g(for)g(the)g(primary)g(arra)m(y)-8 b(,)35 b(so)227 +2941 y(for)d(example,)g(if)g(the)g(input)f(\014lename)g(=)h(`m)m +(y\014le.\014ts[2]')h(then)e(hdun)m(um)e(=)j(3)g(will)g(b)s(e)f +(returned.)43 b(FIT-)227 3054 y(SIO)35 b(do)s(es)h(not)g(op)s(en)g(the) +g(\014le)g(to)h(c)m(hec)m(k)h(if)e(the)g(extension)h(actually)h(exists) +e(if)h(an)e(extension)i(n)m(um)m(b)s(er)227 3167 y(is)43 +b(sp)s(eci\014ed.)75 b(If)42 b(an)g(extension)h(*name*)g(is)f(included) +g(in)g(the)g(\014le)g(name)h(sp)s(eci\014cation)g(\(e.g.)77 +b(`m)m(y-)227 3280 y(\014le.\014ts[EVENTS]')30 b(then)f(this)h(routine) +g(will)g(ha)m(v)m(e)h(to)f(op)s(en)f(the)h(FITS)f(\014le)h(and)f(lo)s +(ok)h(for)g(the)g(p)s(osition)227 3393 y(of)38 b(the)h(named)e +(extension,)k(then)d(close)h(\014le)f(again.)64 b(This)38 +b(is)g(not)g(p)s(ossible)f(if)h(the)g(\014le)g(is)g(b)s(eing)g(read)227 +3506 y(from)e(the)g(stdin)f(stream,)j(and)d(an)h(error)f(will)h(b)s(e)g +(returned)e(in)i(this)g(case.)58 b(If)35 b(the)h(\014lename)g(do)s(es)g +(not)227 3619 y(sp)s(ecify)29 b(an)g(explicit)h(extension)g(\(e.g.)42 +b('m)m(y\014le.\014ts'\))30 b(then)f(hdun)m(um)e(=)h(-99)j(will)e(b)s +(e)g(returned,)f(whic)m(h)h(is)227 3731 y(functionally)34 +b(equiv)-5 b(alen)m(t)35 b(to)g(hdun)m(um)c(=)i(1.)50 +b(This)33 b(routine)g(is)h(mainly)g(used)e(for)i(bac)m(kw)m(ard)g +(compati-)227 3844 y(bilit)m(y)g(in)e(the)g(fto)s(ols)h(soft)m(w)m(are) +h(pac)m(k)-5 b(age)34 b(and)e(is)g(not)g(recommended)g(for)g(general)i +(use.)46 b(It)32 b(is)h(generally)227 3957 y(b)s(etter)i(and)g(more)g +(e\016cien)m(t)h(to)g(\014rst)e(op)s(en)g(the)h(FITS)f(\014le)h(with)g +(FTNOPN,)g(then)g(use)f(FTGHDN)i(to)227 4070 y(determine)30 +b(whic)m(h)g(HDU)g(in)f(the)h(\014le)g(has)g(b)s(een)f(op)s(ened,)g +(rather)g(than)h(calling)h(FTEXTN)f(follo)m(w)m(ed)h(b)m(y)227 +4183 y(a)g(call)h(to)f(FTNOPN.)382 4434 y Fe(FTEXTN\(filename,)43 +b(>)48 b(nhdu,)e(status\))0 4686 y Fh(12)g Fi(Return)30 +b(the)g(name)h(of)f(the)h(op)s(ened)e(FITS)h(\014le.)382 +4937 y Fe(FTFLNM\(unit,)44 b(>)k(filename,)d(status\))0 +5188 y Fh(13)h Fi(Return)30 b(the)g(I/O)g(mo)s(de)g(of)h(the)g(op)s(en) +e(FITS)h(\014le)g(\(READONL)-8 b(Y)32 b(=)e(0,)h(READ)m(WRITE)g(=)f +(1\).)382 5440 y Fe(FTFLMD\(unit,)44 b(>)k(iomode,)e(status\))0 +5691 y Fh(14)g Fi(Return)30 b(the)g(\014le)h(t)m(yp)s(e)f(of)h(the)f +(op)s(ened)g(FITS)g(\014le)g(\(e.g.)42 b('\014le://',)32 +b('ftp://',)g(etc.\).)p eop end +%%Page: 38 44 +TeXDict begin 38 43 bop 0 299 a Fi(38)1319 b Fg(CHAPTER)29 +b(6.)112 b(AD)m(V)-10 b(ANCED)32 b(INTERF)-10 b(A)m(CE)30 +b(SUBR)m(OUTINES)382 555 y Fe(FTURLT\(unit,)44 b(>)k(urltype,)d +(status\))0 819 y Fh(15)h Fi(P)m(arse)27 b(the)f(input)f(\014lename)i +(or)f(URL)g(in)m(to)h(its)g(comp)s(onen)m(t)f(parts:)39 +b(the)26 b(\014le)g(t)m(yp)s(e)h(\(\014le://,)h(ftp://,)g(h)m(ttp://,) +227 932 y(etc\),)34 b(the)e(base)g(input)e(\014le)i(name,)g(the)g(name) +g(of)g(the)g(output)f(\014le)h(that)g(the)g(input)f(\014le)g(is)h(to)g +(b)s(e)f(copied)227 1045 y(to)38 b(prior)e(to)h(op)s(ening,)h(the)f +(HDU)g(or)f(extension)i(sp)s(eci\014cation,)h(the)e(\014ltering)f(sp)s +(eci\014er,)i(the)f(binning)227 1157 y(sp)s(eci\014er,)e(and)e(the)i +(column)f(sp)s(eci\014er.)51 b(Blank)34 b(strings)g(will)h(b)s(e)e +(returned)g(for)h(an)m(y)g(comp)s(onen)m(ts)g(that)227 +1270 y(are)d(not)g(presen)m(t)f(in)g(the)h(input)e(\014le)i(name.)334 +1534 y Fe(FTIURL\(filename,)43 b(>)48 b(filetype,)d(infile,)h(outfile,) +g(extspec,)f(filter,)716 1647 y(binspec,)g(colspec,)h(status\))0 +1910 y Fh(16)g Fi(P)m(arse)e(the)g(input)f(\014le)h(name)f(and)g +(return)g(the)h(ro)s(ot)g(\014le)f(name.)81 b(The)43 +b(ro)s(ot)h(name)g(includes)f(the)h(\014le)227 2023 y(t)m(yp)s(e)35 +b(if)g(sp)s(eci\014ed,)h(\(e.g.)56 b('ftp://')37 b(or)e('h)m(ttp://'\)) +i(and)d(the)h(full)g(path)g(name,)h(to)g(the)f(exten)m(t)i(that)e(it)h +(is)227 2136 y(sp)s(eci\014ed)26 b(in)f(the)i(input)e(\014lename.)39 +b(It)26 b(do)s(es)g(not)g(include)g(the)g(HDU)h(name)f(or)g(n)m(um)m(b) +s(er,)g(or)g(an)m(y)h(\014ltering)227 2249 y(sp)s(eci\014cations.)334 +2513 y Fe(FTRTNM\(filename,)43 b(>)48 b(rootname,)d(status\))0 +2776 y Fh(16)h Fi(T)-8 b(est)36 b(if)g(the)g(input)f(\014le)h(or)f(a)i +(compressed)e(v)m(ersion)h(of)g(the)g(\014le)g(\(with)g(a)g(.gz,)i(.Z,) +e(.z,)i(or)e(.zip)g(extension\))227 2889 y(exists)j(on)f(disk.)63 +b(The)37 b(returned)g(v)-5 b(alue)38 b(of)g(the)h('exists')g(parameter) +f(will)g(ha)m(v)m(e)i(1)e(of)g(the)g(4)g(follo)m(wing)227 +3002 y(v)-5 b(alues:)370 3257 y Fe(2:)95 b(the)47 b(file)g(does)g(not)f +(exist,)h(but)f(a)i(compressed)d(version)h(does)g(exist)370 +3370 y(1:)95 b(the)47 b(disk)g(file)g(does)f(exist)370 +3483 y(0:)95 b(neither)46 b(the)h(file)g(nor)g(a)g(compressed)e +(version)h(of)h(the)g(file)g(exist)323 3596 y(-1:)94 +b(the)47 b(input)g(file)f(name)h(is)g(not)g(a)g(disk)g(file)g(\(could)f +(be)h(a)g(ftp,)g(http,)561 3709 y(smem,)g(or)g(mem)g(file,)f(or)h(a)h +(file)e(piped)h(in)g(on)g(the)g(STDIN)f(stream\))286 +3973 y(FTEXIST\(filename,)d(>)48 b(exists,)e(status\);)0 +4311 y Fd(6.2)135 b(HDU-Lev)l(el)47 b(Op)t(erations)0 +4562 y Fi(When)30 b(a)h(FITS)f(\014le)g(is)h(\014rst)e(op)s(ened)h(or)g +(created,)i(the)f(in)m(ternal)g(bu\013ers)e(in)h(FITSIO)f +(automatically)34 b(p)s(oin)m(t)c(to)0 4675 y(the)g(\014rst)g(HDU)h(in) +f(the)g(\014le.)41 b(The)29 b(follo)m(wing)j(routines)e(ma)m(y)h(b)s(e) +e(used)h(to)h(mo)m(v)m(e)g(to)g(another)f(HDU)h(in)f(the)h(\014le.)0 +4788 y(Note)j(that)f(the)g(HDU)g(n)m(um)m(b)s(ering)f(con)m(v)m(en)m +(tion)i(used)e(in)g(FITSIO)g(denotes)h(the)f(primary)g(arra)m(y)h(as)g +(the)g(\014rst)0 4901 y(HDU,)e(the)g(\014rst)f(extension)h(in)f(a)g +(FITS)g(\014le)g(is)h(the)f(second)h(HDU,)g(and)f(so)h(on.)0 +5164 y Fh(1)81 b Fi(Mo)m(v)m(e)32 b(to)f(a)g(sp)s(eci\014ed)f +(\(absolute\))h(HDU)g(in)g(the)f(FITS)g(\014le)g(\(nhdu)f(=)h(1)h(for)f +(the)g(FITS)g(primary)f(arra)m(y\))382 5428 y Fe(FTMAHD\(unit,nhdu,)43 +b(>)k(hdutype,status\))0 5691 y Fh(2)81 b Fi(Mo)m(v)m(e)32 +b(to)f(a)g(new)f(\(existing\))i(HDU)f(forw)m(ard)f(or)g(bac)m(kw)m +(ards)h(relativ)m(e)h(to)f(the)g(CHDU)p eop end +%%Page: 39 45 +TeXDict begin 39 44 bop 0 299 a Fg(6.2.)72 b(HDU-LEVEL)31 +b(OPERA)-8 b(TIONS)2414 b Fi(39)382 555 y Fe(FTMRHD\(unit,nmove,)43 +b(>)k(hdutype,status\))0 788 y Fh(3)81 b Fi(Mo)m(v)m(e)22 +b(to)f(the)f(\(\014rst\))h(HDU)g(whic)m(h)f(has)g(the)g(sp)s(eci\014ed) +g(extension)h(t)m(yp)s(e)f(and)g(EXTNAME)g(\(or)h(HDUNAME\))227 +901 y(and)34 b(EXTVER)g(k)m(eyw)m(ord)h(v)-5 b(alues.)53 +b(The)34 b(hdut)m(yp)s(e)f(parameter)i(ma)m(y)g(ha)m(v)m(e)g(a)g(v)-5 +b(alue)35 b(of)f(IMA)m(GE)p 3665 901 28 4 v 34 w(HDU)227 +1014 y(\(0\),)43 b(ASCI)s(I)p 669 1014 V 31 w(TBL)c(\(1\),)j(BINAR)-8 +b(Y)p 1468 1014 V 34 w(TBL)39 b(\(2\),)j(or)d(ANY)p 2234 +1014 V 34 w(HDU)g(\(-1\))i(where)d(ANY)p 3173 1014 V +34 w(HDU)h(means)g(that)227 1127 y(only)30 b(the)g(extname)h(and)e +(extv)m(er)i(v)-5 b(alues)30 b(will)g(b)s(e)f(used)g(to)i(lo)s(cate)g +(the)f(correct)h(extension.)42 b(If)29 b(the)h(input)227 +1240 y(v)-5 b(alue)27 b(of)f(extv)m(er)h(is)f(0)g(then)g(the)g(EXTVER)g +(k)m(eyw)m(ord)g(is)g(ignored)g(and)g(the)g(\014rst)f(HDU)i(with)e(a)i +(matc)m(hing)227 1353 y(EXTNAME)g(\(or)g(HDUNAME\))h(k)m(eyw)m(ord)f +(will)g(b)s(e)f(found.)38 b(If)26 b(no)h(matc)m(hing)h(HDU)f(is)g +(found)e(in)h(the)h(\014le)227 1466 y(then)i(the)g(curren)m(t)g(HDU)h +(will)f(remain)g(unc)m(hanged)g(and)g(a)g(status)g(=)g(BAD)p +2884 1466 V 34 w(HDU)p 3123 1466 V 33 w(NUM)h(\(301\))h(will)f(b)s(e) +227 1578 y(returned.)382 1811 y Fe(FTMNHD\(unit,)44 b(hdutype,)i +(extname,)f(extver,)h(>)i(status\))0 2044 y Fh(4)81 b +Fi(Get)31 b(the)g(n)m(um)m(b)s(er)e(of)h(the)h(curren)m(t)f(HDU)h(in)f +(the)h(FITS)e(\014le)i(\(primary)f(arra)m(y)g(=)g(1\))382 +2277 y Fe(FTGHDN\(unit,)44 b(>)k(nhdu\))0 2510 y Fh(5)81 +b Fi(Return)39 b(the)i(t)m(yp)s(e)g(of)g(the)g(curren)m(t)f(HDU)i(in)e +(the)h(FITS)f(\014le.)71 b(The)41 b(p)s(ossible)f(v)-5 +b(alues)41 b(for)f(hdut)m(yp)s(e)g(are)227 2623 y(IMA)m(GE)p +546 2623 V 34 w(HDU)31 b(\(0\),)h(ASCI)s(I)p 1242 2623 +V 31 w(TBL)e(\(1\),)i(or)e(BINAR)-8 b(Y)p 2133 2623 V +34 w(TBL)30 b(\(2\).)382 2856 y Fe(FTGHDT\(unit,)44 b(>)k(hdutype,)d +(status\))0 3089 y Fh(6)81 b Fi(Return)29 b(the)i(total)h(n)m(um)m(b)s +(er)d(of)i(HDUs)f(in)h(the)f(FITS)g(\014le.)41 b(The)29 +b(CHDU)i(remains)f(unc)m(hanged.)382 3322 y Fe(FTTHDU\(unit,)44 +b(>)k(hdunum,)e(status\))0 3554 y Fh(7)81 b Fi(Create)36 +b(\(app)s(end\))e(a)h(new)g(empt)m(y)g(HDU)h(follo)m(wing)h(the)e(last) +h(extension)g(that)g(has)f(b)s(een)f(previously)h(ac-)227 +3667 y(cessed)41 b(b)m(y)f(the)g(program.)70 b(This)40 +b(will)g(o)m(v)m(erwrite)i(an)m(y)f(extensions)g(in)e(an)i(existing)g +(FITS)e(\014le)i(if)f(the)227 3780 y(program)31 b(has)g(not)g(already)h +(mo)m(v)m(ed)g(to)f(that)h(\(or)f(a)h(later\))g(extension)g(using)e +(the)h(FTMAHD)h(or)f(FTM-)227 3893 y(RHD)24 b(routines.)38 +b(F)-8 b(or)25 b(example,)g(if)f(an)f(existing)h(FITS)f(\014le)g(con)m +(tains)i(a)f(primary)e(arra)m(y)i(and)f(5)g(extensions)227 +4006 y(and)31 b(a)h(program)f(\(1\))h(op)s(ens)f(the)g(FITS)g(\014le,)h +(\(2\))g(mo)m(v)m(es)h(to)f(extension)g(4,)g(\(3\))g(mo)m(v)m(es)h(bac) +m(k)f(to)g(the)f(pri-)227 4119 y(mary)36 b(arra)m(y)-8 +b(,)38 b(and)e(\(4\))h(then)e(calls)i(FTCRHD,)f(then)g(the)g(new)g +(extension)g(will)g(b)s(e)g(written)f(follo)m(wing)227 +4232 y(the)c(4th)f(extension,)i(o)m(v)m(erwriting)g(the)e(existing)h +(5th)g(extension.)382 4465 y Fe(FTCRHD\(unit,)44 b(>)k(status\))0 +4698 y Fh(8)81 b Fi(Create)30 b(a)f(primary)f(arra)m(y)i(\(if)g(none)f +(already)g(exists\),)i(or)e(insert)g(a)h(new)f(IMA)m(GE)h(extension)g +(immediately)227 4811 y(follo)m(wing)25 b(the)e(CHDU,)g(or)g(insert)g +(a)g(new)g(Primary)f(Arra)m(y)h(at)h(the)f(b)s(eginning)f(of)h(the)g +(\014le.)38 b(An)m(y)23 b(follo)m(wing)227 4924 y(extensions)29 +b(in)g(the)g(\014le)f(will)h(b)s(e)f(shifted)h(do)m(wn)f(to)h(mak)m(e)h +(ro)s(om)e(for)h(the)g(new)f(extension.)40 b(If)29 b(the)g(CHDU)227 +5036 y(is)h(the)g(last)g(HDU)g(in)g(the)f(\014le)h(then)f(the)h(new)f +(image)i(extension)f(will)g(simply)f(b)s(e)g(app)s(ended)f(to)i(the)g +(end)227 5149 y(of)k(the)h(\014le.)52 b(One)33 b(can)h(force)h(a)g(new) +e(primary)g(arra)m(y)i(to)g(b)s(e)e(inserted)h(at)h(the)f(b)s(eginning) +f(of)h(the)h(FITS)227 5262 y(\014le)29 b(b)m(y)f(setting)i(status)e(=)h +(-9)g(prior)e(to)j(calling)g(the)e(routine.)40 b(In)28 +b(this)g(case)i(the)e(existing)i(primary)d(arra)m(y)227 +5375 y(will)f(b)s(e)f(con)m(v)m(erted)i(to)g(an)e(IMA)m(GE)h +(extension.)40 b(The)25 b(new)g(extension)i(\(or)f(primary)e(arra)m +(y\))j(will)f(b)s(ecome)227 5488 y(the)32 b(CHDU.)f(The)g(FTI)s(IMGLL)f +(routine)i(is)f(iden)m(tical)i(to)e(the)h(FTI)s(IMG)f(routine)g(except) +h(that)f(the)h(4th)227 5601 y(parameter)25 b(\(the)g(length)g(of)f(eac) +m(h)h(axis\))g(is)g(an)f(arra)m(y)h(of)f(64-bit)i(in)m(tegers)f(rather) +f(than)g(an)g(arra)m(y)h(of)g(32-bit)227 5714 y(in)m(tegers.)p +eop end +%%Page: 40 46 +TeXDict begin 40 45 bop 0 299 a Fi(40)1319 b Fg(CHAPTER)29 +b(6.)112 b(AD)m(V)-10 b(ANCED)32 b(INTERF)-10 b(A)m(CE)30 +b(SUBR)m(OUTINES)382 555 y Fe(FTIIMG\(unit,bitpix,naxis)o(,na)o(xes,)41 +b(>)48 b(status\))382 668 y(FTIIMGLL\(unit,bitpix,nax)o(is,)o(naxe)o +(sll,)41 b(>)47 b(status\))0 921 y Fh(9)81 b Fi(Insert)30 +b(a)i(new)f(ASCI)s(I)f(T)-8 b(ABLE)31 b(extension)h(immediately)h +(follo)m(wing)f(the)g(CHDU.)g(An)m(y)f(follo)m(wing)i(exten-)227 +1034 y(sions)26 b(will)g(b)s(e)f(shifted)g(do)m(wn)g(to)h(mak)m(e)h(ro) +s(om)e(for)h(the)f(new)g(extension.)40 b(If)25 b(there)h(are)g(no)g +(other)f(follo)m(wing)227 1147 y(extensions)32 b(then)f(the)h(new)f +(table)h(extension)g(will)g(simply)f(b)s(e)g(app)s(ended)f(to)i(the)f +(end)g(of)h(the)f(\014le.)44 b(The)227 1260 y(new)33 +b(extension)h(will)f(b)s(ecome)h(the)f(CHDU.)h(The)f(FTIT)-8 +b(ABLL)33 b(routine)g(is)g(iden)m(tical)i(to)f(the)g(FTIT)-8 +b(AB)227 1373 y(routine)36 b(except)g(that)g(the)f(2nd)g(and)g(3rd)g +(parameters)g(\(that)i(giv)m(e)g(the)e(size)h(of)g(the)f(table\))i(are) +f(64-bit)227 1486 y(in)m(tegers)c(rather)e(than)g(32-bit)i(in)m +(tegers.)382 1739 y Fe(FTITAB\(unit,rowlen,nrows)o(,tf)o(ield)o(s,tt)o +(ype)o(,tbc)o(ol,t)o(for)o(m,tu)o(nit,)o(ext)o(name)o(,)42 +b(>)716 1852 y(status\))382 1965 y(FTITABLL\(unit,rowlenll,n)o(row)o +(sll,)o(tfie)o(lds)o(,tty)o(pe,t)o(bco)o(l,tf)o(orm,)o(tun)o(it,e)o +(xtna)o(me,)f(>)716 2077 y(status\))0 2331 y Fh(10)46 +b Fi(Insert)25 b(a)h(new)f(binary)f(table)j(extension)f(immediately)g +(follo)m(wing)h(the)f(CHDU.)g(An)m(y)g(follo)m(wing)g(extensions)227 +2443 y(will)39 b(b)s(e)f(shifted)g(do)m(wn)g(to)h(mak)m(e)g(ro)s(om)g +(for)f(the)g(new)g(extension.)66 b(If)38 b(there)h(are)f(no)h(other)f +(follo)m(wing)227 2556 y(extensions)f(then)g(the)f(new)g(bin)m(table)h +(extension)h(will)f(simply)f(b)s(e)g(app)s(ended)e(to)k(the)e(end)g(of) +h(the)g(\014le.)227 2669 y(The)23 b(new)g(extension)h(will)f(b)s(ecome) +h(the)f(CHDU.)h(The)f(FTIBINLL)g(routine)g(is)g(iden)m(tical)i(to)f +(the)g(FTIBIN)227 2782 y(routine)30 b(except)i(that)e(the)h(2nd)e +(parameter)i(\(that)g(giv)m(es)g(the)g(length)f(of)h(the)f(table\))h +(is)g(a)f(64-bit)i(in)m(teger)227 2895 y(rather)f(than)f(a)g(32-bit)i +(in)m(teger.)382 3148 y Fe(FTIBIN\(unit,nrows,tfield)o(s,t)o(type)o +(,tfo)o(rm,)o(tuni)o(t,ex)o(tna)o(me,v)o(arid)o(at)41 +b(>)48 b(status\))382 3261 y(FTIBINLL\(unit,nrowsll,tf)o(iel)o(ds,t)o +(type)o(,tf)o(orm,)o(tuni)o(t,e)o(xtna)o(me,v)o(ari)o(dat)41 +b(>)48 b(status\))0 3627 y Fh(11)e Fi(Resize)26 b(an)e(image)i(b)m(y)e +(mo)s(di\014ng)f(the)i(size,)i(dimensions,)e(and/or)f(datat)m(yp)s(e)h +(of)g(the)g(curren)m(t)f(primary)f(arra)m(y)227 3740 +y(or)29 b(image)i(extension.)40 b(If)29 b(the)g(new)g(image,)i(as)e(sp) +s(eci\014ed)f(b)m(y)h(the)g(input)f(argumen)m(ts,)i(is)f(larger)h(than) +f(the)227 3853 y(curren)m(t)34 b(existing)h(image)g(in)f(the)g(FITS)f +(\014le)h(then)f(zero)i(\014ll)f(data)h(will)f(b)s(e)f(inserted)h(at)g +(the)g(end)g(of)g(the)227 3966 y(curren)m(t)25 b(image)h(and)e(an)m(y)i +(follo)m(wing)g(extensions)g(will)f(b)s(e)f(mo)m(v)m(ed)i(further)e +(bac)m(k)h(in)g(the)g(\014le.)39 b(Similarly)-8 b(,)27 +b(if)227 4079 y(the)h(new)e(image)j(is)e(smaller)h(than)f(the)g(curren) +m(t)g(image)h(then)f(an)m(y)h(follo)m(wing)h(extensions)e(will)h(b)s(e) +e(shifted)227 4192 y(up)32 b(to)m(w)m(ards)i(the)g(b)s(eginning)e(of)h +(the)h(FITS)e(\014le)h(and)g(the)g(image)h(data)g(will)g(b)s(e)e +(truncated)h(to)h(the)f(new)227 4304 y(size.)41 b(This)25 +b(routine)h(rewrites)h(the)f(BITPIX,)h(NAXIS,)f(and)g(NAXISn)g(k)m(eyw) +m(ords)g(with)g(the)h(appropriate)227 4417 y(v)-5 b(alues)37 +b(for)f(new)h(image.)60 b(The)36 b(FTRSIMLL)g(routine)g(is)h(iden)m +(tical)h(to)g(the)e(FTRSIM)g(routine)h(except)227 4530 +y(that)30 b(the)g(4th)g(parameter)g(\(the)g(length)g(of)f(eac)m(h)i +(axis\))f(is)g(an)f(arra)m(y)h(of)g(64-bit)h(in)m(tegers)f(rather)g +(than)f(an)227 4643 y(arra)m(y)i(of)g(32-bit)g(in)m(tegers.)382 +4896 y Fe(FTRSIM\(unit,bitpix,naxis)o(,na)o(xes,)o(stat)o(us\))382 +5009 y(FTRSIMLL\(unit,bitpix,nax)o(is,)o(naxe)o(sll,)o(sta)o(tus\))0 +5262 y Fh(12)46 b Fi(Delete)34 b(the)f(CHDU)g(in)f(the)g(FITS)f +(\014le.)47 b(An)m(y)32 b(follo)m(wing)i(HDUs)f(will)g(b)s(e)e(shifted) +h(forw)m(ard)g(in)g(the)g(\014le,)h(to)227 5375 y(\014ll)38 +b(in)f(the)g(gap)h(created)g(b)m(y)g(the)f(deleted)h(HDU.)h(In)d(the)i +(case)g(of)g(deleting)g(the)g(primary)e(arra)m(y)i(\(the)227 +5488 y(\014rst)30 b(HDU)h(in)f(the)h(\014le\))g(then)f(the)h(curren)m +(t)f(primary)f(arra)m(y)i(will)g(b)s(e)f(replace)h(b)m(y)g(a)g(n)m(ull) +f(primary)f(arra)m(y)227 5601 y(con)m(taining)k(the)f(minim)m(um)e(set) +i(of)g(required)e(k)m(eyw)m(ords)i(and)e(no)i(data.)44 +b(If)31 b(there)g(are)h(more)f(extensions)227 5714 y(in)f(the)g(\014le) +g(follo)m(wing)i(the)e(one)g(that)h(is)f(deleted,)h(then)f(the)g(the)g +(CHDU)h(will)f(b)s(e)g(rede\014ned)e(to)j(p)s(oin)m(t)f(to)p +eop end +%%Page: 41 47 +TeXDict begin 41 46 bop 0 299 a Fg(6.3.)72 b(DEFINE)31 +b(OR)f(REDEFINE)h(THE)f(STR)m(UCTURE)f(OF)h(THE)g(CHDU)1042 +b Fi(41)227 555 y(the)27 b(follo)m(wing)h(extension.)41 +b(If)26 b(there)h(are)g(no)g(follo)m(wing)h(extensions)f(then)g(the)g +(CHDU)g(will)g(b)s(e)f(rede\014ned)227 668 y(to)35 b(p)s(oin)m(t)f(to)h +(the)f(previous)f(HDU.)i(The)e(output)h(HDUTYPE)g(parameter)h +(indicates)f(the)h(t)m(yp)s(e)f(of)g(the)227 781 y(new)c(CHDU)h(after)g +(the)f(previous)g(CHDU)h(has)f(b)s(een)g(deleted.)382 +1001 y Fe(FTDHDU\(unit,)44 b(>)k(hdutype,status\))0 1222 +y Fh(13)e Fi(Cop)m(y)36 b(all)h(or)f(part)g(of)g(the)g(input)f(FITS)g +(\014le)h(and)g(app)s(end)e(it)i(to)h(the)f(end)g(of)g(the)g(output)g +(FITS)f(\014le.)57 b(If)227 1335 y('previous')39 b(\(an)g(in)m(teger)h +(parameter\))g(is)f(not)g(equal)g(to)h(0,)h(then)e(an)m(y)g(HDUs)g +(preceding)g(the)g(curren)m(t)227 1448 y(HDU)f(in)e(the)h(input)e +(\014le)i(will)g(b)s(e)f(copied)h(to)g(the)g(output)f(\014le.)60 +b(Similarly)-8 b(,)39 b('curren)m(t')e(and)f('follo)m(wing')227 +1561 y(determine)j(whether)f(the)h(curren)m(t)g(HDU,)h(and/or)e(an)m(y) +i(follo)m(wing)g(HDUs)f(in)g(the)g(input)f(\014le)g(will)i(b)s(e)227 +1674 y(copied)32 b(to)g(the)g(output)f(\014le.)43 b(If)31 +b(all)h(3)g(parameters)g(are)g(not)f(equal)h(to)g(zero,)h(then)e(the)g +(en)m(tire)h(input)f(\014le)227 1786 y(will)g(b)s(e)e(copied.)41 +b(On)30 b(return,)f(the)h(curren)m(t)g(HDU)h(in)f(the)g(input)f(\014le) +h(will)h(b)s(e)e(unc)m(hanged,)h(and)g(the)g(last)227 +1899 y(copied)h(HDU)g(will)g(b)s(e)f(the)g(curren)m(t)g(HDU)i(in)e(the) +g(output)g(\014le.)382 2120 y Fe(FTCPFL\(iunit,)44 b(ounit,)i +(previous,)f(current,)h(following,)f(>)i(status\))0 2340 +y Fh(14)f Fi(Cop)m(y)35 b(the)f(en)m(tire)i(CHDU)f(from)f(the)g(FITS)g +(\014le)h(asso)s(ciated)h(with)e(IUNIT)g(to)i(the)e(CHDU)h(of)g(the)g +(FITS)227 2453 y(\014le)g(asso)s(ciated)h(with)e(OUNIT.)g(The)g(output) +g(HDU)h(m)m(ust)f(b)s(e)g(empt)m(y)h(and)e(not)i(already)g(con)m(tain)h +(an)m(y)227 2566 y(k)m(eyw)m(ords.)41 b(Space)29 b(will)g(b)s(e)g +(reserv)m(ed)g(for)g(MOREKEYS)f(additional)h(k)m(eyw)m(ords)h(in)e(the) +i(output)e(header)227 2679 y(if)j(there)f(is)h(not)f(already)h(enough)f +(space.)382 2899 y Fe(FTCOPY\(iunit,ounit,morek)o(eys)o(,)42 +b(>)47 b(status\))0 3120 y Fh(15)f Fi(Cop)m(y)27 b(the)h(header)f +(\(and)g(not)g(the)g(data\))i(from)d(the)i(CHDU)g(asso)s(ciated)g(with) +f(in)m(unit)g(to)h(the)f(CHDU)h(asso-)227 3233 y(ciated)f(with)e +(outunit.)39 b(If)25 b(the)g(curren)m(t)h(output)f(HDU)h(is)f(not)h +(completely)h(empt)m(y)-8 b(,)27 b(then)e(the)h(CHDU)g(will)227 +3346 y(b)s(e)e(closed)i(and)e(a)i(new)e(HDU)h(will)h(b)s(e)e(app)s +(ended)f(to)j(the)f(output)f(\014le.)39 b(This)24 b(routine)h(will)g +(automatically)227 3459 y(transform)31 b(the)g(necessary)h(k)m(eyw)m +(ords)f(when)g(cop)m(ying)h(a)f(primary)g(arra)m(y)h(to)f(and)g(image)i +(extension,)f(or)227 3572 y(an)27 b(image)h(extension)f(to)g(a)h +(primary)d(arra)m(y)-8 b(.)41 b(An)26 b(empt)m(y)h(output)f(data)i +(unit)e(will)h(b)s(e)f(created)i(\(all)g(v)-5 b(alues)227 +3684 y(=)30 b(0\).)382 3905 y Fe(FTCPHD\(inunit,)44 b(outunit,)h(>)j +(status\))0 4125 y Fh(16)e Fi(Cop)m(y)d(just)g(the)g(data)h(from)f(the) +g(CHDU)h(asso)s(ciated)g(with)f(IUNIT)g(to)h(the)f(CHDU)h(asso)s +(ciated)g(with)227 4238 y(OUNIT.)26 b(This)f(will)h(o)m(v)m(erwrite)h +(an)m(y)f(data)g(previously)g(in)f(the)h(OUNIT)f(CHDU.)h(This)f(lo)m(w) +i(lev)m(el)g(routine)227 4351 y(is)g(used)e(b)m(y)i(FTCOPY,)f(but)g(it) +g(ma)m(y)i(also)f(b)s(e)f(useful)f(in)i(certain)g(application)h +(programs)e(whic)m(h)g(w)m(an)m(t)h(to)227 4464 y(cop)m(y)j(the)f(data) +h(from)f(one)g(FITS)f(\014le)h(to)h(another)f(but)g(also)h(w)m(an)m(t)g +(to)g(mo)s(dify)e(the)h(header)g(k)m(eyw)m(ords)g(in)227 +4577 y(the)j(pro)s(cess.)44 b(all)33 b(the)f(required)f(header)g(k)m +(eyw)m(ords)h(m)m(ust)g(b)s(e)f(written)h(to)g(the)g(OUNIT)f(CHDU)h(b)s +(efore)227 4690 y(calling)g(this)e(routine)382 4910 y +Fe(FTCPDT\(iunit,ounit,)42 b(>)48 b(status\))0 5238 y +Fd(6.3)135 b(De\014ne)45 b(or)g(Rede\014ne)h(the)f(structure)g(of)g +(the)g(CHDU)0 5488 y Fi(It)32 b(should)f(rarely)h(b)s(e)g(necessary)g +(to)h(call)g(the)f(subroutines)f(in)g(this)h(section.)47 +b(FITSIO)30 b(in)m(ternally)j(calls)g(these)0 5601 y(routines)h(whenev) +m(er)g(necessary)-8 b(,)36 b(so)e(an)m(y)g(calls)h(to)g(these)f +(routines)g(b)m(y)g(application)h(programs)f(will)g(lik)m(ely)i(b)s(e)0 +5714 y(redundan)m(t.)p eop end +%%Page: 42 48 +TeXDict begin 42 47 bop 0 299 a Fi(42)1319 b Fg(CHAPTER)29 +b(6.)112 b(AD)m(V)-10 b(ANCED)32 b(INTERF)-10 b(A)m(CE)30 +b(SUBR)m(OUTINES)0 555 y Fh(1)81 b Fi(This)36 b(routine)h(forces)h +(FITSIO)e(to)i(scan)f(the)g(curren)m(t)g(header)g(k)m(eyw)m(ords)h +(that)f(de\014ne)g(the)g(structure)g(of)227 668 y(the)31 +b(HDU)f(\(suc)m(h)g(as)h(the)f(NAXISn,)g(PCOUNT)f(and)g(GCOUNT)h(k)m +(eyw)m(ords\))h(so)f(that)h(it)f(can)h(initialize)227 +781 y(the)36 b(in)m(ternal)g(bu\013ers)e(that)i(describ)s(e)f(the)h +(HDU)g(structure.)55 b(This)35 b(routine)h(ma)m(y)g(b)s(e)e(used)h +(instead)h(of)227 894 y(the)j(more)g(complicated)i(calls)f(to)f(ftp)s +(def,)h(ftadef)f(or)g(ftb)s(def.)65 b(This)38 b(routine)h(is)g(also)h +(v)m(ery)f(useful)f(for)227 1007 y(reinitializing)e(the)e(structure)g +(of)g(an)f(HDU,)i(if)f(the)g(n)m(um)m(b)s(er)e(of)i(ro)m(ws)g(in)g(a)g +(table,)i(as)e(sp)s(eci\014ed)f(b)m(y)h(the)227 1120 +y(NAXIS2)d(k)m(eyw)m(ord,)g(has)f(b)s(een)g(mo)s(di\014ed)f(from)h(its) +h(initial)g(v)-5 b(alue.)382 1361 y Fe(FTRDEF\(unit,)44 +b(>)k(status\))141 b(\(DEPRECATED\))0 1602 y Fh(2)81 +b Fi(De\014ne)27 b(the)g(structure)g(of)g(the)g(primary)f(arra)m(y)i +(or)f(IMA)m(GE)h(extension.)40 b(When)27 b(writing)g(GR)m(OUP)m(ed)h +(FITS)227 1715 y(\014les)43 b(that)h(b)m(y)e(con)m(v)m(en)m(tion)k(set) +d(the)g(NAXIS1)g(k)m(eyw)m(ord)h(equal)f(to)h(0,)i(ftp)s(def)c(m)m(ust) +h(b)s(e)f(called)i(with)227 1828 y(naxes\(1\))27 b(=)e(1,)i(NOT)e(0,)i +(otherwise)f(FITSIO)e(will)i(rep)s(ort)f(an)g(error)g(status=308)i +(when)d(trying)i(to)g(write)227 1941 y(data)31 b(to)g(a)g(group.)40 +b(Note:)i(it)31 b(is)g(usually)f(simpler)g(to)h(call)h(FTRDEF)e(rather) +h(than)f(this)g(routine.)382 2182 y Fe(FTPDEF\(unit,bitpix,naxis)o(,na) +o(xes,)o(pcou)o(nt,)o(gcou)o(nt,)41 b(>)48 b(status\))93 +b(\(DEPRECATED\))0 2424 y Fh(3)81 b Fi(De\014ne)32 b(the)h(structure)f +(of)g(an)h(ASCI)s(I)e(table)i(\(T)-8 b(ABLE\))33 b(extension.)48 +b(Note:)e(it)33 b(is)f(usually)g(simpler)g(to)i(call)227 +2537 y(FTRDEF)d(rather)f(than)h(this)f(routine.)382 2778 +y Fe(FTADEF\(unit,rowlen,tfiel)o(ds,)o(tbco)o(l,tf)o(orm)o(,nro)o(ws)42 +b(>)47 b(status\))f(\(DEPRECATED\))0 3019 y Fh(4)81 b +Fi(De\014ne)35 b(the)h(structure)f(of)g(a)h(binary)f(table)h(\(BINT)-8 +b(ABLE\))37 b(extension.)56 b(Note:)d(it)36 b(is)f(usually)g(simpler)g +(to)227 3132 y(call)d(FTRDEF)f(rather)f(than)g(this)g(routine.)382 +3373 y Fe(FTBDEF\(unit,tfields,tfor)o(m,v)o(arid)o(at,n)o(row)o(s)42 +b(>)47 b(status\))f(\(DEPRECATED\))0 3615 y Fh(5)81 b +Fi(De\014ne)34 b(the)g(size)h(of)f(the)g(Curren)m(t)f(Data)i(Unit,)h(o) +m(v)m(erriding)e(the)g(length)h(of)f(the)g(data)h(unit)e(as)h +(previously)227 3728 y(de\014ned)e(b)m(y)h(ftp)s(def,)g(ftadef,)i(or)e +(ftb)s(def.)48 b(This)33 b(is)g(useful)f(if)i(one)f(do)s(es)g(not)h +(kno)m(w)f(the)g(total)i(size)f(of)g(the)227 3841 y(data)f(unit)f(un)m +(til)h(after)f(the)h(data)g(ha)m(v)m(e)g(b)s(een)f(written.)46 +b(The)32 b(size)h(\(in)f(b)m(ytes\))h(of)g(an)f(ASCI)s(I)f(or)h(Binary) +227 3954 y(table)27 b(is)f(giv)m(en)g(b)m(y)g(NAXIS1)g(*)g(NAXIS2.)40 +b(\(Note)27 b(that)f(to)h(determine)f(the)f(v)-5 b(alue)27 +b(of)f(NAXIS1)f(it)i(is)f(often)227 4066 y(more)32 b(con)m(v)m(enien)m +(t)h(to)f(read)f(the)g(v)-5 b(alue)32 b(of)f(the)h(NAXIS1)f(k)m(eyw)m +(ord)h(from)e(the)i(output)e(\014le,)i(rather)f(than)227 +4179 y(computing)f(the)g(ro)m(w)g(length)h(directly)f(from)f(all)i(the) +f(TF)m(ORM)h(k)m(eyw)m(ord)f(v)-5 b(alues\).)41 b(Note:)h(it)30 +b(is)g(usually)227 4292 y(simpler)g(to)h(call)h(FTRDEF)f(rather)f(than) +g(this)g(routine.)382 4534 y Fe(FTDDEF\(unit,bytlen,)42 +b(>)48 b(status\))e(\(DEPRECATED\))0 4775 y Fh(6)81 b +Fi(De\014ne)22 b(the)g(zero)i(indexed)d(b)m(yte)i(o\013set)g(of)g(the)f +('heap')h(measured)e(from)h(the)h(start)g(of)f(the)g(binary)g(table)h +(data.)227 4888 y(By)30 b(default)g(the)f(heap)h(is)f(assumed)g(to)h +(start)g(immediately)h(follo)m(wing)g(the)f(regular)f(table)i(data,)f +(i.e.,)h(at)227 5001 y(lo)s(cation)38 b(NAXIS1)f(x)g(NAXIS2.)59 +b(This)36 b(routine)g(is)h(only)f(relev)-5 b(an)m(t)38 +b(for)e(binary)g(tables)h(whic)m(h)g(con)m(tain)227 5114 +y(v)-5 b(ariable)36 b(length)g(arra)m(y)f(columns)g(\(with)h(TF)m(ORMn) +f(=)f('Pt'\).)57 b(This)34 b(subroutine)g(also)i(automatically)227 +5227 y(writes)23 b(the)g(v)-5 b(alue)23 b(of)g(theap)g(to)h(a)f(k)m +(eyw)m(ord)g(in)g(the)g(extension)g(header.)38 b(This)22 +b(subroutine)g(m)m(ust)h(b)s(e)f(called)227 5339 y(after)27 +b(the)f(required)f(k)m(eyw)m(ords)i(ha)m(v)m(e)g(b)s(een)e(written)h +(\(with)g(ftph)m(bn\))f(and)h(after)g(the)h(table)g(structure)e(has)227 +5452 y(b)s(een)30 b(de\014ned)f(\(with)h(ftb)s(def)7 +b(\))30 b(but)g(b)s(efore)g(an)m(y)g(data)h(is)g(written)f(to)h(the)g +(table.)382 5694 y Fe(FTPTHP\(unit,theap,)43 b(>)k(status\))p +eop end +%%Page: 43 49 +TeXDict begin 43 48 bop 0 299 a Fg(6.4.)72 b(FITS)30 +b(HEADER)h(I/O)f(SUBR)m(OUTINES)2086 b Fi(43)0 555 y +Fd(6.4)135 b(FITS)44 b(Header)i(I/O)f(Subroutines)0 809 +y Fb(6.4.1)112 b(Header)38 b(Space)h(and)f(P)m(osition)f(Routines)0 +1019 y Fh(1)81 b Fi(Reserv)m(e)37 b(space)g(in)f(the)h(CHU)f(for)h +(MOREKEYS)e(more)i(header)f(k)m(eyw)m(ords.)59 b(This)36 +b(subroutine)f(ma)m(y)j(b)s(e)227 1132 y(called)e(to)g(reserv)m(e)g +(space)f(for)g(k)m(eyw)m(ords)g(whic)m(h)g(are)g(to)h(b)s(e)e(written)h +(at)g(a)h(later)g(time,)h(after)e(the)g(data)227 1245 +y(unit)h(or)g(subsequen)m(t)f(extensions)h(ha)m(v)m(e)h(b)s(een)e +(written)h(to)h(the)f(FITS)f(\014le.)58 b(If)35 b(this)h(subroutine)f +(is)h(not)227 1358 y(explicitly)29 b(called,)g(then)e(the)g(initial)i +(size)e(of)h(the)f(FITS)f(header)h(will)h(b)s(e)e(limited)i(to)g(the)f +(space)h(a)m(v)-5 b(ailable)227 1471 y(at)24 b(the)g(time)g(that)g(the) +g(\014rst)f(data)h(is)g(written)f(to)h(the)g(asso)s(ciated)h(data)f +(unit.)38 b(FITSIO)22 b(has)i(the)f(abilit)m(y)i(to)227 +1584 y(dynamically)g(add)e(more)h(space)h(to)g(the)f(header)g(if)g +(needed,)h(ho)m(w)m(ev)m(er)g(it)g(is)f(more)g(e\016cien)m(t)h(to)g +(preallo)s(cate)227 1697 y(the)31 b(required)e(space)i(if)g(the)f(size) +h(is)g(kno)m(wn)f(in)g(adv)-5 b(ance.)382 1958 y Fe +(FTHDEF\(unit,morekeys,)42 b(>)47 b(status\))0 2219 y +Fh(2)81 b Fi(Return)23 b(the)i(n)m(um)m(b)s(er)e(of)h(existing)i(k)m +(eyw)m(ords)e(in)h(the)f(CHU)g(\(NOT)h(including)f(the)g(END)h(k)m(eyw) +m(ord)g(whic)m(h)f(is)227 2332 y(not)g(considered)f(a)g(real)h(k)m(eyw) +m(ord\))g(and)f(the)g(remaining)h(space)f(a)m(v)-5 b(ailable)26 +b(to)e(write)f(additional)i(k)m(eyw)m(ords)227 2445 y(in)39 +b(the)h(CHU.)f(\(returns)f(KEYSADD)i(=)f(-1)h(if)f(the)g(header)g(has)g +(not)h(y)m(et)g(b)s(een)e(closed\).)69 b(Note)40 b(that)227 +2558 y(FITSIO)23 b(will)i(attempt)g(to)g(dynamically)g(add)e(space)i +(for)f(more)g(k)m(eyw)m(ords)h(if)f(required)f(when)g(app)s(ending)227 +2671 y(new)30 b(k)m(eyw)m(ords)h(to)g(a)g(header.)382 +2932 y Fe(FTGHSP\(iunit,)44 b(>)j(keysexist,keysadd,status\))0 +3194 y Fh(3)81 b Fi(Return)38 b(the)i(n)m(um)m(b)s(er)e(of)h(k)m(eyw)m +(ords)h(in)f(the)g(header)g(and)g(the)g(curren)m(t)h(p)s(osition)f(in)g +(the)g(header.)68 b(This)227 3307 y(returns)37 b(the)g(n)m(um)m(b)s(er) +f(of)i(the)g(k)m(eyw)m(ord)g(record)f(that)h(will)g(b)s(e)f(read)g +(next)h(\(or)g(one)g(greater)g(than)g(the)227 3420 y(p)s(osition)29 +b(of)f(the)h(last)g(k)m(eyw)m(ord)g(that)g(w)m(as)f(read)g(or)h +(written\).)40 b(A)29 b(v)-5 b(alue)28 b(of)h(1)g(is)f(returned)f(if)h +(the)h(p)s(oin)m(ter)227 3533 y(is)i(p)s(ositioned)f(at)h(the)g(b)s +(eginning)e(of)i(the)g(header.)382 3794 y Fe(FTGHPS\(iunit,)44 +b(>)j(keysexist,key_no,status\))0 4086 y Fb(6.4.2)112 +b(Read)38 b(or)f(W)-9 b(rite)37 b(Standard)i(Header)e(Routines)0 +4306 y Fi(These)31 b(subroutines)e(pro)m(vide)i(a)g(simple)g(metho)s(d) +f(of)h(reading)g(or)g(writing)g(most)g(of)g(the)g(k)m(eyw)m(ord)g(v)-5 +b(alues)31 b(that)0 4419 y(are)d(normally)g(required)f(in)h(a)g(FITS)f +(\014les.)40 b(These)27 b(subroutines)g(are)h(pro)m(vided)f(for)h(con)m +(v)m(enience)h(only)f(and)g(are)0 4532 y(not)36 b(required)e(to)i(b)s +(e)f(used.)55 b(If)35 b(preferred,)h(users)e(ma)m(y)i(call)h(the)f(lo)m +(w)m(er-lev)m(el)i(subroutines)c(describ)s(ed)h(in)g(the)0 +4644 y(previous)30 b(section)i(to)g(individually)f(read)f(or)h(write)g +(the)g(required)f(k)m(eyw)m(ords.)43 b(Note)32 b(that)g(in)e(most)i +(cases,)g(the)0 4757 y(required)26 b(k)m(eyw)m(ords)h(suc)m(h)g(as)g +(NAXIS,)f(TFIELD,)h(TTYPEn,)g(etc,)i(whic)m(h)d(de\014ne)g(the)h +(structure)f(of)h(the)g(HDU)0 4870 y(m)m(ust)j(b)s(e)g(written)g(to)i +(the)e(header)g(b)s(efore)g(an)m(y)h(data)g(can)g(b)s(e)e(written)i(to) +g(the)g(image)g(or)g(table.)0 5132 y Fh(1)81 b Fi(Put)37 +b(the)i(primary)e(header)h(or)g(IMA)m(GE)h(extension)f(k)m(eyw)m(ords)h +(in)m(to)g(the)f(CHU.)g(There)g(are)g(2)h(a)m(v)-5 b(ailable)227 +5245 y(routines:)39 b(The)27 b(simpler)f(FTPHPS)h(routine)g(is)g(equiv) +-5 b(alen)m(t)29 b(to)e(calling)i(ftphpr)c(with)i(the)g(default)h(v)-5 +b(alues)227 5357 y(of)35 b(SIMPLE)f(=)g(true,)i(p)s(coun)m(t)e(=)g(0,)i +(gcoun)m(t)g(=)e(1,)i(and)e(EXTEND)h(=)f(true.)53 b(PCOUNT,)34 +b(GCOUNT)227 5470 y(and)23 b(EXTEND)h(k)m(eyw)m(ords)g(are)h(not)f +(required)f(in)g(the)h(primary)f(header)g(and)h(are)g(only)g(written)g +(if)f(p)s(coun)m(t)227 5583 y(is)31 b(not)g(equal)h(to)g(zero,)g(gcoun) +m(t)g(is)f(not)g(equal)g(to)h(zero)g(or)f(one,)g(and)g(if)g(extend)g +(is)g(TR)m(UE,)g(resp)s(ectiv)m(ely)-8 b(.)227 5696 y(When)30 +b(writing)h(to)g(an)f(IMA)m(GE)i(extension,)f(the)f(SIMPLE)g(and)g +(EXTEND)g(parameters)h(are)g(ignored.)p eop end +%%Page: 44 50 +TeXDict begin 44 49 bop 0 299 a Fi(44)1319 b Fg(CHAPTER)29 +b(6.)112 b(AD)m(V)-10 b(ANCED)32 b(INTERF)-10 b(A)m(CE)30 +b(SUBR)m(OUTINES)382 555 y Fe(FTPHPS\(unit,bitpix,naxis)o(,na)o(xes,)41 +b(>)48 b(status\))382 781 y(FTPHPR\(unit,simple,bitpi)o(x,n)o(axis)o +(,nax)o(es,)o(pcou)o(nt,g)o(cou)o(nt,e)o(xten)o(d,)41 +b(>)48 b(status\))0 1082 y Fh(2)81 b Fi(Get)44 b(primary)e(header)h(or) +h(IMA)m(GE)g(extension)g(k)m(eyw)m(ords)g(from)f(the)g(CHU.)h(When)f +(reading)g(from)g(an)227 1195 y(IMA)m(GE)32 b(extension)f(the)f(SIMPLE) +g(and)f(EXTEND)i(parameters)g(are)f(ignored.)382 1495 +y Fe(FTGHPR\(unit,maxdim,)42 b(>)48 b(simple,bitpix,naxis,naxe)o(s,p)o +(coun)o(t,gc)o(oun)o(t,ex)o(tend)o(,)716 1608 y(status\))0 +1909 y Fh(3)81 b Fi(Put)34 b(the)h(ASCI)s(I)f(table)i(header)f(k)m(eyw) +m(ords)g(in)m(to)h(the)f(CHU.)h(The)e(optional)i(TUNITn)e(and)h +(EXTNAME)227 2021 y(k)m(eyw)m(ords)c(are)g(written)f(only)h(if)f(the)h +(input)e(string)h(v)-5 b(alues)31 b(are)g(not)f(blank.)382 +2322 y Fe(FTPHTB\(unit,rowlen,nrows)o(,tf)o(ield)o(s,tt)o(ype)o(,tbc)o +(ol,t)o(for)o(m,tu)o(nit,)o(ext)o(name)o(,)42 b(>)716 +2435 y(status\))0 2736 y Fh(4)81 b Fi(Get)31 b(the)g(ASCI)s(I)d(table)k +(header)e(k)m(eyw)m(ords)h(from)e(the)i(CHU)382 3036 +y Fe(FTGHTB\(unit,maxdim,)42 b(>)48 b(rowlen,nrows,tfields,tty)o(pe,)o +(tbco)o(l,tf)o(orm)o(,tun)o(it,)716 3149 y(extname,status\))0 +3450 y Fh(5)81 b Fi(Put)34 b(the)h(binary)f(table)i(header)e(k)m(eyw)m +(ords)i(in)m(to)f(the)g(CHU.)g(The)g(optional)h(TUNITn)e(and)g(EXTNAME) +227 3563 y(k)m(eyw)m(ords)i(are)g(written)f(only)g(if)h(the)f(input)f +(string)i(v)-5 b(alues)35 b(are)h(not)f(blank.)55 b(The)35 +b(p)s(coun)m(t)g(parameter,)227 3675 y(whic)m(h)f(sp)s(eci\014es)g(the) +h(size)g(of)g(the)f(v)-5 b(ariable)35 b(length)g(arra)m(y)g(heap,)g +(should)f(initially)h(=)f(0;)j(FITSIO)d(will)227 3788 +y(automatically)27 b(up)s(date)c(the)h(PCOUNT)f(k)m(eyw)m(ord)h(v)-5 +b(alue)24 b(if)g(an)m(y)g(v)-5 b(ariable)25 b(length)f(arra)m(y)h(data) +f(is)g(written)227 3901 y(to)31 b(the)e(heap.)41 b(The)29 +b(TF)m(ORM)g(k)m(eyw)m(ord)h(v)-5 b(alue)30 b(for)g(v)-5 +b(ariable)30 b(length)g(v)m(ector)h(columns)e(should)g(ha)m(v)m(e)i +(the)227 4014 y(form)c('Pt\(len\)')j(or)d('1Pt\(len\)')j(where)d(`t')h +(is)g(the)g(data)g(t)m(yp)s(e)g(co)s(de)f(letter)i(\(A,I,J,E,D,)g +(etc.\))42 b(and)27 b(`len')h(is)227 4127 y(an)g(in)m(teger)i(sp)s +(ecifying)e(the)g(maxim)m(um)g(length)g(of)h(the)f(v)m(ectors)h(in)f +(that)h(column)f(\(len)g(m)m(ust)g(b)s(e)g(greater)227 +4240 y(than)j(or)h(equal)f(to)i(the)e(longest)i(v)m(ector)f(in)f(the)h +(column\).)44 b(If)30 b(`len')i(is)g(not)f(sp)s(eci\014ed)g(when)f(the) +i(table)g(is)227 4353 y(created)27 b(\(e.g.,)i(the)d(input)f(TF)m(ORMn) +h(v)-5 b(alue)26 b(is)g(just)f('1Pt'\))j(then)d(FITSIO)g(will)h(scan)g +(the)g(column)g(when)227 4466 y(the)k(table)g(is)f(\014rst)g(closed)h +(and)f(will)g(app)s(end)f(the)h(maxim)m(um)h(length)f(to)h(the)g(TF)m +(ORM)f(k)m(eyw)m(ord)h(v)-5 b(alue.)227 4579 y(Note)28 +b(that)e(if)g(the)g(table)h(is)f(subsequen)m(tly)g(mo)s(di\014ed)f(to)i +(increase)f(the)h(maxim)m(um)f(length)g(of)g(the)g(v)m(ectors)227 +4692 y(then)k(the)h(mo)s(difying)f(program)g(is)g(resp)s(onsible)g(for) +g(also)h(up)s(dating)e(the)i(TF)m(ORM)g(k)m(eyw)m(ord)g(v)-5 +b(alue.)382 4992 y Fe(FTPHBN\(unit,nrows,tfield)o(s,t)o(type)o(,tfo)o +(rm,)o(tuni)o(t,ex)o(tna)o(me,v)o(arid)o(at,)41 b(>)48 +b(status\))0 5293 y Fh(6)81 b Fi(Get)31 b(the)g(binary)e(table)i +(header)g(k)m(eyw)m(ords)f(from)g(the)h(CHU)382 5593 +y Fe(FTGHBN\(unit,maxdim,)42 b(>)48 b(nrows,tfields,ttype,tfor)o(m,t)o +(unit)o(,ext)o(nam)o(e,va)o(rida)o(t,)716 5706 y(status\))p +eop end +%%Page: 45 51 +TeXDict begin 45 50 bop 0 299 a Fg(6.4.)72 b(FITS)30 +b(HEADER)h(I/O)f(SUBR)m(OUTINES)2086 b Fi(45)0 555 y +Fb(6.4.3)112 b(W)-9 b(rite)37 b(Keyw)m(ord)g(Subroutines)0 +764 y Fh(1)81 b Fi(Put)30 b(\(app)s(end\))f(an)h(80-c)m(haracter)j +(record)e(in)m(to)g(the)g(CHU.)382 1020 y Fe(FTPREC\(unit,card,)43 +b(>)k(status\))0 1276 y Fh(2)81 b Fi(Put)36 b(\(app)s(end\))g(a)i +(COMMENT)f(k)m(eyw)m(ord)g(in)m(to)h(the)g(CHU.)f(Multiple)h(COMMENT)f +(k)m(eyw)m(ords)g(will)h(b)s(e)227 1389 y(written)31 +b(if)f(the)h(input)e(commen)m(t)i(string)g(is)f(longer)h(than)f(72)i(c) +m(haracters.)382 1645 y Fe(FTPCOM\(unit,comment,)42 b(>)48 +b(status\))0 1901 y Fh(3)81 b Fi(Put)24 b(\(app)s(end\))g(a)h(HISTOR)-8 +b(Y)25 b(k)m(eyw)m(ord)g(in)m(to)h(the)f(CHU.)g(Multiple)h(HISTOR)-8 +b(Y)24 b(k)m(eyw)m(ords)h(will)h(b)s(e)e(written)227 +2014 y(if)31 b(the)f(input)g(history)g(string)g(is)h(longer)g(than)f +(72)h(c)m(haracters.)382 2270 y Fe(FTPHIS\(unit,history,)42 +b(>)48 b(status\))0 2526 y Fh(4)81 b Fi(Put)36 b(\(app)s(end\))f(the)h +(D)m(A)-8 b(TE)38 b(k)m(eyw)m(ord)f(in)m(to)g(the)f(CHU.)h(The)f(k)m +(eyw)m(ord)g(v)-5 b(alue)37 b(will)g(con)m(tain)h(the)e(curren)m(t)227 +2639 y(system)c(date)g(as)g(a)f(c)m(haracter)i(string)f(in)f +('dd/mm/yy')g(format.)44 b(If)31 b(a)h(D)m(A)-8 b(TE)32 +b(k)m(eyw)m(ord)g(already)g(exists)227 2752 y(in)j(the)g(header,)i +(then)d(this)h(subroutine)f(will)i(simply)e(up)s(date)h(the)g(k)m(eyw)m +(ord)g(v)-5 b(alue)36 b(in-place)g(with)f(the)227 2865 +y(curren)m(t)30 b(date.)382 3121 y Fe(FTPDAT\(unit,)44 +b(>)k(status\))0 3377 y Fh(5)81 b Fi(Put)22 b(\(app)s(end\))f(a)i(new)f +(k)m(eyw)m(ord)h(of)g(the)f(appropriate)h(datat)m(yp)s(e)g(in)m(to)h +(the)e(CHU.)h(Note)h(that)f(FTPKYS)f(will)227 3490 y(only)33 +b(write)g(string)f(v)-5 b(alues)33 b(up)e(to)j(68)f(c)m(haracters)h(in) +e(length;)i(longer)f(strings)g(will)f(b)s(e)g(truncated.)47 +b(The)227 3603 y(FTPKLS)27 b(routine)h(can)h(b)s(e)f(used)f(to)i(write) +f(longer)h(strings,)g(using)e(a)i(non-standard)e(FITS)h(con)m(v)m(en)m +(tion.)227 3716 y(The)23 b(E)h(and)f(D)h(v)m(ersions)g(of)g(this)f +(routine)h(ha)m(v)m(e)h(the)f(added)f(feature)h(that)g(if)g(the)g +('decimals')h(parameter)f(is)227 3829 y(negativ)m(e,)i(then)20 +b(the)i('G')g(displa)m(y)f(format)g(rather)g(then)g(the)g('E')h(format) +f(will)h(b)s(e)e(used)g(when)g(constructing)227 3942 +y(the)25 b(k)m(eyw)m(ord)f(v)-5 b(alue,)26 b(taking)f(the)g(absolute)g +(v)-5 b(alue)24 b(of)h('decimals')g(for)f(the)g(precision.)39 +b(This)23 b(will)i(suppress)227 4055 y(trailing)35 b(zeros,)h(and)d +(will)i(use)e(a)i(\014xed)e(format)h(rather)g(than)f(an)h(exp)s(onen)m +(tial)h(format,)h(dep)s(ending)c(on)227 4168 y(the)f(magnitude)f(of)h +(the)f(v)-5 b(alue.)382 4424 y Fe(FTPKY[JKLS]\(unit,keyword)o(,ke)o +(yval)o(,com)o(men)o(t,)42 b(>)47 b(status\))382 4537 +y(FTPKY[EDFG]\(unit,keyword)o(,ke)o(yval)o(,dec)o(ima)o(ls,c)o(omme)o +(nt,)41 b(>)48 b(status\))0 4793 y Fh(6)81 b Fi(Put)33 +b(\(app)s(end\))h(a)g(string)g(v)-5 b(alued)34 b(k)m(eyw)m(ord)h(in)m +(to)g(the)g(CHU)f(whic)m(h)g(ma)m(y)g(b)s(e)g(longer)h(than)e(68)i(c)m +(haracters)227 4906 y(in)j(length.)64 b(This)37 b(uses)h(the)g(Long)g +(String)g(Keyw)m(ord)g(con)m(v)m(en)m(tion)i(that)e(is)g(describ)s(ed)f +(in)h(the)g("Usage)227 5019 y(Guidelines)33 b(and)e(Suggestions")j +(section)f(of)g(this)f(do)s(cumen)m(t.)46 b(Since)33 +b(this)f(uses)g(a)g(non-standard)g(FITS)227 5132 y(con)m(v)m(en)m(tion) +38 b(to)d(enco)s(de)h(the)f(long)h(k)m(eyw)m(ord)f(string,)i(programs)d +(whic)m(h)h(use)g(this)g(routine)g(should)f(also)227 +5245 y(call)e(the)e(FTPLSW)g(routine)h(to)g(add)e(some)i(COMMENT)f(k)m +(eyw)m(ords)h(to)g(w)m(arn)f(users)f(of)i(the)f(FITS)g(\014le)227 +5357 y(that)36 b(this)f(con)m(v)m(en)m(tion)j(is)d(b)s(eing)g(used.)55 +b(FTPLSW)35 b(also)h(writes)g(a)f(k)m(eyw)m(ord)h(called)h(LONGSTRN)d +(to)227 5470 y(record)c(the)h(v)m(ersion)f(of)h(the)f(longstring)h(con) +m(v)m(en)m(tion)h(that)f(has)f(b)s(een)g(used,)f(in)h(case)h(a)g(new)f +(con)m(v)m(en)m(tion)227 5583 y(is)f(adopted)g(at)g(some)g(p)s(oin)m(t) +f(in)h(the)f(future.)40 b(If)28 b(the)g(LONGSTRN)g(k)m(eyw)m(ord)h(is)g +(already)g(presen)m(t)f(in)h(the)227 5696 y(header,)i(then)f(FTPLSW)g +(will)g(simply)g(return)g(and)f(will)i(not)g(write)f(duplicate)h(k)m +(eyw)m(ords.)p eop end +%%Page: 46 52 +TeXDict begin 46 51 bop 0 299 a Fi(46)1319 b Fg(CHAPTER)29 +b(6.)112 b(AD)m(V)-10 b(ANCED)32 b(INTERF)-10 b(A)m(CE)30 +b(SUBR)m(OUTINES)382 555 y Fe(FTPKLS\(unit,keyword,keyv)o(al,)o(comm)o +(ent,)41 b(>)47 b(status\))382 668 y(FTPLSW\(unit,)d(>)k(status\))0 +889 y Fh(7)81 b Fi(Put)30 b(\(app)s(end\))g(a)h(new)f(k)m(eyw)m(ord)h +(with)f(an)h(unde\014ned,)e(or)h(n)m(ull,)h(v)-5 b(alue)31 +b(in)m(to)h(the)f(CHU.)g(The)f(v)-5 b(alue)31 b(string)227 +1002 y(of)g(the)f(k)m(eyw)m(ord)h(is)g(left)g(blank)f(in)g(this)g +(case.)382 1223 y Fe(FTPKYU\(unit,keyword,comm)o(ent)o(,)42 +b(>)47 b(status\))0 1445 y Fh(8)81 b Fi(Put)41 b(\(app)s(end\))g(a)i(n) +m(um)m(b)s(ered)d(sequence)j(of)f(k)m(eyw)m(ords)g(in)m(to)h(the)g +(CHU.)f(One)f(ma)m(y)i(app)s(end)d(the)j(same)227 1558 +y(commen)m(t)37 b(to)g(ev)m(ery)g(k)m(eyw)m(ord)g(\(and)f(eliminate)h +(the)g(need)f(to)h(ha)m(v)m(e)g(an)f(arra)m(y)h(of)f(iden)m(tical)i +(commen)m(t)227 1670 y(strings,)g(one)e(for)g(eac)m(h)h(k)m(eyw)m +(ord\))g(b)m(y)f(including)g(the)g(amp)s(ersand)e(c)m(haracter)k(as)e +(the)h(last)g(non-blank)227 1783 y(c)m(haracter)g(in)e(the)h +(\(\014rst\))f(COMMENTS)f(string)h(parameter.)56 b(This)35 +b(same)g(string)h(will)f(then)g(b)s(e)g(used)227 1896 +y(for)30 b(the)g(commen)m(t)h(\014eld)f(in)f(all)i(the)f(k)m(eyw)m +(ords.)41 b(\(Note)32 b(that)e(the)g(SPP)f(v)m(ersion)i(of)f(these)g +(routines)g(only)227 2009 y(supp)s(orts)f(a)i(single)g(commen)m(t)g +(string\).)382 2230 y Fe(FTPKN[JKLS]\(unit,keyroot)o(,st)o(artn)o(o,no) +o(_ke)o(ys,k)o(eyva)o(ls,)o(comm)o(ents)o(,)42 b(>)47 +b(status\))382 2343 y(FTPKN[EDFG]\(unit,keyroot)o(,st)o(artn)o(o,no)o +(_ke)o(ys,k)o(eyva)o(ls,)o(deci)o(mals)o(,co)o(mmen)o(ts,)41 +b(>)907 2456 y(status\))0 2677 y Fh(9)81 b Fi(Cop)m(y)21 +b(an)h(indexed)f(k)m(eyw)m(ord)i(from)e(one)h(HDU)h(to)f(another,)i(mo) +s(difying)e(the)g(index)f(n)m(um)m(b)s(er)f(of)i(the)g(k)m(eyw)m(ord) +227 2790 y(name)37 b(in)f(the)g(pro)s(cess.)58 b(F)-8 +b(or)37 b(example,)i(this)d(routine)h(could)f(read)g(the)h(TLMIN3)f(k)m +(eyw)m(ord)h(from)f(the)227 2903 y(input)28 b(HDU)h(\(b)m(y)f(giving)h +(k)m(eyro)s(ot)h(=)d("TLMIN")i(and)f(inn)m(um)f(=)h(3\))h(and)f(write)g +(it)h(to)g(the)f(output)g(HDU)227 3016 y(with)36 b(the)g(k)m(eyw)m(ord) +h(name)f(TLMIN4)g(\(b)m(y)g(setting)i(outn)m(um)d(=)h(4\).)58 +b(If)36 b(the)g(input)f(k)m(eyw)m(ord)i(do)s(es)f(not)227 +3129 y(exist,)c(then)e(this)g(routine)g(simply)g(returns)f(without)i +(indicating)g(an)f(error.)382 3350 y Fe(FTCPKY\(inunit,)44 +b(outunit,)h(innum,)h(outnum,)g(keyroot,)g(>)h(status\))0 +3571 y Fh(10)f Fi(Put)33 b(\(app)s(end\))f(a)h('triple)h(precision')g +(k)m(eyw)m(ord)f(in)m(to)h(the)g(CHU)f(in)g(F28.16)i(format.)49 +b(The)33 b(\015oating)h(p)s(oin)m(t)227 3684 y(k)m(eyw)m(ord)c(v)-5 +b(alue)30 b(is)f(constructed)h(b)m(y)f(concatenating)j(the)d(input)g +(in)m(teger)i(v)-5 b(alue)29 b(with)g(the)h(input)e(double)227 +3797 y(precision)22 b(fraction)h(v)-5 b(alue)23 b(\(whic)m(h)f(m)m(ust) +g(ha)m(v)m(e)h(a)f(v)-5 b(alue)23 b(b)s(et)m(w)m(een)g(0.0)g(and)e +(1.0\).)40 b(The)21 b(FTGKYT)h(routine)227 3910 y(should)35 +b(b)s(e)h(used)f(to)i(read)f(this)f(k)m(eyw)m(ord)i(v)-5 +b(alue,)38 b(b)s(ecause)e(the)g(other)h(k)m(eyw)m(ord)f(reading)g +(subroutines)227 4023 y(will)31 b(not)g(preserv)m(e)f(the)h(full)f +(precision)g(of)h(the)f(v)-5 b(alue.)382 4244 y Fe +(FTPKYT\(unit,keyword,intv)o(al,)o(dblv)o(al,c)o(omm)o(ent,)41 +b(>)48 b(status\))0 4466 y Fh(11)e Fi(W)-8 b(rite)36 +b(k)m(eyw)m(ords)g(to)f(the)h(CHDU)f(that)h(are)f(de\014ned)f(in)g(an)h +(ASCI)s(I)f(template)i(\014le.)55 b(The)34 b(format)i(of)f(the)227 +4578 y(template)d(\014le)f(is)f(describ)s(ed)f(under)g(the)i(ftgthd)f +(routine)g(b)s(elo)m(w.)382 4800 y Fe(FTPKTP\(unit,)44 +b(filename,)i(>)h(status\))0 5021 y Fh(12)f Fi(App)s(end)28 +b(the)i(ph)m(ysical)g(units)g(string)g(to)g(an)g(existing)h(k)m(eyw)m +(ord.)41 b(This)29 b(routine)h(uses)f(a)h(lo)s(cal)i(con)m(v)m(en)m +(tion,)227 5134 y(sho)m(wn)g(in)g(the)h(follo)m(wing)h(example,)g(in)e +(whic)m(h)g(the)h(k)m(eyw)m(ord)g(units)f(are)h(enclosed)g(in)f(square) +g(brac)m(k)m(ets)227 5247 y(in)e(the)h(b)s(eginning)f(of)g(the)h(k)m +(eyw)m(ord)g(commen)m(t)g(\014eld.)239 5468 y Fe(VELOCITY=)809 +b(12.3)46 b(/)i([km/s])e(orbital)g(speed)382 5694 y +(FTPUNT\(unit,keyword,unit)o(s,)41 b(>)48 b(status\))p +eop end +%%Page: 47 53 +TeXDict begin 47 52 bop 0 299 a Fg(6.4.)72 b(FITS)30 +b(HEADER)h(I/O)f(SUBR)m(OUTINES)2086 b Fi(47)0 555 y +Fb(6.4.4)112 b(Insert)38 b(Keyw)m(ord)f(Subroutines)0 +762 y Fh(1)81 b Fi(Insert)26 b(a)h(new)f(k)m(eyw)m(ord)h(record)g(in)m +(to)g(the)g(CHU)g(at)g(the)g(sp)s(eci\014ed)f(p)s(osition)h(\(i.e.,)i +(immediately)f(preceding)227 875 y(the)34 b(\(k)m(eyno\)th)g(k)m(eyw)m +(ord)g(in)f(the)h(header.\))49 b(This)33 b('insert)g(record')h +(subroutine)e(is)h(somewhat)h(less)g(e\016-)227 988 y(cien)m(t)28 +b(then)f(the)g('app)s(end)e(record')i(subroutine)f(\(FTPREC\))g +(describ)s(ed)g(ab)s(o)m(v)m(e)i(b)s(ecause)f(the)g(remaining)227 +1101 y(k)m(eyw)m(ords)k(in)f(the)h(header)f(ha)m(v)m(e)h(to)g(b)s(e)f +(shifted)g(do)m(wn)g(one)h(slot.)382 1349 y Fe +(FTIREC\(unit,key_no,card,)41 b(>)47 b(status\))0 1598 +y Fh(2)81 b Fi(Insert)36 b(a)h(new)f(k)m(eyw)m(ord)i(in)m(to)g(the)f +(CHU.)g(The)f(new)g(k)m(eyw)m(ord)i(is)f(inserted)f(immediately)i +(follo)m(wing)h(the)227 1711 y(last)27 b(k)m(eyw)m(ord)g(that)f(has)g +(b)s(een)g(read)g(from)f(the)h(header.)40 b(The)25 b(FTIKLS)g +(subroutine)g(w)m(orks)h(the)g(same)h(as)227 1824 y(the)h(FTIKYS)e +(subroutine,)h(except)i(it)f(also)g(supp)s(orts)e(long)i(string)f(v)-5 +b(alues)28 b(greater)g(than)f(68)h(c)m(haracters)227 +1937 y(in)36 b(length.)59 b(These)36 b('insert)g(k)m(eyw)m(ord')h +(subroutines)e(are)i(somewhat)g(less)f(e\016cien)m(t)i(then)e(the)g +('app)s(end)227 2049 y(k)m(eyw)m(ord')30 b(subroutines)e(describ)s(ed)g +(ab)s(o)m(v)m(e)i(b)s(ecause)f(the)g(remaining)h(k)m(eyw)m(ords)f(in)g +(the)g(header)g(ha)m(v)m(e)h(to)227 2162 y(b)s(e)g(shifted)g(do)m(wn)g +(one)h(slot.)382 2411 y Fe(FTIKEY\(unit,)44 b(card,)j(>)g(status\))382 +2524 y(FTIKY[JKLS]\(unit,keyword)o(,ke)o(yval)o(,com)o(men)o(t,)42 +b(>)47 b(status\))382 2637 y(FTIKLS\(unit,keyword,keyv)o(al,)o(comm)o +(ent,)41 b(>)47 b(status\))382 2750 y(FTIKY[EDFG]\(unit,keyword)o(,ke)o +(yval)o(,dec)o(ima)o(ls,c)o(omme)o(nt,)41 b(>)48 b(status\))0 +2998 y Fh(3)81 b Fi(Insert)32 b(a)i(new)f(k)m(eyw)m(ord)h(with)f(an)h +(unde\014ned,)e(or)h(n)m(ull,)h(v)-5 b(alue)34 b(in)m(to)h(the)e(CHU.)h +(The)f(v)-5 b(alue)34 b(string)f(of)h(the)227 3111 y(k)m(eyw)m(ord)d +(is)g(left)g(blank)f(in)g(this)g(case.)382 3359 y Fe +(FTIKYU\(unit,keyword,comm)o(ent)o(,)42 b(>)47 b(status\))0 +3648 y Fb(6.4.5)112 b(Read)38 b(Keyw)m(ord)g(Subroutines)0 +3867 y Fi(These)29 b(routines)f(return)g(the)h(v)-5 b(alue)29 +b(of)g(the)g(sp)s(eci\014ed)f(k)m(eyw)m(ord\(s\).)41 +b(Wild)30 b(card)e(c)m(haracters)i(\(*,)h(?,)e(or)g(#\))f(ma)m(y)0 +3980 y(b)s(e)f(used)h(when)f(sp)s(ecifying)h(the)g(name)g(of)g(the)g(k) +m(eyw)m(ord)h(to)g(b)s(e)e(read:)39 b(a)29 b(')10 b(?')40 +b(will)28 b(matc)m(h)h(an)m(y)g(single)f(c)m(haracter)0 +4093 y(at)38 b(that)g(p)s(osition)f(in)g(the)h(k)m(eyw)m(ord)g(name)f +(and)g(a)g('*')i(will)e(matc)m(h)h(an)m(y)g(length)g(\(including)f +(zero\))h(string)g(of)0 4206 y(c)m(haracters.)65 b(The)37 +b('#')h(c)m(haracter)h(will)f(matc)m(h)h(an)m(y)f(consecutiv)m(e)i +(string)e(of)g(decimal)h(digits)f(\(0)h(-)f(9\).)64 b(Note)0 +4319 y(that)30 b(when)f(a)g(wild)g(card)h(is)f(used)g(in)g(the)h(input) +e(k)m(eyw)m(ord)i(name,)g(the)g(routine)f(will)h(only)g(searc)m(h)g +(for)f(a)h(matc)m(h)0 4432 y(from)h(the)h(curren)m(t)g(header)g(p)s +(osition)g(to)g(the)h(end)e(of)h(the)g(header.)45 b(It)32 +b(will)g(not)g(resume)g(the)g(searc)m(h)g(from)g(the)0 +4545 y(top)i(of)h(the)f(header)g(bac)m(k)h(to)g(the)f(original)h +(header)f(p)s(osition)g(as)h(is)f(done)g(when)f(no)h(wildcards)f(are)i +(included)0 4657 y(in)f(the)g(k)m(eyw)m(ord)h(name.)52 +b(If)33 b(the)h(desired)g(k)m(eyw)m(ord)h(string)f(is)g(8-c)m +(haracters)i(long)f(\(the)f(maxim)m(um)g(length)h(of)0 +4770 y(a)h(k)m(eyw)m(ord)g(name\))g(then)g(a)g('*')g(ma)m(y)h(b)s(e)e +(app)s(ended)f(as)h(the)h(nin)m(th)g(c)m(haracter)h(of)f(the)f(input)g +(name)h(to)g(force)0 4883 y(the)31 b(k)m(eyw)m(ord)g(searc)m(h)h(to)f +(stop)g(at)g(the)g(end)f(of)h(the)g(header)g(\(e.g.,)i('COMMENT)d(*')i +(will)f(searc)m(h)g(for)g(the)g(next)0 4996 y(COMMENT)37 +b(k)m(eyw)m(ord\).)64 b(The)37 b(\013grec)i(routine)f(ma)m(y)g(b)s(e)f +(used)g(to)i(set)f(the)g(starting)g(p)s(osition)g(when)f(doing)0 +5109 y(wild)30 b(card)g(searc)m(hes.)0 5357 y Fh(1)81 +b Fi(Get)37 b(the)f(n)m(th)f(80-c)m(haracter)k(header)d(record)g(from)f +(the)h(CHU.)h(The)e(\014rst)g(k)m(eyw)m(ord)i(in)e(the)h(header)g(is)g +(at)227 5470 y(k)m(ey)p 365 5470 28 4 v 34 w(no)42 b(=)f(1;)49 +b(if)42 b(k)m(ey)p 996 5470 V 34 w(no)g(=)f(0)i(then)e(this)h +(subroutine)f(simple)h(mo)m(v)m(es)i(the)e(in)m(ternal)h(p)s(oin)m(ter) +f(to)h(the)227 5583 y(b)s(eginning)35 b(of)h(the)g(header)f(so)h(that)g +(subsequen)m(t)f(k)m(eyw)m(ord)h(op)s(erations)g(will)g(start)g(at)g +(the)g(top)g(of)g(the)227 5696 y(header;)31 b(it)g(also)g(returns)e(a)i +(blank)f(card)g(v)-5 b(alue)31 b(in)f(this)g(case.)p +eop end +%%Page: 48 54 +TeXDict begin 48 53 bop 0 299 a Fi(48)1319 b Fg(CHAPTER)29 +b(6.)112 b(AD)m(V)-10 b(ANCED)32 b(INTERF)-10 b(A)m(CE)30 +b(SUBR)m(OUTINES)382 555 y Fe(FTGREC\(unit,key_no,)42 +b(>)48 b(card,status\))0 817 y Fh(2)81 b Fi(Get)31 b(the)g(name,)f(v)-5 +b(alue)31 b(\(as)g(a)g(string\),)g(and)f(commen)m(t)i(of)e(the)h(n)m +(th)f(k)m(eyw)m(ord)h(in)f(CHU.)h(This)f(routine)g(also)227 +930 y(c)m(hec)m(ks)h(that)f(the)g(returned)e(k)m(eyw)m(ord)i(name)f +(\(KEYW)m(ORD\))i(con)m(tains)g(only)e(legal)i(ASCI)s(I)d(c)m +(haracters.)227 1043 y(Call)j(FTGREC)f(and)g(FTPSV)m(C)g(to)h(b)m +(ypass)f(this)g(error)g(c)m(hec)m(k.)382 1305 y Fe +(FTGKYN\(unit,key_no,)42 b(>)48 b(keyword,value,comment,st)o(atu)o(s\)) +0 1568 y Fh(3)81 b Fi(Get)31 b(the)g(80-c)m(haracter)i(header)d(record) +g(for)g(the)h(named)f(k)m(eyw)m(ord)382 1830 y Fe +(FTGCRD\(unit,keyword,)42 b(>)48 b(card,status\))0 2092 +y Fh(4)81 b Fi(Get)26 b(the)f(next)h(k)m(eyw)m(ord)f(whose)g(name)h +(matc)m(hes)g(one)f(of)h(the)f(strings)g(in)g('inclist')i(but)d(do)s +(es)h(not)g(matc)m(h)i(an)m(y)227 2205 y(of)32 b(the)f(strings)g(in)g +('exclist'.)45 b(The)30 b(strings)h(in)g(inclist)h(and)f(exclist)h(ma)m +(y)g(con)m(tain)h(wild)d(card)h(c)m(haracters)227 2318 +y(\(*,)38 b(?,)e(and)e(#\))i(as)f(describ)s(ed)f(at)i(the)f(b)s +(eginning)f(of)i(this)f(section.)56 b(This)34 b(routine)h(searc)m(hes)h +(from)f(the)227 2431 y(curren)m(t)28 b(header)f(p)s(osition)h(to)g(the) +g(end)f(of)h(the)g(header,)g(only)-8 b(,)29 b(and)e(do)s(es)g(not)h +(con)m(tin)m(ue)h(the)f(searc)m(h)g(from)227 2544 y(the)41 +b(top)g(of)g(the)g(header)g(bac)m(k)h(to)f(the)g(original)h(p)s +(osition.)73 b(The)40 b(curren)m(t)h(header)f(p)s(osition)h(ma)m(y)h(b) +s(e)227 2657 y(reset)33 b(with)e(the)h(ftgrec)h(routine.)44 +b(Note)33 b(that)g(nexc)f(ma)m(y)g(b)s(e)f(set)h(=)g(0)g(if)f(there)h +(are)g(no)g(k)m(eyw)m(ords)g(to)h(b)s(e)227 2769 y(excluded.)41 +b(This)29 b(routine)i(returns)e(status)i(=)f(202)h(if)g(a)f(matc)m +(hing)i(k)m(eyw)m(ord)f(is)f(not)h(found.)382 3032 y +Fe(FTGNXK\(unit,inclist,ninc)o(,ex)o(clis)o(t,ne)o(xc,)41 +b(>)48 b(card,status\))0 3294 y Fh(5)81 b Fi(Get)30 b(the)g(literal)i +(k)m(eyw)m(ord)e(v)-5 b(alue)30 b(as)g(a)g(c)m(haracter)i(string.)40 +b(Regardless)31 b(of)f(the)g(datat)m(yp)s(e)g(of)g(the)g(k)m(eyw)m +(ord,)227 3407 y(this)37 b(routine)g(simply)g(returns)f(the)h(string)g +(of)g(c)m(haracters)i(in)d(the)i(v)-5 b(alue)37 b(\014eld)g(of)g(the)g +(k)m(eyw)m(ord)h(along)227 3520 y(with)30 b(the)h(commen)m(t)g +(\014eld.)382 3782 y Fe(FTGKEY\(unit,keyword,)42 b(>)48 +b(value,comment,status\))0 4044 y Fh(6)81 b Fi(Get)31 +b(a)g(k)m(eyw)m(ord)g(v)-5 b(alue)30 b(\(with)h(the)f(appropriate)h +(datat)m(yp)s(e\))g(and)f(commen)m(t)i(from)e(the)g(CHU)382 +4306 y Fe(FTGKY[EDJKLS]\(unit,keywo)o(rd,)41 b(>)48 b +(keyval,comment,status\))0 4568 y Fh(7)81 b Fi(Get)24 +b(a)g(sequence)g(of)g(n)m(um)m(b)s(ered)e(k)m(eyw)m(ord)i(v)-5 +b(alues.)38 b(These)24 b(routines)f(do)g(not)h(supp)s(ort)e(wild)h +(card)g(c)m(haracters)227 4681 y(in)30 b(the)h(ro)s(ot)g(name.)382 +4943 y Fe(FTGKN[EDJKLS]\(unit,keyro)o(ot,)o(star)o(tno,)o(max)o(_key)o +(s,)42 b(>)47 b(keyvals,nfound,status\))0 5206 y Fh(8)81 +b Fi(Get)27 b(the)f(v)-5 b(alue)26 b(of)h(a)f(\015oating)h(p)s(oin)m(t) +f(k)m(eyw)m(ord,)i(returning)d(the)h(in)m(teger)h(and)f(fractional)h +(parts)f(of)g(the)g(v)-5 b(alue)227 5319 y(in)32 b(separate)g +(subroutine)f(argumen)m(ts.)45 b(This)31 b(subroutine)f(ma)m(y)j(b)s(e) +e(used)g(to)h(read)g(an)m(y)g(k)m(eyw)m(ord)g(but)f(is)227 +5431 y(esp)s(ecially)h(useful)d(for)i(reading)f(the)h('triple)g +(precision')f(k)m(eyw)m(ords)h(written)g(b)m(y)f(FTPKYT.)382 +5694 y Fe(FTGKYT\(unit,keyword,)42 b(>)48 b(intval,dblval,comment,s)o +(tat)o(us\))p eop end +%%Page: 49 55 +TeXDict begin 49 54 bop 0 299 a Fg(6.4.)72 b(FITS)30 +b(HEADER)h(I/O)f(SUBR)m(OUTINES)2086 b Fi(49)0 555 y +Fh(9)81 b Fi(Get)24 b(the)g(ph)m(ysical)h(units)e(string)g(in)h(an)f +(existing)i(k)m(eyw)m(ord.)39 b(This)23 b(routine)h(uses)f(a)h(lo)s +(cal)h(con)m(v)m(en)m(tion,)j(sho)m(wn)227 668 y(in)33 +b(the)h(follo)m(wing)g(example,)h(in)e(whic)m(h)g(the)g(k)m(eyw)m(ord)h +(units)f(are)g(enclosed)h(in)f(square)g(brac)m(k)m(ets)i(in)e(the)227 +781 y(b)s(eginning)h(of)h(the)g(k)m(eyw)m(ord)g(commen)m(t)h(\014eld.) +53 b(A)35 b(blank)g(string)f(is)h(returned)f(if)g(no)h(units)f(are)h +(de\014ned)227 894 y(for)30 b(the)h(k)m(eyw)m(ord.)191 +1132 y Fe(VELOCITY=)809 b(12.3)46 b(/)i([km/s])e(orbital)g(speed)382 +1358 y(FTGUNT\(unit,keyword,)c(>)48 b(units,status\))0 +1645 y Fb(6.4.6)112 b(Mo)s(dify)39 b(Keyw)m(ord)e(Subroutines)0 +1864 y Fi(Wild)32 b(card)f(c)m(haracters,)j(as)e(describ)s(ed)e(in)h +(the)h(Read)g(Keyw)m(ord)f(section,)i(ab)s(o)m(v)m(e,)g(ma)m(y)g(b)s(e) +d(used)h(when)g(sp)s(eci-)0 1977 y(fying)f(the)h(name)f(of)h(the)f(k)m +(eyw)m(ord)h(to)g(b)s(e)f(mo)s(di\014ed.)0 2215 y Fh(1)81 +b Fi(Mo)s(dify)30 b(\(o)m(v)m(erwrite\))i(the)f(n)m(th)f(80-c)m +(haracter)j(header)d(record)h(in)f(the)g(CHU)382 2453 +y Fe(FTMREC\(unit,key_no,card,)41 b(>)47 b(status\))0 +2692 y Fh(2)81 b Fi(Mo)s(dify)37 b(\(o)m(v)m(erwrite\))j(the)e(80-c)m +(haracter)j(header)c(record)h(for)f(the)h(named)f(k)m(eyw)m(ord)h(in)g +(the)g(CHU.)g(This)227 2805 y(can)31 b(b)s(e)f(used)f(to)i(o)m(v)m +(erwrite)h(the)f(name)f(of)h(the)f(k)m(eyw)m(ord)h(as)g(w)m(ell)g(as)g +(its)g(v)-5 b(alue)30 b(and)g(commen)m(t)i(\014elds.)382 +3043 y Fe(FTMCRD\(unit,keyword,card)o(,)42 b(>)47 b(status\))0 +3281 y Fh(3)81 b Fi(Mo)s(dify)33 b(\(o)m(v)m(erwrite\))k(the)d(name)g +(of)h(an)f(existing)h(k)m(eyw)m(ord)f(in)g(the)h(CHU)f(preserving)f +(the)i(curren)m(t)e(v)-5 b(alue)227 3394 y(and)30 b(commen)m(t)h +(\014elds.)382 3632 y Fe(FTMNAM\(unit,oldkey,keywo)o(rd,)41 +b(>)48 b(status\))0 3870 y Fh(4)81 b Fi(Mo)s(dify)30 +b(\(o)m(v)m(erwrite\))i(the)f(commen)m(t)g(\014eld)f(of)h(an)f +(existing)h(k)m(eyw)m(ord)g(in)f(the)h(CHU)382 4108 y +Fe(FTMCOM\(unit,keyword,comm)o(ent)o(,)42 b(>)47 b(status\))0 +4347 y Fh(5)81 b Fi(Mo)s(dify)24 b(the)h(v)-5 b(alue)25 +b(and)f(commen)m(t)i(\014elds)e(of)h(an)f(existing)i(k)m(eyw)m(ord)f +(in)f(the)h(CHU.)g(The)f(FTMKLS)g(subrou-)227 4459 y(tine)35 +b(w)m(orks)e(the)h(same)h(as)f(the)g(FTMKYS)f(subroutine,)h(except)h +(it)g(also)f(supp)s(orts)e(long)j(string)f(v)-5 b(alues)227 +4572 y(greater)38 b(than)f(68)h(c)m(haracters)g(in)f(length.)60 +b(Optionally)-8 b(,)40 b(one)d(ma)m(y)h(mo)s(dify)e(only)h(the)g(v)-5 +b(alue)37 b(\014eld)g(and)227 4685 y(lea)m(v)m(e)32 b(the)d(commen)m(t) +i(\014eld)e(unc)m(hanged)g(b)m(y)g(setting)h(the)g(input)e(COMMENT)h +(parameter)h(equal)g(to)g(the)227 4798 y(amp)s(ersand)f(c)m(haracter)k +(\(&\).)42 b(The)30 b(E)g(and)g(D)h(v)m(ersions)g(of)g(this)g(routine)f +(ha)m(v)m(e)i(the)f(added)f(feature)h(that)227 4911 y(if)26 +b(the)h('decimals')g(parameter)g(is)f(negativ)m(e,)k(then)c(the)g('G')h +(displa)m(y)f(format)h(rather)f(then)g(the)g('E')h(format)227 +5024 y(will)i(b)s(e)f(used)f(when)h(constructing)h(the)f(k)m(eyw)m(ord) +h(v)-5 b(alue,)30 b(taking)f(the)g(absolute)g(v)-5 b(alue)29 +b(of)f('decimals')i(for)227 5137 y(the)37 b(precision.)60 +b(This)35 b(will)i(suppress)e(trailing)i(zeros,)i(and)d(will)h(use)g(a) +g(\014xed)e(format)i(rather)g(than)f(an)227 5250 y(exp)s(onen)m(tial)c +(format,)f(dep)s(ending)d(on)j(the)f(magnitude)h(of)f(the)h(v)-5 +b(alue.)382 5488 y Fe(FTMKY[JKLS]\(unit,keyword)o(,ke)o(yval)o(,com)o +(men)o(t,)42 b(>)47 b(status\))382 5601 y(FTMKLS\(unit,keyword,keyv)o +(al,)o(comm)o(ent,)41 b(>)47 b(status\))382 5714 y +(FTMKY[EDFG]\(unit,keyword)o(,ke)o(yval)o(,dec)o(ima)o(ls,c)o(omme)o +(nt,)41 b(>)48 b(status\))p eop end +%%Page: 50 56 +TeXDict begin 50 55 bop 0 299 a Fi(50)1319 b Fg(CHAPTER)29 +b(6.)112 b(AD)m(V)-10 b(ANCED)32 b(INTERF)-10 b(A)m(CE)30 +b(SUBR)m(OUTINES)0 555 y Fh(6)81 b Fi(Mo)s(dify)22 b(the)g(v)-5 +b(alue)23 b(of)f(an)g(existing)i(k)m(eyw)m(ord)e(to)h(b)s(e)f +(unde\014ned,)g(or)g(n)m(ull.)38 b(The)22 b(v)-5 b(alue)22 +b(string)h(of)f(the)g(k)m(eyw)m(ord)227 668 y(is)30 b(set)h(to)g +(blank.)40 b(Optionally)-8 b(,)31 b(one)f(ma)m(y)h(lea)m(v)m(e)h(the)f +(commen)m(t)g(\014eld)e(unc)m(hanged)h(b)m(y)g(setting)h(the)f(input) +227 781 y(COMMENT)g(parameter)h(equal)g(to)g(the)g(amp)s(ersand)e(c)m +(haracter)j(\(&\).)382 1034 y Fe(FTMKYU\(unit,keyword,comm)o(ent)o(,)42 +b(>)47 b(status\))0 1324 y Fb(6.4.7)112 b(Up)s(date)39 +b(Keyw)m(ord)e(Subroutines)0 1532 y Fh(1)81 b Fi(Up)s(date)36 +b(an)g(80-c)m(haracter)j(record)d(in)g(the)h(CHU.)f(If)g(the)g(sp)s +(eci\014ed)g(k)m(eyw)m(ord)h(already)f(exists)h(then)f(that)227 +1645 y(header)j(record)f(will)h(b)s(e)f(replaced)i(with)e(the)h(input)f +(CARD)g(string.)66 b(If)38 b(it)i(do)s(es)e(not)h(exist)g(then)g(the) +227 1758 y(new)f(record)g(will)g(b)s(e)f(added)h(to)g(the)g(header.)64 +b(The)37 b(FTUKLS)g(subroutine)g(w)m(orks)h(the)g(same)h(as)f(the)227 +1870 y(FTUKYS)28 b(subroutine,)g(except)i(it)f(also)h(supp)s(orts)c +(long)j(string)g(v)-5 b(alues)29 b(greater)h(than)e(68)h(c)m(haracters) +h(in)227 1983 y(length.)382 2237 y Fe(FTUCRD\(unit,keyword,card)o(,)42 +b(>)47 b(status\))0 2490 y Fh(2)81 b Fi(Up)s(date)44 +b(the)i(v)-5 b(alue)45 b(and)g(commen)m(t)h(\014elds)e(of)h(a)h(k)m +(eyw)m(ord)f(in)g(the)g(CHU.)h(The)e(sp)s(eci\014ed)g(k)m(eyw)m(ord)i +(is)227 2603 y(mo)s(di\014ed)38 b(if)g(it)h(already)g(exists)g(\(b)m(y) +g(calling)h(FTMKYx\))f(otherwise)f(a)h(new)f(k)m(eyw)m(ord)h(is)g +(created)g(b)m(y)227 2716 y(calling)f(FTPKYx.)58 b(The)36 +b(E)g(and)f(D)i(v)m(ersions)f(of)h(this)f(routine)g(ha)m(v)m(e)h(the)g +(added)e(feature)i(that)g(if)f(the)227 2828 y('decimals')c(parameter)g +(is)f(negativ)m(e,)i(then)d(the)h('G')h(displa)m(y)f(format)g(rather)g +(then)f(the)h('E')g(format)h(will)227 2941 y(b)s(e)41 +b(used)f(when)h(constructing)h(the)f(k)m(eyw)m(ord)h(v)-5 +b(alue,)45 b(taking)d(the)f(absolute)h(v)-5 b(alue)42 +b(of)g('decimals')g(for)227 3054 y(the)37 b(precision.)60 +b(This)35 b(will)i(suppress)e(trailing)i(zeros,)i(and)d(will)h(use)g(a) +g(\014xed)e(format)i(rather)g(than)f(an)227 3167 y(exp)s(onen)m(tial)c +(format,)f(dep)s(ending)d(on)j(the)f(magnitude)h(of)f(the)h(v)-5 +b(alue.)382 3420 y Fe(FTUKY[JKLS]\(unit,keyword)o(,ke)o(yval)o(,com)o +(men)o(t,)42 b(>)47 b(status\))382 3533 y(FTUKLS\(unit,keyword,keyv)o +(al,)o(comm)o(ent,)41 b(>)47 b(status\))382 3646 y +(FTUKY[EDFG]\(unit,keyword)o(,ke)o(yval)o(,dec)o(ima)o(ls,c)o(omme)o +(nt,)41 b(>)48 b(status\))0 3899 y Fh(3)81 b Fi(Up)s(date)23 +b(the)g(v)-5 b(alue)24 b(of)g(an)f(existing)i(k)m(eyw)m(ord)f(to)g(b)s +(e)f(unde\014ned,)f(or)i(n)m(ull,)h(or)e(insert)h(a)f(new)g +(unde\014ned-v)-5 b(alue)227 4012 y(k)m(eyw)m(ord)30 +b(if)f(it)h(do)s(esn't)f(already)h(exist.)41 b(The)29 +b(v)-5 b(alue)30 b(string)f(of)g(the)h(k)m(eyw)m(ord)f(is)h(left)g +(blank)f(in)f(this)i(case.)382 4265 y Fe(FTUKYU\(unit,keyword,comm)o +(ent)o(,)42 b(>)47 b(status\))0 4555 y Fb(6.4.8)112 b(Delete)38 +b(Keyw)m(ord)f(Subroutines)0 4763 y Fh(1)81 b Fi(Delete)32 +b(an)e(existing)h(k)m(eyw)m(ord)g(record.)40 b(The)30 +b(space)h(previously)f(o)s(ccupied)g(b)m(y)g(the)g(k)m(eyw)m(ord)h(is)f +(reclaimed)227 4876 y(b)m(y)c(mo)m(ving)h(all)g(the)f(follo)m(wing)i +(header)e(records)g(up)f(one)h(ro)m(w)h(in)e(the)i(header.)39 +b(The)25 b(\014rst)h(routine)g(deletes)227 4989 y(a)34 +b(k)m(eyw)m(ord)f(at)h(a)g(sp)s(eci\014ed)e(p)s(osition)h(in)g(the)g +(header)g(\(the)h(\014rst)e(k)m(eyw)m(ord)i(is)f(at)h(p)s(osition)f +(1\),)i(whereas)227 5102 y(the)d(second)g(routine)g(deletes)h(a)f(sp)s +(eci\014cally)g(named)f(k)m(eyw)m(ord.)46 b(Wild)32 b(card)f(c)m +(haracters,)j(as)e(describ)s(ed)227 5215 y(in)f(the)g(Read)g(Keyw)m +(ord)f(section,)i(ab)s(o)m(v)m(e,)g(ma)m(y)g(b)s(e)e(used)g(when)f(sp)s +(ecifying)i(the)g(name)g(of)g(the)f(k)m(eyw)m(ord)227 +5328 y(to)h(b)s(e)f(deleted)h(\(b)s(e)f(careful!\).)382 +5581 y Fe(FTDREC\(unit,key_no,)42 b(>)48 b(status\))382 +5694 y(FTDKEY\(unit,keyword,)42 b(>)48 b(status\))p eop +end +%%Page: 51 57 +TeXDict begin 51 56 bop 0 299 a Fg(6.5.)72 b(D)m(A)-8 +b(T)g(A)32 b(SCALING)e(AND)h(UNDEFINED)h(PIXEL)e(P)-8 +b(ARAMETERS)1083 b Fi(51)0 555 y Fd(6.5)135 b(Data)46 +b(Scaling)g(and)e(Unde\014ned)h(Pixel)h(P)l(arameters)0 +805 y Fi(These)24 b(subroutines)f(de\014ne)h(or)h(mo)s(dify)e(the)i(in) +m(ternal)g(parameters)g(used)f(b)m(y)g(FITSIO)g(to)h(either)g(scale)h +(the)e(data)0 918 y(or)33 b(to)i(represen)m(t)e(unde\014ned)e(pixels.) +50 b(Generally)35 b(FITSIO)d(will)i(scale)g(the)g(data)g(according)g +(to)g(the)g(v)-5 b(alues)34 b(of)0 1031 y(the)e(BSCALE)g(and)f(BZER)m +(O)h(\(or)h(TSCALn)d(and)i(TZER)m(On\))f(k)m(eyw)m(ords,)i(ho)m(w)m(ev) +m(er)h(these)e(subroutines)f(ma)m(y)0 1144 y(b)s(e)h(used)h(to)h(o)m(v) +m(erride)g(the)f(k)m(eyw)m(ord)h(v)-5 b(alues.)49 b(This)32 +b(ma)m(y)i(b)s(e)f(useful)f(when)g(one)i(w)m(an)m(ts)f(to)h(read)f(or)g +(write)h(the)0 1257 y(ra)m(w)c(unscaled)f(v)-5 b(alues)29 +b(in)h(the)f(FITS)g(\014le.)40 b(Similarly)-8 b(,)31 +b(FITSIO)d(generally)j(uses)e(the)g(v)-5 b(alue)30 b(of)g(the)f(BLANK)h +(or)0 1370 y(TNULLn)35 b(k)m(eyw)m(ord)h(to)g(signify)f(an)h +(unde\014ned)d(pixel,)k(but)e(these)h(routines)g(ma)m(y)g(b)s(e)e(used) +h(to)h(o)m(v)m(erride)h(this)0 1483 y(v)-5 b(alue.)41 +b(These)30 b(subroutines)f(do)i(not)f(create)i(or)f(mo)s(dify)e(the)i +(corresp)s(onding)e(header)h(k)m(eyw)m(ord)h(v)-5 b(alues.)0 +1727 y Fh(1)81 b Fi(Reset)26 b(the)g(scaling)g(factors)g(in)f(the)h +(primary)f(arra)m(y)h(or)f(image)i(extension;)h(do)s(es)d(not)g(c)m +(hange)i(the)f(BSCALE)227 1840 y(and)i(BZER)m(O)g(k)m(eyw)m(ord)h(v)-5 +b(alues)28 b(and)g(only)g(a\013ects)i(the)e(automatic)j(scaling)e(p)s +(erformed)e(when)g(the)h(data)227 1953 y(elemen)m(ts)f(are)f +(written/read)g(to/from)g(the)g(FITS)f(\014le.)39 b(When)25 +b(reading)h(from)f(a)h(FITS)f(\014le)g(the)h(returned)227 +2066 y(data)i(v)-5 b(alue)28 b(=)f(\(the)h(v)-5 b(alue)28 +b(giv)m(en)h(in)e(the)g(FITS)g(arra)m(y\))h(*)g(BSCALE)f(+)g(BZER)m(O.) +g(The)g(in)m(v)m(erse)i(form)m(ula)227 2179 y(is)34 b(used)f(when)g +(writing)h(data)h(v)-5 b(alues)34 b(to)g(the)g(FITS)g(\014le.)51 +b(\(NOTE:)34 b(BSCALE)f(and)g(BZER)m(O)h(m)m(ust)g(b)s(e)227 +2292 y(declared)d(as)g(Double)g(Precision)g(v)-5 b(ariables\).)382 +2536 y Fe(FTPSCL\(unit,bscale,bzero)o(,)42 b(>)47 b(status\))0 +2780 y Fh(2)81 b Fi(Reset)39 b(the)f(scaling)i(parameters)e(for)h(a)f +(table)h(column;)k(do)s(es)38 b(not)g(c)m(hange)i(the)e(TSCALn)f(or)h +(TZER)m(On)227 2893 y(k)m(eyw)m(ord)29 b(v)-5 b(alues)29 +b(and)e(only)i(a\013ects)g(the)g(automatic)h(scaling)f(p)s(erformed)e +(when)g(the)i(data)g(elemen)m(ts)h(are)227 3006 y(written/read)i +(to/from)g(the)g(FITS)f(\014le.)44 b(When)31 b(reading)g(from)g(a)h +(FITS)f(\014le)g(the)h(returned)e(data)i(v)-5 b(alue)227 +3119 y(=)40 b(\(the)h(v)-5 b(alue)40 b(giv)m(en)h(in)f(the)g(FITS)g +(arra)m(y\))g(*)h(TSCAL)e(+)g(TZER)m(O.)h(The)f(in)m(v)m(erse)i(form)m +(ula)g(is)f(used)227 3232 y(when)33 b(writing)h(data)h(v)-5 +b(alues)35 b(to)f(the)h(FITS)e(\014le.)52 b(\(NOTE:)34 +b(TSCAL)f(and)g(TZER)m(O)g(m)m(ust)h(b)s(e)f(declared)227 +3345 y(as)e(Double)g(Precision)g(v)-5 b(ariables\).)382 +3589 y Fe(FTTSCL\(unit,colnum,tscal)o(,tz)o(ero,)41 b(>)48 +b(status\))0 3833 y Fh(3)81 b Fi(De\014ne)36 b(the)g(in)m(teger)i(v)-5 +b(alue)36 b(to)h(b)s(e)e(used)h(to)h(signify)f(unde\014ned)e(pixels)i +(in)g(the)g(primary)f(arra)m(y)i(or)f(image)227 3946 +y(extension.)59 b(This)35 b(is)h(only)g(used)g(if)g(BITPIX)g(=)f(8,)j +(16,)h(32.)59 b(or)36 b(64)h(This)e(do)s(es)h(not)g(create)i(or)e(c)m +(hange)227 4059 y(the)27 b(v)-5 b(alue)28 b(of)f(the)g(BLANK)g(k)m(eyw) +m(ord)h(in)e(the)i(header.)39 b(FTPNULLL)27 b(is)g(iden)m(tical)h(to)g +(FTPNUL)f(except)227 4172 y(that)k(the)g(blank)f(v)-5 +b(alue)31 b(is)f(a)h(64-bit)g(in)m(teger)h(instead)f(of)f(a)h(32-bit)h +(in)m(teger.)382 4416 y Fe(FTPNUL\(unit,blank,)43 b(>)k(status\))382 +4529 y(FTPNULLL\(unit,blankll,)42 b(>)47 b(status\))0 +4774 y Fh(4)81 b Fi(De\014ne)36 b(the)g(string)g(to)g(b)s(e)f(used)g +(to)i(signify)f(unde\014ned)e(pixels)i(in)f(a)h(column)g(in)g(an)f +(ASCI)s(I)g(table.)58 b(This)227 4887 y(do)s(es)30 b(not)h(create)h(or) +e(c)m(hange)i(the)e(v)-5 b(alue)31 b(of)g(the)f(TNULLn)g(k)m(eyw)m +(ord.)382 5131 y Fe(FTSNUL\(unit,colnum,snull)41 b(>)47 +b(status\))0 5375 y Fh(5)81 b Fi(De\014ne)34 b(the)h(v)-5 +b(alue)34 b(to)h(b)s(e)f(used)g(to)h(signify)f(unde\014ned)e(pixels)j +(in)f(an)g(in)m(teger)i(column)e(in)g(a)g(binary)g(table)227 +5488 y(\(where)42 b(TF)m(ORMn)f(=)g('B',)i('I',)f('J',)f(or)h('K'\).)g +(This)f(do)s(es)g(not)h(create)h(or)e(c)m(hange)i(the)e(v)-5 +b(alue)42 b(of)g(the)227 5601 y(TNULLn)d(k)m(eyw)m(ord.)71 +b(FTTNULLL)39 b(is)i(iden)m(tical)h(to)e(FTTNUL)g(except)h(that)g(the)f +(tn)m(ull)h(v)-5 b(alue)40 b(is)h(a)227 5714 y(64-bit)32 +b(in)m(teger)g(instead)e(of)h(a)g(32-bit)g(in)m(teger.)p +eop end +%%Page: 52 58 +TeXDict begin 52 57 bop 0 299 a Fi(52)1319 b Fg(CHAPTER)29 +b(6.)112 b(AD)m(V)-10 b(ANCED)32 b(INTERF)-10 b(A)m(CE)30 +b(SUBR)m(OUTINES)382 555 y Fe(FTTNUL\(unit,colnum,tnull)41 +b(>)47 b(status\))382 668 y(FTTNULLL\(unit,colnum,tnu)o(lll)o(l)42 +b(>)47 b(status\))0 1001 y Fd(6.6)135 b(FITS)44 b(Primary)h(Arra)l(y)g +(or)g(IMA)l(GE)g(Extension)h(I/O)f(Subroutines)0 1251 +y Fi(These)26 b(subroutines)f(put)h(or)h(get)g(data)h(v)-5 +b(alues)26 b(in)h(the)f(primary)g(data)h(arra)m(y)g(\(i.e.,)i(the)e +(\014rst)f(HDU)h(in)f(the)h(FITS)0 1364 y(\014le\))35 +b(or)g(an)f(IMA)m(GE)i(extension.)54 b(The)34 b(data)i(arra)m(y)f(is)f +(represen)m(ted)h(as)g(a)g(single)g(one-dimensional)h(arra)m(y)f(of)0 +1477 y(pixels)h(regardless)h(of)f(the)g(actual)h(dimensionalit)m(y)g +(of)g(the)f(arra)m(y)-8 b(,)38 b(and)e(the)g(FPIXEL)g(parameter)g(giv)m +(es)i(the)0 1590 y(p)s(osition)28 b(within)e(this)i(1-D)g(arra)m(y)g +(of)g(the)g(\014rst)e(pixel)i(to)g(read)g(or)f(write.)40 +b(Automatic)29 b(data)f(t)m(yp)s(e)g(con)m(v)m(ersion)h(is)0 +1703 y(p)s(erformed)g(for)i(n)m(umeric)g(data)g(\(except)i(for)d +(complex)i(data)f(t)m(yp)s(es\))h(if)f(the)g(data)g(t)m(yp)s(e)g(of)g +(the)g(primary)f(arra)m(y)0 1816 y(\(de\014ned)e(b)m(y)g(the)h(BITPIX)f +(k)m(eyw)m(ord\))h(di\013ers)g(from)f(the)g(data)i(t)m(yp)s(e)e(of)h +(the)g(arra)m(y)g(in)f(the)h(calling)h(subroutine.)0 +1929 y(The)41 b(data)i(v)-5 b(alues)42 b(are)g(also)h(scaled)f(b)m(y)g +(the)g(BSCALE)f(and)g(BZER)m(O)h(header)f(v)-5 b(alues)42 +b(as)g(they)g(are)g(b)s(eing)0 2042 y(written)32 b(or)g(read)g(from)g +(the)g(FITS)g(arra)m(y)-8 b(.)47 b(The)31 b(ftpscl)i(subroutine)e(MUST) +g(b)s(e)h(called)h(to)g(de\014ne)e(the)i(scaling)0 2155 +y(parameters)h(when)e(writing)i(data)g(to)g(the)g(FITS)f(arra)m(y)h(or) +f(to)h(o)m(v)m(erride)h(the)f(default)f(scaling)i(v)-5 +b(alue)34 b(giv)m(en)g(in)0 2267 y(the)d(header)f(when)f(reading)i(the) +f(FITS)g(arra)m(y)-8 b(.)0 2428 y(Tw)m(o)41 b(sets)f(of)h(subroutines)e +(are)i(pro)m(vided)f(to)h(read)g(the)f(data)i(arra)m(y)f(whic)m(h)f +(di\013er)g(in)g(the)h(w)m(a)m(y)g(unde\014ned)0 2541 +y(pixels)35 b(are)h(handled.)55 b(The)35 b(\014rst)f(set)i(of)g +(routines)f(\(FTGPVx\))h(simply)f(return)f(an)h(arra)m(y)h(of)g(data)g +(elemen)m(ts)0 2653 y(in)c(whic)m(h)g(unde\014ned)e(pixels)i(are)h(set) +f(equal)h(to)g(a)g(v)-5 b(alue)32 b(sp)s(eci\014ed)g(b)m(y)g(the)g +(user)g(in)g(the)g('n)m(ullv)-5 b(al')33 b(parameter.)0 +2766 y(An)h(additional)i(feature)f(of)f(these)h(subroutines)f(is)g +(that)i(if)e(the)h(user)f(sets)h(n)m(ullv)-5 b(al)35 +b(=)f(0,)i(then)f(no)f(c)m(hec)m(ks)i(for)0 2879 y(unde\014ned)d +(pixels)j(will)g(b)s(e)e(p)s(erformed,)i(th)m(us)f(increasing)h(the)g +(sp)s(eed)e(of)i(the)g(program.)55 b(The)35 b(second)h(set)g(of)0 +2992 y(routines)31 b(\(FTGPFx\))i(returns)d(the)i(data)g(elemen)m(t)h +(arra)m(y)f(and,)f(in)h(addition,)g(a)g(logical)i(arra)m(y)e(whic)m(h)f +(de\014nes)0 3105 y(whether)40 b(the)g(corresp)s(onding)f(data)i(pixel) +g(is)f(unde\014ned.)69 b(The)39 b(latter)j(set)f(of)f(subroutines)f(ma) +m(y)i(b)s(e)f(more)0 3218 y(con)m(v)m(enien)m(t)33 b(to)g(use)e(in)g +(some)g(circumstances,)i(ho)m(w)m(ev)m(er,)g(it)f(requires)f(an)g +(additional)h(arra)m(y)g(of)g(logical)i(v)-5 b(alues)0 +3331 y(whic)m(h)36 b(can)g(b)s(e)g(un)m(wieldy)f(when)h(w)m(orking)g +(with)g(large)h(data)g(arra)m(ys.)58 b(Also)37 b(for)f(programmer)g +(con)m(v)m(enience,)0 3444 y(sets)j(of)g(subroutines)f(to)h(directly)h +(read)e(or)h(write)g(2)g(and)g(3)g(dimensional)g(arra)m(ys)g(ha)m(v)m +(e)h(b)s(een)e(pro)m(vided,)j(as)0 3557 y(w)m(ell)31 +b(as)f(a)g(set)g(of)g(subroutines)e(to)i(read)g(or)g(write)f(an)m(y)h +(con)m(tiguous)h(rectangular)g(subset)e(of)h(pixels)g(within)f(the)0 +3670 y(n-dimensional)h(arra)m(y)-8 b(.)0 3925 y Fh(1)81 +b Fi(Get)39 b(the)g(data)h(t)m(yp)s(e)e(of)h(the)g(image)h(\(=)f +(BITPIX)f(v)-5 b(alue\).)67 b(P)m(ossible)39 b(returned)f(v)-5 +b(alues)39 b(are:)58 b(8,)41 b(16,)h(32,)227 4038 y(64,)36 +b(-32,)h(or)d(-64)h(corresp)s(onding)e(to)h(unsigned)f(b)m(yte,)j +(signed)e(2-b)m(yte)h(in)m(teger,)i(signed)d(4-b)m(yte)h(in)m(teger,) +227 4151 y(signed)c(8-b)m(yte)g(in)m(teger,)h(real,)g(and)d(double.)227 +4300 y(The)d(second)f(subroutine)g(is)h(similar)g(to)g(FTGIDT,)h +(except)f(that)h(if)f(the)f(image)j(pixel)e(v)-5 b(alues)26 +b(are)g(scaled,)227 4413 y(with)h(non-default)g(v)-5 +b(alues)27 b(for)g(the)h(BZER)m(O)f(and)f(BSCALE)g(k)m(eyw)m(ords,)j +(then)e(this)g(routine)g(will)g(return)227 4526 y(the)32 +b('equiv)-5 b(alen)m(t')33 b(data)e(t)m(yp)s(e)h(that)f(is)g(needed)g +(to)h(store)g(the)f(scaled)h(v)-5 b(alues.)43 b(F)-8 +b(or)32 b(example,)g(if)f(BITPIX)227 4639 y(=)39 b(16)g(and)g(BSCALE)f +(=)g(0.1)i(then)f(the)g(equiv)-5 b(alen)m(t)40 b(data)f(t)m(yp)s(e)g +(is)g(\015oating)h(p)s(oin)m(t,)h(and)d(-32)i(will)g(b)s(e)227 +4752 y(returned.)65 b(There)39 b(are)g(2)g(sp)s(ecial)h(cases:)58 +b(if)39 b(the)g(image)h(con)m(tains)g(unsigned)e(2-b)m(yte)i(in)m +(teger)g(v)-5 b(alues,)227 4865 y(with)40 b(BITPIX)g(=)f(16,)44 +b(BSCALE)39 b(=)h(1,)j(and)c(BZER)m(O)h(=)g(32768,)45 +b(then)39 b(this)h(routine)g(will)h(return)e(a)227 4978 +y(non-standard)26 b(v)-5 b(alue)27 b(of)g(20)h(for)f(the)g(bitpix)g(v) +-5 b(alue.)40 b(Similarly)27 b(if)f(the)i(image)g(con)m(tains)g +(unsigned)e(4-b)m(yte)227 5091 y(in)m(tegers,)32 b(then)e(bitpix)g +(will)h(b)s(e)f(returned)f(with)h(a)h(v)-5 b(alue)31 +b(of)f(40.)382 5346 y Fe(FTGIDT\(unit,)44 b(>)k(bitpix,status\))382 +5459 y(FTGIET\(unit,)c(>)k(bitpix,status\))0 5714 y Fh(2)81 +b Fi(Get)31 b(the)g(dimension)e(\(n)m(um)m(b)s(er)h(of)g(axes)h(=)f +(NAXIS\))h(of)f(the)h(image)p eop end +%%Page: 53 59 +TeXDict begin 53 58 bop 0 299 a Fg(6.6.)72 b(FITS)30 +b(PRIMAR)-8 b(Y)31 b(ARRA)-8 b(Y)31 b(OR)f(IMA)m(GE)h(EXTENSION)e(I/O)i +(SUBR)m(OUTINES)589 b Fi(53)382 555 y Fe(FTGIDM\(unit,)44 +b(>)k(naxis,status\))0 807 y Fh(3)81 b Fi(Get)38 b(the)f(size)h(of)f +(all)h(the)f(dimensions)g(of)g(the)g(image.)62 b(The)37 +b(FTGISZLL)e(routine)i(returns)f(an)h(arra)m(y)h(of)227 +920 y(64-bit)32 b(in)m(tegers)g(instead)e(of)h(32-bit)g(in)m(tegers.) +382 1172 y Fe(FTGISZ\(unit,)44 b(maxdim,)i(>)i(naxes,status\))382 +1285 y(FTGISZLL\(unit,)c(maxdim,)i(>)h(naxesll,status\))0 +1537 y Fh(4)81 b Fi(Get)35 b(the)f(parameters)g(that)h(de\014ne)e(the)h +(t)m(yp)s(e)g(and)g(size)g(of)h(the)f(image.)53 b(This)33 +b(routine)h(simply)f(com)m(bines)227 1649 y(calls)40 +b(to)f(the)g(ab)s(o)m(v)m(e)h(3)f(routines.)65 b(The)38 +b(FTGIPRLL)g(routine)h(returns)e(an)i(arra)m(y)g(of)g(64-bit)h(in)m +(tegers)227 1762 y(instead)31 b(of)f(32-bit)i(in)m(tegers.)382 +2014 y Fe(FTGIPR\(unit,)44 b(maxdim,)i(>)i(bitpix,)d(naxis,)h(naxes,)h +(int)f(*status\))382 2127 y(FTGIPRLL\(unit,)e(maxdim,)i(>)h(bitpix,)f +(naxis,)g(naxesll,)f(int)i(*status\))0 2379 y Fh(5)81 +b Fi(Put)30 b(elemen)m(ts)h(in)m(to)h(the)e(data)h(arra)m(y)382 +2631 y Fe(FTPPR[BIJKED]\(unit,group)o(,fp)o(ixel)o(,nel)o(eme)o(nts,)o +(valu)o(es,)41 b(>)48 b(status\))0 2883 y Fh(6)81 b Fi(Put)30 +b(elemen)m(ts)i(in)m(to)f(the)g(data)g(arra)m(y)-8 b(,)32 +b(substituting)e(the)g(appropriate)h(FITS)f(n)m(ull)g(v)-5 +b(alue)31 b(for)f(all)i(elemen)m(ts)227 2996 y(whic)m(h)c(are)f(equal)i +(to)f(the)f(v)-5 b(alue)28 b(of)g(NULL)-10 b(V)g(AL.)28 +b(F)-8 b(or)28 b(in)m(teger)h(FITS)e(arra)m(ys,)i(the)e(n)m(ull)h(v)-5 +b(alue)28 b(de\014ned)e(b)m(y)227 3109 y(the)k(previous)f(call)i(to)g +(FTPNUL)e(will)h(b)s(e)f(substituted;)h(for)f(\015oating)i(p)s(oin)m(t) +f(FITS)f(arra)m(ys)h(\(BITPIX)f(=)227 3221 y(-32)j(or)e(-64\))i(then)e +(the)h(sp)s(ecial)g(IEEE)e(NaN)i(\(Not-a-Num)m(b)s(er\))h(v)-5 +b(alue)31 b(will)g(b)s(e)f(substituted.)382 3473 y Fe +(FTPPN[BIJKED]\(unit,group)o(,fp)o(ixel)o(,nel)o(eme)o(nts,)o(valu)o +(es,)o(null)o(val)41 b(>)48 b(status\))0 3725 y Fh(7)81 +b Fi(Set)30 b(data)h(arra)m(y)g(elemen)m(ts)h(as)e(unde\014ned)382 +3977 y Fe(FTPPRU\(unit,group,fpixel)o(,ne)o(leme)o(nts,)41 +b(>)47 b(status\))0 4229 y Fh(8)81 b Fi(Get)36 b(elemen)m(ts)g(from)f +(the)g(data)h(arra)m(y)-8 b(.)55 b(Unde\014ned)34 b(arra)m(y)h(elemen)m +(ts)i(will)e(b)s(e)g(returned)f(with)g(a)i(v)-5 b(alue)35 +b(=)227 4342 y(n)m(ullv)-5 b(al,)31 b(unless)f(n)m(ullv)-5 +b(al)31 b(=)f(0)h(in)f(whic)m(h)g(case)h(no)g(c)m(hec)m(ks)g(for)g +(unde\014ned)d(pixels)i(will)h(b)s(e)f(p)s(erformed.)382 +4594 y Fe(FTGPV[BIJKED]\(unit,group)o(,fp)o(ixel)o(,nel)o(eme)o(nts,)o +(null)o(val)o(,)42 b(>)47 b(values,anyf,status\))0 4845 +y Fh(9)81 b Fi(Get)32 b(elemen)m(ts)g(and)f(n)m(ull\015ags)g(from)g +(data)h(arra)m(y)-8 b(.)44 b(An)m(y)32 b(unde\014ned)d(arra)m(y)i +(elemen)m(ts)i(will)e(ha)m(v)m(e)i(the)e(corre-)227 4958 +y(sp)s(onding)e(\015agv)-5 b(als)31 b(elemen)m(t)h(set)f(equal)g(to)g +(.TR)m(UE.)382 5210 y Fe(FTGPF[BIJKED]\(unit,group)o(,fp)o(ixel)o(,nel) +o(eme)o(nts,)41 b(>)48 b(values,flagvals,anyf,st)o(atu)o(s\))0 +5462 y Fh(10)e Fi(Put)30 b(v)-5 b(alues)31 b(in)m(to)g(group)f +(parameters)382 5714 y Fe(FTPGP[BIJKED]\(unit,group)o(,fp)o(arm,)o +(npar)o(m,v)o(alue)o(s,)42 b(>)47 b(status\))p eop end +%%Page: 54 60 +TeXDict begin 54 59 bop 0 299 a Fi(54)1319 b Fg(CHAPTER)29 +b(6.)112 b(AD)m(V)-10 b(ANCED)32 b(INTERF)-10 b(A)m(CE)30 +b(SUBR)m(OUTINES)0 555 y Fh(11)46 b Fi(Get)31 b(v)-5 +b(alues)31 b(from)f(group)g(parameters)382 809 y Fe +(FTGGP[BIJKED]\(unit,group)o(,fp)o(arm,)o(npar)o(m,)41 +b(>)48 b(values,status\))0 1062 y Fi(The)32 b(follo)m(wing)h(4)g +(subroutines)e(transfer)g(FITS)h(images)h(with)f(2)g(or)g(3)h +(dimensions)e(to)i(or)f(from)g(a)h(data)f(arra)m(y)0 +1175 y(whic)m(h)h(has)g(b)s(een)g(declared)g(in)g(the)h(calling)h +(program.)49 b(The)33 b(dimensionalit)m(y)h(of)g(the)f(FITS)g(image)h +(is)f(passed)0 1288 y(b)m(y)26 b(the)g(naxis1,)h(naxis2,)h(and)d +(naxis3)i(parameters)f(and)f(the)h(declared)h(dimensions)e(of)h(the)g +(program)g(arra)m(y)h(are)0 1401 y(passed)k(in)f(the)i(dim1)e(and)h +(dim2)g(parameters.)43 b(Note)32 b(that)g(the)f(program)g(arra)m(y)g +(do)s(es)g(not)g(ha)m(v)m(e)i(to)e(ha)m(v)m(e)i(the)0 +1514 y(same)28 b(dimensions)f(as)h(the)g(FITS)e(arra)m(y)-8 +b(,)30 b(but)d(m)m(ust)g(b)s(e)g(at)i(least)f(as)g(big.)40 +b(F)-8 b(or)29 b(example)f(if)f(a)h(FITS)f(image)i(with)0 +1627 y(NAXIS1)i(=)f(NAXIS2)h(=)f(400)i(is)e(read)h(in)m(to)g(a)g +(program)f(arra)m(y)h(whic)m(h)f(is)h(dimensioned)f(as)g(512)i(x)f(512) +g(pixels,)0 1739 y(then)d(the)g(image)i(will)e(just)g(\014ll)g(the)h +(lo)m(w)m(er)g(left)g(corner)f(of)h(the)f(arra)m(y)h(with)f(pixels)g +(in)g(the)h(range)f(1)h(-)f(400)i(in)e(the)0 1852 y(X)k(an)g(Y)h +(directions.)47 b(This)31 b(has)h(the)h(e\013ect)g(of)g(taking)g(a)g +(con)m(tiguous)g(set)g(of)f(pixel)h(v)-5 b(alue)33 b(in)e(the)i(FITS)e +(arra)m(y)0 1965 y(and)k(writing)g(them)g(to)h(a)g(non-con)m(tiguous)g +(arra)m(y)f(in)g(program)g(memory)g(\(i.e.,)j(there)e(are)f(no)m(w)h +(some)f(blank)0 2078 y(pixels)c(around)e(the)h(edge)i(of)e(the)h(image) +g(in)f(the)h(program)f(arra)m(y\).)0 2332 y Fh(11)46 +b Fi(Put)30 b(2-D)i(image)f(in)m(to)h(the)e(data)h(arra)m(y)382 +2585 y Fe(FTP2D[BIJKED]\(unit,group)o(,di)o(m1,n)o(axis)o(1,n)o(axis)o +(2,im)o(age)o(,)42 b(>)47 b(status\))0 2838 y Fh(12)f +Fi(Put)30 b(3-D)i(cub)s(e)d(in)m(to)j(the)e(data)h(arra)m(y)382 +3092 y Fe(FTP3D[BIJKED]\(unit,group)o(,di)o(m1,d)o(im2,)o(nax)o(is1,)o +(naxi)o(s2,)o(naxi)o(s3,c)o(ube)o(,)42 b(>)47 b(status\))0 +3345 y Fh(13)f Fi(Get)29 b(2-D)f(image)h(from)f(the)f(data)i(arra)m(y) +-8 b(.)41 b(Unde\014ned)26 b(pixels)h(in)h(the)g(arra)m(y)g(will)g(b)s +(e)f(set)h(equal)g(to)h(the)e(v)-5 b(alue)227 3458 y(of)31 +b('n)m(ullv)-5 b(al',)31 b(unless)f(n)m(ullv)-5 b(al=0)31 +b(in)f(whic)m(h)g(case)i(no)e(testing)i(for)e(unde\014ned)e(pixels)i +(will)h(b)s(e)f(p)s(erformed.)382 3712 y Fe(FTG2D[BIJKED]\(unit,group)o +(,nu)o(llva)o(l,di)o(m1,)o(naxi)o(s1,n)o(axi)o(s2,)41 +b(>)48 b(image,anyf,status\))0 3965 y Fh(14)e Fi(Get)31 +b(3-D)h(cub)s(e)e(from)g(the)g(data)h(arra)m(y)-8 b(.)42 +b(Unde\014ned)29 b(pixels)i(in)f(the)g(arra)m(y)h(will)g(b)s(e)f(set)h +(equal)g(to)g(the)f(v)-5 b(alue)227 4078 y(of)31 b('n)m(ullv)-5 +b(al',)31 b(unless)f(n)m(ullv)-5 b(al=0)31 b(in)f(whic)m(h)g(case)i(no) +e(testing)i(for)e(unde\014ned)e(pixels)i(will)h(b)s(e)f(p)s(erformed.) +382 4331 y Fe(FTG3D[BIJKED]\(unit,group)o(,nu)o(llva)o(l,di)o(m1,)o +(dim2)o(,nax)o(is1)o(,nax)o(is2,)o(nax)o(is3,)41 b(>)1002 +4444 y(cube,anyf,status\))0 4698 y Fi(The)i(follo)m(wing)h(subroutines) +e(transfer)h(a)h(rectangular)g(subset)e(of)i(the)f(pixels)g(in)g(a)h +(FITS)e(N-dimensional)0 4811 y(image)31 b(to)g(or)f(from)f(an)h(arra)m +(y)g(whic)m(h)g(has)g(b)s(een)f(declared)h(in)g(the)g(calling)i +(program.)40 b(The)29 b(fpixels)h(and)f(lpixels)0 4924 +y(parameters)e(are)h(in)m(teger)g(arra)m(ys)g(whic)m(h)f(sp)s(ecify)f +(the)i(starting)g(and)e(ending)h(pixels)g(in)g(eac)m(h)h(dimension)e +(of)i(the)0 5036 y(FITS)36 b(image)i(that)f(are)g(to)h(b)s(e)e(read)g +(or)h(written.)60 b(\(Note)38 b(that)g(these)f(are)g(the)g(starting)g +(and)f(ending)h(pixels)0 5149 y(in)d(the)h(FITS)f(image,)k(not)d(in)f +(the)h(declared)g(arra)m(y\).)55 b(The)34 b(arra)m(y)i(parameter)f(is)g +(treated)g(simply)g(as)g(a)g(large)0 5262 y(one-dimensional)c(arra)m(y) +f(of)h(the)f(appropriate)g(datat)m(yp)s(e)h(con)m(taining)h(the)e +(pixel)g(v)-5 b(alues;)31 b(The)e(pixel)i(v)-5 b(alues)30 +b(in)0 5375 y(the)c(FITS)f(arra)m(y)i(are)f(read/written)g(from/to)h +(this)f(program)f(arra)m(y)i(in)e(strict)i(sequence)f(without)g(an)m(y) +h(gaps;)g(it)0 5488 y(is)i(up)e(to)j(the)f(calling)h(routine)f(to)g +(correctly)h(in)m(terpret)f(the)g(dimensionalit)m(y)h(of)f(this)g(arra) +m(y)-8 b(.)41 b(The)28 b(t)m(w)m(o)i(families)0 5601 +y(of)d(FITS)g(reading)g(routines)g(\(FTGSVx)g(and)g(FTGSFx)g +(subroutines\))f(also)j(ha)m(v)m(e)f(an)f('incs')h(parameter)f(whic)m +(h)0 5714 y(de\014nes)j(the)h(data)h(sampling)e(in)m(terv)-5 +b(al)32 b(in)f(eac)m(h)h(dimension)e(of)h(the)g(FITS)f(arra)m(y)-8 +b(.)43 b(F)-8 b(or)32 b(example,)g(if)f(incs\(1\)=2)p +eop end +%%Page: 55 61 +TeXDict begin 55 60 bop 0 299 a Fg(6.7.)72 b(FITS)30 +b(ASCI)s(I)f(AND)i(BINAR)-8 b(Y)31 b(T)-8 b(ABLE)31 b(D)m(A)-8 +b(T)g(A)32 b(I/O)e(SUBR)m(OUTINES)979 b Fi(55)0 555 y(and)33 +b(incs\(2\)=3)h(when)f(reading)g(a)h(2-dimensional)g(FITS)f(image,)i +(then)e(only)h(ev)m(ery)g(other)f(pixel)h(in)f(the)h(\014rst)0 +668 y(dimension)e(and)h(ev)m(ery)h(3rd)e(pixel)i(in)f(the)g(second)g +(dimension)f(will)i(b)s(e)e(returned)g(in)h(the)g('arra)m(y')h +(parameter.)0 781 y([Note:)39 b(the)25 b(FTGSSx)f(family)i(of)e +(routines)h(whic)m(h)g(w)m(ere)g(presen)m(t)g(in)f(previous)g(v)m +(ersions)h(of)g(FITSIO)f(ha)m(v)m(e)i(b)s(een)0 894 y(sup)s(erseded)i +(b)m(y)j(the)f(more)h(general)g(FTGSVx)f(family)h(of)g(routines.])0 +1152 y Fh(15)46 b Fi(Put)30 b(an)g(arbitrary)g(data)h(subsection)g(in)m +(to)g(the)g(data)g(arra)m(y)-8 b(.)382 1411 y Fe +(FTPSS[BIJKED]\(unit,group)o(,na)o(xis,)o(naxe)o(s,f)o(pixe)o(ls,l)o +(pix)o(els,)o(arra)o(y,)41 b(>)48 b(status\))0 1669 y +Fh(16)e Fi(Get)30 b(an)e(arbitrary)g(data)i(subsection)e(from)g(the)h +(data)g(arra)m(y)-8 b(.)42 b(Unde\014ned)27 b(pixels)h(in)h(the)f(arra) +m(y)i(will)e(b)s(e)g(set)227 1782 y(equal)k(to)h(the)e(v)-5 +b(alue)33 b(of)e('n)m(ullv)-5 b(al',)33 b(unless)e(n)m(ullv)-5 +b(al=0)33 b(in)e(whic)m(h)g(case)i(no)e(testing)i(for)e(unde\014ned)f +(pixels)227 1895 y(will)h(b)s(e)f(p)s(erformed.)382 2154 +y Fe(FTGSV[BIJKED]\(unit,group)o(,na)o(xis,)o(naxe)o(s,f)o(pixe)o(ls,l) +o(pix)o(els,)o(incs)o(,nu)o(llva)o(l,)42 b(>)1002 2266 +y(array,anyf,status\))0 2525 y Fh(17)k Fi(Get)34 b(an)f(arbitrary)g +(data)g(subsection)g(from)g(the)g(data)g(arra)m(y)-8 +b(.)50 b(An)m(y)33 b(Unde\014ned)e(pixels)i(in)g(the)g(arra)m(y)h(will) +227 2638 y(ha)m(v)m(e)e(the)e(corresp)s(onding)g('\015agv)-5 +b(als')31 b(elemen)m(t)h(set)f(equal)g(to)g(.TR)m(UE.)382 +2896 y Fe(FTGSF[BIJKED]\(unit,group)o(,na)o(xis,)o(naxe)o(s,f)o(pixe)o +(ls,l)o(pix)o(els,)o(incs)o(,)42 b(>)1002 3009 y +(array,flagvals,anyf,statu)o(s\))0 3343 y Fd(6.7)135 +b(FITS)44 b(ASCI)t(I)g(and)h(Binary)g(T)-11 b(able)45 +b(Data)h(I/O)f(Subroutines)0 3596 y Fb(6.7.1)112 b(Column)39 +b(Information)f(Subroutines)0 3805 y Fh(1)81 b Fi(Get)37 +b(the)f(n)m(um)m(b)s(er)f(of)i(ro)m(ws)f(or)g(columns)g(in)g(the)h +(curren)m(t)f(FITS)g(table.)59 b(The)36 b(n)m(um)m(b)s(er)f(of)h(ro)m +(ws)h(is)f(giv)m(en)227 3918 y(b)m(y)f(the)h(NAXIS2)f(k)m(eyw)m(ord)h +(and)f(the)g(n)m(um)m(b)s(er)f(of)h(columns)g(is)g(giv)m(en)h(b)m(y)g +(the)f(TFIELDS)g(k)m(eyw)m(ord)g(in)227 4031 y(the)d(header)f(of)h(the) +g(table.)45 b(The)31 b(FTGNR)-10 b(WLL)32 b(routine)g(is)f(iden)m +(tical)i(to)g(FTGNR)-10 b(W)32 b(except)h(that)f(the)227 +4144 y(n)m(um)m(b)s(er)d(of)i(ro)m(ws)f(is)h(returned)e(as)h(a)h +(64-bit)h(in)m(teger)g(rather)e(than)g(a)h(32-bit)g(in)m(teger.)382 +4402 y Fe(FTGNRW\(unit,)44 b(>)k(nrows,)e(status\))382 +4515 y(FTGNRWLL\(unit,)e(>)j(nrowsll,)f(status\))382 +4628 y(FTGNCL\(unit,)e(>)k(ncols,)e(status\))0 4886 y +Fh(2)81 b Fi(Get)25 b(the)f(table)i(column)e(n)m(um)m(b)s(er)f(\(and)h +(name\))h(of)f(the)h(column)f(whose)g(name)g(matc)m(hes)i(an)e(input)g +(template)227 4999 y(name.)38 b(The)21 b(table)i(column)e(names)h(are)g +(de\014ned)e(b)m(y)i(the)g(TTYPEn)e(k)m(eyw)m(ords)i(in)f(the)h(FITS)f +(header.)37 b(If)22 b(a)227 5112 y(column)i(do)s(es)g(not)g(ha)m(v)m(e) +h(a)f(TTYPEn)f(k)m(eyw)m(ord,)j(then)d(these)h(routines)g(assume)g +(that)g(the)h(name)e(consists)227 5225 y(of)i(all)h(blank)f(c)m +(haracters.)40 b(These)25 b(2)g(subroutines)e(p)s(erform)h(the)h(same)g +(function)g(except)h(that)f(FTGCNO)227 5338 y(only)j(returns)e(the)h(n) +m(um)m(b)s(er)f(of)h(the)g(matc)m(hing)i(column)e(whereas)g(FTGCNN)g +(also)h(returns)e(the)i(name)f(of)227 5451 y(the)k(column.)40 +b(If)30 b(CASESEN)f(=)h(.true.)41 b(then)30 b(the)h(column)f(name)g +(matc)m(h)i(will)e(b)s(e)g(case-sensitiv)m(e.)227 5601 +y(The)41 b(input)e(column)i(name)g(template)h(\(COL)-8 +b(TEMPLA)g(TE\))41 b(is)g(\(1\))g(either)h(the)f(exact)h(name)f(of)g +(the)227 5714 y(column)36 b(to)i(b)s(e)d(searc)m(hed)i(for,)h(or)e +(\(2\))i(it)f(ma)m(y)g(con)m(tain)g(wild)f(cards)g(c)m(haracters)i +(\(*,)h(?,)f(or)e(#\),)i(or)f(\(3\))p eop end +%%Page: 56 62 +TeXDict begin 56 61 bop 0 299 a Fi(56)1319 b Fg(CHAPTER)29 +b(6.)112 b(AD)m(V)-10 b(ANCED)32 b(INTERF)-10 b(A)m(CE)30 +b(SUBR)m(OUTINES)227 555 y Fi(it)k(ma)m(y)g(con)m(tain)g(the)f(n)m(um)m +(b)s(er)f(of)h(the)g(desired)g(column)g(\(where)g(the)g(n)m(um)m(b)s +(er)f(is)h(expressed)f(as)h(ASCI)s(I)227 668 y(digits\).)41 +b(The)28 b(\014rst)g(2)h(wild)f(cards)g(b)s(eha)m(v)m(e)h(similarly)g +(to)g(UNIX)g(\014lename)g(matc)m(hing:)40 b(the)29 b('*')g(c)m +(haracter)227 781 y(matc)m(hes)e(an)m(y)g(sequence)f(of)h(c)m +(haracters)g(\(including)f(zero)h(c)m(haracters\))h(and)d(the)i(')10 +b(?')39 b(c)m(haracter)28 b(matc)m(hes)227 894 y(an)m(y)40 +b(single)h(c)m(haracter.)71 b(The)39 b(#)h(wildcard)f(will)h(matc)m(h)h +(an)m(y)f(consecutiv)m(e)i(string)e(of)g(decimal)g(digits)227 +1007 y(\(0-9\).)45 b(As)31 b(an)g(example,)h(the)f(template)h(strings)f +('AB?DE',)h('AB*E',)h(and)d('AB*CDE')j(will)e(all)h(matc)m(h)227 +1120 y(the)26 b(string)g('ABCDE'.)i(If)d(more)h(than)g(one)g(column)g +(name)g(in)g(the)g(table)h(matc)m(hes)g(the)f(template)i(string,)227 +1233 y(then)33 b(the)h(\014rst)f(matc)m(h)h(is)f(returned)g(and)f(the)i +(status)g(v)-5 b(alue)34 b(will)f(b)s(e)g(set)h(to)g(237)h(as)f(a)f(w)m +(arning)h(that)g(a)227 1346 y(unique)g(matc)m(h)i(w)m(as)f(not)g +(found.)53 b(T)-8 b(o)35 b(\014nd)f(the)h(other)g(cases)g(that)h(matc)m +(h)g(the)f(template,)i(simply)e(call)227 1458 y(the)27 +b(subroutine)f(again)i(lea)m(ving)h(the)e(input)f(status)h(v)-5 +b(alue)28 b(equal)f(to)h(237)g(and)f(the)g(next)g(matc)m(hing)h(name) +227 1571 y(will)k(then)g(b)s(e)f(returned.)43 b(Rep)s(eat)32 +b(this)g(pro)s(cess)f(un)m(til)h(a)g(status)g(=)g(219)h(\(column)e +(name)h(not)g(found\))f(is)227 1684 y(returned.)40 b(If)30 +b(these)h(subroutines)e(fail)i(to)g(matc)m(h)g(the)g(template)h(to)f +(an)m(y)g(of)f(the)h(columns)f(in)g(the)h(table,)227 +1797 y(they)i(lastly)g(c)m(hec)m(k)h(if)f(the)f(template)i(can)f(b)s(e) +e(in)m(terpreted)i(as)g(a)g(simple)f(p)s(ositiv)m(e)h(in)m(teger)h +(\(e.g.,)h('7',)f(or)227 1910 y('512'\))i(and)d(if)g(so,)i(they)f +(return)e(that)j(column)e(n)m(um)m(b)s(er.)49 b(If)33 +b(no)g(matc)m(hes)i(are)f(found)e(then)h(a)h(status)g(=)227 +2023 y(219)e(error)e(is)g(returned.)227 2187 y(Note)h(that)e(the)h +(FITS)e(Standard)g(recommends)g(that)i(only)f(letters,)i(digits,)f(and) +f(the)g(underscore)f(c)m(har-)227 2300 y(acter)44 b(b)s(e)e(used)g(in)h +(column)f(names)h(\(with)g(no)f(em)m(b)s(edded)g(spaces)h(in)g(the)g +(name\).)78 b(T)-8 b(railing)43 b(blank)227 2412 y(c)m(haracters)32 +b(are)f(not)f(signi\014can)m(t.)382 2699 y Fe +(FTGCNO\(unit,casesen,colt)o(emp)o(late)o(,)42 b(>)47 +b(colnum,status\))382 2812 y(FTGCNN\(unit,casesen,colt)o(emp)o(late)o +(,)42 b(>)47 b(colname,colnum,status\))0 3098 y Fh(3)81 +b Fi(Get)39 b(the)g(datat)m(yp)s(e)h(of)e(a)h(column)g(in)f(an)g(ASCI)s +(I)g(or)g(binary)g(table.)66 b(This)38 b(routine)h(returns)e(an)i(in)m +(teger)227 3211 y(co)s(de)34 b(v)-5 b(alue)33 b(corresp)s(onding)f(to)i +(the)g(datat)m(yp)s(e)g(of)f(the)g(column.)49 b(\(See)34 +b(the)f(FTBNFM)i(and)d(FT)-8 b(ASFM)227 3324 y(subroutines)27 +b(in)h(the)g(Utilities)j(section)e(of)f(this)g(do)s(cumen)m(t)g(for)g +(a)h(list)g(of)f(the)h(co)s(de)f(v)-5 b(alues\).)41 b(The)27 +b(v)m(ector)227 3437 y(rep)s(eat)38 b(coun)m(t)g(\(whic)m(h)g(is)g(alw) +m(a)m(y)h(1)f(for)g(ASCI)s(I)e(table)i(columns\))g(is)g(also)g +(returned.)62 b(If)37 b(the)h(sp)s(eci\014ed)227 3550 +y(column)32 b(has)f(an)g(ASCI)s(I)f(c)m(haracter)j(datat)m(yp)s(e)g +(\(co)s(de)f(=)f(16\))i(then)e(the)h(width)e(of)i(a)g(unit)f(string)h +(in)f(the)227 3663 y(column)i(is)g(also)h(returned.)48 +b(Note)34 b(that)g(this)e(routine)h(supp)s(orts)f(the)h(lo)s(cal)h(con) +m(v)m(en)m(tion)h(for)e(sp)s(ecifying)227 3776 y(arra)m(ys)f(of)f +(strings)g(within)f(a)i(binary)e(table)i(c)m(haracter)g(column,)g +(using)e(the)h(syn)m(tax)h(TF)m(ORM)f(=)g('rAw')227 3889 +y(where)f('r')g(is)h(the)f(total)i(n)m(um)m(b)s(er)d(of)i(c)m +(haracters)g(\(=)g(the)f(width)g(of)g(the)g(column\))h(and)f('w')g(is)g +(the)h(width)227 4002 y(of)39 b(a)f(unit)g(string)g(within)g(the)g +(column.)64 b(Th)m(us)37 b(if)h(the)g(column)g(has)g(TF)m(ORM)h(=)f +('60A12')i(then)e(this)227 4114 y(routine)29 b(will)g(return)f(dataco)s +(de)i(=)e(16,)i(rep)s(eat)f(=)f(60,)j(and)d(width)g(=)g(12.)41 +b(\(The)29 b(TDIMn)f(k)m(eyw)m(ord)h(ma)m(y)227 4227 +y(also)35 b(b)s(e)e(used)g(to)h(sp)s(ecify)f(the)h(unit)f(string)h +(length;)i(The)d(pair)g(of)h(k)m(eyw)m(ords)g(TF)m(ORMn)f(=)g('60A')j +(and)227 4340 y(TDIMn)30 b(=)g('\(12,5\)')j(w)m(ould)e(ha)m(v)m(e)g +(the)g(same)g(e\013ect)g(as)g(TF)m(ORMn)f(=)g('60A12'\).)227 +4504 y(The)h(second)h(routine,)g(FTEQTY)f(is)g(similar)h(except)h(that) +f(in)f(the)h(case)g(of)g(scaled)g(in)m(teger)h(columns)e(it)227 +4617 y(returns)23 b(the)h('equiv)-5 b(alen)m(t')25 b(data)f(t)m(yp)s(e) +g(that)h(is)e(needed)h(to)g(store)g(the)g(scaled)g(v)-5 +b(alues,)26 b(and)d(not)h(necessarily)227 4730 y(the)38 +b(ph)m(ysical)h(data)f(t)m(yp)s(e)g(of)g(the)g(unscaled)g(v)-5 +b(alues)38 b(as)g(stored)g(in)g(the)g(FITS)f(table.)64 +b(F)-8 b(or)38 b(example)h(if)227 4843 y(a)c('1I')g(column)f(in)g(a)g +(binary)g(table)h(has)f(TSCALn)f(=)g(1)i(and)f(TZER)m(On)f(=)g(32768,) +38 b(then)c(this)g(column)227 4956 y(e\013ectiv)m(ely)27 +b(con)m(tains)e(unsigned)e(short)g(in)m(teger)j(v)-5 +b(alues,)25 b(and)f(th)m(us)f(the)h(returned)f(v)-5 b(alue)24 +b(of)g(t)m(yp)s(eco)s(de)h(will)227 5068 y(b)s(e)32 b(the)h(co)s(de)g +(for)g(an)f(unsigned)g(short)g(in)m(teger,)j(not)e(a)g(signed)g(short)f +(in)m(teger.)49 b(Similarly)-8 b(,)34 b(if)f(a)g(column)227 +5181 y(has)d(TTYPEn)g(=)g('1I')h(and)f(TSCALn)e(=)i(0.12,)j(then)d(the) +g(returned)g(t)m(yp)s(eco)s(de)g(will)h(b)s(e)f(the)h(co)s(de)f(for)h +(a)227 5294 y('real')h(column.)382 5581 y Fe(FTGTCL\(unit,colnum,)42 +b(>)48 b(datacode,repeat,width,st)o(atu)o(s\))382 5694 +y(FTEQTY\(unit,colnum,)42 b(>)48 b(datacode,repeat,width,st)o(atu)o +(s\))p eop end +%%Page: 57 63 +TeXDict begin 57 62 bop 0 299 a Fg(6.7.)72 b(FITS)30 +b(ASCI)s(I)f(AND)i(BINAR)-8 b(Y)31 b(T)-8 b(ABLE)31 b(D)m(A)-8 +b(T)g(A)32 b(I/O)e(SUBR)m(OUTINES)979 b Fi(57)0 555 y +Fh(4)81 b Fi(Return)22 b(the)i(displa)m(y)g(width)f(of)g(a)h(column.)39 +b(This)22 b(is)i(the)g(length)g(of)f(the)h(string)g(that)g(will)g(b)s +(e)f(returned)f(when)227 668 y(reading)33 b(the)g(column)g(as)g(a)g +(formatted)g(string.)48 b(The)32 b(displa)m(y)h(width)f(is)h +(determined)g(b)m(y)f(the)h(TDISPn)227 781 y(k)m(eyw)m(ord,)e(if)g +(presen)m(t,)f(otherwise)h(b)m(y)f(the)h(data)g(t)m(yp)s(e)g(of)f(the)h +(column.)382 1012 y Fe(FTGCDW\(unit,)44 b(colnum,)i(>)i(dispwidth,)d +(status\))0 1243 y Fh(5)81 b Fi(Get)29 b(information)f(ab)s(out)g(an)g +(existing)h(ASCI)s(I)e(table)i(column.)40 b(\(NOTE:)28 +b(TSCAL)f(and)g(TZER)m(O)h(m)m(ust)g(b)s(e)227 1356 y(declared)j(as)g +(Double)g(Precision)g(v)-5 b(ariables\).)41 b(All)31 +b(the)g(returned)e(parameters)i(are)f(scalar)i(quan)m(tities.)382 +1586 y Fe(FTGACL\(unit,colnum,)42 b(>)716 1699 y +(ttype,tbcol,tunit,tform,)o(tsca)o(l,t)o(zero)o(,snu)o(ll,)o(tdis)o +(p,st)o(atu)o(s\))0 1930 y Fh(6)81 b Fi(Get)29 b(information)f(ab)s +(out)f(an)h(existing)h(binary)e(table)i(column.)40 b(\(NOTE:)28 +b(TSCAL)e(and)i(TZER)m(O)f(m)m(ust)h(b)s(e)227 2043 y(declared)j(as)f +(Double)g(Precision)h(v)-5 b(ariables\).)41 b(D)m(A)-8 +b(T)g(A)g(TYPE)32 b(is)e(a)g(c)m(haracter)i(string)d(whic)m(h)h +(returns)f(the)227 2156 y(datat)m(yp)s(e)35 b(of)g(the)f(column)g(as)g +(de\014ned)f(b)m(y)h(the)g(TF)m(ORMn)g(k)m(eyw)m(ord)h(\(e.g.,)i('I',)e +('J','E',)g('D',)g(etc.\).)54 b(In)227 2269 y(the)27 +b(case)g(of)g(an)f(ASCI)s(I)f(c)m(haracter)j(column,)f(D)m(A)-8 +b(T)g(A)g(TYPE)29 b(will)d(ha)m(v)m(e)i(a)f(v)-5 b(alue)27 +b(of)f(the)h(form)f('An')g(where)227 2382 y('n')34 b(is)g(an)g(in)m +(teger)i(expressing)e(the)g(width)f(of)h(the)h(\014eld)e(in)h(c)m +(haracters.)53 b(F)-8 b(or)35 b(example,)h(if)e(TF)m(ORM)g(=)227 +2495 y('160A8')39 b(then)e(FTGBCL)f(will)h(return)f(D)m(A)-8 +b(T)g(A)g(TYPE='A8')39 b(and)d(REPEA)-8 b(T=20.)60 b(All)37 +b(the)g(returned)227 2608 y(parameters)31 b(are)g(scalar)g(quan)m +(tities.)382 2838 y Fe(FTGBCL\(unit,colnum,)42 b(>)716 +2951 y(ttype,tunit,datatype,rep)o(eat,)o(tsc)o(al,t)o(zero)o(,tn)o +(ull,)o(tdis)o(p,s)o(tatu)o(s\))0 3182 y Fh(7)81 b Fi(Put)31 +b(\(app)s(end\))g(a)i(TDIMn)f(k)m(eyw)m(ord)g(whose)g(v)-5 +b(alue)33 b(has)f(the)g(form)g('\(l,m,n...\)')47 b(where)32 +b(l,)h(m,)f(n...)46 b(are)33 b(the)227 3295 y(dimensions)d(of)g(a)h(m)m +(ultidimensional)g(arra)m(y)g(column)f(in)g(a)h(binary)f(table.)382 +3526 y Fe(FTPTDM\(unit,colnum,naxis)o(,na)o(xes,)41 b(>)48 +b(status\))0 3757 y Fh(8)81 b Fi(Return)29 b(the)h(n)m(um)m(b)s(er)e +(of)i(and)g(size)g(of)g(the)g(dimensions)g(of)g(a)g(table)h(column.)40 +b(Normally)31 b(this)f(information)227 3870 y(is)h(giv)m(en)h(b)m(y)f +(the)g(TDIMn)f(k)m(eyw)m(ord,)i(but)e(if)h(this)g(k)m(eyw)m(ord)g(is)g +(not)g(presen)m(t)g(then)g(this)f(routine)h(returns)227 +3983 y(NAXIS)f(=)g(1)h(and)f(NAXES\(1\))h(equal)g(to)g(the)g(rep)s(eat) +g(coun)m(t)g(in)f(the)g(TF)m(ORM)h(k)m(eyw)m(ord.)382 +4213 y Fe(FTGTDM\(unit,colnum,maxdi)o(m,)41 b(>)48 b +(naxis,naxes,status\))0 4444 y Fh(9)81 b Fi(Deco)s(de)33 +b(the)g(input)f(TDIMn)h(k)m(eyw)m(ord)g(string)f(\(e.g.)50 +b('\(100,200\)'\))37 b(and)32 b(return)g(the)h(n)m(um)m(b)s(er)e(of)i +(and)f(size)227 4557 y(of)c(the)g(dimensions)f(of)h(a)g(binary)f(table) +h(column.)40 b(If)27 b(the)h(input)f(tdimstr)g(c)m(haracter)i(string)f +(is)g(n)m(ull,)g(then)227 4670 y(this)d(routine)f(returns)f(naxis)h(=)h +(1)f(and)g(naxes[0])i(equal)e(to)i(the)e(rep)s(eat)h(coun)m(t)g(in)f +(the)g(TF)m(ORM)h(k)m(eyw)m(ord.)227 4783 y(This)30 b(routine)g(is)h +(called)g(b)m(y)f(FTGTDM.)382 5014 y Fe(FTDTDM\(unit,tdimstr,coln)o +(um,)o(maxd)o(im,)41 b(>)48 b(naxis,naxes,)c(status\))0 +5245 y Fh(10)i Fi(Return)32 b(the)h(optimal)h(n)m(um)m(b)s(er)e(of)h +(ro)m(ws)g(to)h(read)f(or)g(write)g(at)h(one)f(time)h(for)e(maxim)m(um) +h(I/O)g(e\016ciency)-8 b(.)227 5358 y(Refer)31 b(to)g(the)g +(\\Optimizing)g(Co)s(de")f(section)i(in)e(Chapter)g(5)g(for)h(more)f +(discussion)g(on)g(ho)m(w)h(to)g(use)f(this)227 5470 +y(routine.)382 5701 y Fe(FFGRSZ\(unit,)44 b(>)k(nrows,status\))p +eop end +%%Page: 58 64 +TeXDict begin 58 63 bop 0 299 a Fi(58)1319 b Fg(CHAPTER)29 +b(6.)112 b(AD)m(V)-10 b(ANCED)32 b(INTERF)-10 b(A)m(CE)30 +b(SUBR)m(OUTINES)0 555 y Fb(6.7.2)112 b(Lo)m(w-Lev)m(el)39 +b(T)-9 b(able)38 b(Access)f(Subroutines)0 774 y Fi(The)d(follo)m(wing)h +(subroutines)e(pro)m(vide)i(lo)m(w-lev)m(el)i(access)e(to)g(the)g(data) +g(in)f(ASCI)s(I)e(or)i(binary)g(tables)h(and)f(are)0 +887 y(mainly)29 b(useful)f(as)i(an)f(e\016cien)m(t)h(w)m(a)m(y)g(to)g +(cop)m(y)g(all)g(or)f(part)g(of)g(a)g(table)h(from)f(one)g(lo)s(cation) +i(to)f(another.)40 b(These)0 1000 y(routines)24 b(simply)g(read)g(or)h +(write)f(the)h(sp)s(eci\014ed)e(n)m(um)m(b)s(er)g(of)i(consecutiv)m(e)h +(b)m(ytes)f(in)f(an)g(ASCI)s(I)f(or)h(binary)g(table,)0 +1113 y(without)37 b(regard)g(for)f(column)h(b)s(oundaries)e(or)i(the)g +(ro)m(w)g(length)h(in)e(the)h(table.)61 b(The)37 b(\014rst)f(t)m(w)m(o) +i(subroutines)0 1226 y(read)29 b(or)h(write)g(consecutiv)m(e)h(b)m +(ytes)f(in)f(a)h(table)g(to)h(or)e(from)g(a)h(c)m(haracter)h(string)e +(v)-5 b(ariable,)31 b(while)f(the)f(last)i(t)m(w)m(o)0 +1339 y(subroutines)f(read)i(or)g(write)g(consecutiv)m(e)h(b)m(ytes)g +(to)f(or)g(from)f(a)h(v)-5 b(ariable)33 b(declared)f(as)g(a)g(n)m +(umeric)f(data)i(t)m(yp)s(e)0 1452 y(\(e.g.,)40 b(INTEGER,)d +(INTEGER*2,)i(REAL,)d(DOUBLE)h(PRECISION\).)f(These)g(routines)h(do)f +(not)h(p)s(erform)0 1564 y(an)m(y)c(mac)m(hine)g(dep)s(enden)m(t)f +(data)i(con)m(v)m(ersion)g(or)e(b)m(yte)i(sw)m(apping,)f(except)h(that) +f(con)m(v)m(ersion)h(to/from)f(ASCI)s(I)0 1677 y(format)d(is)g(p)s +(erformed)e(b)m(y)h(the)h(FTGTBS)f(and)g(FTPTBS)g(routines)h(on)f(mac)m +(hines)h(whic)m(h)g(do)f(not)h(use)f(ASCI)s(I)0 1790 +y(c)m(haracter)j(co)s(des)e(in)g(the)h(in)m(ternal)g(data)g(represen)m +(tations)h(\(e.g.,)g(on)e(IBM)h(mainframe)f(computers\).)0 +2049 y Fh(1)81 b Fi(Read)26 b(a)h(consecutiv)m(e)h(string)f(of)f(c)m +(haracters)i(from)e(an)g(ASCI)s(I)f(table)i(in)m(to)h(a)e(c)m(haracter) +i(v)-5 b(ariable)28 b(\(spanning)227 2162 y(columns)k(and)g(m)m +(ultiple)h(ro)m(ws)f(if)g(necessary\))h(This)f(routine)g(should)f(not)i +(b)s(e)e(used)h(with)g(binary)f(tables)227 2275 y(b)s(ecause)g(of)f +(complications)i(related)g(to)f(passing)f(string)g(v)-5 +b(ariables)31 b(b)s(et)m(w)m(een)g(C)f(and)g(F)-8 b(ortran.)382 +2533 y Fe(FTGTBS\(unit,frow,startch)o(ar,)o(ncha)o(rs,)41 +b(>)48 b(string,status\))0 2792 y Fh(2)81 b Fi(W)-8 b(rite)31 +b(a)g(consecutiv)m(e)h(string)e(of)h(c)m(haracters)g(to)g(an)f(ASCI)s +(I)f(table)i(from)f(a)h(c)m(haracter)h(v)-5 b(ariable)31 +b(\(spanning)227 2905 y(columns)h(and)g(m)m(ultiple)h(ro)m(ws)f(if)g +(necessary\))h(This)f(routine)g(should)f(not)i(b)s(e)e(used)h(with)g +(binary)f(tables)227 3018 y(b)s(ecause)g(of)f(complications)i(related)g +(to)f(passing)f(string)g(v)-5 b(ariables)31 b(b)s(et)m(w)m(een)g(C)f +(and)g(F)-8 b(ortran.)382 3277 y Fe(FTPTBS\(unit,frow,startch)o(ar,)o +(ncha)o(rs,s)o(tri)o(ng,)41 b(>)48 b(status\))0 3535 +y Fh(3)81 b Fi(Read)27 b(a)h(consecutiv)m(e)i(arra)m(y)e(of)g(b)m(ytes) +g(from)f(an)g(ASCI)s(I)f(or)i(binary)e(table)j(in)m(to)f(a)g(n)m +(umeric)g(v)-5 b(ariable)28 b(\(span-)227 3648 y(ning)k(columns)f(and)h +(m)m(ultiple)g(ro)m(ws)g(if)g(necessary\).)46 b(The)32 +b(arra)m(y)g(parameter)g(ma)m(y)h(b)s(e)e(declared)h(as)h(an)m(y)227 +3761 y(n)m(umerical)i(datat)m(yp)s(e)g(as)g(long)g(as)g(the)f(arra)m(y) +h(is)f(at)h(least)h('nc)m(hars')f(b)m(ytes)f(long,)j(e.g.,)f(if)f(nc)m +(hars)f(=)g(17,)227 3874 y(then)c(declare)i(the)e(arra)m(y)h(as)g +(INTEGER*4)g(ARRA)-8 b(Y\(5\).)382 4133 y Fe(FTGTBB\(unit,frow,startch) +o(ar,)o(ncha)o(rs,)41 b(>)48 b(array,status\))0 4391 +y Fh(4)81 b Fi(W)-8 b(rite)32 b(a)f(consecutiv)m(e)i(arra)m(y)f(of)f(b) +m(ytes)g(to)h(an)e(ASCI)s(I)g(or)h(binary)f(table)i(from)e(a)i(n)m +(umeric)e(v)-5 b(ariable)32 b(\(span-)227 4504 y(ning)j(columns)f(and)g +(m)m(ultiple)h(ro)m(ws)g(if)f(necessary\))i(The)e(arra)m(y)h(parameter) +g(ma)m(y)h(b)s(e)e(declared)h(as)g(an)m(y)227 4617 y(n)m(umerical)g +(datat)m(yp)s(e)g(as)g(long)g(as)g(the)f(arra)m(y)h(is)f(at)h(least)h +('nc)m(hars')f(b)m(ytes)f(long,)j(e.g.,)f(if)f(nc)m(hars)f(=)g(17,)227 +4730 y(then)c(declare)i(the)e(arra)m(y)h(as)g(INTEGER*4)g(ARRA)-8 +b(Y\(5\).)382 4989 y Fe(FTPTBB\(unit,frow,startch)o(ar,)o(ncha)o(rs,a)o +(rra)o(y,)42 b(>)47 b(status\))0 5279 y Fb(6.7.3)112 +b(Edit)37 b(Ro)m(ws)g(or)h(Columns)0 5488 y Fh(1)81 b +Fi(Insert)26 b(blank)h(ro)m(ws)h(in)m(to)g(an)f(existing)h(ASCI)s(I)e +(or)h(binary)g(table)h(\(in)g(the)f(CDU\).)h(All)g(the)g(ro)m(ws)f(F)m +(OLLO)m(W-)227 5601 y(ING)35 b(ro)m(w)g(FR)m(O)m(W)g(are)g(shifted)f +(do)m(wn)g(b)m(y)h(NR)m(O)m(WS)g(ro)m(ws.)53 b(If)34 +b(FR)m(O)m(W)i(or)e(FR)m(O)m(WLL)i(equals)e(0)h(then)227 +5714 y(the)27 b(blank)f(ro)m(ws)h(are)g(inserted)f(at)h(the)g(b)s +(eginning)f(of)g(the)h(table.)41 b(These)26 b(routines)g(mo)s(dify)g +(the)h(NAXIS2)p eop end +%%Page: 59 65 +TeXDict begin 59 64 bop 0 299 a Fg(6.7.)72 b(FITS)30 +b(ASCI)s(I)f(AND)i(BINAR)-8 b(Y)31 b(T)-8 b(ABLE)31 b(D)m(A)-8 +b(T)g(A)32 b(I/O)e(SUBR)m(OUTINES)979 b Fi(59)227 555 +y(k)m(eyw)m(ord)35 b(to)h(re\015ect)f(the)g(new)f(n)m(um)m(b)s(er)f(of) +i(ro)m(ws)g(in)f(the)h(table.)54 b(Note)36 b(that)f(it)h(is)e(*not*)i +(necessary)f(to)227 668 y(insert)c(ro)m(ws)f(in)g(a)h(table)g(b)s +(efore)f(writing)g(data)i(to)f(those)g(ro)m(ws)f(\(indeed,)g(it)h(w)m +(ould)g(b)s(e)e(ine\016cien)m(t)j(to)f(do)227 781 y(so\).)54 +b(Instead,)35 b(one)g(ma)m(y)g(simply)f(write)h(data)g(to)g(an)m(y)g +(ro)m(w)f(of)h(the)g(table,)h(whether)e(that)h(ro)m(w)g(of)f(data)227 +894 y(already)d(exists)g(or)g(not.)382 1116 y Fe +(FTIROW\(unit,frow,nrows,)41 b(>)48 b(status\))382 1229 +y(FTIROWLL\(unit,frowll,nro)o(wsl)o(l,)42 b(>)47 b(status\))0 +1451 y Fh(2)81 b Fi(Delete)25 b(ro)m(ws)f(from)f(an)g(existing)i(ASCI)s +(I)c(or)j(binary)f(table)h(\(in)f(the)h(CDU\).)g(The)f(NR)m(O)m(WS)h +(\(or)g(NR)m(O)m(WSLL\))227 1564 y(is)e(the)g(n)m(um)m(b)s(er)f(of)h +(ro)m(ws)g(are)g(deleted,)i(starting)f(with)f(ro)m(w)g(FR)m(O)m(W)h +(\(or)f(FR)m(O)m(WLL\),)h(and)f(an)m(y)g(remaining)227 +1677 y(ro)m(ws)f(in)g(the)f(table)i(are)f(shifted)g(up)e(to)j(\014ll)f +(in)f(the)h(space.)38 b(These)21 b(routines)f(mo)s(dify)g(the)h(NAXIS2) +g(k)m(eyw)m(ord)227 1790 y(to)31 b(re\015ect)g(the)g(new)f(n)m(um)m(b)s +(er)f(of)h(ro)m(ws)h(in)f(the)g(table.)382 2012 y Fe +(FTDROW\(unit,frow,nrows,)41 b(>)48 b(status\))382 2125 +y(FTDROWLL\(unit,frowll,nro)o(wsl)o(l,)42 b(>)47 b(status\))0 +2348 y Fh(3)81 b Fi(Delete)26 b(a)f(list)g(of)g(ro)m(ws)f(from)g(an)h +(ASCI)s(I)e(or)h(binary)g(table)h(\(in)g(the)f(CDU\).)i(In)e(the)g +(\014rst)g(routine,)i('ro)m(wrange')227 2461 y(is)i(a)g(c)m(haracter)h +(string)f(listing)h(the)f(ro)m(ws)f(or)h(ro)m(w)g(ranges)g(to)g(delete) +h(\(e.g.,)i('2-4,)e(5,)g(8-9'\).)42 b(In)27 b(the)h(second)227 +2574 y(routine,)37 b('ro)m(wlist')f(is)f(an)f(in)m(teger)j(arra)m(y)e +(of)g(ro)m(w)g(n)m(um)m(b)s(ers)e(to)j(b)s(e)e(deleted)i(from)e(the)h +(table.)56 b(nro)m(ws)34 b(is)227 2686 y(the)e(n)m(um)m(b)s(er)e(of)h +(ro)m(w)h(n)m(um)m(b)s(ers)e(in)h(the)g(list.)45 b(The)31 +b(\014rst)f(ro)m(w)i(in)f(the)g(table)i(is)e(1)h(not)f(0.)44 +b(The)31 b(list)h(of)g(ro)m(w)227 2799 y(n)m(um)m(b)s(ers)d(m)m(ust)h +(b)s(e)g(sorted)h(in)f(ascending)g(order.)382 3022 y +Fe(FTDRRG\(unit,rowrange,)42 b(>)47 b(status\))382 3135 +y(FTDRWS\(unit,rowlist,nrow)o(s,)41 b(>)48 b(status\))0 +3357 y Fh(4)81 b Fi(Insert)43 b(a)i(blank)f(column)h(\(or)f(columns\))h +(in)m(to)g(an)f(existing)i(ASCI)s(I)d(or)h(binary)g(table)h(\(in)g(the) +f(CDU\).)227 3470 y(COLNUM)c(sp)s(eci\014es)g(the)h(column)f(n)m(um)m +(b)s(er)f(that)i(the)f(\(\014rst\))g(new)g(column)g(should)f(o)s(ccup)m +(y)i(in)f(the)227 3583 y(table.)58 b(NCOLS)34 b(sp)s(eci\014es)h(ho)m +(w)h(man)m(y)g(columns)f(are)h(to)g(b)s(e)f(inserted.)57 +b(An)m(y)35 b(existing)i(columns)e(from)227 3696 y(this)k(p)s(osition)f +(and)g(higher)g(are)h(mo)m(v)m(ed)g(o)m(v)m(er)h(to)f(allo)m(w)h(ro)s +(om)e(for)h(the)f(new)g(column\(s\).)65 b(The)38 b(index)227 +3808 y(n)m(um)m(b)s(er)j(on)h(all)h(the)f(follo)m(wing)h(k)m(eyw)m +(ords)g(will)f(b)s(e)f(incremen)m(ted)i(if)f(necessary)g(to)h +(re\015ect)f(the)g(new)227 3921 y(p)s(osition)32 b(of)f(the)g +(column\(s\))h(in)f(the)g(table:)43 b(TBCOLn,)30 b(TF)m(ORMn,)i +(TTYPEn,)e(TUNITn,)h(TNULLn,)227 4034 y(TSCALn,)22 b(TZER)m(On,)g +(TDISPn,)g(TDIMn,)h(TLMINn,)g(TLMAXn,)f(TDMINn,)i(TDMAXn,)f(TCTYPn,)227 +4147 y(TCRPXn,)30 b(TCR)-10 b(VLn,)29 b(TCDL)-8 b(Tn,)30 +b(TCR)m(OTn,)f(and)g(TCUNIn.)382 4370 y Fe(FTICOL\(unit,colnum,ttype)o +(,tf)o(orm,)41 b(>)48 b(status\))382 4482 y(FTICLS\(unit,colnum,ncols)o +(,tt)o(ype,)o(tfor)o(m,)41 b(>)48 b(status\))0 4705 y +Fh(5)81 b Fi(Mo)s(dify)37 b(the)g(v)m(ector)i(length)f(of)f(a)h(binary) +e(table)i(column)f(\(e.g.,)k(c)m(hange)e(a)e(column)g(from)g(TF)m(ORMn) +g(=)227 4818 y('1E')31 b(to)h('20E'\).)g(The)e(v)m(ector)i(length)e(ma) +m(y)h(b)s(e)f(increased)h(or)f(decreased)h(from)f(the)g(curren)m(t)h(v) +-5 b(alue.)382 5040 y Fe(FTMVEC\(unit,colnum,newve)o(cle)o(n,)42 +b(>)47 b(status\))0 5262 y Fh(6)81 b Fi(Delete)29 b(a)f(column)g(from)f +(an)g(existing)i(ASCI)s(I)d(or)i(binary)e(table)j(\(in)f(the)f(CDU\).)i +(The)e(index)g(n)m(um)m(b)s(er)f(of)i(all)227 5375 y(the)k(k)m(eyw)m +(ords)h(listed)f(ab)s(o)m(v)m(e)i(\(for)e(FTICOL\))f(will)h(b)s(e)g +(decremen)m(ted)g(if)g(necessary)h(to)g(re\015ect)f(the)g(new)227 +5488 y(p)s(osition)26 b(of)g(the)g(column\(s\))g(in)f(the)h(table.)40 +b(Those)26 b(index)f(k)m(eyw)m(ords)h(that)g(refer)f(to)i(the)f +(deleted)g(column)227 5601 y(will)33 b(also)g(b)s(e)f(deleted.)47 +b(Note)33 b(that)g(the)g(ph)m(ysical)g(size)g(of)f(the)h(FITS)e(\014le) +i(will)f(not)h(b)s(e)e(reduced)h(b)m(y)g(this)227 5714 +y(op)s(eration,)e(and)e(the)h(empt)m(y)g(FITS)f(blo)s(c)m(ks)h(if)g(an) +m(y)g(at)g(the)g(end)f(of)h(the)g(\014le)g(will)g(b)s(e)f(padded)g +(with)g(zeros.)p eop end +%%Page: 60 66 +TeXDict begin 60 65 bop 0 299 a Fi(60)1319 b Fg(CHAPTER)29 +b(6.)112 b(AD)m(V)-10 b(ANCED)32 b(INTERF)-10 b(A)m(CE)30 +b(SUBR)m(OUTINES)382 555 y Fe(FTDCOL\(unit,colnum,)42 +b(>)48 b(status\))0 795 y Fh(7)81 b Fi(Cop)m(y)30 b(a)g(column)g(from)g +(one)g(HDU)h(to)g(another)f(\(or)h(to)g(the)f(same)h(HDU\).)g(If)f +(createcol)j(=)c(TR)m(UE,)i(then)f(a)227 908 y(new)20 +b(column)g(will)h(b)s(e)f(inserted)g(in)g(the)h(output)f(table,)k(at)d +(p)s(osition)f(`outcolumn',)j(otherwise)e(the)g(existing)227 +1021 y(output)29 b(column)f(will)h(b)s(e)f(o)m(v)m(erwritten)i(\(in)f +(whic)m(h)f(case)i(it)f(m)m(ust)f(ha)m(v)m(e)i(a)f(compatible)h(datat)m +(yp)s(e\).)42 b(Note)227 1134 y(that)31 b(the)g(\014rst)e(column)i(in)f +(a)g(table)i(is)e(at)h(coln)m(um)g(=)f(1.)382 1373 y +Fe(FTCPCL\(inunit,outunit,in)o(col)o(num,)o(outc)o(oln)o(um,c)o(reat)o +(eco)o(l,)42 b(>)47 b(status\);)0 1661 y Fb(6.7.4)112 +b(Read)38 b(and)h(W)-9 b(rite)36 b(Column)j(Data)e(Routines)0 +1880 y Fi(These)22 b(subroutines)f(put)h(or)g(get)i(data)f(v)-5 +b(alues)22 b(in)g(the)h(curren)m(t)f(ASCI)s(I)f(or)h(Binary)g(table)i +(extension.)38 b(Automatic)0 1992 y(data)21 b(t)m(yp)s(e)g(con)m(v)m +(ersion)g(is)f(p)s(erformed)f(for)h(n)m(umerical)h(data)g(t)m(yp)s(es)g +(\(B,I,J,E,D\))h(if)e(the)h(data)g(t)m(yp)s(e)f(of)h(the)f(column)0 +2105 y(\(de\014ned)32 b(b)m(y)i(the)f(TF)m(ORM)h(k)m(eyw)m(ord\))g +(di\013ers)f(from)f(the)i(data)g(t)m(yp)s(e)f(of)h(the)f(calling)i +(subroutine.)48 b(The)33 b(data)0 2218 y(v)-5 b(alues)30 +b(are)h(also)g(scaled)f(b)m(y)g(the)g(TSCALn)f(and)g(TZER)m(On)g +(header)h(v)-5 b(alues)30 b(as)g(they)g(are)h(b)s(eing)e(written)h(to)h +(or)0 2331 y(read)j(from)f(the)h(FITS)f(arra)m(y)-8 b(.)51 +b(The)33 b(fttscl)i(subroutine)d(MUST)i(b)s(e)f(used)g(to)h(de\014ne)f +(the)h(scaling)h(parameters)0 2444 y(when)d(writing)h(data)h(to)g(the)f +(table)h(or)f(to)h(o)m(v)m(erride)g(the)g(default)f(scaling)h(v)-5 +b(alues)34 b(giv)m(en)g(in)f(the)g(header)g(when)0 2557 +y(reading)27 b(from)g(the)g(table.)40 b(Note)29 b(that)e(it)h(is)f +(*not*)h(necessary)f(to)h(insert)f(ro)m(ws)g(in)f(a)i(table)g(b)s +(efore)e(writing)h(data)0 2670 y(to)j(those)h(ro)m(ws)e(\(indeed,)h(it) +g(w)m(ould)g(b)s(e)f(ine\016cien)m(t)i(to)f(do)g(so\).)41 +b(Instead,)30 b(one)g(ma)m(y)g(simply)f(write)h(data)g(to)h(an)m(y)0 +2783 y(ro)m(w)f(of)h(the)g(table,)g(whether)f(that)h(ro)m(w)f(of)h +(data)g(already)g(exists)g(or)f(not.)0 2943 y(In)i(the)i(case)g(of)f +(binary)g(tables)h(with)f(v)m(ector)h(elemen)m(ts,)i(the)d('felem')h +(parameter)g(de\014nes)e(the)i(starting)g(pixel)0 3056 +y(within)k(the)g(elemen)m(t)i(v)m(ector.)65 b(This)38 +b(parameter)g(is)g(ignored)h(with)e(ASCI)s(I)g(tables.)65 +b(Similarly)-8 b(,)41 b(in)d(the)g(case)0 3169 y(of)45 +b(binary)e(tables)i(the)g('nelemen)m(ts')h(parameter)f(sp)s(eci\014es)f +(the)g(total)i(n)m(um)m(b)s(er)d(of)i(v)m(ector)h(v)-5 +b(alues)45 b(read)f(or)0 3282 y(written)36 b(\(con)m(tin)m(uing)h(on)f +(subsequen)m(t)f(ro)m(ws)g(if)h(required\))f(and)h(not)g(the)g(n)m(um)m +(b)s(er)e(of)i(table)h(elemen)m(ts.)58 b(Tw)m(o)0 3395 +y(sets)36 b(of)f(subroutines)g(are)g(pro)m(vided)g(to)i(get)f(the)g +(column)f(data)h(whic)m(h)f(di\013er)g(in)h(the)f(w)m(a)m(y)i +(unde\014ned)c(pixels)0 3508 y(are)f(handled.)42 b(The)31 +b(\014rst)g(set)h(of)f(routines)h(\(FTGCV\))g(simply)f(return)f(an)h +(arra)m(y)h(of)f(data)h(elemen)m(ts)h(in)e(whic)m(h)0 +3620 y(unde\014ned)41 b(pixels)j(are)g(set)g(equal)g(to)h(a)f(v)-5 +b(alue)44 b(sp)s(eci\014ed)f(b)m(y)g(the)h(user)f(in)g(the)h('n)m(ullv) +-5 b(al')44 b(parameter.)81 b(An)0 3733 y(additional)44 +b(feature)g(of)g(these)g(subroutines)e(is)i(that)g(if)f(the)h(user)e +(sets)i(n)m(ullv)-5 b(al)44 b(=)f(0,)48 b(then)43 b(no)g(c)m(hec)m(ks)i +(for)0 3846 y(unde\014ned)33 b(pixels)j(will)g(b)s(e)e(p)s(erformed,)i +(th)m(us)f(increasing)h(the)g(sp)s(eed)e(of)i(the)g(program.)55 +b(The)35 b(second)h(set)g(of)0 3959 y(routines)h(\(FTGCF\))h(returns)d +(the)i(data)h(elemen)m(t)g(arra)m(y)g(and)e(in)h(addition)g(a)g +(logical)j(arra)m(y)d(of)g(\015ags)g(whic)m(h)0 4072 +y(de\014nes)29 b(whether)h(the)h(corresp)s(onding)e(data)i(pixel)g(is)f +(unde\014ned.)0 4232 y(An)m(y)41 b(column,)i(regardless)e(of)g(it's)g +(in)m(trinsic)g(datat)m(yp)s(e,)k(ma)m(y)c(b)s(e)f(read)h(as)g(a)g +(string.)71 b(It)41 b(should)f(b)s(e)g(noted)0 4345 y(ho)m(w)m(ev)m(er) +32 b(that)f(reading)f(a)h(n)m(umeric)f(column)g(as)h(a)f(string)h(is)f +(10)h(-)g(100)g(times)g(slo)m(w)m(er)h(than)e(reading)g(the)h(same)0 +4458 y(column)g(as)h(a)g(n)m(um)m(b)s(er)e(due)h(to)h(the)g(large)h(o)m +(v)m(erhead)f(in)g(constructing)g(the)g(formatted)g(strings.)44 +b(The)31 b(displa)m(y)0 4571 y(format)26 b(of)g(the)h(returned)d +(strings)i(will)g(b)s(e)g(determined)f(b)m(y)h(the)g(TDISPn)f(k)m(eyw)m +(ord,)j(if)d(it)i(exists,)h(otherwise)e(b)m(y)0 4684 +y(the)i(datat)m(yp)s(e)h(of)g(the)f(column.)40 b(The)28 +b(length)g(of)h(the)f(returned)f(strings)h(can)g(b)s(e)g(determined)g +(with)f(the)i(ftgcdw)0 4797 y(routine.)41 b(The)30 b(follo)m(wing)h +(TDISPn)f(displa)m(y)g(formats)h(are)f(curren)m(tly)h(supp)s(orted:)191 +5036 y Fe(Iw.m)142 b(Integer)191 5149 y(Ow.m)g(Octal)46 +b(integer)191 5262 y(Zw.m)142 b(Hexadecimal)45 b(integer)191 +5375 y(Fw.d)142 b(Fixed)46 b(floating)g(point)191 5488 +y(Ew.d)142 b(Exponential)45 b(floating)g(point)191 5601 +y(Dw.d)142 b(Exponential)45 b(floating)g(point)191 5714 +y(Gw.d)142 b(General;)46 b(uses)g(Fw.d)h(if)g(significance)d(not)j +(lost,)g(else)f(Ew.d)p eop end +%%Page: 61 67 +TeXDict begin 61 66 bop 0 299 a Fg(6.7.)72 b(FITS)30 +b(ASCI)s(I)f(AND)i(BINAR)-8 b(Y)31 b(T)-8 b(ABLE)31 b(D)m(A)-8 +b(T)g(A)32 b(I/O)e(SUBR)m(OUTINES)979 b Fi(61)0 555 y(where)37 +b(w)h(is)g(the)g(width)f(in)h(c)m(haracters)h(of)f(the)h(displa)m(y)m +(ed)f(v)-5 b(alues,)41 b(m)c(is)h(the)g(minim)m(um)g(n)m(um)m(b)s(er)e +(of)i(digits)0 668 y(displa)m(y)m(ed,)31 b(and)f(d)g(is)g(the)h(n)m(um) +m(b)s(er)e(of)h(digits)h(to)g(the)g(righ)m(t)g(of)g(the)f(decimal.)42 +b(The)30 b(.m)g(\014eld)g(is)g(optional.)0 920 y Fh(1)81 +b Fi(Put)30 b(elemen)m(ts)i(in)m(to)g(an)e(ASCI)s(I)f(or)i(binary)f +(table)i(column)e(\(in)h(the)g(CDU\).)g(\(The)g(SPP)f(FSPCLS)f(routine) +227 1033 y(has)38 b(an)f(additional)i(in)m(teger)g(argumen)m(t)f(after) +h(the)f(V)-10 b(ALUES)37 b(c)m(haracter)i(string)f(whic)m(h)f(sp)s +(eci\014es)h(the)227 1145 y(size)31 b(of)g(the)g(1st)g(dimension)e(of)i +(this)f(2-D)i(CHAR)e(arra)m(y\).)227 1294 y(The)24 b(alternate)i(v)m +(ersion)f(of)g(these)g(routines,)h(whose)e(names)g(end)g(in)g('LL')h +(after)g(the)g(datat)m(yp)s(e)g(c)m(haracter,)227 1407 +y(supp)s(ort)34 b(large)j(tables)f(with)g(more)f(then)h(2*31)h(ro)m +(ws.)57 b(When)35 b(calling)i(these)f(routines,)h(the)f(fro)m(w)g(and) +227 1520 y(felem)31 b(parameters)g(*m)m(ust*)g(b)s(e)f(64-bit)h(in)m +(teger*8)i(v)-5 b(ariables,)31 b(instead)g(of)g(normal)f(4-b)m(yte)i +(in)m(tegers.)382 1772 y Fe(FTPCL[SLBIJKEDCM]\(unit,c)o(oln)o(um,f)o +(row,)o(fel)o(em,n)o(elem)o(ent)o(s,va)o(lues)o(,)42 +b(>)47 b(status\))382 1885 y(FTPCL[LBIJKEDCM]LL\(unit,)o(col)o(num,)o +(frow)o(,fe)o(lem,)o(nele)o(men)o(ts,v)o(alue)o(s,)41 +b(>)48 b(status\))0 2136 y Fh(2)81 b Fi(Put)29 b(elemen)m(ts)i(in)m(to) +g(an)f(ASCI)s(I)e(or)i(binary)f(table)i(column)e(\(in)h(the)g(CDU\))g +(substituting)g(the)g(appropriate)227 2249 y(FITS)c(n)m(ull)g(v)-5 +b(alue)26 b(for)g(an)m(y)h(elemen)m(ts)g(that)g(are)f(equal)h(to)g +(NULL)-10 b(V)g(AL.)26 b(F)-8 b(or)27 b(ASCI)s(I)e(T)-8 +b(ABLE)26 b(extensions,)227 2362 y(the)31 b(n)m(ull)f(v)-5 +b(alue)31 b(de\014ned)e(b)m(y)h(the)g(previous)g(call)i(to)f(FTSNUL)f +(will)g(b)s(e)g(substituted;)g(F)-8 b(or)31 b(in)m(teger)h(FITS)227 +2475 y(columns,)39 b(in)e(a)h(binary)f(table)h(the)f(n)m(ull)h(v)-5 +b(alue)37 b(de\014ned)g(b)m(y)g(the)g(previous)g(call)i(to)f(FTTNUL)f +(will)h(b)s(e)227 2588 y(substituted;)28 b(F)-8 b(or)28 +b(\015oating)h(p)s(oin)m(t)e(FITS)f(columns)h(a)h(sp)s(ecial)g(IEEE)f +(NaN)h(\(Not-a-Num)m(b)s(er\))h(v)-5 b(alue)28 b(will)227 +2701 y(b)s(e)i(substituted.)227 2850 y(The)24 b(alternate)i(v)m(ersion) +f(of)g(these)g(routines,)h(whose)e(names)g(end)g(in)g('LL')h(after)g +(the)g(datat)m(yp)s(e)g(c)m(haracter,)227 2963 y(supp)s(ort)34 +b(large)j(tables)f(with)g(more)f(then)h(2*31)h(ro)m(ws.)57 +b(When)35 b(calling)i(these)f(routines,)h(the)f(fro)m(w)g(and)227 +3075 y(felem)31 b(parameters)g(*m)m(ust*)g(b)s(e)f(64-bit)h(in)m +(teger*8)i(v)-5 b(ariables,)31 b(instead)g(of)g(normal)f(4-b)m(yte)i +(in)m(tegers.)382 3327 y Fe(FTPCN[SBIJKED]\(unit,coln)o(um,)o(frow)o +(,fel)o(em,)o(nele)o(ment)o(s,v)o(alue)o(s,nu)o(llv)o(al)42 +b(>)47 b(status\))382 3440 y(FTPCN[SBIJKED]LL\(unit,co)o(lnu)o(m,\(I)o +(*8\))41 b(frow,\(I*8\))k(felem,nelements,values,)764 +3553 y(nullval)g(>)j(status\))0 3804 y Fh(3)81 b Fi(Put)37 +b(bit)h(v)-5 b(alues)38 b(in)m(to)h(a)f(binary)f(b)m(yte)h(\('B'\))i +(or)d(bit)h(\('X'\))h(table)g(column)f(\(in)f(the)h(CDU\).)h(LRA)-8 +b(Y)38 b(is)g(an)227 3917 y(arra)m(y)c(of)g(logical)h(v)-5 +b(alues)34 b(corresp)s(onding)e(to)i(the)g(sequence)f(of)h(bits)f(to)h +(b)s(e)f(written.)49 b(If)33 b(LRA)-8 b(Y)34 b(is)f(true)227 +4030 y(then)f(the)g(corresp)s(onding)f(bit)h(is)g(set)g(to)h(1,)g +(otherwise)f(the)g(bit)g(is)g(set)h(to)g(0.)45 b(Note)34 +b(that)e(in)g(the)g(case)h(of)227 4143 y('X')g(columns,)g(FITSIO)e +(will)i(write)f(to)h(all)g(8)g(bits)f(of)g(eac)m(h)i(b)m(yte)f(whether) +e(they)i(are)g(formally)f(v)-5 b(alid)33 b(or)227 4256 +y(not.)46 b(Th)m(us)31 b(if)h(the)g(column)g(is)f(de\014ned)g(as)h +('4X',)i(and)d(one)h(calls)h(FTPCLX)f(with)f(fbit=1)h(and)f(n)m(bit=8,) +227 4369 y(then)j(all)h(8)f(bits)g(will)g(b)s(e)f(written)h(in)m(to)h +(the)f(\014rst)g(b)m(yte)g(\(as)h(opp)s(osed)e(to)i(writing)e(the)i +(\014rst)e(4)h(bits)g(in)m(to)227 4482 y(the)d(\014rst)f(ro)m(w)g(and)g +(then)g(the)h(next)f(4)h(bits)f(in)m(to)i(the)e(next)h(ro)m(w\),)g(ev)m +(en)g(though)f(the)h(last)g(4)g(bits)f(of)h(eac)m(h)227 +4595 y(b)m(yte)g(are)g(formally)g(not)g(de\014ned.)382 +4846 y Fe(FTPCLX\(unit,colnum,frow,)o(fbi)o(t,nb)o(it,l)o(ray)o(,)42 +b(>)47 b(status\))0 5098 y Fh(4)81 b Fi(Set)30 b(table)h(elemen)m(ts)h +(in)e(a)h(column)f(as)h(unde\014ned)382 5349 y Fe +(FTPCLU\(unit,colnum,frow,)o(fel)o(em,n)o(elem)o(ent)o(s,)42 +b(>)47 b(status\))0 5601 y Fh(5)81 b Fi(Get)34 b(elemen)m(ts)g(from)f +(an)g(ASCI)s(I)f(or)h(binary)g(table)h(column)f(\(in)g(the)g(CDU\).)i +(These)e(routines)g(return)f(the)227 5714 y(v)-5 b(alues)30 +b(of)g(the)g(table)h(column)f(arra)m(y)g(elemen)m(ts.)42 +b(Unde\014ned)28 b(arra)m(y)j(elemen)m(ts)g(will)f(b)s(e)f(returned)g +(with)h(a)p eop end +%%Page: 62 68 +TeXDict begin 62 67 bop 0 299 a Fi(62)1319 b Fg(CHAPTER)29 +b(6.)112 b(AD)m(V)-10 b(ANCED)32 b(INTERF)-10 b(A)m(CE)30 +b(SUBR)m(OUTINES)227 555 y Fi(v)-5 b(alue)26 b(=)g(n)m(ullv)-5 +b(al,)27 b(unless)e(n)m(ullv)-5 b(al)26 b(=)f(0)h(\(or)g(=)f(')h(')g +(for)f(ftgcvs\))i(in)e(whic)m(h)g(case)i(no)e(c)m(hec)m(king)j(for)d +(unde\014ned)227 668 y(v)-5 b(alues)28 b(will)g(b)s(e)f(p)s(erformed.) +39 b(The)27 b(ANYF)h(parameter)g(is)g(set)g(to)g(true)g(if)g(an)m(y)f +(of)h(the)g(returned)f(elemen)m(ts)227 781 y(are)f(unde\014ned.)37 +b(\(Note:)i(the)26 b(ftgcl)g(routine)f(simple)g(gets)h(an)g(arra)m(y)f +(of)g(logical)j(data)e(v)-5 b(alues)25 b(without)h(an)m(y)227 +894 y(c)m(hec)m(ks)39 b(for)e(unde\014ned)e(v)-5 b(alues;)41 +b(use)c(the)g(ftgc\015)h(routine)f(to)h(c)m(hec)m(k)g(for)f +(unde\014ned)e(logical)40 b(elemen)m(ts\).)227 1007 y(\(The)29 +b(SPP)f(FSGCVS)g(routine)g(has)h(an)f(additional)i(in)m(teger)g +(argumen)m(t)f(after)g(the)g(V)-10 b(ALUES)28 b(c)m(haracter)227 +1120 y(string)j(whic)m(h)f(sp)s(eci\014es)g(the)g(size)i(of)e(the)h +(1st)g(dimension)e(of)i(this)f(2-D)i(CHAR)e(arra)m(y\).)227 +1272 y(The)24 b(alternate)i(v)m(ersion)f(of)g(these)g(routines,)h +(whose)e(names)g(end)g(in)g('LL')h(after)g(the)g(datat)m(yp)s(e)g(c)m +(haracter,)227 1385 y(supp)s(ort)34 b(large)j(tables)f(with)g(more)f +(then)h(2*31)h(ro)m(ws.)57 b(When)35 b(calling)i(these)f(routines,)h +(the)f(fro)m(w)g(and)227 1498 y(felem)31 b(parameters)g(*m)m(ust*)g(b)s +(e)f(64-bit)h(in)m(teger*8)i(v)-5 b(ariables,)31 b(instead)g(of)g +(normal)f(4-b)m(yte)i(in)m(tegers.)382 1761 y Fe +(FTGCL\(unit,colnum,frow,f)o(ele)o(m,ne)o(leme)o(nts)o(,)42 +b(>)47 b(values,status\))382 1874 y(FTGCV[SBIJKEDCM]\(unit,co)o(lnu)o +(m,fr)o(ow,f)o(ele)o(m,ne)o(leme)o(nts)o(,nul)o(lval)o(,)42 +b(>)1098 1987 y(values,anyf,status\))382 2100 y +(FTGCV[BIJKEDCM]LL\(unit,c)o(oln)o(um,\()o(I*8\))f(frow,)46 +b(\(I*8\))h(felem,)f(nelements,)716 2213 y(nullval,)f(>)j +(values,anyf,status\))0 2476 y Fh(6)81 b Fi(Get)44 b(elemen)m(ts)h(and) +d(n)m(ull)i(\015ags)f(from)g(an)h(ASCI)s(I)d(or)j(binary)e(table)j +(column)e(\(in)g(the)h(CHDU\).)g(These)227 2589 y(routines)29 +b(return)e(the)i(v)-5 b(alues)29 b(of)g(the)g(table)h(column)e(arra)m +(y)i(elemen)m(ts.)41 b(An)m(y)29 b(unde\014ned)d(arra)m(y)k(elemen)m +(ts)227 2702 y(will)37 b(ha)m(v)m(e)h(the)f(corresp)s(onding)f(\015agv) +-5 b(als)37 b(elemen)m(t)i(set)e(equal)g(to)h(.TR)m(UE.)f(The)f(ANYF)i +(parameter)f(is)227 2815 y(set)30 b(to)g(true)g(if)f(an)m(y)h(of)f(the) +h(returned)e(elemen)m(ts)j(are)f(unde\014ned.)38 b(\(The)29 +b(SPP)f(FSGCFS)h(routine)h(has)f(an)227 2928 y(additional)e(in)m(teger) +h(argumen)m(t)e(after)h(the)f(V)-10 b(ALUES)26 b(c)m(haracter)i(string) +e(whic)m(h)f(sp)s(eci\014es)h(the)h(size)f(of)h(the)227 +3041 y(1st)k(dimension)f(of)h(this)f(2-D)h(CHAR)g(arra)m(y\).)227 +3193 y(The)24 b(alternate)i(v)m(ersion)f(of)g(these)g(routines,)h +(whose)e(names)g(end)g(in)g('LL')h(after)g(the)g(datat)m(yp)s(e)g(c)m +(haracter,)227 3306 y(supp)s(ort)34 b(large)j(tables)f(with)g(more)f +(then)h(2*31)h(ro)m(ws.)57 b(When)35 b(calling)i(these)f(routines,)h +(the)f(fro)m(w)g(and)227 3418 y(felem)31 b(parameters)g(*m)m(ust*)g(b)s +(e)f(64-bit)h(in)m(teger*8)i(v)-5 b(ariables,)31 b(instead)g(of)g +(normal)f(4-b)m(yte)i(in)m(tegers.)382 3682 y Fe +(FTGCF[SLBIJKEDCM]\(unit,c)o(oln)o(um,f)o(row,)o(fel)o(em,n)o(elem)o +(ent)o(s,)42 b(>)1193 3795 y(values,flagvals,anyf,stat)o(us\))382 +3908 y(FTGCF[BIJKED]LL\(unit,col)o(num)o(,)g(\(I*8\))k(frow,)h(\(I*8\)) +f(felem,nelements,)d(>)1193 4021 y(values,flagvals,anyf,stat)o(us\))0 +4284 y Fh(7)81 b Fi(Get)29 b(an)f(arbitrary)g(data)h(subsection)f(from) +g(an)g(N-dimensional)h(arra)m(y)g(in)f(a)g(binary)g(table)h(v)m(ector)h +(column.)227 4397 y(Unde\014ned)k(pixels)h(in)g(the)h(arra)m(y)g(will)f +(b)s(e)g(set)h(equal)f(to)h(the)g(v)-5 b(alue)36 b(of)f('n)m(ullv)-5 +b(al',)38 b(unless)c(n)m(ullv)-5 b(al=0)36 b(in)227 4510 +y(whic)m(h)d(case)i(no)e(testing)i(for)e(unde\014ned)e(pixels)j(will)f +(b)s(e)g(p)s(erformed.)49 b(The)32 b(\014rst)h(and)g(last)h(ro)m(ws)g +(in)f(the)227 4623 y(table)28 b(to)f(b)s(e)f(read)g(are)h(sp)s +(eci\014ed)e(b)m(y)i(fpixels\(naxis+1\))g(and)f(lpixels\(naxis+1\),)j +(and)d(hence)g(are)h(treated)227 4736 y(as)f(the)f(next)h(higher)f +(dimension)g(of)g(the)h(FITS)e(N-dimensional)i(arra)m(y)-8 +b(.)40 b(The)25 b(INCS)f(parameter)i(sp)s(eci\014es)227 +4848 y(the)31 b(sampling)f(in)m(terv)-5 b(al)32 b(in)e(eac)m(h)h +(dimension)f(b)s(et)m(w)m(een)h(the)g(data)g(elemen)m(ts)g(that)g(will) +g(b)s(e)f(returned.)382 5112 y Fe(FTGSV[BIJKED]\(unit,colnu)o(m,n)o +(axis)o(,nax)o(es,)o(fpix)o(els,)o(lpi)o(xels)o(,inc)o(s,n)o(ullv)o +(al,)41 b(>)1002 5225 y(array,anyf,status\))0 5488 y +Fh(8)81 b Fi(Get)29 b(an)f(arbitrary)g(data)h(subsection)f(from)g(an)g +(N-dimensional)h(arra)m(y)g(in)f(a)g(binary)g(table)h(v)m(ector)h +(column.)227 5601 y(An)m(y)39 b(Unde\014ned)e(pixels)i(in)g(the)g(arra) +m(y)g(will)g(ha)m(v)m(e)h(the)f(corresp)s(onding)f('\015agv)-5 +b(als')40 b(elemen)m(t)g(set)f(equal)227 5714 y(to)d(.TR)m(UE.)f(The)f +(\014rst)g(and)g(last)i(ro)m(ws)f(in)f(the)h(table)h(to)f(b)s(e)g(read) +f(are)h(sp)s(eci\014ed)f(b)m(y)h(fpixels\(naxis+1\))p +eop end +%%Page: 63 69 +TeXDict begin 63 68 bop 0 299 a Fg(6.7.)72 b(FITS)30 +b(ASCI)s(I)f(AND)i(BINAR)-8 b(Y)31 b(T)-8 b(ABLE)31 b(D)m(A)-8 +b(T)g(A)32 b(I/O)e(SUBR)m(OUTINES)979 b Fi(63)227 555 +y(and)39 b(lpixels\(naxis+1\),)k(and)38 b(hence)i(are)f(treated)i(as)e +(the)g(next)h(higher)f(dimension)f(of)i(the)f(FITS)g(N-)227 +668 y(dimensional)g(arra)m(y)-8 b(.)66 b(The)38 b(INCS)g(parameter)h +(sp)s(eci\014es)f(the)g(sampling)h(in)m(terv)-5 b(al)40 +b(in)e(eac)m(h)i(dimension)227 781 y(b)s(et)m(w)m(een)31 +b(the)g(data)g(elemen)m(ts)h(that)f(will)f(b)s(e)g(returned.)382 +1028 y Fe(FTGSF[BIJKED]\(unit,colnu)o(m,n)o(axis)o(,nax)o(es,)o(fpix)o +(els,)o(lpi)o(xels)o(,inc)o(s,)41 b(>)1002 1141 y +(array,flagvals,anyf,statu)o(s\))0 1389 y Fh(9)81 b Fi(Get)33 +b(bit)g(v)-5 b(alues)34 b(from)e(a)h(b)m(yte)h(\('B'\))g(or)f(bit)g +(\(`X`\))h(table)g(column)f(\(in)g(the)g(CDU\).)g(LRA)-8 +b(Y)34 b(is)f(an)f(arra)m(y)i(of)227 1502 y(logical)41 +b(v)-5 b(alues)39 b(corresp)s(onding)f(to)h(the)g(sequence)f(of)h(bits) +g(to)g(b)s(e)f(read.)65 b(If)38 b(LRA)-8 b(Y)39 b(is)f(true)h(then)f +(the)227 1615 y(corresp)s(onding)c(bit)g(w)m(as)g(set)h(to)g(1,)h +(otherwise)f(the)f(bit)h(w)m(as)f(set)h(to)g(0.)53 b(Note)35 +b(that)g(in)f(the)h(case)g(of)f('X')227 1728 y(columns,)41 +b(FITSIO)d(will)h(read)f(all)i(8)f(bits)g(of)g(eac)m(h)h(b)m(yte)f +(whether)f(they)h(are)g(formally)h(v)-5 b(alid)39 b(or)f(not.)227 +1840 y(Th)m(us)c(if)g(the)h(column)f(is)g(de\014ned)f(as)i('4X',)h(and) +d(one)i(calls)h(FTGCX)e(with)g(fbit=1)h(and)e(n)m(bit=8,)j(then)227 +1953 y(all)30 b(8)g(bits)f(will)g(b)s(e)g(read)g(from)g(the)g(\014rst)f +(b)m(yte)i(\(as)g(opp)s(osed)e(to)i(reading)f(the)h(\014rst)e(4)i(bits) +f(from)f(the)i(\014rst)227 2066 y(ro)m(w)g(and)e(then)h(the)h(\014rst)e +(4)i(bits)f(from)g(the)g(next)g(ro)m(w\),)i(ev)m(en)f(though)f(the)g +(last)h(4)g(bits)f(of)g(eac)m(h)i(b)m(yte)f(are)227 2179 +y(formally)h(not)g(de\014ned.)382 2427 y Fe(FTGCX\(unit,colnum,frow,f)o +(bit)o(,nbi)o(t,)42 b(>)47 b(lray,status\))0 2674 y Fh(10)f +Fi(Read)31 b(an)m(y)g(consecutiv)m(e)h(set)f(of)g(bits)g(from)f(an)g +('X')i(or)e('B')i(column)e(and)g(in)m(terpret)h(them)g(as)g(an)f +(unsigned)227 2787 y(n-bit)k(in)m(teger.)54 b(NBIT)35 +b(m)m(ust)f(b)s(e)f(less)i(than)f(or)g(equal)h(to)g(16)g(when)f +(calling)h(FTGCXI,)g(and)f(less)g(than)227 2900 y(or)e(equal)g(to)g(32) +g(when)e(calling)j(FTGCXJ;)f(there)f(is)h(no)f(limit)h(on)g(the)f(v)-5 +b(alue)32 b(of)g(NBIT)f(for)g(FTGCXD,)227 3013 y(but)38 +b(the)h(returned)e(double)i(precision)f(v)-5 b(alue)39 +b(only)g(has)f(48)i(bits)e(of)h(precision)g(on)f(most)h(32-bit)h(w)m +(ord)227 3126 y(mac)m(hines.)64 b(The)37 b(NBITS)g(bits)h(are)g(in)m +(terpreted)g(as)g(an)g(unsigned)e(in)m(teger)k(unless)d(NBITS)g(=)g(16) +i(\(in)227 3239 y(FTGCXI\))e(or)g(32)g(\(in)g(FTGCXJ\))f(in)g(whic)m(h) +h(case)g(the)g(string)g(of)f(bits)h(are)g(in)m(terpreted)f(as)h(16-bit) +h(or)227 3352 y(32-bit)j(2's)f(complemen)m(t)h(signed)e(in)m(tegers.)69 +b(If)39 b(NR)m(O)m(WS)i(is)e(greater)i(than)e(1)h(then)f(the)h(same)g +(set)g(of)227 3464 y(bits)34 b(will)g(b)s(e)f(read)h(from)f(sequen)m +(tial)i(ro)m(ws)f(in)f(the)h(table)g(starting)h(with)e(ro)m(w)h(FR)m(O) +m(W.)h(Note)g(that)g(the)227 3577 y(n)m(um)m(b)s(ering)27 +b(con)m(v)m(en)m(tion)j(used)d(here)g(for)h(the)g(FBIT)f(parameter)i +(adopts)e(1)h(for)g(the)g(\014rst)f(elemen)m(t)i(of)f(the)227 +3690 y(v)m(ector)k(of)f(bits;)f(this)h(is)f(the)h(Most)g(Signi\014can)m +(t)g(Bit)g(of)g(the)f(in)m(teger)i(v)-5 b(alue.)382 3938 +y Fe(FTGCX[IJD]\(unit,colnum,f)o(row)o(,nro)o(ws,f)o(bit)o(,nbi)o(t,)42 +b(>)47 b(array,status\))0 4185 y Fh(11)f Fi(Get)37 b(the)e(descriptor)h +(for)f(a)h(v)-5 b(ariable)37 b(length)f(column)f(in)g(a)h(binary)f +(table.)57 b(The)35 b(descriptor)h(consists)g(of)227 +4298 y(2)c(in)m(teger)g(parameters:)42 b(the)31 b(n)m(um)m(b)s(er)f(of) +h(elemen)m(ts)i(in)d(the)h(arra)m(y)h(and)e(the)h(starting)h(o\013set)g +(relativ)m(e)h(to)227 4411 y(the)28 b(start)f(of)g(the)h(heap.)39 +b(The)27 b(\014rst)f(routine)h(returns)f(a)h(single)h(descriptor)f +(whereas)g(the)g(second)g(routine)227 4524 y(returns)i(the)i +(descriptors)f(for)g(a)h(range)g(of)f(ro)m(ws)h(in)f(the)g(table.)382 +4771 y Fe(FTGDES\(unit,colnum,rownu)o(m,)41 b(>)48 b +(nelements,offset,status\))382 4884 y(FTGDESLL\(unit,colnum,row)o(num)o +(,)42 b(>)47 b(nelementsll,offsetll,statu)o(s\))382 5110 +y(FFGDESS\(unit,colnum,firs)o(tro)o(w,nr)o(ows)41 b(>)48 +b(nelements,offset,)43 b(status\))382 5223 y(FFGDESSLL\(unit,colnum,fi) +o(rst)o(row,)o(nrow)o(s)f(>)47 b(nelementsll,offsetll,)42 +b(status\))0 5470 y Fh(12)k Fi(W)-8 b(rite)33 b(the)f(descriptor)g(for) +f(a)i(v)-5 b(ariable)32 b(length)g(column)g(in)f(a)i(binary)e(table.)45 +b(These)32 b(subroutines)e(can)j(b)s(e)227 5583 y(used)f(in)h +(conjunction)g(with)g(FTGDES)g(to)g(enable)h(2)f(or)g(more)g(arra)m(ys) +h(to)f(p)s(oin)m(t)g(to)h(the)f(same)g(storage)227 5696 +y(lo)s(cation)f(to)f(sa)m(v)m(e)h(storage)g(space)f(if)f(the)h(arra)m +(ys)g(are)g(iden)m(tical.)p eop end +%%Page: 64 70 +TeXDict begin 64 69 bop 0 299 a Fi(64)1319 b Fg(CHAPTER)29 +b(6.)112 b(AD)m(V)-10 b(ANCED)32 b(INTERF)-10 b(A)m(CE)30 +b(SUBR)m(OUTINES)382 555 y Fe(FTPDES\(unit,colnum,rownu)o(m,n)o(elem)o +(ents)o(,of)o(fset)o(,)42 b(>)47 b(status\))382 668 y +(FTPDESLL\(unit,colnum,row)o(num)o(,nel)o(emen)o(tsl)o(l,of)o(fset)o +(ll,)41 b(>)48 b(status\))0 1001 y Fd(6.8)135 b(Ro)l(w)46 +b(Selection)g(and)f(Calculator)h(Routines)0 1252 y Fi(These)21 +b(routines)f(all)i(parse)f(and)f(ev)-5 b(aluate)23 b(an)d(input)g +(string)h(con)m(taining)i(a)e(user)f(de\014ned)g(arithmetic)i +(expression.)0 1365 y(The)29 b(\014rst)f(3)i(routines)f(select)i(ro)m +(ws)e(in)g(a)h(FITS)e(table,)j(based)e(on)g(whether)g(the)g(expression) +g(ev)-5 b(aluates)31 b(to)f(true)0 1477 y(\(not)e(equal)f(to)h(zero\))g +(or)f(false)h(\(zero\).)41 b(The)27 b(other)g(routines)g(ev)-5 +b(aluate)29 b(the)e(expression)g(and)f(calculate)k(a)d(v)-5 +b(alue)0 1590 y(for)35 b(eac)m(h)h(ro)m(w)g(of)f(the)h(table.)56 +b(The)35 b(allo)m(w)m(ed)i(expression)e(syn)m(tax)g(is)h(describ)s(ed)e +(in)h(the)g(ro)m(w)h(\014lter)f(section)h(in)0 1703 y(the)h(earlier)h +(`Extended)e(File)i(Name)f(Syn)m(tax')g(c)m(hapter)h(of)f(this)f(do)s +(cumen)m(t.)60 b(The)36 b(expression)h(ma)m(y)g(also)h(b)s(e)0 +1816 y(written)28 b(to)i(a)e(text)i(\014le,)f(and)f(the)h(name)f(of)h +(the)f(\014le,)h(prep)s(ended)e(with)h(a)h('@')f(c)m(haracter)i(ma)m(y) +f(b)s(e)f(supplied)f(for)0 1929 y(the)34 b('expr')g(parameter)g(\(e.g.) +53 b('@\014lename.txt'\).)f(The)34 b(expression)f(in)h(the)g(\014le)g +(can)g(b)s(e)f(arbitrarily)h(complex)0 2042 y(and)h(extend)h(o)m(v)m +(er)h(m)m(ultiple)f(lines)g(of)g(the)f(\014le.)57 b(Lines)36 +b(that)g(b)s(egin)f(with)g(2)h(slash)g(c)m(haracters)h(\('//'\))h(will) +e(b)s(e)0 2155 y(ignored)30 b(and)g(ma)m(y)h(b)s(e)f(used)g(to)h(add)e +(commen)m(ts)j(to)f(the)f(\014le.)0 2412 y Fh(1)81 b +Fi(Ev)-5 b(aluate)38 b(a)f(b)s(o)s(olean)g(expression)g(o)m(v)m(er)h +(the)g(indicated)f(ro)m(ws,)i(returning)d(an)h(arra)m(y)h(of)f(\015ags) +g(indicating)227 2525 y(whic)m(h)30 b(ro)m(ws)h(ev)-5 +b(aluated)31 b(to)g(TR)m(UE/F)-10 b(ALSE)430 2783 y Fe +(FTFROW\(unit,expr,firstr)o(ow,)41 b(nrows,)46 b(>)i(n_good_rows,)c +(row_status,)h(status\))0 3040 y Fh(2)81 b Fi(Find)29 +b(the)i(\014rst)f(ro)m(w)g(whic)m(h)g(satis\014es)h(the)g(input)e(b)s +(o)s(olean)h(expression)430 3298 y Fe(FTFFRW\(unit,)44 +b(expr,)i(>)i(rownum,)e(status\))0 3555 y Fh(3)81 b Fi(Ev)-5 +b(aluate)35 b(an)f(expression)h(on)f(all)h(ro)m(ws)g(of)f(a)h(table.)54 +b(If)34 b(the)g(input)g(and)g(output)g(\014les)g(are)h(not)g(the)f +(same,)227 3668 y(cop)m(y)i(the)g(TR)m(UE)f(ro)m(ws)g(to)h(the)f +(output)g(\014le;)j(if)d(the)g(output)g(table)h(is)f(not)h(empt)m(y)-8 +b(,)37 b(then)e(this)g(routine)227 3781 y(will)28 b(app)s(end)e(the)i +(new)f(selected)i(ro)m(ws)e(after)h(the)g(existing)h(ro)m(ws.)39 +b(If)27 b(the)h(\014les)g(are)f(the)h(same,)h(delete)g(the)227 +3894 y(F)-10 b(ALSE)30 b(ro)m(ws)h(\(preserv)m(e)f(the)h(TR)m(UE)f(ro)m +(ws\).)430 4151 y Fe(FTSROW\(inunit,)43 b(outunit,)j(expr,)g(>)i +(status\))0 4409 y Fh(4)81 b Fi(Calculate)28 b(an)f(expression)f(for)h +(the)f(indicated)i(ro)m(ws)e(of)h(a)g(table,)i(returning)d(the)h +(results,)g(cast)h(as)f(datat)m(yp)s(e)227 4522 y(\(TSHOR)-8 +b(T,)32 b(TDOUBLE,)h(etc\),)h(in)e(arra)m(y)-8 b(.)48 +b(If)31 b(n)m(ulv)-5 b(al==NULL,)33 b(UNDEFs)g(will)f(b)s(e)g(zero)s +(ed)g(out.)47 b(F)-8 b(or)227 4634 y(v)m(ector)37 b(results,)f(the)f(n) +m(um)m(b)s(er)e(of)i(elemen)m(ts)i(returned)c(ma)m(y)j(b)s(e)e(less)h +(than)g(nelemen)m(ts)g(if)g(nelemen)m(ts)h(is)227 4747 +y(not)30 b(an)g(ev)m(en)h(m)m(ultiple)f(of)g(the)g(result)g(dimension.) +40 b(Call)30 b(FTTEXP)g(to)g(obtain)h(the)f(dimensions)f(of)h(the)227 +4860 y(results.)430 5118 y Fe(FTCROW\(unit,datatype,ex)o(pr,)o(firs)o +(trow)o(,ne)o(leme)o(nts,)o(nul)o(val,)41 b(>)620 5231 +y(array,anynul,status\))0 5488 y Fh(5)81 b Fi(Ev)-5 b(aluate)33 +b(an)g(expression)f(and)h(write)f(the)h(result)g(either)g(to)h(a)f +(column)f(\(if)h(the)g(expression)f(is)h(a)g(function)227 +5601 y(of)d(other)g(columns)g(in)f(the)h(table\))h(or)f(to)g(a)h(k)m +(eyw)m(ord)f(\(if)g(the)g(expression)f(ev)-5 b(aluates)32 +b(to)e(a)g(constan)m(t)i(and)227 5714 y(is)f(not)f(a)h(function)f(of)h +(other)f(columns)h(in)f(the)g(table\).)42 b(In)30 b(the)h(former)e +(case,)j(the)f(parName)f(parameter)p eop end +%%Page: 65 71 +TeXDict begin 65 70 bop 0 299 a Fg(6.9.)72 b(CELESTIAL)29 +b(COORDINA)-8 b(TE)30 b(SYSTEM)f(SUBR)m(OUTINES)1307 +b Fi(65)227 555 y(is)40 b(the)g(name)f(of)h(the)g(column)f(\(whic)m(h)h +(ma)m(y)g(or)f(ma)m(y)h(not)g(already)g(exist\))h(in)m(to)f(whic)m(h)g +(to)g(write)g(the)227 668 y(results,)e(and)f(parInfo)e(con)m(tains)j +(an)f(optional)g(TF)m(ORM)g(k)m(eyw)m(ord)g(v)-5 b(alue)38 +b(if)e(a)h(new)f(column)h(is)f(b)s(eing)227 781 y(created.)42 +b(If)28 b(a)h(TF)m(ORM)h(v)-5 b(alue)29 b(is)g(not)g(sp)s(eci\014ed)g +(then)f(a)i(default)f(format)g(will)h(b)s(e)e(used,)h(dep)s(ending)e +(on)227 894 y(the)35 b(expression.)54 b(If)34 b(the)h(expression)f(ev) +-5 b(aluates)37 b(to)e(a)g(constan)m(t,)i(then)e(the)g(result)f(will)h +(b)s(e)f(written)h(to)227 1007 y(the)28 b(k)m(eyw)m(ord)g(name)f(giv)m +(en)h(b)m(y)g(the)f(parName)h(parameter,)h(and)d(the)i(parInfo)e +(parameter)i(ma)m(y)g(b)s(e)f(used)227 1120 y(to)k(supply)e(an)h +(optional)i(commen)m(t)f(for)f(the)g(k)m(eyw)m(ord.)42 +b(If)29 b(the)i(k)m(eyw)m(ord)g(do)s(es)f(not)g(already)h(exist,)g +(then)227 1233 y(the)f(name)f(of)h(the)g(k)m(eyw)m(ord)g(m)m(ust)f(b)s +(e)g(preceded)g(with)g(a)h('#')f(c)m(haracter,)j(otherwise)e(the)f +(result)h(will)g(b)s(e)227 1346 y(written)h(to)g(a)g(column)f(with)g +(that)h(name.)430 1605 y Fe(FTCALC\(inunit,)43 b(expr,)k(outunit,)e +(parName,)h(parInfo,)f(>)j(status\))0 1865 y Fh(6)81 +b Fi(This)38 b(calculator)k(routine)e(is)f(similar)h(to)g(the)g +(previous)f(routine,)j(except)f(that)f(the)g(expression)f(is)h(only)227 +1978 y(ev)-5 b(aluated)42 b(o)m(v)m(er)f(the)f(sp)s(eci\014ed)g(ro)m(w) +g(ranges.)70 b(nranges)39 b(sp)s(eci\014es)h(the)g(n)m(um)m(b)s(er)f +(of)h(ro)m(w)h(ranges,)i(and)227 2091 y(\014rstro)m(w)30 +b(and)g(lastro)m(w)h(giv)m(e)h(the)f(starting)g(and)f(ending)g(ro)m(w)g +(n)m(um)m(b)s(er)f(of)i(eac)m(h)g(range.)430 2350 y Fe +(FTCALC_RNG\(inunit,)42 b(expr,)47 b(outunit,)e(parName,)h(parInfo,)573 +2463 y(nranges,)f(firstrow,)h(lastrow,)f(>)j(status\))0 +2723 y Fh(7)81 b Fi(Ev)-5 b(aluate)36 b(the)f(giv)m(en)h(expression)f +(and)g(return)f(dimension)g(and)h(t)m(yp)s(e)g(information)g(on)g(the)h +(result.)54 b(The)227 2836 y(returned)37 b(dimensions)f(corresp)s(ond)g +(to)j(a)e(single)i(ro)m(w)e(en)m(try)h(of)f(the)h(requested)f +(expression,)j(and)d(are)227 2949 y(equiv)-5 b(alen)m(t)26 +b(to)f(the)g(result)f(of)g(\014ts)p 1380 2949 28 4 v +33 w(read)p 1585 2949 V 32 w(tdim\(\).)40 b(Note)25 b(that)g(strings)f +(are)h(considered)f(to)h(b)s(e)f(one)g(elemen)m(t)227 +3062 y(regardless)31 b(of)g(string)f(length.)41 b(If)30 +b(maxdim)g(==)g(0,)h(then)f(naxes)g(is)h(optional.)430 +3321 y Fe(FTTEXP\(unit,)44 b(expr,)i(maxdim)g(>)i(datatype,)d(nelem,)h +(naxis,)g(naxes,)g(status\))0 3655 y Fd(6.9)135 b(Celestial)48 +b(Co)t(ordinate)e(System)f(Subroutines)0 3905 y Fi(The)36 +b(FITS)g(comm)m(unit)m(y)h(has)f(adopted)h(a)g(set)g(of)g(k)m(eyw)m +(ord)g(con)m(v)m(en)m(tions)h(that)f(de\014ne)f(the)h(transformations)0 +4018 y(needed)30 b(to)i(con)m(v)m(ert)g(b)s(et)m(w)m(een)f(pixel)g(lo)s +(cations)h(in)e(an)h(image)h(and)e(the)g(corresp)s(onding)g(celestial)j +(co)s(ordinates)0 4131 y(on)25 b(the)h(sky)-8 b(,)27 +b(or)e(more)g(generally)-8 b(,)29 b(that)d(de\014ne)e(w)m(orld)h(co)s +(ordinates)i(that)e(are)h(to)g(b)s(e)f(asso)s(ciated)i(with)e(an)m(y)h +(pixel)0 4244 y(lo)s(cation)36 b(in)e(an)h(n-dimensional)f(FITS)g(arra) +m(y)-8 b(.)54 b(CFITSIO)33 b(is)h(distributed)g(with)g(a)h(couple)f(of) +h(self-con)m(tained)0 4357 y(W)-8 b(orld)28 b(Co)s(ordinate)f(System)f +(\(W)m(CS\))i(routines,)g(ho)m(w)m(ev)m(er,)h(these)f(routines)f(DO)g +(NOT)f(supp)s(ort)f(all)j(the)f(latest)0 4470 y(W)m(CS)38 +b(con)m(v)m(en)m(tions,)k(so)d(it)g(is)f(STR)m(ONGL)-8 +b(Y)38 b(RECOMMENDED)h(that)f(soft)m(w)m(are)i(dev)m(elop)s(ers)e(use)g +(a)h(more)0 4583 y(robust)30 b(external)h(W)m(CS)f(library)-8 +b(.)41 b(Sev)m(eral)31 b(recommended)f(libraries)h(are:)95 +4842 y Fe(WCSLIB)47 b(-)95 b(supported)45 b(by)i(Mark)g(Calabretta)95 +4955 y(WCSTools)f(-)h(supported)f(by)h(Doug)g(Mink)95 +5068 y(AST)g(library)f(-)i(developed)d(by)i(the)g(U.K.)g(Starlink)e +(project)0 5328 y Fi(More)30 b(information)f(ab)s(out)g(the)g(W)m(CS)g +(k)m(eyw)m(ord)h(con)m(v)m(en)m(tions)h(and)d(links)h(to)h(all)g(of)f +(these)g(W)m(CS)g(libraries)h(can)0 5441 y(b)s(e)g(found)f(on)h(the)h +(FITS)e(Supp)s(ort)g(O\016ce)h(w)m(eb)g(site)i(at)f(h)m +(ttp://\014ts.gsfc.nasa.go)m(v)j(under)29 b(the)h(W)m(CS)h(link.)0 +5601 y(The)i(functions)h(pro)m(vided)g(in)f(these)i(external)f(W)m(CS)g +(libraries)h(will)f(need)g(access)h(to)g(the)f(W)m(CS)g(information)0 +5714 y(con)m(tained)i(in)f(the)h(FITS)e(\014le)i(headers.)55 +b(One)35 b(con)m(v)m(enien)m(t)i(w)m(a)m(y)f(to)g(pass)f(this)g +(information)h(to)g(the)f(external)p eop end +%%Page: 66 72 +TeXDict begin 66 71 bop 0 299 a Fi(66)1319 b Fg(CHAPTER)29 +b(6.)112 b(AD)m(V)-10 b(ANCED)32 b(INTERF)-10 b(A)m(CE)30 +b(SUBR)m(OUTINES)0 555 y Fi(library)39 b(is)h(to)h(use)e(FITSIO)g(to)h +(cop)m(y)h(the)f(header)f(k)m(eyw)m(ords)h(in)m(to)h(one)f(long)g(c)m +(haracter)i(string,)g(and)d(then)0 668 y(pass)29 b(this)h(string)g(to)g +(an)g(in)m(terface)h(routine)f(in)g(the)g(external)g(library)g(that)g +(will)g(extract)i(the)e(necessary)g(W)m(CS)0 781 y(information)h +(\(e.g.,)h(see)f(the)f(astFitsChan)h(and)f(astPutCards)g(routines)g(in) +g(the)h(Starlink)f(AST)g(library\).)0 941 y(The)24 b(follo)m(wing)j +(FITSIO)c(routines)i(DO)g(NOT)f(supp)s(ort)f(the)i(more)g(recen)m(t)h +(W)m(CS)f(con)m(v)m(en)m(tions)i(that)f(ha)m(v)m(e)g(b)s(een)0 +1054 y(appro)m(v)m(ed)37 b(as)h(part)f(of)g(the)h(FITS)e(standard.)61 +b(Consequen)m(tly)-8 b(,)39 b(the)f(follo)m(wing)g(routines)g(ARE)f(NO) +m(W)h(DEP-)0 1167 y(RECA)-8 b(TED.)29 b(It)f(is)h(STR)m(ONGL)-8 +b(Y)28 b(RECOMMENDED)h(that)g(soft)m(w)m(are)h(dev)m(elop)s(ers)f(not)g +(use)f(these)h(routines,)0 1280 y(and)h(instead)g(use)g(an)h(external)g +(W)m(CS)f(library)-8 b(,)31 b(as)g(describ)s(ed)e(ab)s(o)m(v)m(e.)0 +1440 y(These)21 b(routines)g(are)g(included)f(mainly)h(for)g(bac)m(kw)m +(ard)g(compatibilit)m(y)j(with)c(existing)i(soft)m(w)m(are.)39 +b(They)21 b(supp)s(ort)0 1553 y(the)30 b(follo)m(wing)i(standard)d(map) +g(pro)5 b(jections:)41 b(-SIN,)30 b(-T)-8 b(AN,)31 b(-AR)m(C,)g(-NCP)-8 +b(,)30 b(-GLS,)g(-MER,)h(and)e(-AIT)h(\(these)0 1666 +y(are)f(the)g(legal)h(v)-5 b(alues)29 b(for)f(the)h(co)s(ordt)m(yp)s(e) +f(parameter\).)41 b(These)28 b(routines)h(are)g(based)f(on)g(similar)h +(functions)f(in)0 1779 y(Classic)j(AIPS.)f(All)h(the)g(angular)f(quan)m +(tities)i(are)f(giv)m(en)g(in)f(units)g(of)g(degrees.)0 +2048 y Fh(1)81 b Fi(Get)22 b(the)g(v)-5 b(alues)21 b(of)h(all)g(the)g +(standard)f(FITS)f(celestial)k(co)s(ordinate)f(system)e(k)m(eyw)m(ords) +h(from)f(the)h(header)f(of)h(a)227 2161 y(FITS)j(image)i(\(i.e.,)h(the) +d(primary)g(arra)m(y)h(or)f(an)h(image)g(extension\).)40 +b(These)26 b(v)-5 b(alues)25 b(ma)m(y)h(then)g(b)s(e)e(passed)227 +2274 y(to)39 b(the)e(subroutines)g(that)h(p)s(erform)e(the)i(co)s +(ordinate)g(transformations.)63 b(If)37 b(an)m(y)h(or)g(all)g(of)g(the) +g(W)m(CS)227 2387 y(k)m(eyw)m(ords)32 b(are)f(not)g(presen)m(t,)h(then) +f(default)g(v)-5 b(alues)31 b(will)h(b)s(e)e(returned.)41 +b(If)31 b(the)g(\014rst)g(co)s(ordinate)g(axis)h(is)227 +2500 y(the)d(declination-lik)m(e)j(co)s(ordinate,)e(then)e(this)g +(routine)h(will)g(sw)m(ap)f(them)h(so)g(that)g(the)g(longitudinal-lik)m +(e)227 2612 y(co)s(ordinate)i(is)g(returned)e(as)i(the)f(\014rst)g +(axis.)227 2767 y(If)35 b(the)h(\014le)f(uses)g(the)g(new)m(er)h('CDj)p +1454 2767 28 4 v 32 w(i')g(W)m(CS)f(transformation)h(matrix)g(k)m(eyw)m +(ords)f(instead)h(of)f(old)h(st)m(yle)227 2880 y('CDEL)-8 +b(Tn')37 b(and)f('CR)m(OT)-8 b(A2')38 b(k)m(eyw)m(ords,)h(then)e(this)f +(routine)h(will)g(calculate)j(and)c(return)g(the)h(v)-5 +b(alues)227 2993 y(of)33 b(the)g(equiv)-5 b(alen)m(t)35 +b(old-st)m(yle)f(k)m(eyw)m(ords.)49 b(Note)34 b(that)g(the)f(con)m(v)m +(ersion)h(from)e(the)i(new-st)m(yle)g(k)m(eyw)m(ords)227 +3106 y(to)e(the)f(old-st)m(yle)h(v)-5 b(alues)31 b(is)g(sometimes)g +(only)g(an)g(appro)m(ximation,)h(so)e(if)h(the)g(appro)m(ximation)h(is) +e(larger)227 3219 y(than)37 b(an)h(in)m(ternally)g(de\014ned)e +(threshold)h(lev)m(el,)k(then)c(CFITSIO)f(will)i(still)g(return)e(the)i +(appro)m(ximate)227 3332 y(W)m(CS)f(k)m(eyw)m(ord)g(v)-5 +b(alues,)39 b(but)d(will)h(also)h(return)d(with)i(status)g(=)f(506,)k +(to)e(w)m(arn)e(the)h(calling)h(program)227 3445 y(that)30 +b(appro)m(ximations)f(ha)m(v)m(e)h(b)s(een)e(made.)40 +b(It)29 b(is)g(then)f(up)g(to)h(the)g(calling)i(program)d(to)h(decide)h +(whether)227 3558 y(the)k(appro)m(ximations)g(are)g(su\016cien)m(tly)g +(accurate)i(for)d(the)h(particular)f(application,)j(or)e(whether)f +(more)227 3671 y(precise)e(W)m(CS)f(transformations)h(m)m(ust)f(b)s(e)g +(p)s(erformed)f(using)h(new-st)m(yle)h(W)m(CS)g(k)m(eyw)m(ords)f +(directly)-8 b(.)382 3940 y Fe(FTGICS\(unit,)44 b(>)k +(xrval,yrval,xrpix,yrpix)o(,xin)o(c,yi)o(nc,)o(rot,)o(coor)o(dty)o +(pe,s)o(tatu)o(s\))0 4209 y Fh(2)81 b Fi(Get)34 b(the)f(v)-5 +b(alues)33 b(of)g(all)h(the)f(standard)f(FITS)h(celestial)i(co)s +(ordinate)f(system)f(k)m(eyw)m(ords)g(from)g(the)g(header)227 +4322 y(of)j(a)h(FITS)e(table)h(where)g(the)g(X)g(and)f(Y)h(\(or)g(RA)g +(and)g(DEC)f(co)s(ordinates)i(are)f(stored)g(in)g(2)g(separate)227 +4435 y(columns)c(of)g(the)g(table.)46 b(These)31 b(v)-5 +b(alues)32 b(ma)m(y)h(then)e(b)s(e)h(passed)f(to)h(the)g(subroutines)f +(that)h(p)s(erform)f(the)227 4548 y(co)s(ordinate)g(transformations.) +382 4817 y Fe(FTGTCS\(unit,xcol,ycol,)42 b(>)716 4930 +y(xrval,yrval,xrpix,yrpix,)o(xinc)o(,yi)o(nc,r)o(ot,c)o(oor)o(dtyp)o +(e,st)o(atu)o(s\))0 5199 y Fh(3)81 b Fi(Calculate)42 +b(the)g(celestial)h(co)s(ordinate)f(corresp)s(onding)e(to)i(the)f +(input)f(X)h(and)g(Y)g(pixel)g(lo)s(cation)i(in)e(the)227 +5312 y(image.)382 5581 y Fe(FTWLDP\(xpix,ypix,xrval,y)o(rva)o(l,xr)o +(pix,)o(yrp)o(ix,x)o(inc,)o(yin)o(c,ro)o(t,)1241 5694 +y(coordtype,)k(>)i(xpos,ypos,status\))p eop end +%%Page: 67 73 +TeXDict begin 67 72 bop 0 299 a Fg(6.10.)73 b(FILE)30 +b(CHECKSUM)f(SUBR)m(OUTINES)2080 b Fi(67)0 555 y Fh(4)81 +b Fi(Calculate)42 b(the)g(X)f(and)f(Y)h(pixel)h(lo)s(cation)g(corresp)s +(onding)e(to)i(the)f(input)f(celestial)k(co)s(ordinate)e(in)f(the)227 +668 y(image.)382 932 y Fe(FTXYPX\(xpos,ypos,xrval,y)o(rva)o(l,xr)o +(pix,)o(yrp)o(ix,x)o(inc,)o(yin)o(c,ro)o(t,)1241 1045 +y(coordtype,)k(>)i(xpix,ypix,status\))0 1383 y Fd(6.10)136 +b(File)45 b(Chec)l(ksum)g(Subroutines)0 1634 y Fi(The)33 +b(follo)m(wing)h(routines)f(either)h(compute)f(or)h(v)-5 +b(alidate)34 b(the)g(c)m(hec)m(ksums)f(for)g(the)h(CHDU.)g(The)e(D)m(A) +-8 b(T)g(ASUM)0 1747 y(k)m(eyw)m(ord)33 b(is)f(used)f(to)i(store)f(the) +h(n)m(umerical)f(v)-5 b(alue)33 b(of)f(the)g(32-bit,)i(1's)f(complemen) +m(t)g(c)m(hec)m(ksum)g(for)f(the)g(data)0 1860 y(unit)26 +b(alone.)40 b(If)25 b(there)h(is)h(no)e(data)i(unit)f(then)f(the)h(v)-5 +b(alue)27 b(is)f(set)g(to)h(zero.)40 b(The)26 b(n)m(umerical)g(v)-5 +b(alue)27 b(is)f(stored)g(as)g(an)0 1973 y(ASCI)s(I)20 +b(string)i(of)h(digits,)h(enclosed)f(in)e(quotes,)k(b)s(ecause)d(the)g +(v)-5 b(alue)23 b(ma)m(y)f(b)s(e)f(to)s(o)i(large)g(to)g(represen)m(t)f +(as)g(a)h(32-bit)0 2086 y(signed)28 b(in)m(teger.)41 +b(The)27 b(CHECKSUM)g(k)m(eyw)m(ord)i(is)f(used)f(to)h(store)h(the)f +(ASCI)s(I)e(enco)s(ded)i(COMPLEMENT)f(of)0 2199 y(the)f(c)m(hec)m(ksum) +h(for)f(the)h(en)m(tire)g(HDU.)g(Storing)f(the)h(complemen)m(t,)h +(rather)e(than)g(the)h(actual)g(c)m(hec)m(ksum,)h(forces)0 +2312 y(the)k(c)m(hec)m(ksum)h(for)f(the)h(whole)f(HDU)h(to)g(equal)g +(zero.)47 b(If)31 b(the)i(\014le)f(has)g(b)s(een)f(mo)s(di\014ed)g +(since)i(the)f(c)m(hec)m(ksums)0 2425 y(w)m(ere)39 b(computed,)i(then)e +(the)g(HDU)g(c)m(hec)m(ksum)h(will)f(usually)f(not)h(equal)h(zero.)66 +b(These)39 b(c)m(hec)m(ksum)g(k)m(eyw)m(ord)0 2538 y(con)m(v)m(en)m +(tions)34 b(are)f(based)f(on)g(a)g(pap)s(er)f(b)m(y)h(Rob)g(Seaman)g +(published)f(in)h(the)g(pro)s(ceedings)g(of)g(the)h(AD)m(ASS)f(IV)0 +2651 y(conference)f(in)f(Baltimore)i(in)f(No)m(v)m(em)m(b)s(er)g(1994)h +(and)e(a)h(later)g(revision)g(in)f(June)f(1995.)0 2914 +y Fh(1)81 b Fi(Compute)33 b(and)g(write)h(the)g(D)m(A)-8 +b(T)g(ASUM)35 b(and)e(CHECKSUM)g(k)m(eyw)m(ord)h(v)-5 +b(alues)34 b(for)f(the)h(CHDU)g(in)m(to)h(the)227 3027 +y(curren)m(t)25 b(header.)38 b(The)24 b(D)m(A)-8 b(T)g(ASUM)27 +b(v)-5 b(alue)25 b(is)f(the)h(32-bit)h(c)m(hec)m(ksum)f(for)f(the)h +(data)g(unit,)h(expressed)e(as)h(a)227 3140 y(decimal)32 +b(in)m(teger)f(enclosed)g(in)f(single)h(quotes.)41 b(The)30 +b(CHECKSUM)g(k)m(eyw)m(ord)g(v)-5 b(alue)31 b(is)f(a)h(16-c)m(haracter) +227 3253 y(string)j(whic)m(h)f(is)h(the)f(ASCI)s(I-enco)s(ded)f(v)-5 +b(alue)34 b(for)g(the)f(complemen)m(t)i(of)f(the)f(c)m(hec)m(ksum)i +(for)e(the)h(whole)227 3366 y(HDU.)h(If)e(these)g(k)m(eyw)m(ords)h +(already)g(exist,)h(their)e(v)-5 b(alues)34 b(will)g(b)s(e)f(up)s +(dated)f(only)h(if)g(necessary)h(\(i.e.,)i(if)227 3479 +y(the)31 b(\014le)f(has)g(b)s(een)g(mo)s(di\014ed)f(since)i(the)g +(original)g(k)m(eyw)m(ord)g(v)-5 b(alues)31 b(w)m(ere)g(computed\).)382 +3743 y Fe(FTPCKS\(unit,)44 b(>)k(status\))0 4007 y Fh(2)81 +b Fi(Up)s(date)28 b(the)h(CHECKSUM)e(k)m(eyw)m(ord)i(v)-5 +b(alue)29 b(in)f(the)h(CHDU,)g(assuming)f(that)h(the)f(D)m(A)-8 +b(T)g(ASUM)30 b(k)m(eyw)m(ord)227 4119 y(exists)36 b(and)f(already)h +(has)f(the)h(correct)g(v)-5 b(alue.)56 b(This)35 b(routine)g +(calculates)j(the)e(new)f(c)m(hec)m(ksum)h(for)f(the)227 +4232 y(curren)m(t)40 b(header)g(unit,)j(adds)c(it)i(to)g(the)f(data)h +(unit)f(c)m(hec)m(ksum,)k(enco)s(des)c(the)g(v)-5 b(alue)41 +b(in)m(to)g(an)f(ASCI)s(I)227 4345 y(string,)31 b(and)f(writes)g(the)h +(string)f(to)h(the)g(CHECKSUM)e(k)m(eyw)m(ord.)382 4609 +y Fe(FTUCKS\(unit,)44 b(>)k(status\))0 4873 y Fh(3)81 +b Fi(V)-8 b(erify)35 b(the)f(CHDU)h(b)m(y)g(computing)f(the)h(c)m(hec)m +(ksums)g(and)f(comparing)h(them)f(with)g(the)h(k)m(eyw)m(ords.)53 +b(The)227 4986 y(data)34 b(unit)f(is)g(v)m(eri\014ed)g(correctly)h(if)f +(the)h(computed)f(c)m(hec)m(ksum)g(equals)h(the)f(v)-5 +b(alue)34 b(of)f(the)g(D)m(A)-8 b(T)g(ASUM)227 5099 y(k)m(eyw)m(ord.)64 +b(The)37 b(c)m(hec)m(ksum)i(for)f(the)g(en)m(tire)g(HDU)h(\(header)f +(plus)f(data)i(unit\))e(is)h(correct)h(if)f(it)h(equals)227 +5212 y(zero.)55 b(The)34 b(output)g(D)m(A)-8 b(T)g(A)m(OK)37 +b(and)d(HDUOK)h(parameters)g(in)f(this)h(subroutine)e(are)i(in)m +(tegers)h(whic)m(h)227 5325 y(will)27 b(ha)m(v)m(e)g(a)f(v)-5 +b(alue)27 b(=)f(1)g(if)g(the)h(data)f(or)g(HDU)h(is)f(v)m(eri\014ed)h +(correctly)-8 b(,)29 b(a)d(v)-5 b(alue)27 b(=)e(0)i(if)f(the)g(D)m(A)-8 +b(T)g(ASUM)28 b(or)227 5437 y(CHECKSUM)h(k)m(eyw)m(ord)g(is)h(not)f +(presen)m(t,)h(or)f(v)-5 b(alue)30 b(=)f(-1)h(if)f(the)h(computed)f(c)m +(hec)m(ksum)h(is)f(not)h(correct.)382 5701 y Fe(FTVCKS\(unit,)44 +b(>)k(dataok,hduok,status\))p eop end +%%Page: 68 74 +TeXDict begin 68 73 bop 0 299 a Fi(68)1319 b Fg(CHAPTER)29 +b(6.)112 b(AD)m(V)-10 b(ANCED)32 b(INTERF)-10 b(A)m(CE)30 +b(SUBR)m(OUTINES)0 555 y Fh(4)81 b Fi(Compute)25 b(and)h(return)f(the)i +(c)m(hec)m(ksum)g(v)-5 b(alues)26 b(for)g(the)h(CHDU)f(\(as)h(double)f +(precision)h(v)-5 b(ariables\))27 b(without)227 668 y(creating)46 +b(or)e(mo)s(difying)g(the)h(CHECKSUM)e(and)h(D)m(A)-8 +b(T)g(ASUM)46 b(k)m(eyw)m(ords.)83 b(This)44 b(routine)g(is)h(used)227 +781 y(in)m(ternally)32 b(b)m(y)e(FTV)m(CKS,)g(but)g(ma)m(y)h(b)s(e)e +(useful)h(in)g(other)h(situations)g(as)f(w)m(ell.)382 +1050 y Fe(FTGCKS\(unit,)44 b(>)k(datasum,hdusum,status\))0 +1319 y Fh(5)81 b Fi(Enco)s(de)33 b(a)h(c)m(hec)m(ksum)h(v)-5 +b(alue)34 b(\(stored)g(in)g(a)g(double)g(precision)g(v)-5 +b(ariable\))35 b(in)m(to)f(a)h(16-c)m(haracter)h(string.)51 +b(If)227 1432 y(COMPLEMENT)30 b(=)g(.true.)41 b(then)30 +b(the)g(32-bit)i(sum)d(v)-5 b(alue)31 b(will)g(b)s(e)f(complemen)m(ted) +h(b)s(efore)f(enco)s(ding.)382 1701 y Fe(FTESUM\(sum,complement,)42 +b(>)47 b(checksum\))0 1971 y Fh(6)81 b Fi(Deco)s(de)39 +b(a)f(16)h(c)m(haracter)h(c)m(hec)m(ksum)e(string)g(in)m(to)h(a)g +(double)e(precision)h(v)-5 b(alue.)65 b(If)37 b(COMPLEMENT)g(=)227 +2083 y(.true.)k(then)30 b(the)h(32-bit)g(sum)f(v)-5 b(alue)31 +b(will)f(b)s(e)g(complemen)m(ted)i(after)e(deco)s(ding.)382 +2353 y Fe(FTDSUM\(checksum,compleme)o(nt,)41 b(>)48 b(sum\))0 +2697 y Fd(6.11)180 b(Date)46 b(and)f(Time)g(Utilit)l(y)i(Routines)0 +2950 y Fi(The)29 b(follo)m(wing)i(routines)f(help)f(to)i(construct)f +(or)f(parse)h(the)g(FITS)f(date/time)i(strings.)41 b(Starting)30 +b(in)f(the)h(y)m(ear)0 3063 y(2000,)k(the)d(FITS)g(D)m(A)-8 +b(TE)32 b(k)m(eyw)m(ord)g(v)-5 b(alues)31 b(\(and)h(the)f(v)-5 +b(alues)32 b(of)f(other)h(`D)m(A)-8 b(TE-')33 b(k)m(eyw)m(ords\))f(m)m +(ust)f(ha)m(v)m(e)i(the)0 3176 y(form)j('YYYY-MM-DD')k(\(date)e(only\)) +f(or)g('YYYY-MM-DDThh:mm:ss.ddd...')61 b(\(date)38 b(and)e(time\))h +(where)0 3288 y(the)30 b(n)m(um)m(b)s(er)f(of)i(decimal)g(places)g(in)f +(the)g(seconds)g(v)-5 b(alue)31 b(is)f(optional.)42 b(These)30 +b(times)h(are)f(in)g(UTC.)g(The)g(older)0 3401 y('dd/mm/yy')g(date)h +(format)g(ma)m(y)g(not)g(b)s(e)e(used)h(for)g(dates)h(after)g(01)g(Jan) +m(uary)f(2000.)0 3670 y Fh(1)81 b Fi(Get)31 b(the)g(curren)m(t)f +(system)g(date.)42 b(The)29 b(returned)h(y)m(ear)h(has)f(4)h(digits)g +(\(1999,)h(2000,)h(etc.\))382 3940 y Fe(FTGSDT\()46 b(>)h(day,)g +(month,)f(year,)g(status)g(\))0 4209 y Fh(2)81 b Fi(Get)34 +b(the)g(curren)m(t)g(system)f(date)i(and)e(time)h(string)g +(\('YYYY-MM-DDThh:mm:ss'\).)53 b(The)33 b(time)i(will)f(b)s(e)227 +4322 y(in)26 b(UTC/GMT)g(if)g(a)m(v)-5 b(ailable,)29 +b(as)e(indicated)f(b)m(y)g(a)g(returned)f(timeref)h(v)-5 +b(alue)27 b(=)e(0.)40 b(If)26 b(the)g(returned)e(v)-5 +b(alue)227 4435 y(of)31 b(timeref)g(=)g(1)g(then)f(this)h(indicates)g +(that)h(it)f(w)m(as)g(not)g(p)s(ossible)f(to)h(con)m(v)m(ert)i(the)d +(lo)s(cal)i(time)g(to)f(UTC,)227 4547 y(and)f(th)m(us)g(the)h(lo)s(cal) +g(time)g(w)m(as)g(returned.)382 4817 y Fe(FTGSTM\(>)45 +b(datestr,)h(timeref,)f(status\))0 5086 y Fh(3)81 b Fi(Construct)26 +b(a)i(date)g(string)f(from)g(the)g(input)f(date)i(v)-5 +b(alues.)40 b(If)27 b(the)g(y)m(ear)h(is)g(b)s(et)m(w)m(een)f(1900)i +(and)e(1998,)j(inclu-)227 5199 y(siv)m(e,)38 b(then)c(the)i(returned)d +(date)j(string)f(will)g(ha)m(v)m(e)i(the)e(old)g(FITS)f(format)i +(\('dd/mm/yy'\),)h(otherwise)227 5312 y(the)32 b(date)g(string)f(will)g +(ha)m(v)m(e)i(the)e(new)g(FITS)g(format)g(\('YYYY-MM-DD'\).)36 +b(Use)c(FTTM2S)f(instead)g(to)227 5425 y(alw)m(a)m(ys)h(return)d(a)i +(date)g(string)g(using)e(the)i(new)f(FITS)g(format.)382 +5694 y Fe(FTDT2S\()46 b(year,)g(month,)g(day,)h(>)g(datestr,)f +(status\))p eop end +%%Page: 69 75 +TeXDict begin 69 74 bop 0 299 a Fg(6.12.)73 b(GENERAL)30 +b(UTILITY)g(SUBR)m(OUTINES)1979 b Fi(69)0 555 y Fh(4)81 +b Fi(Construct)34 b(a)i(new-format)f(date)h(+)f(time)h(string)f +(\('YYYY-MM-DDThh:mm:ss.ddd...'\).)57 b(If)34 b(the)i(y)m(ear,)227 +668 y(mon)m(th,)d(and)e(da)m(y)h(v)-5 b(alues)32 b(all)h(=)e(0)h(then)g +(only)g(the)g(time)g(is)g(enco)s(ded)f(with)h(format)g +('hh:mm:ss.ddd...'.)227 781 y(The)j(decimals)h(parameter)g(sp)s +(eci\014es)e(ho)m(w)i(man)m(y)f(decimal)h(places)g(of)f(fractional)i +(seconds)e(to)h(include)227 894 y(in)30 b(the)h(string.)41 +b(If)29 b(`decimals')j(is)f(negativ)m(e,)h(then)f(only)f(the)h(date)g +(will)f(b)s(e)g(return)f(\('YYYY-MM-DD'\).)382 1154 y +Fe(FTTM2S\()46 b(year,)g(month,)g(day,)h(hour,)f(minute,)g(second,)g +(decimals,)764 1267 y(>)h(datestr,)f(status\))0 1527 +y Fh(5)81 b Fi(Return)44 b(the)g(date)i(as)f(read)f(from)h(the)g(input) +e(string,)49 b(where)44 b(the)h(string)g(ma)m(y)g(b)s(e)f(in)h(either)g +(the)g(old)227 1640 y(\('dd/mm/yy'\))31 b(or)g(new)e +(\('YYYY-MM-DDThh:mm:ss')k(or)d('YYYY-MM-DD'\))k(FITS)c(format.)382 +1900 y Fe(FTS2DT\(datestr,)43 b(>)48 b(year,)e(month,)g(day,)h +(status\))0 2160 y Fh(6)81 b Fi(Return)30 b(the)h(date)h(and)f(time)h +(as)f(read)g(from)g(the)h(input)e(string,)h(where)g(the)h(string)f(ma)m +(y)h(b)s(e)e(in)h(either)h(the)227 2273 y(old)d(or)f(new)g(FITS)g +(format.)40 b(The)28 b(returned)f(hours,)h(min)m(utes,)h(and)f(seconds) +g(v)-5 b(alues)29 b(will)f(b)s(e)g(set)h(to)g(zero)227 +2386 y(if)k(the)h(input)e(string)h(do)s(es)g(not)h(include)f(the)g +(time)h(\('dd/mm/yy')f(or)h('YYYY-MM-DD'\))j(.)c(Similarly)-8 +b(,)227 2499 y(the)36 b(returned)e(y)m(ear,)j(mon)m(th,)g(and)d(date)i +(v)-5 b(alues)36 b(will)f(b)s(e)g(set)h(to)g(zero)g(if)f(the)g(date)h +(is)f(not)h(included)e(in)227 2612 y(the)d(input)e(string)i +(\('hh:mm:ss.ddd...'\).)382 2872 y Fe(FTS2TM\(datestr,)43 +b(>)48 b(year,)e(month,)g(day,)h(hour,)f(minute,)g(second,)g(status\))0 +3206 y Fd(6.12)136 b(General)45 b(Utilit)l(y)i(Subroutines)0 +3456 y Fi(The)30 b(follo)m(wing)i(utilit)m(y)f(subroutines)f(ma)m(y)h +(b)s(e)e(useful)h(for)g(certain)h(applications:)0 3716 +y Fh(1)81 b Fi(Return)29 b(the)i(starting)g(b)m(yte)g(address)e(of)i +(the)f(CHDU)h(and)f(the)h(next)f(HDU.)382 3976 y Fe(FTGHAD\(iunit,)44 +b(>)j(curaddr,)f(nextaddr\))0 4236 y Fh(2)81 b Fi(Con)m(v)m(ert)31 +b(a)g(c)m(haracter)h(string)e(to)h(upp)s(ercase)e(\(op)s(erates)j(in)e +(place\).)382 4496 y Fe(FTUPCH\(string\))0 4756 y Fh(3)81 +b Fi(Compare)43 b(the)i(input)e(template)i(string)f(against)h(the)g +(reference)f(string)g(to)h(see)g(if)f(they)g(matc)m(h.)82 +b(The)227 4869 y(template)36 b(string)f(ma)m(y)g(con)m(tain)g(wildcard) +f(c)m(haracters:)51 b('*')35 b(will)g(matc)m(h)g(an)m(y)g(sequence)g +(of)f(c)m(haracters)227 4982 y(\(including)j(zero)h(c)m(haracters\))g +(and)e(')10 b(?')60 b(will)38 b(matc)m(h)f(an)m(y)g(single)h(c)m +(haracter)g(in)f(the)g(reference)g(string.)227 5095 y(The)31 +b('#')g(c)m(haracter)i(will)f(matc)m(h)g(an)m(y)f(consecutiv)m(e)j +(string)d(of)g(decimal)h(digits)g(\(0)g(-)g(9\).)43 b(If)31 +b(CASESN)f(=)227 5208 y(.true.)45 b(then)31 b(the)g(matc)m(h)i(will)f +(b)s(e)f(case)h(sensitiv)m(e.)46 b(The)31 b(returned)f(MA)-8 +b(TCH)32 b(parameter)g(will)g(b)s(e)f(.true.)227 5321 +y(if)j(the)h(2)f(strings)g(matc)m(h,)j(and)c(EXA)m(CT)h(will)h(b)s(e)e +(.true.)53 b(if)34 b(the)g(matc)m(h)h(is)g(exact)g(\(i.e.,)i(if)d(no)g +(wildcard)227 5434 y(c)m(haracters)e(w)m(ere)f(used)f(in)g(the)g(matc)m +(h\).)42 b(Both)31 b(strings)f(m)m(ust)h(b)s(e)e(68)j(c)m(haracters)f +(or)g(less)f(in)g(length.)382 5694 y Fe(FTCMPS\(str_template,)42 +b(string,)k(casesen,)f(>)j(match,)e(exact\))p eop end +%%Page: 70 76 +TeXDict begin 70 75 bop 0 299 a Fi(70)1319 b Fg(CHAPTER)29 +b(6.)112 b(AD)m(V)-10 b(ANCED)32 b(INTERF)-10 b(A)m(CE)30 +b(SUBR)m(OUTINES)0 555 y Fh(4)81 b Fi(T)-8 b(est)31 b(that)g(the)f(k)m +(eyw)m(ord)h(name)f(con)m(tains)i(only)e(legal)j(c)m(haracters:)42 +b(A-Z,0-9,)32 b(h)m(yphen,)d(and)h(underscore.)382 820 +y Fe(FTTKEY\(keyword,)43 b(>)48 b(status\))0 1085 y Fh(5)81 +b Fi(T)-8 b(est)31 b(that)g(the)f(k)m(eyw)m(ord)h(record)f(con)m(tains) +i(only)e(legal)i(prin)m(table)f(ASCI)s(I)e(c)m(haracters)382 +1350 y Fe(FTTREC\(card,)44 b(>)k(status\))0 1615 y Fh(6)81 +b Fi(T)-8 b(est)25 b(whether)f(the)h(curren)m(t)f(header)h(con)m(tains) +g(an)m(y)g(NULL)g(\(ASCI)s(I)e(0\))j(c)m(haracters.)40 +b(These)24 b(c)m(haracters)j(are)227 1728 y(illegal)37 +b(in)d(the)h(header,)g(but)f(they)g(will)h(go)g(undetected)g(b)m(y)f +(most)h(of)g(the)f(CFITSIO)f(k)m(eyw)m(ord)i(header)227 +1841 y(routines,)29 b(b)s(ecause)f(the)h(n)m(ull)f(is)g(in)m(terpreted) +g(as)h(the)f(normal)g(end-of-string)h(terminator.)41 +b(This)27 b(routine)227 1954 y(returns)h(the)g(p)s(osition)h(of)g(the)g +(\014rst)f(n)m(ull)g(c)m(haracter)i(in)f(the)f(header,)h(or)g(zero)g +(if)g(there)g(are)g(no)f(n)m(ulls.)40 b(F)-8 b(or)227 +2067 y(example)37 b(a)f(returned)f(v)-5 b(alue)37 b(of)f(110)h(w)m +(ould)f(indicate)h(that)g(the)f(\014rst)f(NULL)h(is)g(lo)s(cated)h(in)f +(the)g(30th)227 2180 y(c)m(haracter)28 b(of)f(the)g(second)f(k)m(eyw)m +(ord)h(in)f(the)h(header)f(\(recall)i(that)f(eac)m(h)h(header)e(record) +h(is)f(80)h(c)m(haracters)227 2293 y(long\).)56 b(Note)36 +b(that)g(this)f(is)g(one)g(of)g(the)g(few)g(FITSIO)f(routines)h(in)f +(whic)m(h)h(the)g(returned)f(v)-5 b(alue)36 b(is)f(not)227 +2406 y(necessarily)d(equal)e(to)i(the)e(status)h(v)-5 +b(alue\).)382 2671 y Fe(FTNCHK\(unit,)44 b(>)k(status\))0 +2935 y Fh(7)81 b Fi(P)m(arse)27 b(a)f(header)h(k)m(eyw)m(ord)g(record)f +(and)g(return)f(the)i(name)f(of)h(the)f(k)m(eyw)m(ord)h(and)f(the)h +(length)f(of)h(the)g(name.)227 3048 y(The)34 b(k)m(eyw)m(ord)h(name)f +(normally)h(o)s(ccupies)f(the)h(\014rst)e(8)i(c)m(haracters)g(of)g(the) +f(record,)i(except)f(under)e(the)227 3161 y(HIERAR)m(CH)e(con)m(v)m(en) +m(tion)h(where)e(the)h(name)f(can)h(b)s(e)f(up)f(to)i(70)g(c)m +(haracters)h(in)e(length.)382 3426 y Fe(FTGKNM\(card,)44 +b(>)k(keyname,)d(keylength,)g(staThe)h('\\#')h(character)e(will)i +(match)f(any)h(consecutive)e(string)191 3539 y(of)i(decimal)f(digits)g +(\(0)h(-)h(9\).)f(tus\))0 3804 y Fh(8)81 b Fi(P)m(arse)34 +b(a)h(header)f(k)m(eyw)m(ord)h(record.)52 b(This)33 b(subroutine)g +(parses)h(the)g(input)g(header)g(record)g(to)h(return)e(the)227 +3917 y(v)-5 b(alue)27 b(\(as)g(a)g(c)m(haracter)g(string\))g(and)f +(commen)m(t)h(strings.)39 b(If)26 b(the)g(k)m(eyw)m(ord)h(has)f(no)g(v) +-5 b(alue)27 b(\(columns)f(9-10)227 4030 y(not)h(equal)f(to)h('=)f +('\),)i(then)e(the)g(v)-5 b(alue)27 b(string)f(is)g(returned)f(blank)h +(and)f(the)h(commen)m(t)i(string)e(is)g(set)g(equal)227 +4143 y(to)31 b(column)g(9)f(-)h(80)g(of)g(the)f(input)g(string.)382 +4408 y Fe(FTPSVC\(card,)44 b(>)k(value,comment,status\))0 +4673 y Fh(9)81 b Fi(Construct)35 b(a)i(sequence)f(k)m(eyw)m(ord)h(name) +f(\(R)m(OOT)g(+)g(nnn\).)57 b(This)35 b(subroutine)g(app)s(ends)f(the)j +(sequence)227 4786 y(n)m(um)m(b)s(er)29 b(to)i(the)g(ro)s(ot)g(string)f +(to)h(create)h(a)f(k)m(eyw)m(ord)g(name)f(\(e.g.,)i('NAXIS')f(+)f(2)h +(=)f('NAXIS2'\))382 5051 y Fe(FTKEYN\(keyroot,seq_no,)42 +b(>)47 b(keyword,status\))0 5316 y Fh(10)f Fi(Construct)30 +b(a)g(sequence)g(k)m(eyw)m(ord)h(name)f(\(n)f(+)h(R)m(OOT\).)g(This)f +(subroutine)g(concatenates)j(the)f(sequence)227 5429 +y(n)m(um)m(b)s(er)20 b(to)j(the)e(fron)m(t)h(of)g(the)f(ro)s(ot)h +(string)g(to)g(create)h(a)f(k)m(eyw)m(ord)g(name)g(\(e.g.,)j(1)d(+)f +('CTYP')g(=)g('1CTYP'\))382 5694 y Fe(FTNKEY\(seq_no,keyroot,)42 +b(>)47 b(keyword,status\))p eop end +%%Page: 71 77 +TeXDict begin 71 76 bop 0 299 a Fg(6.12.)73 b(GENERAL)30 +b(UTILITY)g(SUBR)m(OUTINES)1979 b Fi(71)0 555 y Fh(11)46 +b Fi(Determine)35 b(the)f(datat)m(yp)s(e)g(of)g(a)g(k)m(eyw)m(ord)h(v) +-5 b(alue)34 b(string.)50 b(This)33 b(subroutine)g(parses)g(the)h(k)m +(eyw)m(ord)g(v)-5 b(alue)227 668 y(string)31 b(\(usually)f(columns)g +(11-30)j(of)d(the)h(header)f(record\))g(to)i(determine)e(its)h(datat)m +(yp)s(e.)382 936 y Fe(FTDTYP\(value,)44 b(>)j(dtype,status\))0 +1204 y Fh(11)f Fi(Return)c(the)i(class)g(of)f(input)f(header)h(record.) +79 b(The)43 b(record)g(is)g(classi\014ed)g(in)m(to)h(one)g(of)f(the)g +(follo)m(wing)227 1317 y(categories)36 b(\(the)e(class)f(v)-5 +b(alues)34 b(are)f(de\014ned)f(in)h(\014tsio.h\).)49 +b(Note)35 b(that)e(this)g(is)g(one)h(of)f(the)g(few)g(FITSIO)227 +1430 y(routines)e(that)f(do)s(es)h(not)f(return)f(a)i(status)g(v)-5 +b(alue.)334 1697 y Fe(Class)94 b(Value)619 b(Keywords)95 +1810 y(TYP_STRUC_KEY)92 b(10)j(SIMPLE,)46 b(BITPIX,)g(NAXIS,)g(NAXISn,) +g(EXTEND,)g(BLOCKED,)1002 1923 y(GROUPS,)g(PCOUNT,)g(GCOUNT,)g(END)1002 +2036 y(XTENSION,)g(TFIELDS,)f(TTYPEn,)h(TBCOLn,)g(TFORMn,)g(THEAP,)1002 +2149 y(and)h(the)g(first)f(4)i(COMMENT)e(keywords)f(in)i(the)g(primary) +f(array)1002 2262 y(that)h(define)f(the)h(FITS)g(format.)95 +2375 y(TYP_CMPRS_KEY)92 b(20)j(The)47 b(experimental)e(keywords)g(used) +i(in)g(the)g(compressed)1002 2488 y(image)g(format)f(ZIMAGE,)g +(ZCMPTYPE,)f(ZNAMEn,)h(ZVALn,)1002 2601 y(ZTILEn,)g(ZBITPIX,)g +(ZNAXISn,)f(ZSCALE,)h(ZZERO,)g(ZBLANK)95 2714 y(TYP_SCAL_KEY)140 +b(30)95 b(BSCALE,)46 b(BZERO,)g(TSCALn,)g(TZEROn)95 2826 +y(TYP_NULL_KEY)140 b(40)95 b(BLANK,)46 b(TNULLn)95 2939 +y(TYP_DIM_KEY)188 b(50)95 b(TDIMn)95 3052 y(TYP_RANG_KEY)140 +b(60)95 b(TLMINn,)46 b(TLMAXn,)g(TDMINn,)g(TDMAXn,)g(DATAMIN,)f +(DATAMAX)95 3165 y(TYP_UNIT_KEY)140 b(70)95 b(BUNIT,)46 +b(TUNITn)95 3278 y(TYP_DISP_KEY)140 b(80)95 b(TDISPn)95 +3391 y(TYP_HDUID_KEY)d(90)j(EXTNAME,)46 b(EXTVER,)g(EXTLEVEL,)f +(HDUNAME,)g(HDUVER,)h(HDULEVEL)95 3504 y(TYP_CKSUM_KEY)f(100)94 +b(CHECKSUM,)46 b(DATASUM)95 3617 y(TYP_WCS_KEY)141 b(110)94 +b(CTYPEn,)46 b(CUNITn,)g(CRVALn,)g(CRPIXn,)g(CROTAn,)f(CDELTn)1002 +3730 y(CDj_is,)h(PVj_ms,)g(LONPOLEs,)f(LATPOLEs)1002 +3843 y(TCTYPn,)h(TCTYns,)g(TCUNIn,)g(TCUNns,)g(TCRVLn,)f(TCRVns,)h +(TCRPXn,)1002 3956 y(TCRPks,)g(TCDn_k,)g(TCn_ks,)g(TPVn_m,)g(TPn_ms,)f +(TCDLTn,)h(TCROTn)1002 4068 y(jCTYPn,)g(jCTYns,)g(jCUNIn,)g(jCUNns,)g +(jCRVLn,)f(jCRVns,)h(iCRPXn,)1002 4181 y(iCRPns,)g(jiCDn,)94 +b(jiCDns,)46 b(jPVn_m,)g(jPn_ms,)f(jCDLTn,)h(jCROTn)1002 +4294 y(\(i,j,m,n)g(are)h(integers,)e(s)i(is)h(any)f(letter\))95 +4407 y(TYP_REFSYS_KEY)d(120)j(EQUINOXs,)f(EPOCH,)g(MJD-OBSs,)f +(RADECSYS,)g(RADESYSs)95 4520 y(TYP_COMM_KEY)140 b(130)47 +b(COMMENT,)f(HISTORY,)f(\(blank)h(keyword\))95 4633 y(TYP_CONT_KEY)140 +b(140)47 b(CONTINUE)95 4746 y(TYP_USER_KEY)140 b(150)47 +b(all)g(other)g(keywords)430 4972 y(class)f(=)h(FTGKCL)f(\(char)h +(*card\))0 5240 y Fh(12)f Fi(P)m(arse)f(the)g('TF)m(ORM')h(binary)e +(table)i(column)e(format)h(string.)84 b(This)44 b(subroutine)g(parses)g +(the)h(input)227 5352 y(TF)m(ORM)27 b(c)m(haracter)g(string)f(and)g +(returns)f(the)h(in)m(teger)h(datat)m(yp)s(e)g(co)s(de,)h(the)e(rep)s +(eat)g(coun)m(t)h(of)f(the)g(\014eld,)227 5465 y(and,)f(in)e(the)h +(case)g(of)g(c)m(haracter)h(string)e(\014elds,)i(the)e(length)h(of)g +(the)g(unit)f(string.)38 b(The)23 b(follo)m(wing)i(datat)m(yp)s(e)227 +5578 y(co)s(des)e(are)h(returned)e(\(the)h(negativ)m(e)i(of)f(the)f(v) +-5 b(alue)23 b(is)g(returned)f(if)h(the)g(column)g(con)m(tains)h(v)-5 +b(ariable-length)227 5691 y(arra)m(ys\):)p eop end +%%Page: 72 78 +TeXDict begin 72 77 bop 0 299 a Fi(72)1319 b Fg(CHAPTER)29 +b(6.)112 b(AD)m(V)-10 b(ANCED)32 b(INTERF)-10 b(A)m(CE)30 +b(SUBR)m(OUTINES)764 555 y Fe(Datatype)761 b(DATACODE)46 +b(value)764 668 y(bit,)g(X)907 b(1)764 781 y(byte,)46 +b(B)811 b(11)764 894 y(logical,)45 b(L)668 b(14)764 1007 +y(ASCII)46 b(character,)f(A)286 b(16)764 1120 y(short)46 +b(integer,)g(I)381 b(21)764 1233 y(integer,)45 b(J)668 +b(41)764 1346 y(real,)46 b(E)811 b(42)764 1458 y(double)46 +b(precision,)f(D)238 b(82)764 1571 y(complex)809 b(83)764 +1684 y(double)46 b(complex)475 b(163)382 1910 y(FTBNFM\(tform,)44 +b(>)j(datacode,repeat,width,stat)o(us\))0 2147 y Fh(13)f +Fi(P)m(arse)38 b(the)f('TF)m(ORM')h(k)m(eyw)m(ord)g(v)-5 +b(alue)37 b(that)h(de\014nes)e(the)h(column)g(format)h(in)e(an)h(ASCI)s +(I)f(table.)62 b(This)227 2260 y(routine)31 b(parses)g(the)g(input)g +(TF)m(ORM)g(c)m(haracter)i(string)e(and)f(returns)g(the)i(datat)m(yp)s +(e)g(co)s(de,)f(the)h(width)227 2373 y(of)40 b(the)h(column,)h(and)e +(\(if)g(it)g(is)h(a)f(\015oating)h(p)s(oin)m(t)f(column\))g(the)g(n)m +(um)m(b)s(er)f(of)h(decimal)h(places)g(to)g(the)227 2486 +y(righ)m(t)28 b(of)g(the)f(decimal)h(p)s(oin)m(t.)40 +b(The)27 b(returned)f(datat)m(yp)s(e)i(co)s(des)f(are)h(the)g(same)f +(as)h(for)f(the)g(binary)g(table,)227 2599 y(listed)41 +b(ab)s(o)m(v)m(e,)j(with)c(the)g(follo)m(wing)i(additional)f(rules:)60 +b(in)m(teger)42 b(columns)e(that)g(are)h(b)s(et)m(w)m(een)g(1)g(and)227 +2712 y(4)36 b(c)m(haracters)i(wide)d(are)i(de\014ned)d(to)j(b)s(e)e +(short)h(in)m(tegers)h(\(co)s(de)f(=)g(21\).)58 b(Wider)36 +b(in)m(teger)h(columns)f(are)227 2825 y(de\014ned)j(to)i(b)s(e)e +(regular)h(in)m(tegers)i(\(co)s(de)e(=)g(41\).)71 b(Similarly)-8 +b(,)43 b(Fixed)d(decimal)h(p)s(oin)m(t)f(columns)g(\(with)227 +2937 y(TF)m(ORM)30 b(=)g('Fw.d'\))g(are)g(de\014ned)f(to)h(b)s(e)g +(single)g(precision)g(reals)g(\(co)s(de)h(=)e(42\))i(if)f(w)f(is)h(b)s +(et)m(w)m(een)g(1)h(and)227 3050 y(7)i(c)m(haracters)h(wide,)f +(inclusiv)m(e.)47 b(Wider)32 b('F')h(columns)f(will)h(return)e(a)i +(double)f(precision)g(data)h(co)s(de)g(\(=)227 3163 y(82\).)54 +b('Ew.d')34 b(format)g(columns)g(will)h(ha)m(v)m(e)g(dataco)s(de)g(=)f +(42,)j(and)c('Dw.d')i(format)f(columns)g(will)h(ha)m(v)m(e)227 +3276 y(dataco)s(de)d(=)e(82.)382 3513 y Fe(FTASFM\(tform,)44 +b(>)j(datacode,width,decimals,st)o(atus)o(\))0 3750 y +Fh(14)f Fi(Calculate)32 b(the)f(starting)g(column)g(p)s(ositions)f(and) +g(total)i(ASCI)s(I)d(table)j(width)d(based)i(on)f(the)h(input)e(arra)m +(y)227 3863 y(of)e(ASCI)s(I)e(table)i(TF)m(ORM)g(v)-5 +b(alues.)40 b(The)26 b(SP)-8 b(A)m(CE)27 b(input)e(parameter)i +(de\014nes)f(ho)m(w)h(man)m(y)f(blank)h(spaces)227 3976 +y(to)40 b(lea)m(v)m(e)i(b)s(et)m(w)m(een)e(eac)m(h)g(column)g(\(it)g +(is)f(recommended)g(to)h(ha)m(v)m(e)h(one)e(space)h(b)s(et)m(w)m(een)g +(columns)f(for)227 4089 y(b)s(etter)31 b(h)m(uman)e(readabilit)m(y\).) +382 4326 y Fe(FTGABC\(tfields,tform,spa)o(ce,)41 b(>)48 +b(rowlen,tbcol,status\))0 4563 y Fh(15)e Fi(P)m(arse)36 +b(a)f(template)h(string)f(and)g(return)f(a)h(formatted)h(80-c)m +(haracter)h(string)e(suitable)h(for)f(app)s(ending)e(to)227 +4675 y(\(or)40 b(deleting)h(from\))e(a)h(FITS)f(header)h(\014le.)68 +b(This)39 b(subroutine)f(is)i(useful)f(for)g(parsing)g(lines)h(from)f +(an)227 4788 y(ASCI)s(I)34 b(template)j(\014le)f(and)e(reformatting)j +(them)e(in)m(to)i(legal)g(FITS)d(header)i(records.)55 +b(The)35 b(formatted)227 4901 y(string)c(ma)m(y)g(then)f(b)s(e)g +(passed)g(to)i(the)e(FTPREC,)h(FTMCRD,)g(or)f(FTDKEY)h(subroutines)e +(to)j(app)s(end)227 5014 y(or)f(mo)s(dify)e(a)i(FITS)f(header)g +(record.)382 5251 y Fe(FTGTHD\(template,)43 b(>)48 b +(card,hdtype,status\))0 5488 y Fi(The)23 b(input)h(TEMPLA)-8 +b(TE)23 b(c)m(haracter)j(string)e(generally)h(should)e(con)m(tain)i(3)g +(tok)m(ens:)38 b(\(1\))25 b(the)f(KEYNAME,)h(\(2\))0 +5601 y(the)h(V)-10 b(ALUE,)26 b(and)f(\(3\))i(the)f(COMMENT)g(string.) +39 b(The)25 b(TEMPLA)-8 b(TE)26 b(string)g(m)m(ust)f(adhere)h(to)g(the) +g(follo)m(wing)0 5714 y(format:)p eop end +%%Page: 73 79 +TeXDict begin 73 78 bop 0 299 a Fg(6.12.)73 b(GENERAL)30 +b(UTILITY)g(SUBR)m(OUTINES)1979 b Fi(73)0 555 y Fh(-)80 +b Fi(The)24 b(KEYNAME)g(tok)m(en)h(m)m(ust)e(b)s(egin)h(in)f(columns)h +(1-8)h(and)e(b)s(e)h(a)g(maxim)m(um)g(of)g(8)g(c)m(haracters)h(long.)39 +b(If)24 b(the)227 668 y(\014rst)32 b(8)h(c)m(haracters)h(of)e(the)h +(template)h(line)e(are)h(blank)f(then)g(the)h(remainder)f(of)g(the)h +(line)g(is)f(considered)227 781 y(to)42 b(b)s(e)e(a)h(FITS)f(commen)m +(t)h(\(with)g(a)g(blank)f(k)m(eyw)m(ord)h(name\).)72 +b(A)41 b(legal)i(FITS)d(k)m(eyw)m(ord)h(name)f(ma)m(y)227 +894 y(only)35 b(con)m(tain)i(the)e(c)m(haracters)h(A-Z,)f(0-9,)j(and)c +('-')i(\(min)m(us)f(sign\))g(and)f(underscore.)54 b(This)34 +b(subroutine)227 1007 y(will)42 b(automatically)i(con)m(v)m(ert)f(an)m +(y)f(lo)m(w)m(ercase)i(c)m(haracters)e(to)h(upp)s(ercase)d(in)h(the)h +(output)f(string.)73 b(If)227 1120 y(KEYNAME)33 b(=)f('COMMENT')h(or)g +('HISTOR)-8 b(Y')32 b(then)h(the)f(remainder)g(of)h(the)g(line)g(is)g +(considered)f(to)227 1233 y(b)s(e)e(a)h(FITS)e(COMMENT)h(or)h(HISTOR)-8 +b(Y)30 b(record,)g(resp)s(ectiv)m(ely)-8 b(.)0 1490 y +Fh(-)80 b Fi(The)26 b(V)-10 b(ALUE)26 b(tok)m(en)h(m)m(ust)e(b)s(e)h +(separated)g(from)f(the)i(KEYNAME)f(tok)m(en)h(b)m(y)f(one)g(or)g(more) +g(spaces)g(and/or)227 1603 y(an)i('=')g(c)m(haracter.)41 +b(The)27 b(datat)m(yp)s(e)i(of)f(the)g(V)-10 b(ALUE)27 +b(tok)m(en)i(\(n)m(umeric,)g(logical,)i(or)d(c)m(haracter)h(string\))f +(is)227 1716 y(automatically)35 b(determined)c(and)h(the)g(output)f +(CARD)h(string)g(is)g(formatted)g(accordingly)-8 b(.)47 +b(The)31 b(v)-5 b(alue)227 1829 y(tok)m(en)34 b(ma)m(y)f(b)s(e)f +(forced)g(to)i(b)s(e)e(in)m(terpreted)g(as)h(a)g(string)g(\(e.g.)48 +b(if)33 b(it)g(is)f(a)h(string)g(of)f(n)m(umeric)h(digits\))g(b)m(y)227 +1942 y(enclosing)g(it)f(in)f(single)h(quotes.)45 b(If)31 +b(the)h(v)-5 b(alue)32 b(tok)m(en)g(is)g(a)g(c)m(haracter)h(string)e +(that)i(con)m(tains)f(1)g(or)g(more)227 2055 y(em)m(b)s(edded)39 +b(blank)g(space)h(c)m(haracters)h(or)e(slash)h(\('/'\))h(c)m(haracters) +g(then)e(the)g(en)m(tire)i(c)m(haracter)g(string)227 +2168 y(m)m(ust)31 b(b)s(e)e(enclosed)i(in)f(single)h(quotes.)0 +2425 y Fh(-)80 b Fi(The)28 b(COMMENT)g(tok)m(en)h(is)f(optional,)i(but) +e(if)g(presen)m(t)g(m)m(ust)g(b)s(e)g(separated)g(from)g(the)h(V)-10 +b(ALUE)28 b(tok)m(en)h(b)m(y)227 2538 y(a)i(blank)f(space)h(or)f(a)h +('/')g(c)m(haracter.)0 2796 y Fh(-)80 b Fi(One)32 b(exception)i(to)f +(the)g(ab)s(o)m(v)m(e)h(rules)e(is)g(that)h(if)g(the)f(\014rst)g +(non-blank)g(c)m(haracter)i(in)e(the)h(template)h(string)227 +2909 y(is)h(a)g(min)m(us)f(sign)h(\('-'\))h(follo)m(w)m(ed)g(b)m(y)f(a) +g(single)g(tok)m(en,)i(or)e(a)g(single)g(tok)m(en)h(follo)m(w)m(ed)g(b) +m(y)f(an)f(equal)i(sign,)227 3022 y(then)29 b(it)g(is)g(in)m(terpreted) +f(as)h(the)g(name)g(of)g(a)g(k)m(eyw)m(ord)g(whic)m(h)f(is)h(to)g(b)s +(e)f(deleted)i(from)e(the)h(FITS)f(header.)0 3279 y Fh(-)80 +b Fi(The)40 b(second)g(exception)h(is)f(that)h(if)f(the)g(template)h +(string)f(starts)g(with)g(a)h(min)m(us)e(sign)h(and)f(is)h(follo)m(w)m +(ed)227 3392 y(b)m(y)33 b(2)g(tok)m(ens)g(then)g(the)f(second)h(tok)m +(en)h(is)e(in)m(terpreted)h(as)g(the)g(new)f(name)g(for)h(the)g(k)m +(eyw)m(ord)g(sp)s(eci\014ed)227 3505 y(b)m(y)h(\014rst)e(tok)m(en.)52 +b(In)33 b(this)g(case)i(the)e(old)h(k)m(eyw)m(ord)g(name)g(\(\014rst)f +(tok)m(en\))i(is)e(returned)g(in)g(c)m(haracters)i(1-8)227 +3618 y(of)e(the)g(returned)e(CARD)i(string,)g(and)f(the)h(new)f(k)m +(eyw)m(ord)h(name)g(\(the)g(second)f(tok)m(en\))i(is)f(returned)e(in) +227 3731 y(c)m(haracters)c(41-48)h(of)e(the)f(returned)g(CARD)g +(string.)40 b(These)25 b(old)h(and)f(new)g(names)g(ma)m(y)h(then)f(b)s +(e)g(passed)227 3844 y(to)31 b(the)g(FTMNAM)g(subroutine)f(whic)m(h)g +(will)g(c)m(hange)i(the)e(k)m(eyw)m(ord)h(name.)0 4101 +y(The)f(HDTYPE)g(output)g(parameter)h(indicates)g(ho)m(w)g(the)f +(returned)g(CARD)g(string)g(should)g(b)s(e)g(in)m(terpreted:)382 +4359 y Fe(hdtype)857 b(interpretation)382 4472 y(------)523 +b(-------------------------)o(----)o(---)o(----)o(----)o(---)o(----)o +(--)525 4585 y(-2)572 b(Modify)46 b(the)h(name)g(of)g(the)g(keyword)f +(given)g(in)h(CARD\(1:8\))1193 4698 y(to)g(the)g(new)g(name)g(given)f +(in)h(CARD\(41:48\))525 4924 y(-1)572 b(CARD\(1:8\))45 +b(contains)h(the)h(name)g(of)g(a)g(keyword)f(to)h(be)g(deleted)1193 +5036 y(from)g(the)g(FITS)f(header.)573 5262 y(0)572 b(append)46 +b(the)h(CARD)g(string)f(to)h(the)g(FITS)g(header)f(if)h(the)1193 +5375 y(keyword)f(does)h(not)g(already)e(exist,)h(otherwise)g(update) +1193 5488 y(the)h(value/comment)d(if)j(the)g(keyword)f(is)h(already)f +(present)1193 5601 y(in)h(the)g(header.)p eop end +%%Page: 74 80 +TeXDict begin 74 79 bop 0 299 a Fi(74)1319 b Fg(CHAPTER)29 +b(6.)112 b(AD)m(V)-10 b(ANCED)32 b(INTERF)-10 b(A)m(CE)30 +b(SUBR)m(OUTINES)573 555 y Fe(1)572 b(simply)46 b(append)g(this)h +(keyword)f(to)h(the)g(FITS)g(header)f(\(CARD)1193 668 +y(is)h(either)f(a)i(HISTORY)e(or)h(COMMENT)f(keyword\).)573 +894 y(2)572 b(This)47 b(is)g(a)g(FITS)g(END)g(record;)f(it)h(should)f +(not)h(be)g(written)1193 1007 y(to)g(the)g(FITS)g(header)f(because)g +(FITSIO)g(automatically)1193 1120 y(appends)g(the)h(END)g(record)f +(when)h(the)f(header)h(is)g(closed.)0 1380 y Fi(EXAMPLES:)30 +b(The)g(follo)m(wing)i(lines)e(illustrate)i(v)-5 b(alid)31 +b(input)e(template)j(strings:)286 1639 y Fe(INTVAL)46 +b(7)i(This)f(is)g(an)g(integer)f(keyword)286 1752 y(RVAL)524 +b(34.6)142 b(/)239 b(This)46 b(is)i(a)f(floating)f(point)g(keyword)286 +1865 y(EVAL=-12.45E-03)92 b(This)46 b(is)i(a)f(floating)f(point)g +(keyword)g(in)h(exponential)e(notation)286 1978 y(lval)i(F)g(This)g(is) +g(a)h(boolean)e(keyword)859 2091 y(This)h(is)g(a)g(comment)f(keyword)g +(with)h(a)g(blank)f(keyword)g(name)286 2204 y(SVAL1)h(=)g('Hello)f +(world')142 b(/)95 b(this)47 b(is)g(a)g(string)f(keyword)286 +2317 y(SVAL2)94 b('123.5')g(this)47 b(is)g(also)f(a)i(string)e(keyword) +286 2430 y(sval3)94 b(123+)h(/)g(this)47 b(is)g(also)f(a)i(string)e +(keyword)g(with)g(the)h(value)g('123+)189 b(')286 2543 +y(#)48 b(the)f(following)e(template)h(line)g(deletes)g(the)h(DATE)g +(keyword)286 2655 y(-)h(DATE)286 2768 y(#)g(the)f(following)e(template) +h(line)g(modifies)g(the)h(NAME)f(keyword)g(to)h(OBJECT)286 +2881 y(-)h(NAME)e(OBJECT)0 3141 y Fh(16)g Fi(P)m(arse)35 +b(the)g(input)f(string)h(con)m(taining)h(a)f(list)h(of)f(ro)m(ws)f(or)h +(ro)m(w)g(ranges,)h(and)e(return)g(in)m(teger)i(arra)m(ys)f(con-)227 +3254 y(taining)27 b(the)f(\014rst)f(and)g(last)i(ro)m(w)f(in)f(eac)m(h) +i(range.)40 b(F)-8 b(or)26 b(example,)i(if)d(ro)m(wlist)i(=)e("3-5,)k +(6,)e(8-9")h(then)d(it)i(will)227 3367 y(return)34 b(n)m(umranges)h(=)g +(3,)h(rangemin)f(=)g(3,)i(6,)g(8)e(and)g(rangemax)g(=)g(5,)i(6,)g(9.)55 +b(A)m(t)36 b(most,)h('maxranges')227 3480 y(n)m(um)m(b)s(er)31 +b(of)h(ranges)f(will)h(b)s(e)g(returned.)43 b('maxro)m(ws')32 +b(is)g(the)g(maxim)m(um)g(n)m(um)m(b)s(er)e(of)i(ro)m(ws)g(in)f(the)h +(table;)227 3593 y(an)m(y)e(ro)m(ws)f(or)g(ranges)g(larger)h(than)f +(this)g(will)g(b)s(e)g(ignored.)40 b(The)29 b(ro)m(ws)g(m)m(ust)g(b)s +(e)f(sp)s(eci\014ed)h(in)f(increasing)227 3706 y(order,)33 +b(and)f(the)g(ranges)h(m)m(ust)f(not)g(o)m(v)m(erlap.)48 +b(A)33 b(min)m(us)e(sign)i(ma)m(y)g(b)s(e)e(use)h(to)h(sp)s(ecify)f +(all)h(the)g(ro)m(ws)f(to)227 3819 y(the)h(upp)s(er)d(or)j(lo)m(w)m(er) +h(b)s(ound,)d(so)i("50-")h(means)e(all)i(the)f(ro)m(ws)f(from)g(50)h +(to)h(the)e(end)g(of)h(the)f(table,)j(and)227 3931 y("-")d(means)e(all) +h(the)g(ro)m(ws)f(in)g(the)h(table,)g(from)f(1)h(-)g(maxro)m(ws.)191 +4191 y Fe(FTRWRG\(rowlist,)44 b(maxrows,)h(maxranges,)g(>)525 +4304 y(numranges,)g(rangemin,)g(rangemax,)h(status\))p +eop end +%%Page: 75 81 +TeXDict begin 75 80 bop 0 1225 a Ff(Chapter)65 b(7)0 +1687 y Fl(The)77 b(CFITSIO)f(Iterator)i(F)-19 b(unction)0 +2180 y Fi(The)41 b(\014ts)p 325 2180 28 4 v 33 w(iterate)p +614 2180 V 34 w(data)i(function)e(in)h(CFITSIO)e(pro)m(vides)i(a)g +(unique)e(metho)s(d)i(of)g(executing)h(an)e(arbitrary)0 +2293 y(user-supplied)35 b(`w)m(ork')i(function)f(that)h(op)s(erates)g +(on)g(ro)m(ws)f(of)h(data)g(in)f(FITS)g(tables)h(or)f(on)h(pixels)f(in) +h(FITS)0 2406 y(images.)i(Rather)24 b(than)e(explicitly)j(reading)e +(and)g(writing)g(the)g(FITS)g(images)h(or)f(columns)g(of)g(data,)i(one) +f(instead)0 2518 y(calls)36 b(the)g(CFITSIO)d(iterator)k(routine,)g +(passing)e(to)h(it)g(the)f(name)g(of)h(the)f(user's)g(w)m(ork)g +(function)g(that)h(is)f(to)0 2631 y(b)s(e)30 b(executed)h(along)g(with) +f(a)h(list)g(of)f(all)h(the)f(table)i(columns)e(or)g(image)h(arra)m(ys) +g(that)g(are)f(to)h(b)s(e)f(passed)g(to)h(the)0 2744 +y(w)m(ork)37 b(function.)61 b(The)37 b(CFITSIO)e(iterator)k(function)e +(then)g(do)s(es)g(all)h(the)f(w)m(ork)g(of)h(allo)s(cating)h(memory)e +(for)0 2857 y(the)28 b(arra)m(ys,)h(reading)f(the)g(input)e(data)j +(from)e(the)h(FITS)f(\014le,)h(passing)g(them)g(to)g(the)g(w)m(ork)g +(function,)g(and)f(then)0 2970 y(writing)36 b(an)m(y)h(output)f(data)h +(bac)m(k)h(to)f(the)f(FITS)g(\014le)g(after)h(the)g(w)m(ork)g(function) +f(exits.)59 b(Because)38 b(it)f(is)g(often)0 3083 y(more)g(e\016cien)m +(t)i(to)f(pro)s(cess)f(only)g(a)h(subset)f(of)g(the)g(total)i(table)g +(ro)m(ws)e(at)h(one)f(time,)j(the)e(iterator)g(function)0 +3196 y(can)31 b(determine)f(the)h(optim)m(um)f(amoun)m(t)h(of)f(data)h +(to)g(pass)f(in)g(eac)m(h)i(iteration)f(and)f(rep)s(eatedly)h(call)g +(the)g(w)m(ork)0 3309 y(function)f(un)m(til)h(the)f(en)m(tire)i(table)f +(b)s(een)e(pro)s(cessed.)0 3469 y(F)-8 b(or)37 b(man)m(y)f +(applications)h(this)e(single)i(CFITSIO)d(iterator)k(function)d(can)h +(e\013ectiv)m(ely)j(replace)e(all)g(the)f(other)0 3582 +y(CFITSIO)g(routines)i(for)f(reading)h(or)f(writing)h(data)g(in)f(FITS) +g(images)i(or)e(tables.)64 b(Using)37 b(the)h(iterator)h(has)0 +3695 y(sev)m(eral)32 b(imp)s(ortan)m(t)e(adv)-5 b(an)m(tages)32 +b(o)m(v)m(er)g(the)f(traditional)g(metho)s(d)f(of)h(reading)f(and)g +(writing)g(FITS)g(data)h(\014les:)136 3961 y Fc(\017)46 +b Fi(It)33 b(cleanly)h(separates)g(the)f(data)h(I/O)f(from)f(the)h +(routine)g(that)h(op)s(erates)f(on)g(the)g(data.)49 b(This)32 +b(leads)h(to)227 4074 y(a)e(more)g(mo)s(dular)e(and)h(`ob)5 +b(ject)31 b(orien)m(ted')h(programming)e(st)m(yle.)136 +4268 y Fc(\017)46 b Fi(It)27 b(simpli\014es)f(the)h(application)h +(program)f(b)m(y)f(eliminating)i(the)f(need)g(to)g(allo)s(cate)i +(memory)e(for)f(the)h(data)227 4381 y(arra)m(ys)e(and)f(eliminates)i +(most)e(of)h(the)f(calls)i(to)f(the)g(CFITSIO)d(routines)j(that)g +(explicitly)h(read)e(and)g(write)227 4494 y(the)31 b(data.)136 +4689 y Fc(\017)46 b Fi(It)32 b(ensures)e(that)i(the)g(data)g(are)g(pro) +s(cessed)f(as)h(e\016cien)m(tly)h(as)e(p)s(ossible.)44 +b(This)31 b(is)g(esp)s(ecially)i(imp)s(ortan)m(t)227 +4801 y(when)44 b(pro)s(cessing)g(tabular)h(data)h(since)f(the)g +(iterator)h(function)e(will)h(calculate)i(the)e(most)g(e\016cien)m(t) +227 4914 y(n)m(um)m(b)s(er)36 b(of)i(ro)m(ws)g(in)f(the)h(table)g(to)g +(b)s(e)f(passed)g(at)i(one)e(time)i(to)f(the)g(user's)e(w)m(ork)i +(function)f(on)h(eac)m(h)227 5027 y(iteration.)136 5222 +y Fc(\017)46 b Fi(Mak)m(es)39 b(it)e(p)s(ossible)g(for)g(larger)h(pro)5 +b(jects)37 b(to)h(dev)m(elop)g(a)g(library)e(of)i(w)m(ork)f(functions)f +(that)i(all)g(ha)m(v)m(e)h(a)227 5335 y(uniform)29 b(calling)j +(sequence)f(and)f(are)h(all)g(indep)s(enden)m(t)e(of)i(the)f(details)i +(of)e(the)h(FITS)e(\014le)i(format.)0 5601 y(There)f(are)h(basically)h +(2)g(steps)e(in)h(using)f(the)h(CFITSIO)e(iterator)j(function.)42 +b(The)30 b(\014rst)g(step)h(is)g(to)g(design)g(the)0 +5714 y(w)m(ork)26 b(function)f(itself)h(whic)m(h)f(m)m(ust)h(ha)m(v)m +(e)g(a)g(prescrib)s(ed)e(set)i(of)g(input)f(parameters.)39 +b(One)25 b(of)h(these)g(parameters)1905 5942 y(75)p eop +end +%%Page: 76 82 +TeXDict begin 76 81 bop 0 299 a Fi(76)1455 b Fg(CHAPTER)30 +b(7.)112 b(THE)30 b(CFITSIO)e(ITERA)-8 b(TOR)30 b(FUNCTION)0 +555 y Fi(is)f(a)g(structure)g(con)m(taining)i(p)s(oin)m(ters)d(to)i +(the)f(arra)m(ys)h(of)f(data;)h(the)f(w)m(ork)h(function)e(can)i(p)s +(erform)d(an)m(y)i(desired)0 668 y(op)s(erations)k(on)h(these)f(arra)m +(ys)h(and)e(do)s(es)h(not)g(need)g(to)h(w)m(orry)f(ab)s(out)g(ho)m(w)g +(the)h(input)e(data)i(w)m(ere)f(read)g(from)0 781 y(the)e(\014le)f(or)g +(ho)m(w)h(the)f(output)g(data)h(get)h(written)e(bac)m(k)h(to)h(the)e +(\014le.)0 941 y(The)24 b(second)h(step)g(is)f(to)i(design)e(the)h +(driv)m(er)g(routine)f(that)i(op)s(ens)e(all)h(the)g(necessary)g(FITS)f +(\014les)h(and)f(initializes)0 1054 y(the)41 b(input)g(parameters)g(to) +h(the)g(iterator)g(function.)73 b(The)41 b(driv)m(er)g(program)g(calls) +h(the)g(CFITSIO)e(iterator)0 1167 y(function)30 b(whic)m(h)g(then)g +(reads)g(the)h(data)g(and)f(passes)g(it)h(to)g(the)g(user's)e(w)m(ork)i +(function.)0 1327 y(F)-8 b(urther)41 b(details)i(on)f(using)f(the)h +(iterator)h(function)f(can)g(b)s(e)f(found)f(in)i(the)g(companion)g +(CFITSIO)e(User's)0 1440 y(Guide,)31 b(and)e(in)h(the)h(iter)p +874 1440 28 4 v 33 w(a.f,)g(iter)p 1197 1440 V 34 w(b.f)f(and)f(iter)p +1677 1440 V 34 w(c.f)h(example)h(programs.)p eop end +%%Page: 77 83 +TeXDict begin 77 82 bop 0 1225 a Ff(Chapter)65 b(8)0 +1687 y Fl(Extended)77 b(File)g(Name)g(Syn)-6 b(tax)0 +2216 y Fd(8.1)135 b(Ov)l(erview)0 2466 y Fi(CFITSIO)30 +b(supp)s(orts)f(an)j(extended)f(syn)m(tax)h(when)f(sp)s(ecifying)g(the) +h(name)f(of)h(the)g(data)g(\014le)f(to)h(b)s(e)f(op)s(ened)g(or)0 +2579 y(created)g(that)g(includes)f(the)h(follo)m(wing)h(features:)136 +2813 y Fc(\017)46 b Fi(CFITSIO)40 b(can)i(read)f(IRAF)h(format)g +(images)g(whic)m(h)f(ha)m(v)m(e)i(header)e(\014le)h(names)f(that)h(end) +f(with)g(the)227 2926 y('.imh')d(extension,)i(as)e(w)m(ell)g(as)g +(reading)f(and)g(writing)g(FITS)g(\014les,)i(This)e(feature)h(is)f +(implemen)m(ted)h(in)227 3039 y(CFITSIO)29 b(b)m(y)i(\014rst)e(con)m(v) +m(erting)k(the)d(IRAF)h(image)h(in)m(to)f(a)g(temp)s(orary)f(FITS)g +(format)h(\014le)f(in)g(memory)-8 b(,)227 3152 y(then)35 +b(op)s(ening)f(the)h(FITS)f(\014le.)54 b(An)m(y)35 b(of)g(the)g(usual)f +(CFITSIO)g(routines)g(then)h(ma)m(y)g(b)s(e)f(used)g(to)i(read)227 +3265 y(the)31 b(image)g(header)f(or)h(data.)41 b(Similarly)-8 +b(,)31 b(ra)m(w)f(binary)g(data)h(arra)m(ys)f(can)h(b)s(e)f(read)g(b)m +(y)g(con)m(v)m(erting)i(them)227 3378 y(on)f(the)f(\015y)g(in)m(to)h +(virtual)g(FITS)f(images.)136 3557 y Fc(\017)46 b Fi(FITS)37 +b(\014les)g(on)g(the)g(In)m(ternet)h(can)f(b)s(e)g(read)g(\(and)g +(sometimes)h(written\))f(using)g(the)g(FTP)-8 b(,)38 +b(HTTP)-8 b(,)37 b(or)227 3670 y(R)m(OOT)30 b(proto)s(cols.)136 +3849 y Fc(\017)46 b Fi(FITS)30 b(\014les)g(can)h(b)s(e)f(pip)s(ed)f(b)s +(et)m(w)m(een)i(tasks)f(on)h(the)f(stdin)g(and)g(stdout)g(streams.)136 +4028 y Fc(\017)46 b Fi(FITS)20 b(\014les)h(can)g(b)s(e)f(read)g(and)g +(written)h(in)f(shared)g(memory)-8 b(.)38 b(This)20 b(can)h(p)s(oten)m +(tially)h(ac)m(hiev)m(e)h(m)m(uc)m(h)e(b)s(etter)227 +4141 y(data)26 b(I/O)e(p)s(erformance)g(compared)h(to)h(reading)f(and)f +(writing)g(the)h(same)h(FITS)e(\014les)g(on)h(magnetic)h(disk.)136 +4320 y Fc(\017)46 b Fi(Compressed)30 b(FITS)f(\014les)i(in)f(gzip)h(or) +f(Unix)g(COMPRESS)f(format)h(can)h(b)s(e)f(directly)h(read.)136 +4499 y Fc(\017)46 b Fi(Output)28 b(FITS)h(\014les)g(can)g(b)s(e)g +(written)g(directly)h(in)e(compressed)h(gzip)h(format,)g(th)m(us)e(sa)m +(ving)i(disk)f(space.)136 4678 y Fc(\017)46 b Fi(FITS)26 +b(table)h(columns)f(can)h(b)s(e)f(created,)i(mo)s(di\014ed,)f(or)f +(deleted)h('on-the-\015y')g(as)g(the)g(table)g(is)f(op)s(ened)g(b)m(y) +227 4791 y(CFITSIO.)32 b(This)h(creates)i(a)e(virtual)h(FITS)f(\014le)g +(con)m(taining)i(the)f(mo)s(di\014cations)f(that)h(is)g(then)f(op)s +(ened)227 4904 y(b)m(y)e(the)f(application)i(program.)136 +5083 y Fc(\017)46 b Fi(T)-8 b(able)29 b(ro)m(ws)e(ma)m(y)i(b)s(e)e +(selected,)j(or)e(\014ltered)g(out,)g(on)g(the)g(\015y)f(when)g(the)h +(table)h(is)f(op)s(ened)f(b)m(y)g(CFITSIO,)227 5196 y(based)f(on)h(an)f +(arbitrary)h(user-sp)s(eci\014ed)e(expression.)39 b(Only)26 +b(ro)m(ws)h(for)f(whic)m(h)g(the)h(expression)f(ev)-5 +b(aluates)227 5309 y(to)31 b('TR)m(UE')g(are)g(retained)g(in)f(the)g +(cop)m(y)i(of)e(the)h(table)g(that)g(is)f(op)s(ened)g(b)m(y)g(the)h +(application)g(program.)136 5488 y Fc(\017)46 b Fi(Histogram)28 +b(images)g(ma)m(y)f(b)s(e)f(created)h(on)f(the)h(\015y)f(b)m(y)g +(binning)g(the)g(v)-5 b(alues)27 b(in)f(table)i(columns,)f(resulting) +227 5601 y(in)36 b(a)g(virtual)h(N-dimensional)f(FITS)g(image.)59 +b(The)35 b(application)i(program)f(then)g(only)g(sees)g(the)h(FITS)227 +5714 y(image)32 b(\(in)e(the)h(primary)e(arra)m(y\))j(instead)e(of)h +(the)f(original)i(FITS)d(table.)1905 5942 y(77)p eop +end +%%Page: 78 84 +TeXDict begin 78 83 bop 0 299 a Fi(78)1618 b Fg(CHAPTER)30 +b(8.)112 b(EXTENDED)30 b(FILE)h(NAME)f(SYNT)-8 b(AX)0 +555 y Fi(The)43 b(latter)i(3)f(features)g(in)f(particular)h(add)f(v)m +(ery)h(p)s(o)m(w)m(erful)f(data)h(pro)s(cessing)f(capabilities)j +(directly)e(in)m(to)0 668 y(CFITSIO,)29 b(and)g(hence)h(in)m(to)h(ev)m +(ery)f(task)h(that)f(uses)g(CFITSIO)e(to)j(read)f(or)g(write)g(FITS)f +(\014les.)40 b(F)-8 b(or)31 b(example,)0 781 y(these)d(features)f +(transform)f(a)i(v)m(ery)f(simple)g(program)g(that)h(just)f(copies)h +(an)f(input)f(FITS)g(\014le)h(to)h(a)g(new)e(output)0 +894 y(\014le)36 b(\(lik)m(e)h(the)f(`\014tscop)m(y')h(program)f(that)g +(is)g(distributed)f(with)g(CFITSIO\))g(in)m(to)i(a)f(m)m(ultipurp)s +(ose)f(FITS)g(\014le)0 1007 y(pro)s(cessing)24 b(to)s(ol.)40 +b(By)25 b(app)s(ending)f(fairly)g(simple)h(quali\014ers)g(on)m(to)g +(the)g(name)g(of)g(the)g(input)f(FITS)g(\014le,)i(the)f(user)0 +1120 y(can)37 b(p)s(erform)f(quite)i(complex)g(table)g(editing)g(op)s +(erations)f(\(e.g.,)k(create)e(new)d(columns,)j(or)e(\014lter)h(out)f +(ro)m(ws)0 1233 y(in)g(a)g(table\))h(or)f(create)h(FITS)f(images)h(b)m +(y)f(binning)e(or)i(histogramming)h(the)f(v)-5 b(alues)37 +b(in)g(table)h(columns.)60 b(In)0 1346 y(addition,)33 +b(these)g(functions)e(ha)m(v)m(e)j(b)s(een)d(co)s(ded)h(using)f(new)h +(state-of-the)i(art)f(algorithms)g(that)g(are,)g(in)f(some)0 +1458 y(cases,)g(10)f(-)f(100)i(times)f(faster)g(than)f(previous)g +(widely)g(used)g(implemen)m(tations.)0 1619 y(Before)k(describing)f +(the)h(complete)h(syn)m(tax)f(for)f(the)h(extended)f(FITS)g(\014le)g +(names)g(in)g(the)h(next)g(section,)h(here)0 1732 y(are)c(a)g(few)f +(examples)h(of)f(FITS)g(\014le)g(names)h(that)f(giv)m(e)i(a)f(quic)m(k) +g(o)m(v)m(erview)h(of)f(the)f(allo)m(w)m(ed)i(syn)m(tax:)136 +1960 y Fc(\017)46 b Fe('myfile.fits')p Fi(:)37 b(the)31 +b(simplest)f(case)i(of)e(a)h(FITS)f(\014le)g(on)h(disk)e(in)i(the)f +(curren)m(t)g(directory)-8 b(.)136 2137 y Fc(\017)46 +b Fe('myfile.imh')p Fi(:)37 b(op)s(ens)28 b(an)h(IRAF)g(format)g(image) +i(\014le)e(and)f(con)m(v)m(erts)i(it)g(on)f(the)g(\015y)f(in)m(to)i(a)f +(temp)s(orary)227 2250 y(FITS)h(format)h(image)g(in)f(memory)h(whic)m +(h)f(can)g(then)g(b)s(e)g(read)g(with)g(an)m(y)h(other)g(CFITSIO)e +(routine.)136 2427 y Fc(\017)46 b Fe(rawfile.dat[i512,512])p +Fi(:)35 b(op)s(ens)30 b(a)g(ra)m(w)h(binary)e(data)i(arra)m(y)g(\(a)g +(512)g(x)f(512)i(short)e(in)m(teger)h(arra)m(y)g(in)227 +2540 y(this)i(case\))i(and)d(con)m(v)m(erts)j(it)e(on)g(the)g(\015y)g +(in)m(to)h(a)f(temp)s(orary)g(FITS)f(format)h(image)i(in)d(memory)h +(whic)m(h)227 2652 y(can)e(then)f(b)s(e)g(read)g(with)g(an)m(y)h(other) +f(CFITSIO)f(routine.)136 2830 y Fc(\017)46 b Fe(myfile.fits.gz)p +Fi(:)d(if)33 b(this)g(is)g(the)g(name)g(of)h(a)f(new)g(output)g +(\014le,)h(the)f('.gz')i(su\016x)d(will)h(cause)h(it)g(to)g(b)s(e)227 +2942 y(compressed)c(in)g(gzip)h(format)g(when)e(it)i(is)g(written)f(to) +h(disk.)136 3120 y Fc(\017)46 b Fe('myfile.fits.gz[events,)c(2]')p +Fi(:)59 b(op)s(ens)40 b(and)f(uncompresses)g(the)i(gzipp)s(ed)e(\014le) +i(m)m(y\014le.\014ts)f(then)227 3232 y(mo)m(v)m(es)34 +b(to)f(the)f(extension)h(whic)m(h)f(has)f(the)i(k)m(eyw)m(ords)f +(EXTNAME)g(=)g('EVENTS')g(and)g(EXTVER)f(=)227 3345 y(2.)136 +3522 y Fc(\017)46 b Fe('-')p Fi(:)40 b(a)31 b(dash)f(\(min)m(us)g +(sign\))h(signi\014es)f(that)h(the)g(input)f(\014le)g(is)h(to)g(b)s(e)f +(read)g(from)g(the)h(stdin)f(\014le)g(stream,)227 3635 +y(or)h(that)g(the)f(output)g(\014le)h(is)f(to)h(b)s(e)f(written)g(to)h +(the)g(stdout)f(stream.)136 3812 y Fc(\017)46 b Fe +('ftp://legacy.gsfc.nasa.g)o(ov/t)o(est/)o(vel)o(a.fi)o(ts')p +Fi(:)33 b(FITS)28 b(\014les)g(in)g(an)m(y)g(ftp)g(arc)m(hiv)m(e)i(site) +f(on)f(the)227 3925 y(In)m(ternet)j(ma)m(y)g(b)s(e)f(directly)h(op)s +(ened)e(with)h(read-only)h(access.)136 4102 y Fc(\017)46 +b Fe('http://legacy.gsfc.nasa.)o(gov/)o(soft)o(war)o(e/te)o(st.f)o(its) +o(')p Fi(:)d(an)m(y)34 b(v)-5 b(alid)35 b(URL)f(to)h(a)f(FITS)g(\014le) +g(on)227 4215 y(the)d(W)-8 b(eb)31 b(ma)m(y)g(b)s(e)f(op)s(ened)f(with) +h(read-only)h(access.)136 4392 y Fc(\017)46 b Fe +('root://legacy.gsfc.nasa.)o(gov/)o(test)o(/ve)o(la.f)o(its')o +Fi(:)32 b(similar)24 b(to)g(ftp)f(access)i(except)g(that)f(it)g(pro-) +227 4505 y(vides)30 b(write)h(as)f(w)m(ell)h(as)g(read)f(access)h(to)g +(the)f(\014les)h(across)f(the)h(net)m(w)m(ork.)41 b(This)29 +b(uses)h(the)h(ro)s(ot)f(proto)s(col)227 4618 y(dev)m(elop)s(ed)h(at)g +(CERN.)136 4795 y Fc(\017)46 b Fe('shmem://h2[events]')p +Fi(:)35 b(op)s(ens)30 b(the)g(FITS)f(\014le)i(in)f(a)g(shared)f(memory) +i(segmen)m(t)g(and)e(mo)m(v)m(es)j(to)f(the)227 4908 +y(EVENTS)f(extension.)136 5085 y Fc(\017)46 b Fe('mem://')p +Fi(:)52 b(creates)39 b(a)e(scratc)m(h)i(output)d(\014le)i(in)e(core)i +(computer)f(memory)-8 b(.)62 b(The)37 b(resulting)g('\014le')h(will)227 +5198 y(disapp)s(ear)25 b(when)f(the)i(program)f(exits,)i(so)f(this)f +(is)h(mainly)f(useful)g(for)g(testing)i(purp)s(oses)c(when)i(one)g(do)s +(es)227 5311 y(not)31 b(w)m(an)m(t)g(a)g(p)s(ermanen)m(t)f(cop)m(y)h +(of)f(the)h(output)f(\014le.)136 5488 y Fc(\017)46 b +Fe('myfile.fits[3;)e(Images\(10\)]')p Fi(:)49 b(op)s(ens)35 +b(a)i(cop)m(y)g(of)f(the)g(image)i(con)m(tained)f(in)f(the)h(10th)f(ro) +m(w)h(of)227 5601 y(the)26 b('Images')i(column)d(in)h(the)g(binary)g +(table)g(in)g(the)g(3th)h(extension)f(of)g(the)h(FITS)e(\014le.)39 +b(The)26 b(application)227 5714 y(just)k(sees)h(this)f(single)h(image)h +(as)e(the)h(primary)e(arra)m(y)-8 b(.)p eop end +%%Page: 79 85 +TeXDict begin 79 84 bop 0 299 a Fg(8.1.)72 b(O)m(VER)-10 +b(VIEW)3086 b Fi(79)136 555 y Fc(\017)46 b Fe('myfile.fits[1:512:2,)c +(1:512:2]')p Fi(:)49 b(op)s(ens)35 b(a)h(section)h(of)e(the)h(input)f +(image)i(ranging)f(from)f(the)227 668 y(1st)26 b(to)g(the)f(512th)h +(pixel)g(in)e(X)i(and)e(Y,)i(and)e(selects)j(ev)m(ery)e(second)h(pixel) +f(in)g(b)s(oth)f(dimensions,)i(resulting)227 781 y(in)k(a)h(256)h(x)e +(256)i(pixel)e(image)i(in)e(this)g(case.)136 981 y Fc(\017)46 +b Fe('myfile.fits[EVENTS][col)41 b(Rad)47 b(=)h(sqrt\(X**2)d(+)j +(Y**2\)]')p Fi(:)38 b(creates)30 b(and)f(op)s(ens)f(a)h(temp)s(orary) +227 1094 y(\014le)f(on)f(the)g(\015y)g(\(in)g(memory)g(or)g(on)h +(disk\))f(that)g(is)h(iden)m(tical)h(to)f(m)m(y\014le.\014ts)f(except)h +(that)g(it)g(will)g(con)m(tain)227 1207 y(a)41 b(new)f(column)g(in)h +(the)f(EVENTS)g(extension)h(called)h('Rad')f(whose)f(v)-5 +b(alue)41 b(is)f(computed)h(using)f(the)227 1320 y(indicated)31 +b(expression)f(whic)m(h)g(is)h(a)g(function)f(of)g(the)h(v)-5 +b(alues)30 b(in)h(the)f(X)h(and)e(Y)i(columns.)136 1520 +y Fc(\017)46 b Fe('myfile.fits[EVENTS][PHA)41 b(>)48 +b(5]')p Fi(:)37 b(creates)27 b(and)e(op)s(ens)g(a)h(temp)s(orary)f +(FITS)g(\014les)g(that)h(is)g(iden)m(ti-)227 1633 y(cal)k(to)g('m)m +(y\014le.\014ts')f(except)h(that)f(the)g(EVENTS)f(table)i(will)f(only)g +(con)m(tain)h(the)f(ro)m(ws)g(that)h(ha)m(v)m(e)g(v)-5 +b(alues)227 1746 y(of)28 b(the)g(PHA)f(column)g(greater)i(than)e(5.)40 +b(In)27 b(general,)i(an)m(y)f(arbitrary)f(b)s(o)s(olean)h(expression)f +(using)g(a)h(C)f(or)227 1859 y(F)-8 b(ortran-lik)m(e)31 +b(syn)m(tax,)e(whic)m(h)f(ma)m(y)h(com)m(bine)g(AND)g(and)f(OR)f(op)s +(erators,)i(ma)m(y)g(b)s(e)f(used)f(to)i(select)h(ro)m(ws)227 +1972 y(from)g(a)h(table.)136 2172 y Fc(\017)46 b Fe +('myfile.fits[EVENTS][bin)41 b(\(X,Y\)=1,2048,4]')p Fi(:)46 +b(creates)37 b(a)e(temp)s(orary)g(FITS)f(primary)g(arra)m(y)227 +2285 y(image)c(whic)m(h)f(is)g(computed)f(on)h(the)g(\015y)f(b)m(y)g +(binning)g(\(i.e,)j(computing)d(the)h(2-dimensional)h(histogram\))227 +2398 y(of)k(the)f(v)-5 b(alues)34 b(in)f(the)h(X)g(and)e(Y)i(columns)f +(of)h(the)f(EVENTS)g(extension.)50 b(In)33 b(this)g(case)i(the)e(X)h +(and)f(Y)227 2511 y(co)s(ordinates)h(range)g(from)f(1)h(to)g(2048)h +(and)e(the)h(image)g(pixel)g(size)g(is)g(4)f(units)g(in)g(b)s(oth)g +(dimensions,)h(so)227 2624 y(the)d(resulting)f(image)i(is)e(512)i(x)e +(512)i(pixels)f(in)f(size.)136 2824 y Fc(\017)46 b Fi(The)31 +b(\014nal)g(example)i(com)m(bines)f(man)m(y)f(of)h(these)g(feature)g +(in)m(to)g(one)g(complex)g(expression)f(\(it)i(is)e(brok)m(en)227 +2937 y(in)m(to)h(sev)m(eral)f(lines)g(for)f(clarit)m(y\):)323 +3206 y Fe('ftp://legacy.gsfc.nasa)o(.gov)o(/dat)o(a/s)o(ampl)o(e.fi)o +(ts.)o(gz[E)o(VENT)o(S])370 3319 y([col)47 b(phacorr)f(=)h(pha)g(*)h +(1.1)f(-)g(0.3][phacorr)e(>=)i(5.0)g(&&)g(phacorr)f(<=)h(14.0])370 +3432 y([bin)g(\(X,Y\)=32]')227 3701 y Fi(In)37 b(this)h(case,)j +(CFITSIO)36 b(\(1\))j(copies)g(and)e(uncompresses)g(the)h(FITS)f +(\014le)h(from)f(the)h(ftp)f(site)i(on)f(the)227 3814 +y(legacy)g(mac)m(hine,)h(\(2\))e(mo)m(v)m(es)g(to)g(the)g('EVENTS')f +(extension,)i(\(3\))f(calculates)i(a)d(new)g(column)g(called)227 +3927 y('phacorr',)30 b(\(4\))f(selects)h(the)f(ro)m(ws)g(in)f(the)h +(table)h(that)f(ha)m(v)m(e)h(phacorr)e(in)g(the)h(range)g(5)g(to)h(14,) +g(and)e(\014nally)227 4040 y(\(5\))35 b(bins)d(the)h(remaining)g(ro)m +(ws)g(on)h(the)f(X)g(and)g(Y)g(column)g(co)s(ordinates,)i(using)d(a)i +(pixel)f(size)h(=)f(32)h(to)227 4153 y(create)d(a)f(2D)g(image.)42 +b(All)30 b(this)f(pro)s(cessing)g(is)h(completely)h(transparen)m(t)e +(to)i(the)e(application)i(program,)227 4266 y(whic)m(h)f(simply)g(sees) +h(the)g(\014nal)f(2-D)h(image)h(in)e(the)g(primary)g(arra)m(y)h(of)f +(the)h(op)s(ened)f(\014le.)0 4538 y(The)c(full)h(extended)g(CFITSIO)e +(FITS)h(\014le)h(name)g(can)g(con)m(tain)h(sev)m(eral)g(di\013eren)m(t) +g(comp)s(onen)m(ts)f(dep)s(ending)e(on)0 4651 y(the)31 +b(con)m(text.)42 b(These)30 b(comp)s(onen)m(ts)h(are)g(describ)s(ed)e +(in)h(the)g(follo)m(wing)i(sections:)0 4924 y Fe(When)47 +b(creating)e(a)j(new)f(file:)143 5036 y(filetype://BaseFilename\(t)o +(empl)o(ate)o(Name)o(\))0 5262 y(When)g(opening)e(an)j(existing)d +(primary)h(array)g(or)i(image)e(HDU:)143 5375 y +(filetype://BaseFilename\(o)o(utNa)o(me\))o([HDU)o(loca)o(tio)o(n][I)o +(mage)o(Sec)o(tion)o(])0 5601 y(When)h(opening)e(an)j(existing)d(table) +i(HDU:)143 5714 y(filetype://BaseFilename\(o)o(utNa)o(me\))o([HDU)o +(loca)o(tio)o(n][c)o(olFi)o(lte)o(r][r)o(owFi)o(lte)o(r][b)o(inSp)o +(ec])p eop end +%%Page: 80 86 +TeXDict begin 80 85 bop 0 299 a Fi(80)1618 b Fg(CHAPTER)30 +b(8.)112 b(EXTENDED)30 b(FILE)h(NAME)f(SYNT)-8 b(AX)0 +555 y Fi(The)41 b(\014let)m(yp)s(e,)k(BaseFilename,)i(outName,)e(HDUlo) +s(cation,)i(and)41 b(ImageSection)i(comp)s(onen)m(ts,)i(if)d(presen)m +(t,)0 668 y(m)m(ust)30 b(b)s(e)g(giv)m(en)i(in)e(that)h(order,)g(but)f +(the)g(colFilter,)j(ro)m(wFilter,)g(and)c(binSp)s(ec)h(sp)s(eci\014ers) +f(ma)m(y)j(follo)m(w)f(in)g(an)m(y)0 781 y(order.)39 +b(Regardless)29 b(of)g(the)f(order,)g(ho)m(w)m(ev)m(er,)i(the)f +(colFilter)h(sp)s(eci\014er,)e(if)g(presen)m(t,)h(will)g(b)s(e)e(pro)s +(cessed)h(\014rst)f(b)m(y)0 894 y(CFITSIO,)i(follo)m(w)m(ed)j(b)m(y)e +(the)h(ro)m(wFilter)h(sp)s(eci\014er,)e(and)f(\014nally)i(b)m(y)f(the)g +(binSp)s(ec)f(sp)s(eci\014er.)0 1253 y Fd(8.2)135 b(Filet)l(yp)t(e)0 +1508 y Fi(The)37 b(t)m(yp)s(e)g(of)g(\014le)g(determines)g(the)g +(medium)f(on)h(whic)m(h)g(the)g(\014le)g(is)h(lo)s(cated)g(\(e.g.,)i +(disk)d(or)g(net)m(w)m(ork\))h(and,)0 1621 y(hence,)f(whic)m(h)e(in)m +(ternal)h(device)g(driv)m(er)f(is)g(used)f(b)m(y)h(CFITSIO)f(to)i(read) +f(and/or)g(write)g(the)g(\014le.)56 b(Curren)m(tly)0 +1734 y(supp)s(orted)29 b(t)m(yp)s(es)h(are)382 2015 y +Fe(file://)93 b(-)48 b(file)e(on)i(local)e(magnetic)g(disk)g +(\(default\))382 2128 y(ftp://)141 b(-)48 b(a)f(readonly)f(file)g +(accessed)g(with)h(the)g(anonymous)e(FTP)i(protocol.)907 +2241 y(It)g(also)g(supports)93 b(ftp://username:password@)o(host)o(nam) +o(e/..)o(.)907 2354 y(for)47 b(accessing)e(password-protected)e(ftp)k +(sites.)382 2467 y(http://)93 b(-)48 b(a)f(readonly)f(file)g(accessed)g +(with)h(the)g(HTTP)f(protocol.)93 b(It)907 2579 y(supports)45 +b(username:password)e(just)k(like)g(the)g(ftp)g(driver.)907 +2692 y(Proxy)f(HTTP)h(servers)f(are)h(supported)e(using)h(the)h +(http_proxy)907 2805 y(environment)e(variable)g(\(see)i(following)e +(note\).)286 2918 y(stream://)93 b(-)48 b(special)e(driver)g(to)h(read) +g(an)g(input)f(FITS)h(file)f(from)h(the)g(stdin)907 3031 +y(stream,)f(and/or)g(write)g(an)h(output)f(FITS)h(file)g(to)g(the)g +(stdout)143 3144 y(stream.)94 b(This)46 b(driver)g(is)i(fragile)d(and)i +(has)g(limited)143 3257 y(functionality)d(\(see)j(the)g(following)e +(note\).)286 3370 y(gsiftp://)93 b(-)48 b(access)e(files)g(on)h(a)h +(computational)c(grid)j(using)f(the)h(gridftp)907 3483 +y(protocol)e(in)j(the)e(Globus)h(toolkit)e(\(see)i(following)e(note\).) +382 3596 y(root://)93 b(-)48 b(uses)e(the)h(CERN)g(root)g(protocol)e +(for)i(writing)f(as)h(well)g(as)907 3709 y(reading)f(files)g(over)h +(the)g(network.)382 3821 y(shmem://)e(-)j(opens)e(or)h(creates)f(a)i +(file)e(which)h(persists)e(in)i(the)g(computer's)907 +3934 y(shared)f(memory.)382 4047 y(mem://)141 b(-)48 +b(opens)e(a)i(temporary)d(file)i(in)g(core)f(memory.)94 +b(The)47 b(file)907 4160 y(disappears)e(when)h(the)h(program)f(exits)h +(so)g(this)f(is)i(mainly)907 4273 y(useful)e(for)h(test)f(purposes)g +(when)h(a)g(permanent)e(output)h(file)907 4386 y(is)h(not)g(desired.)0 +4667 y Fi(If)35 b(the)h(\014let)m(yp)s(e)g(is)f(not)h(sp)s(eci\014ed,)h +(then)e(t)m(yp)s(e)h(\014le://)h(is)e(assumed.)56 b(The)35 +b(double)g(slashes)h('//')h(are)f(optional)0 4780 y(and)30 +b(ma)m(y)h(b)s(e)e(omitted)j(in)e(most)h(cases.)0 5096 +y Fb(8.2.1)112 b(Notes)37 b(ab)s(out)i(HTTP)d(pro)m(xy)i(serv)m(ers)0 +5320 y Fi(A)32 b(pro)m(xy)g(HTTP)f(serv)m(er)h(ma)m(y)h(b)s(e)e(used)g +(b)m(y)h(de\014ning)f(the)h(address)f(\(URL\))i(and)e(p)s(ort)g(n)m(um) +m(b)s(er)g(of)h(the)g(pro)m(xy)0 5433 y(serv)m(er)f(with)f(the)g(h)m +(ttp)p 801 5433 28 4 v 33 w(pro)m(xy)g(en)m(vironmen)m(t)h(v)-5 +b(ariable.)42 b(F)-8 b(or)31 b(example)191 5714 y Fe(setenv)46 +b(http_proxy)f(http://heasarc.gsfc.nasa)o(.gov)o(:312)o(8)p +eop end +%%Page: 81 87 +TeXDict begin 81 86 bop 0 299 a Fg(8.2.)72 b(FILETYPE)3128 +b Fi(81)0 555 y(will)38 b(cause)g(CFITSIO)f(to)h(use)g(p)s(ort)f(3128)i +(on)f(the)g(heasarc)g(pro)m(xy)g(serv)m(er)g(whenev)m(er)g(reading)g(a) +g(FITS)f(\014le)0 668 y(with)30 b(HTTP)-8 b(.)0 987 y +Fb(8.2.2)112 b(Notes)37 b(ab)s(out)i(the)e(stream)h(\014let)m(yp)s(e)g +(driv)m(er)0 1212 y Fi(The)e(stream)h(driv)m(er)f(can)h(b)s(e)f(used)g +(to)h(e\016cien)m(tly)i(read)d(a)h(FITS)f(\014le)h(from)f(the)h(stdin)f +(\014le)g(stream)h(or)g(write)0 1324 y(a)44 b(FITS)e(to)i(the)g(stdout) +f(\014le)g(stream.)80 b(Ho)m(w)m(ev)m(er,)49 b(b)s(ecause)43 +b(these)h(input)e(and)h(output)g(streams)g(m)m(ust)h(b)s(e)0 +1437 y(accessed)30 b(sequen)m(tially)-8 b(,)31 b(the)e(FITS)f(\014le)g +(reading)h(or)f(writing)h(application)h(m)m(ust)e(also)i(read)e(and)g +(write)h(the)g(\014le)0 1550 y(sequen)m(tially)-8 b(,)33 +b(at)e(least)g(within)f(the)h(tolerances)h(describ)s(ed)d(b)s(elo)m(w.) +0 1710 y(CFITSIO)34 b(supp)s(orts)f(2)j(di\013eren)m(t)f(metho)s(ds)g +(for)g(accessing)i(FITS)d(\014les)h(on)h(the)f(stdin)g(and)f(stdout)h +(streams.)0 1823 y(The)c(original)i(metho)s(d,)f(whic)m(h)f(is)h(in)m +(v)m(ok)m(ed)h(b)m(y)f(sp)s(ecifying)f(a)h(dash)f(c)m(haracter,)j("-",) +g(as)d(the)h(name)g(of)g(the)g(\014le)0 1936 y(when)g(op)s(ening)g(or)h +(creating)h(it,)g(w)m(orks)e(b)m(y)h(storing)g(a)g(complete)h(cop)m(y)g +(of)f(the)g(en)m(tire)g(FITS)f(\014le)h(in)f(memory)-8 +b(.)0 2049 y(In)35 b(this)g(case,)k(when)34 b(reading)i(from)f(stdin,)i +(CFITSIO)d(will)i(cop)m(y)h(the)e(en)m(tire)i(stream)f(in)m(to)h +(memory)e(b)s(efore)0 2162 y(doing)c(an)m(y)h(pro)s(cessing)f(of)h(the) +f(\014le.)44 b(Similarly)-8 b(,)32 b(when)f(writing)g(to)h(stdout,)g +(CFITSIO)d(will)j(create)h(a)f(cop)m(y)g(of)0 2275 y(the)h(en)m(tire)g +(FITS)f(\014le)g(in)h(memory)-8 b(,)33 b(b)s(efore)f(\014nally)h +(\015ushing)e(it)i(out)f(to)i(the)e(stdout)h(stream)g(when)e(the)i +(FITS)0 2388 y(\014le)g(is)g(closed.)49 b(Bu\013ering)33 +b(the)g(en)m(tire)h(FITS)e(\014le)h(in)g(this)f(w)m(a)m(y)i(allo)m(ws)g +(the)f(application)i(to)e(randomly)g(access)0 2501 y(an)m(y)h(part)f +(of)h(the)f(FITS)g(\014le,)i(in)e(an)m(y)h(order,)f(but)g(it)h(also)h +(requires)e(that)h(the)f(user)g(ha)m(v)m(e)i(su\016cien)m(t)f(a)m(v)-5 +b(ailable)0 2614 y(memory)30 b(\(or)g(virtual)g(memory\))g(to)h(store)f +(the)g(en)m(tire)h(\014le,)f(whic)m(h)f(ma)m(y)i(not)f(b)s(e)f(p)s +(ossible)g(in)h(the)g(case)h(of)f(v)m(ery)0 2727 y(large)h(\014les.)0 +2887 y(The)e(new)m(er)g(stream)h(\014let)m(yp)s(e)g(pro)m(vides)f(a)h +(more)f(memory-e\016cien)m(t)i(metho)s(d)e(of)h(accessing)h(FITS)d +(\014les)h(on)h(the)0 3000 y(stdin)37 b(or)h(stdout)g(streams.)64 +b(Instead)38 b(of)g(storing)g(a)g(cop)m(y)h(of)f(the)g(en)m(tire)h +(FITS)e(\014le)h(in)g(memory)-8 b(,)40 b(CFITSIO)0 3113 +y(only)32 b(uses)g(a)g(set)h(of)f(in)m(ternal)h(bu\013er)e(whic)m(h)h +(b)m(y)g(default)g(can)g(store)h(40)g(FITS)e(blo)s(c)m(ks,)i(or)g(ab)s +(out)e(100K)i(b)m(ytes)0 3225 y(of)f(the)f(FITS)g(\014le.)43 +b(The)31 b(application)i(program)e(m)m(ust)g(pro)s(cess)g(the)h(FITS)e +(\014le)i(sequen)m(tially)h(from)e(b)s(eginning)0 3338 +y(to)h(end,)e(within)g(this)h(100K)h(bu\013er.)41 b(Generally)32 +b(sp)s(eaking)f(the)g(application)h(program)f(m)m(ust)f(conform)h(to)h +(the)0 3451 y(follo)m(wing)g(restrictions:)136 3735 y +Fc(\017)46 b Fi(The)36 b(program)f(m)m(ust)h(\014nish)e(reading)i(or)g +(writing)f(the)h(header)g(k)m(eyw)m(ords)g(b)s(efore)f(reading)h(or)g +(writing)227 3848 y(an)m(y)31 b(data)g(in)f(the)h(HDU.)136 +4060 y Fc(\017)46 b Fi(The)24 b(HDU)h(can)f(con)m(tain)i(at)e(most)h +(ab)s(out)f(1400)h(header)f(k)m(eyw)m(ords.)39 b(This)24 +b(is)g(the)g(maxim)m(um)g(that)h(can)f(\014t)227 4172 +y(in)g(the)g(nominal)h(40)g(FITS)e(blo)s(c)m(k)i(bu\013er.)37 +b(In)24 b(principle,)h(this)f(limit)h(could)f(b)s(e)g(increased)g(b)m +(y)g(recompiling)227 4285 y(CFITSIO)29 b(with)h(a)h(larger)g(bu\013er)e +(limit,)j(whic)m(h)e(is)g(set)h(b)m(y)f(the)h(NIOBUF)g(parameter)g(in)f +(\014tsio2.h.)136 4497 y Fc(\017)46 b Fi(The)32 b(program)g(m)m(ust)f +(read)h(or)g(write)h(the)f(data)g(in)g(a)g(sequen)m(tial)i(manner)d +(from)h(the)g(b)s(eginning)f(to)i(the)227 4610 y(end)26 +b(of)g(the)h(HDU.)g(Note)h(that)f(CFITSIO's)e(in)m(ternal)i(100K)g +(bu\013er)e(allo)m(ws)j(a)e(little)j(latitude)e(in)f(meeting)227 +4723 y(this)31 b(requiremen)m(t.)136 4934 y Fc(\017)46 +b Fi(The)30 b(program)g(cannot)h(mo)m(v)m(e)h(bac)m(k)f(to)g(a)g +(previous)f(HDU)h(in)f(the)h(FITS)e(\014le.)136 5146 +y Fc(\017)46 b Fi(Reading)c(or)f(writing)f(of)h(v)-5 +b(ariable)42 b(length)f(arra)m(y)h(columns)e(in)h(binary)f(tables)i(is) +f(not)g(supp)s(orted)e(on)227 5259 y(streams,)29 b(b)s(ecause)f(this)g +(requires)g(mo)m(ving)g(bac)m(k)h(and)f(forth)f(b)s(et)m(w)m(een)i(the) +f(\014xed-length)g(p)s(ortion)g(of)g(the)227 5372 y(binary)i(table)h +(and)f(the)g(follo)m(wing)i(heap)e(area)i(where)e(the)g(arra)m(ys)h +(are)g(actually)h(stored.)136 5583 y Fc(\017)46 b Fi(Reading)25 +b(or)g(writing)f(of)h(tile-compressed)h(images)g(is)e(not)h(supp)s +(orted)e(on)h(streams,)i(b)s(ecause)f(the)g(images)227 +5696 y(are)31 b(in)m(ternally)g(stored)g(using)f(v)-5 +b(ariable)31 b(length)g(arra)m(ys.)p eop end +%%Page: 82 88 +TeXDict begin 82 87 bop 0 299 a Fi(82)1618 b Fg(CHAPTER)30 +b(8.)112 b(EXTENDED)30 b(FILE)h(NAME)f(SYNT)-8 b(AX)0 +555 y Fb(8.2.3)112 b(Notes)37 b(ab)s(out)i(the)e(gsiftp)h(\014let)m(yp) +s(e)0 774 y Fi(DEPENDENCIES:)c(Globus)h(to)s(olkit)h(\(2.4.3)g(or)f +(higher\))f(\(GT\))h(should)f(b)s(e)g(installed.)53 b(There)34 +b(are)h(t)m(w)m(o)h(dif-)0 887 y(feren)m(t)31 b(w)m(a)m(ys)g(to)g +(install)g(GT:)0 1047 y(1\))43 b(goto)h(the)f(globus)f(to)s(olkit)i(w)m +(eb)e(page)i(www.globus.org)e(and)g(follo)m(w)h(the)g(do)m(wnload)g +(and)e(compilation)0 1160 y(instructions;)0 1320 y(2\))j(goto)i(the)d +(Virtual)i(Data)g(T)-8 b(o)s(olkit)45 b(w)m(eb)e(page)i(h)m +(ttp://vdt.cs.wisc.edu/)g(and)e(follo)m(w)i(the)f(instructions)0 +1433 y(\(STR)m(ONGL)-8 b(Y)31 b(SUGGESTED\);)0 1593 y(Once)23 +b(a)h(globus)f(clien)m(t)h(has)f(b)s(een)g(installed)h(in)e(y)m(our)i +(system)f(with)g(a)g(sp)s(eci\014c)g(\015a)m(v)m(our)h(it)f(is)g(p)s +(ossible)g(to)h(compile)0 1706 y(and)30 b(install)h(the)g(CFITSIO)d +(libraries.)41 b(Sp)s(eci\014c)30 b(con\014guration)h(\015ags)f(m)m +(ust)h(b)s(e)e(used:)0 1866 y(1\))21 b({with-gsiftp[[=P)-8 +b(A)g(TH]])22 b(Enable)f(Globus)f(T)-8 b(o)s(olkit)21 +b(gsiftp)g(proto)s(col)g(supp)s(ort)d(P)-8 b(A)g(TH=GLOBUS)p +3532 1866 28 4 v 33 w(LOCA)g(TION)0 1979 y(i.e.)42 b(the)30 +b(lo)s(cation)i(of)f(y)m(our)f(globus)g(installation)0 +2139 y(2\))h({with-gsiftp-\015a)m(v)m(our[[=P)-8 b(A)g(TH])33 +b(de\014nes)d(the)g(sp)s(eci\014c)g(Globus)h(\015a)m(v)m(our)f(ex.)41 +b(gcc32)0 2300 y(Both)31 b(the)g(\015ags)f(m)m(ust)g(b)s(e)g(used)g +(and)f(it)i(is)g(mandatory)f(to)h(set)g(b)s(oth)f(the)g(P)-8 +b(A)g(TH)31 b(and)f(the)h(\015a)m(v)m(our.)0 2460 y(USA)m(GE:)g(T)-8 +b(o)31 b(access)h(\014les)e(on)g(a)h(gridftp)f(serv)m(er)g(it)h(is)g +(necessary)f(to)i(use)e(a)g(gsiftp)h(pre\014x:)0 2620 +y(example:)41 b(gsiftp://remote)p 1003 2620 V 35 w(serv)m(er)p +1271 2620 V 34 w(fqhn/directory/\014lename)0 2780 y(The)f(gridftp)g +(driv)m(er)g(uses)g(a)g(lo)s(cal)i(bu\013er)d(on)i(a)f(temp)s(orary)g +(\014le)h(the)f(\014le)h(is)f(lo)s(cated)i(in)e(the)g(/tmp)h(direc-)0 +2893 y(tory)-8 b(.)73 b(If)40 b(y)m(ou)h(ha)m(v)m(e)h(sp)s(ecial)g(p)s +(ermissions)d(on)i(/tmp)g(or)g(y)m(ou)g(do)f(not)i(ha)m(v)m(e)g(a)f +(/tmp)g(directory)-8 b(,)44 b(it)e(is)e(p)s(os-)0 3006 +y(sible)d(to)h(force)g(another)g(lo)s(cation)g(setting)h(the)e(GSIFTP)p +2068 3006 V 32 w(TMPFILE)g(en)m(vironmen)m(t)h(v)-5 b(ariable)38 +b(\(ex.)62 b(exp)s(ort)0 3119 y(GSIFTP)p 347 3119 V 32 +w(TMPFILE=/y)m(our/lo)s(cation/y)m(ourtmp\014le\).)0 +3279 y(Grid)34 b(FTP)g(supp)s(orts)f(m)m(ulti)h(c)m(hannel)h(transfer.) +52 b(By)35 b(default)f(a)h(single)g(c)m(hannel)g(transmission)f(is)g(a) +m(v)-5 b(ailable.)0 3392 y(Ho)m(w)m(ev)m(er,)34 b(it)d(is)h(p)s +(ossible)e(to)i(mo)s(dify)e(this)i(b)s(eha)m(vior)f(setting)h(the)f +(GSIFTP)p 2691 3392 V 33 w(STREAMS)f(en)m(vironmen)m(t)h(v)-5 +b(ari-)0 3505 y(able)31 b(\(ex.)41 b(exp)s(ort)30 b(GSIFTP)p +1016 3505 V 33 w(STREAMS=8\).)0 3790 y Fb(8.2.4)112 b(Notes)37 +b(ab)s(out)i(the)e(ro)s(ot)g(\014let)m(yp)s(e)0 4009 +y Fi(The)20 b(original)j(ro)s(otd)d(serv)m(er)h(can)h(b)s(e)e(obtained) +h(from:)36 b Fe(ftp://root.cern.ch/root)o(/roo)o(td.t)o(ar.)o(gz)15 +b Fi(but,)22 b(for)0 4122 y(it)33 b(to)h(w)m(ork)f(correctly)h(with)e +(CFITSIO)g(one)h(has)f(to)i(use)e(a)i(mo)s(di\014ed)d(v)m(ersion)j +(whic)m(h)e(supp)s(orts)f(a)i(command)0 4235 y(to)41 +b(return)d(the)j(length)f(of)g(the)g(\014le.)70 b(This)39 +b(mo)s(di\014ed)f(v)m(ersion)j(is)f(a)m(v)-5 b(ailable)42 +b(in)e(ro)s(otd)f(sub)s(directory)g(in)h(the)0 4348 y(CFITSIO)29 +b(ftp)h(area)h(at)286 4577 y Fe(ftp://legacy.gsfc.nasa.gov)o(/so)o +(ftwa)o(re/f)o(its)o(io/c)o(/roo)o(t/r)o(ootd)o(.tar)o(.gz)o(.)0 +4805 y Fi(This)j(small)g(serv)m(er)h(is)g(started)f(either)h(b)m(y)g +(inetd)f(when)f(a)i(clien)m(t)h(requests)e(a)h(connection)h(to)f(a)f +(ro)s(otd)h(serv)m(er)0 4918 y(or)30 b(b)m(y)g(hand)f(\(i.e.)42 +b(from)30 b(the)g(command)g(line\).)42 b(The)29 b(ro)s(otd)h(serv)m(er) +h(w)m(orks)f(with)g(the)g(R)m(OOT)g(TNetFile)i(class.)0 +5031 y(It)e(allo)m(ws)g(remote)h(access)f(to)h(R)m(OOT)e(database)h +(\014les)f(in)g(either)h(read)g(or)f(write)h(mo)s(de.)40 +b(By)30 b(default)f(TNetFile)0 5144 y(assumes)38 b(p)s(ort)g(432)h +(\(whic)m(h)f(requires)g(ro)s(otd)g(to)h(b)s(e)f(started)h(as)f(ro)s +(ot\).)65 b(T)-8 b(o)39 b(run)e(ro)s(otd)h(via)h(inetd)f(add)g(the)0 +5257 y(follo)m(wing)32 b(line)f(to)g(/etc/services:)95 +5485 y Fe(rootd)238 b(432/tcp)0 5714 y Fi(and)30 b(to)h +(/etc/inetd.conf,)i(add)d(the)g(follo)m(wing)i(line:)p +eop end +%%Page: 83 89 +TeXDict begin 83 88 bop 0 299 a Fg(8.2.)72 b(FILETYPE)3128 +b Fi(83)95 555 y Fe(rootd)47 b(stream)f(tcp)h(nowait)f(root)h +(/user/rdm/root/bin/root)o(d)42 b(rootd)k(-i)0 829 y +Fi(F)-8 b(orce)34 b(inetd)e(to)i(reread)e(its)h(conf)f(\014le)h(with)f +("kill)h(-HUP)g(".)47 b(Y)-8 b(ou)33 b(can)g(also)g(start) +g(ro)s(otd)g(b)m(y)f(hand)0 942 y(running)j(directly)i(under)d(y)m(our) +j(priv)-5 b(ate)37 b(accoun)m(t)g(\(no)g(ro)s(ot)g(system)f(privileges) +h(needed\).)59 b(F)-8 b(or)37 b(example)g(to)0 1054 y(start)e(ro)s(otd) +e(listening)i(on)f(p)s(ort)f(5151)j(just)d(t)m(yp)s(e:)49 +b Fe(rootd)d(-p)h(5151)33 b Fi(Notice:)50 b(no)34 b(&)f(is)h(needed.)51 +b(Ro)s(otd)35 b(will)0 1167 y(go)c(in)m(to)h(bac)m(kground)e(b)m(y)g +(itself.)95 1441 y Fe(Rootd)47 b(arguments:)191 1554 +y(-i)763 b(says)47 b(we)g(were)f(started)g(by)h(inetd)191 +1667 y(-p)g(port#)476 b(specifies)45 b(a)j(different)d(port)i(to)g +(listen)f(on)191 1780 y(-d)h(level)476 b(level)46 b(of)i(debug)e(info)h +(written)e(to)j(syslog)1050 1893 y(0)f(=)h(no)f(debug)f(\(default\)) +1050 2005 y(1)h(=)h(minimum)1050 2118 y(2)f(=)h(medium)1050 +2231 y(3)f(=)h(maximum)0 2505 y Fi(Ro)s(otd)29 b(can)f(also)h(b)s(e)f +(con\014gured)g(for)g(anon)m(ymous)g(usage)h(\(lik)m(e)h(anon)m(ymous)e +(ftp\).)40 b(T)-8 b(o)29 b(setup)f(ro)s(otd)g(to)h(accept)0 +2618 y(anon)m(ymous)h(logins)h(do)g(the)f(follo)m(wing)i(\(while)f(b)s +(eing)f(logged)i(in)e(as)g(ro)s(ot\):)143 2891 y Fe(-)48 +b(Add)f(the)f(following)g(line)g(to)i(/etc/passwd:)239 +3117 y(rootd:*:71:72:Anonymous)41 b(rootd:/var/spool/rootd:/b)o(in/)o +(fals)o(e)239 3343 y(where)46 b(you)h(may)g(modify)f(the)h(uid,)f(gid)h +(\(71,)g(72\))g(and)g(the)g(home)f(directory)239 3456 +y(to)h(suite)f(your)h(system.)143 3681 y(-)h(Add)f(the)f(following)g +(line)g(to)i(/etc/group:)239 3907 y(rootd:*:72:rootd)239 +4133 y(where)e(the)h(gid)g(must)f(match)h(the)g(gid)g(in)g +(/etc/passwd.)143 4359 y(-)h(Create)e(the)h(directories:)239 +4585 y(mkdir)f(/var/spool/rootd)239 4698 y(mkdir)g +(/var/spool/rootd/tmp)239 4811 y(chmod)g(777)h(/var/spool/rootd/tmp)239 +5036 y(Where)f(/var/spool/rootd)d(must)k(match)f(the)h(rootd)g(home)f +(directory)g(as)239 5149 y(specified)f(in)i(the)g(rootd)f(/etc/passwd)f +(entry.)143 5375 y(-)j(To)f(make)f(writeable)g(directories)e(for)j +(anonymous)f(do,)h(for)f(example:)239 5601 y(mkdir)g +(/var/spool/rootd/pub)239 5714 y(chown)g(rootd:rootd)f +(/var/spool/rootd/pub)p eop end +%%Page: 84 90 +TeXDict begin 84 89 bop 0 299 a Fi(84)1618 b Fg(CHAPTER)30 +b(8.)112 b(EXTENDED)30 b(FILE)h(NAME)f(SYNT)-8 b(AX)0 +555 y Fi(That's)42 b(all.)76 b(Sev)m(eral)43 b(additional)g(remarks:)64 +b(y)m(ou)42 b(can)g(login)h(to)g(an)f(anon)m(ymous)f(serv)m(er)i +(either)f(with)g(the)0 668 y(names)31 b("anon)m(ymous")h(or)f("ro)s +(otd".)43 b(The)31 b(passw)m(ord)f(should)g(b)s(e)h(of)g(t)m(yp)s(e)g +(user@host.do.main.)43 b(Only)30 b(the)h(@)0 781 y(is)e(enforced)f(for) +h(the)f(time)i(b)s(eing.)39 b(In)28 b(anon)m(ymous)h(mo)s(de)f(the)g +(top)h(of)g(the)g(\014le)f(tree)i(is)e(set)h(to)h(the)e(ro)s(otd)h +(home)0 894 y(directory)-8 b(,)39 b(therefore)e(only)f(\014les)h(b)s +(elo)m(w)f(the)h(home)f(directory)h(can)f(b)s(e)g(accessed.)60 +b(Anon)m(ymous)36 b(mo)s(de)g(only)0 1007 y(w)m(orks)30 +b(when)g(the)g(serv)m(er)h(is)f(started)h(via)g(inetd.)0 +1296 y Fb(8.2.5)112 b(Notes)37 b(ab)s(out)i(the)e(shmem)i(\014let)m(yp) +s(e:)0 1515 y Fi(Shared)34 b(memory)h(\014les)g(are)g(curren)m(tly)g +(supp)s(orted)e(on)i(most)h(Unix)f(platforms,)h(where)f(the)g(shared)f +(memory)0 1627 y(segmen)m(ts)d(are)g(managed)g(b)m(y)f(the)g(op)s +(erating)h(system)g(k)m(ernel)f(and)g(`liv)m(e')i(indep)s(enden)m(tly)d +(of)i(pro)s(cesses.)40 b(They)0 1740 y(are)34 b(not)g(deleted)h(\(b)m +(y)f(default\))g(when)f(the)h(pro)s(cess)f(whic)m(h)h(created)h(them)f +(terminates,)h(although)g(they)f(will)0 1853 y(disapp)s(ear)e(if)h(the) +h(system)f(is)g(reb)s(o)s(oted.)49 b(Applications)34 +b(can)g(create)h(shared)d(memory)h(\014les)g(in)g(CFITSIO)f(b)m(y)0 +1966 y(calling:)143 2214 y Fe(fit_create_file\(&fitsfile)o(ptr,)41 +b("shmem://h2",)j(&status\);)0 2462 y Fi(where)25 b(the)g(ro)s(ot)h +(`\014le')f(names)h(are)f(curren)m(tly)g(restricted)h(to)g(b)s(e)f +('h0',)i('h1',)g('h2',)g('h3',)f(etc.,)i(up)d(to)g(a)h(maxim)m(um)0 +2575 y(n)m(um)m(b)s(er)20 b(de\014ned)f(b)m(y)i(the)g(the)g(v)-5 +b(alue)22 b(of)f(SHARED)p 1746 2575 28 4 v 33 w(MAXSEG)g(\(equal)h(to)f +(16)h(b)m(y)f(default\).)38 b(This)20 b(is)h(a)g(protot)m(yp)s(e)0 +2688 y(implemen)m(tation)30 b(of)f(the)g(shared)f(memory)g(in)m +(terface)i(and)e(a)h(more)g(robust)f(in)m(terface,)j(whic)m(h)d(will)h +(ha)m(v)m(e)h(few)m(er)0 2801 y(restrictions)h(on)f(the)h(n)m(um)m(b)s +(er)e(of)i(\014les)f(and)g(on)g(their)g(names,)h(ma)m(y)g(b)s(e)f(dev)m +(elop)s(ed)g(in)g(the)h(future.)0 2961 y(When)23 b(op)s(ening)h(an)f +(already)h(existing)h(FITS)e(\014le)h(in)f(shared)g(memory)h(one)g +(calls)g(the)g(usual)g(CFITSIO)e(routine:)143 3209 y +Fe(fits_open_file\(&fitsfilep)o(tr,)41 b("shmem://h7",)j(mode,)j +(&status\))0 3457 y Fi(The)26 b(\014le)h(mo)s(de)g(can)g(b)s(e)f(READ)m +(WRITE)h(or)g(READONL)-8 b(Y)28 b(just)e(as)h(with)f(disk)h(\014les.)39 +b(More)28 b(than)e(one)h(pro)s(cess)0 3570 y(can)35 b(op)s(erate)g(on)f +(READONL)-8 b(Y)35 b(mo)s(de)f(\014les)h(at)g(the)f(same)h(time.)54 +b(CFITSIO)33 b(supp)s(orts)f(prop)s(er)h(\014le)i(lo)s(c)m(king)0 +3682 y(\(b)s(oth)27 b(in)h(READONL)-8 b(Y)29 b(and)e(READ)m(WRITE)h(mo) +s(des\),)h(so)f(calls)h(to)f(\014ts)p 2572 3682 V 33 +w(op)s(en)p 2795 3682 V 32 w(\014le)g(ma)m(y)g(b)s(e)f(lo)s(c)m(k)m(ed) +j(out)e(un)m(til)0 3795 y(another)j(other)f(pro)s(cess)g(closes)i(the)e +(\014le.)0 3956 y(When)g(an)g(application)i(is)e(\014nished)f +(accessing)j(a)e(FITS)g(\014le)g(in)g(a)h(shared)e(memory)h(segmen)m +(t,)i(it)f(ma)m(y)g(close)g(it)0 4068 y(\(and)j(the)g(\014le)g(will)g +(remain)f(in)h(the)g(system\))g(with)g(\014ts)p 1955 +4068 V 32 w(close)p 2173 4068 V 34 w(\014le,)h(or)f(delete)h(it)g(with) +e(\014ts)p 3191 4068 V 33 w(delete)p 3455 4068 V 34 w(\014le.)51 +b(Ph)m(ys-)0 4181 y(ical)36 b(deletion)g(is)f(p)s(ostp)s(oned)e(un)m +(til)j(the)f(last)g(pro)s(cess)g(calls)h(\013clos/\013delt.)56 +b(\014ts)p 2801 4181 V 32 w(delete)p 3064 4181 V 34 w(\014le)35 +b(tries)h(to)f(obtain)h(a)0 4294 y(READ)m(WRITE)e(lo)s(c)m(k)g(on)f +(the)g(\014le)h(to)g(b)s(e)e(deleted,)j(th)m(us)e(it)h(can)f(b)s(e)g +(blo)s(c)m(k)m(ed)h(if)f(the)h(ob)5 b(ject)34 b(w)m(as)f(not)h(op)s +(ened)0 4407 y(in)c(READ)m(WRITE)h(mo)s(de.)0 4567 y(A)i(shared)f +(memory)h(managemen)m(t)h(utilit)m(y)g(program)f(called)h(`smem',)f(is) +g(included)f(with)h(the)g(CFITSIO)e(dis-)0 4680 y(tribution.)39 +b(It)27 b(can)g(b)s(e)f(built)h(b)m(y)g(t)m(yping)g(`mak)m(e)h(smem';)g +(then)f(t)m(yp)s(e)g(`smem)f(-h')h(to)h(get)g(a)f(list)g(of)g(v)-5 +b(alid)27 b(options.)0 4793 y(Executing)37 b(smem)f(without)g(an)m(y)h +(options)g(causes)f(it)h(to)g(list)g(all)g(the)g(shared)e(memory)i +(segmen)m(ts)g(curren)m(tly)0 4906 y(residing)c(in)g(the)g(system)h +(and)e(managed)i(b)m(y)f(the)h(shared)e(memory)h(driv)m(er.)49 +b(T)-8 b(o)34 b(get)g(a)g(list)g(of)f(all)h(the)g(shared)0 +5019 y(memory)c(ob)5 b(jects,)32 b(run)d(the)h(system)h(utilit)m(y)g +(program)f(`ip)s(cs)h([-a]'.)0 5351 y Fd(8.3)135 b(Base)46 +b(Filename)0 5601 y Fi(The)31 b(base)g(\014lename)h(is)f(the)h(name)f +(of)h(the)f(\014le)h(optionally)g(including)f(the)h(director/sub)s +(directory)f(path,)h(and)0 5714 y(in)e(the)h(case)g(of)g(`ftp',)f(`h)m +(ttp',)i(and)d(`ro)s(ot')j(\014let)m(yp)s(es,)e(the)h(mac)m(hine)g +(iden)m(ti\014er.)41 b(Examples:)p eop end +%%Page: 85 91 +TeXDict begin 85 90 bop 0 299 a Fg(8.3.)72 b(BASE)30 +b(FILENAME)2830 b Fi(85)191 555 y Fe(myfile.fits)191 +668 y(!data.fits)191 781 y(/data/myfile.fits)191 894 +y(fits.gsfc.nasa.gov/ftp/s)o(ampl)o(eda)o(ta/m)o(yfil)o(e.f)o(its.)o +(gz)0 1120 y Fi(When)29 b(creating)h(a)f(new)f(output)h(\014le)g(on)g +(magnetic)h(disk)e(\(of)i(t)m(yp)s(e)f(\014le://\))h(if)f(the)g(base)g +(\014lename)g(b)s(egins)f(with)0 1233 y(an)34 b(exclamation)j(p)s(oin)m +(t)d(\(!\))54 b(then)34 b(an)m(y)g(existing)i(\014le)e(with)g(that)h +(same)g(basename)g(will)g(b)s(e)e(deleted)i(prior)f(to)0 +1346 y(creating)h(the)f(new)g(FITS)f(\014le.)51 b(Otherwise)34 +b(if)g(the)g(\014le)g(to)g(b)s(e)g(created)h(already)f(exists,)i(then)d +(CFITSIO)g(will)0 1459 y(return)g(an)h(error)f(and)g(will)i(not)f(o)m +(v)m(erwrite)h(the)f(existing)h(\014le.)52 b(Note)35 +b(that)g(the)f(exclamation)i(p)s(oin)m(t,)f(')10 b(!',)36 +b(is)e(a)0 1572 y(sp)s(ecial)28 b(UNIX)g(c)m(haracter,)j(so)d(if)f(it)i +(is)f(used)f(on)g(the)h(command)g(line)g(rather)g(than)f(en)m(tered)h +(at)h(a)f(task)h(prompt,)0 1685 y(it)j(m)m(ust)f(b)s(e)g(preceded)g(b)m +(y)h(a)g(bac)m(kslash)g(to)g(force)g(the)g(UNIX)g(shell)f(to)h(pass)f +(it)i(v)m(erbatim)f(to)g(the)g(application)0 1798 y(program.)0 +1958 y(If)24 b(the)i(output)e(disk)h(\014le)g(name)g(ends)f(with)g(the) +h(su\016x)f('.gz',)k(then)d(CFITSIO)e(will)i(compress)g(the)g(\014le)g +(using)g(the)0 2071 y(gzip)g(compression)f(algorithm)h(b)s(efore)f +(writing)g(it)h(to)g(disk.)38 b(This)23 b(can)i(reduce)f(the)g(amoun)m +(t)h(of)f(disk)g(space)h(used)0 2184 y(b)m(y)34 b(the)h(\014le.)53 +b(Note)36 b(that)f(this)g(feature)g(requires)f(that)h(the)f +(uncompressed)g(\014le)g(b)s(e)g(constructed)h(in)f(memory)0 +2297 y(b)s(efore)c(it)h(is)f(compressed)g(and)g(written)h(to)g(disk,)f +(so)g(it)h(can)g(fail)g(if)f(there)h(is)f(insu\016cien)m(t)h(a)m(v)-5 +b(ailable)33 b(memory)-8 b(.)0 2457 y(An)45 b(input)g(FITS)f(\014le)i +(ma)m(y)g(b)s(e)f(compressed)g(with)h(the)f(gzip)h(or)g(Unix)f +(compress)h(algorithms,)k(in)45 b(whic)m(h)0 2570 y(case)38 +b(CFITSIO)e(will)i(uncompress)e(the)i(\014le)g(on)f(the)h(\015y)e(in)m +(to)j(a)f(temp)s(orary)f(\014le)g(\(in)h(memory)f(or)g(on)h(disk\).)0 +2683 y(Compressed)32 b(\014les)i(ma)m(y)g(only)f(b)s(e)g(op)s(ened)f +(with)h(read-only)h(p)s(ermission.)49 b(When)33 b(sp)s(ecifying)g(the)h +(name)f(of)h(a)0 2796 y(compressed)h(FITS)g(\014le)h(it)g(is)g(not)g +(necessary)g(to)g(app)s(end)e(the)i(\014le)g(su\016x)e(\(e.g.,)39 +b(`.gz')e(or)f(`.Z'\).)g(If)f(CFITSIO)0 2908 y(cannot)24 +b(\014nd)e(the)h(input)f(\014le)i(name)f(without)g(the)g(su\016x,)h +(then)f(it)h(will)g(automatically)i(searc)m(h)e(for)f(a)g(compressed)0 +3021 y(\014le)36 b(with)f(the)h(same)g(ro)s(ot)g(name.)57 +b(In)35 b(the)h(case)h(of)f(reading)g(ftp)f(and)g(h)m(ttp)h(t)m(yp)s(e) +g(\014les,)h(CFITSIO)e(generally)0 3134 y(lo)s(oks)j(for)g(a)g +(compressed)g(v)m(ersion)g(of)g(the)g(\014le)g(\014rst,)h(b)s(efore)e +(trying)h(to)h(op)s(en)e(the)h(uncompressed)e(\014le.)64 +b(By)0 3247 y(default,)37 b(CFITSIO)e(copies)h(\(and)g(uncompressed)e +(if)i(necessary\))g(the)g(ftp)f(or)h(h)m(ttp)g(FITS)f(\014le)g(in)m(to) +i(memory)0 3360 y(on)f(the)g(lo)s(cal)h(mac)m(hine)f(b)s(efore)g(op)s +(ening)f(it.)58 b(This)35 b(will)h(fail)g(if)g(the)g(lo)s(cal)h(mac)m +(hine)g(do)s(es)e(not)h(ha)m(v)m(e)h(enough)0 3473 y(memory)g(to)h +(hold)f(the)g(whole)h(FITS)e(\014le,)k(so)d(in)g(this)g(case,)k(the)c +(output)g(\014lename)g(sp)s(eci\014er)g(\(see)h(the)g(next)0 +3586 y(section\))32 b(can)f(b)s(e)e(used)h(to)h(further)e(con)m(trol)j +(ho)m(w)e(CFITSIO)f(reads)h(ftp)g(and)g(h)m(ttp)g(\014les.)0 +3746 y(If)i(the)h(input)f(\014le)h(is)g(an)g(IRAF)g(image)h(\014le)f +(\(*.imh)g(\014le\))h(then)e(CFITSIO)f(will)j(automatically)h(con)m(v)m +(ert)g(it)e(on)0 3859 y(the)27 b(\015y)g(in)m(to)h(a)g(virtual)f(FITS)f +(image)j(b)s(efore)e(it)g(is)g(op)s(ened)g(b)m(y)g(the)g(application)i +(program.)39 b(IRAF)27 b(images)i(can)0 3972 y(only)h(b)s(e)g(op)s +(ened)g(with)g(READONL)-8 b(Y)31 b(\014le)f(access.)0 +4132 y(Similarly)-8 b(,)32 b(if)f(the)g(input)f(\014le)i(is)f(a)g(ra)m +(w)g(binary)f(data)i(arra)m(y)-8 b(,)33 b(then)d(CFITSIO)g(will)h(con)m +(v)m(ert)i(it)e(on)g(the)h(\015y)e(in)m(to)0 4245 y(a)38 +b(virtual)g(FITS)g(image)h(with)e(the)h(basic)h(set)f(of)g(required)f +(header)h(k)m(eyw)m(ords)g(b)s(efore)g(it)g(is)g(op)s(ened)f(b)m(y)h +(the)0 4358 y(application)32 b(program)f(\(with)g(READONL)-8 +b(Y)31 b(access\).)44 b(In)30 b(this)h(case)h(the)f(data)g(t)m(yp)s(e)g +(and)g(dimensions)f(of)h(the)0 4471 y(image)d(m)m(ust)f(b)s(e)f(sp)s +(eci\014ed)g(in)h(square)g(brac)m(k)m(ets)h(follo)m(wing)g(the)f +(\014lename)g(\(e.g.)41 b(ra)m(w\014le.dat[ib512,512]\).)j(The)0 +4584 y(\014rst)30 b(c)m(haracter)i(\(case)f(insensitiv)m(e\))h +(de\014nes)e(the)g(datat)m(yp)s(e)h(of)g(the)g(arra)m(y:)239 +4810 y Fe(b)429 b(8-bit)46 b(unsigned)g(byte)239 4923 +y(i)381 b(16-bit)46 b(signed)g(integer)239 5036 y(u)381 +b(16-bit)46 b(unsigned)g(integer)239 5149 y(j)381 b(32-bit)46 +b(signed)g(integer)239 5262 y(r)h(or)g(f)143 b(32-bit)46 +b(floating)g(point)239 5375 y(d)381 b(64-bit)46 b(floating)g(point)0 +5601 y Fi(An)40 b(optional)h(second)f(c)m(haracter)i(sp)s(eci\014es)e +(the)h(b)m(yte)f(order)g(of)g(the)h(arra)m(y)g(v)-5 b(alues:)60 +b(b)40 b(or)g(B)h(indicates)g(big)0 5714 y(endian)f(\(as)h(in)f(FITS)f +(\014les)i(and)f(the)g(nativ)m(e)i(format)e(of)h(SUN)f(UNIX)h(w)m +(orkstations)g(and)f(Mac)i(PCs\))e(and)p eop end +%%Page: 86 92 +TeXDict begin 86 91 bop 0 299 a Fi(86)1618 b Fg(CHAPTER)30 +b(8.)112 b(EXTENDED)30 b(FILE)h(NAME)f(SYNT)-8 b(AX)0 +555 y Fi(l)41 b(or)g(L)g(indicates)g(little)i(endian)e(\(nativ)m(e)h +(format)g(of)f(DEC)f(OSF)h(w)m(orkstations)h(and)e(IBM)i(PCs\).)72 +b(If)40 b(this)0 668 y(c)m(haracter)32 b(is)e(omitted)i(then)e(the)g +(arra)m(y)h(is)g(assumed)e(to)i(ha)m(v)m(e)h(the)f(nativ)m(e)g(b)m(yte) +g(order)f(of)h(the)f(lo)s(cal)i(mac)m(hine.)0 781 y(These)d(datat)m(yp) +s(e)h(c)m(haracters)h(are)e(then)g(follo)m(w)m(ed)i(b)m(y)e(a)h(series) +f(of)g(one)h(or)f(more)g(in)m(teger)i(v)-5 b(alues)29 +b(separated)h(b)m(y)0 894 y(commas)h(whic)m(h)g(de\014ne)e(the)i(size)h +(of)e(eac)m(h)i(dimension)e(of)h(the)g(ra)m(w)f(arra)m(y)-8 +b(.)43 b(Arra)m(ys)30 b(with)h(up)e(to)j(5)f(dimensions)0 +1007 y(are)f(curren)m(tly)g(supp)s(orted.)38 b(Finally)-8 +b(,)32 b(a)e(b)m(yte)g(o\013set)g(to)h(the)e(p)s(osition)h(of)g(the)g +(\014rst)e(pixel)i(in)g(the)f(data)i(\014le)e(ma)m(y)0 +1120 y(b)s(e)d(sp)s(eci\014ed)g(b)m(y)h(separating)h(it)f(with)g(a)g +(':')39 b(from)27 b(the)g(last)g(dimension)g(v)-5 b(alue.)40 +b(If)26 b(omitted,)j(it)e(is)g(assumed)f(that)0 1233 +y(the)35 b(o\013set)h(=)f(0.)54 b(This)35 b(parameter)g(ma)m(y)h(b)s(e) +e(used)g(to)i(skip)e(o)m(v)m(er)i(an)m(y)g(header)e(information)i(in)e +(the)h(\014le)g(that)0 1346 y(precedes)30 b(the)h(binary)f(data.)41 +b(F)-8 b(urther)30 b(examples:)95 1603 y Fe(raw.dat[b10000])521 +b(1-dimensional)45 b(10000)h(pixel)g(byte)h(array)95 +1715 y(raw.dat[rb400,400,12])233 b(3-dimensional)45 b(floating)g(point) +h(big-endian)f(array)95 1828 y(img.fits[ib512,512:2880])89 +b(reads)47 b(the)g(512)g(x)g(512)g(short)f(integer)g(array)g(in)1336 +1941 y(a)i(FITS)e(file,)h(skipping)e(over)i(the)g(2880)g(byte)f(header) +0 2198 y Fi(One)25 b(sp)s(ecial)g(case)h(of)f(input)f(\014le)h(is)g +(where)g(the)g(\014lename)g(=)g(`-')h(\(a)f(dash)g(or)g(min)m(us)f +(sign\))h(or)g('stdin')g(or)g('stdout',)0 2311 y(whic)m(h)d +(signi\014es)h(that)h(the)f(input)e(\014le)i(is)g(to)h(b)s(e)e(read)g +(from)h(the)g(stdin)f(stream,)j(or)e(written)f(to)i(the)f(stdout)g +(stream)0 2424 y(if)34 b(a)g(new)g(output)f(\014le)h(is)g(b)s(eing)g +(created.)52 b(In)33 b(the)h(case)h(of)f(reading)h(from)e(stdin,)h +(CFITSIO)f(\014rst)g(copies)i(the)0 2537 y(whole)g(stream)h(in)m(to)g +(a)f(temp)s(orary)g(FITS)f(\014le)i(\(in)f(memory)g(or)g(on)g(disk\),)h +(and)f(subsequen)m(t)f(reading)h(of)h(the)0 2650 y(FITS)c(\014le)h(o)s +(ccurs)g(in)f(this)h(cop)m(y)-8 b(.)49 b(When)33 b(writing)g(to)g +(stdout,)h(CFITSIO)d(\014rst)h(constructs)h(the)g(whole)g(\014le)g(in)0 +2763 y(memory)h(\(since)i(random)d(access)j(is)e(required\),)i(then)e +(\015ushes)f(it)i(out)g(to)g(the)f(stdout)h(stream)g(when)e(the)i +(\014le)0 2876 y(is)30 b(closed.)42 b(In)29 b(addition,)i(if)f(the)g +(output)g(\014lename)g(=)g('-.gz')i(or)e('stdout.gz')h(then)f(it)h +(will)f(b)s(e)g(gzip)g(compressed)0 2989 y(b)s(efore)g(b)s(eing)g +(written)g(to)h(stdout.)0 3149 y(This)25 b(abilit)m(y)j(to)e(read)g +(and)f(write)h(on)g(the)g(stdin)g(and)f(stdout)h(steams)g(allo)m(ws)i +(FITS)d(\014les)h(to)g(b)s(e)g(pip)s(ed)e(b)s(et)m(w)m(een)0 +3262 y(tasks)42 b(in)f(memory)g(rather)g(than)h(ha)m(ving)g(to)g +(create)h(temp)s(orary)e(in)m(termediate)i(FITS)d(\014les)i(on)f(disk.) +73 b(F)-8 b(or)0 3375 y(example)28 b(if)e(task1)i(creates)h(an)e +(output)f(FITS)g(\014le,)i(and)f(task2)g(reads)g(an)g(input)f(FITS)g +(\014le,)i(the)f(FITS)f(\014le)h(ma)m(y)0 3487 y(b)s(e)j(pip)s(ed)f(b)s +(et)m(w)m(een)i(the)f(2)h(tasks)g(b)m(y)f(sp)s(ecifying)143 +3744 y Fe(task1)47 b(-)g(|)g(task2)g(-)0 4001 y Fi(where)30 +b(the)h(v)m(ertical)i(bar)e(is)f(the)h(Unix)g(piping)f(sym)m(b)s(ol.)42 +b(This)30 b(assumes)g(that)i(the)f(2)g(tasks)g(read)g(the)g(name)g(of)0 +4114 y(the)g(FITS)e(\014le)i(o\013)f(of)h(the)g(command)f(line.)0 +4448 y Fd(8.4)135 b(Output)45 b(File)g(Name)h(when)f(Op)t(ening)g(an)g +(Existing)h(File)0 4698 y Fi(An)36 b(optional)i(output)e(\014lename)h +(ma)m(y)h(b)s(e)e(sp)s(eci\014ed)g(in)g(paren)m(theses)h(immediately)h +(follo)m(wing)g(the)f(base)g(\014le)0 4811 y(name)28 +b(to)h(b)s(e)f(op)s(ened.)39 b(This)28 b(is)g(mainly)g(useful)g(in)g +(those)g(cases)i(where)d(CFITSIO)g(creates)j(a)e(temp)s(orary)g(cop)m +(y)0 4924 y(of)i(the)f(input)g(FITS)f(\014le)i(b)s(efore)f(it)h(is)f +(op)s(ened)g(and)f(passed)h(to)h(the)g(application)h(program.)40 +b(This)28 b(happ)s(ens)g(b)m(y)0 5036 y(default)i(when)g(op)s(ening)g +(a)g(net)m(w)m(ork)h(FTP)g(or)f(HTTP-t)m(yp)s(e)g(\014le,)h(when)e +(reading)h(a)h(compressed)f(FITS)g(\014le)g(on)0 5149 +y(a)36 b(lo)s(cal)h(disk,)g(when)e(reading)h(from)g(the)g(stdin)f +(stream,)j(or)d(when)g(a)i(column)e(\014lter,)j(ro)m(w)e(\014lter,)h +(or)f(binning)0 5262 y(sp)s(eci\014er)29 b(is)g(included)g(as)h(part)f +(of)g(the)h(input)f(\014le)g(sp)s(eci\014cation.)41 b(By)30 +b(default)g(this)f(temp)s(orary)g(\014le)g(is)h(created)0 +5375 y(in)g(memory)-8 b(.)41 b(If)29 b(there)h(is)g(not)g(enough)g +(memory)g(to)h(create)g(the)g(\014le)f(cop)m(y)-8 b(,)31 +b(then)f(CFITSIO)e(will)i(exit)h(with)f(an)0 5488 y(error.)45 +b(In)32 b(these)g(cases)h(one)g(can)f(force)h(a)f(p)s(ermanen)m(t)g +(\014le)g(to)h(b)s(e)e(created)i(on)f(disk,)g(instead)h(of)f(a)g(temp)s +(orary)0 5601 y(\014le)38 b(in)f(memory)-8 b(,)40 b(b)m(y)d(supplying)f +(the)i(name)g(in)f(paren)m(theses)h(immediately)h(follo)m(wing)g(the)e +(base)h(\014le)g(name.)0 5714 y(The)30 b(output)g(\014lename)g(can)h +(include)f(the)h(')10 b(!')41 b(clobb)s(er)30 b(\015ag.)p +eop end +%%Page: 87 93 +TeXDict begin 87 92 bop 0 299 a Fg(8.4.)72 b(OUTPUT)30 +b(FILE)g(NAME)h(WHEN)g(OPENING)f(AN)h(EXISTING)e(FILE)967 +b Fi(87)0 555 y(Th)m(us,)48 b(if)d(the)g(input)f(\014lename)h(to)g +(CFITSIO)f(is:)70 b Fe(file1.fits.gz\(file2.fit)o(s\))39 +b Fi(then)44 b(CFITSIO)g(will)0 668 y(uncompress)39 b +(`\014le1.\014ts.gz')j(in)m(to)f(the)f(lo)s(cal)h(disk)e(\014le)h +(`\014le2.\014ts')h(b)s(efore)f(op)s(ening)f(it.)70 b(CFITSIO)38 +b(do)s(es)i(not)0 781 y(automatically)33 b(delete)f(the)e(output)g +(\014le,)h(so)g(it)g(will)f(still)i(exist)f(after)g(the)f(application)i +(program)e(exits.)0 941 y(In)35 b(some)i(cases,)h(sev)m(eral)f +(di\013eren)m(t)g(temp)s(orary)e(FITS)h(\014les)g(will)g(b)s(e)f +(created)i(in)f(sequence,)i(for)e(instance,)i(if)0 1054 +y(one)f(op)s(ens)g(a)g(remote)h(\014le)f(using)g(FTP)-8 +b(,)37 b(then)g(\014lters)g(ro)m(ws)g(in)g(a)h(binary)e(table)i +(extension,)i(then)c(create)j(an)0 1167 y(image)f(b)m(y)f(binning)f(a)h +(pair)g(of)g(columns.)60 b(In)36 b(this)h(case,)j(the)d(remote)h +(\014le)f(will)g(b)s(e)f(copied)h(to)h(a)f(temp)s(orary)0 +1280 y(lo)s(cal)j(\014le,)h(then)d(a)h(second)f(temp)s(orary)h(\014le)f +(will)h(b)s(e)f(created)i(con)m(taining)g(the)e(\014ltered)h(ro)m(ws)f +(of)h(the)g(table,)0 1393 y(and)c(\014nally)g(a)h(third)e(temp)s(orary) +h(\014le)h(con)m(taining)g(the)g(binned)e(image)i(will)g(b)s(e)f +(created.)57 b(In)34 b(cases)i(lik)m(e)h(this)0 1506 +y(where)28 b(m)m(ultiple)h(\014les)f(are)h(created,)h(the)e(out\014le)h +(sp)s(eci\014er)f(will)g(b)s(e)g(in)m(terpreted)h(the)f(name)g(of)h +(the)f(\014nal)g(\014le)h(as)0 1619 y(describ)s(ed)g(b)s(elo)m(w,)i(in) +f(descending)g(priorit)m(y:)136 1869 y Fc(\017)46 b Fi(as)29 +b(the)g(name)g(of)g(the)g(\014nal)f(image)i(\014le)f(if)f(an)h(image)h +(within)e(a)h(single)g(binary)f(table)i(cell)g(is)e(op)s(ened)g(or)h +(if)227 1982 y(an)i(image)g(is)g(created)g(b)m(y)f(binning)g(a)g(table) +i(column.)136 2167 y Fc(\017)46 b Fi(as)33 b(the)f(name)h(of)f(the)h +(\014le)f(con)m(taining)i(the)e(\014ltered)g(table)i(if)e(a)h(column)f +(\014lter)g(and/or)g(a)h(ro)m(w)f(\014lter)h(are)227 +2280 y(sp)s(eci\014ed.)136 2464 y Fc(\017)46 b Fi(as)31 +b(the)f(name)h(of)f(the)h(lo)s(cal)h(cop)m(y)f(of)f(the)h(remote)g(FTP) +f(or)h(HTTP)e(\014le.)136 2649 y Fc(\017)46 b Fi(as)31 +b(the)g(name)g(of)g(the)f(uncompressed)g(v)m(ersion)h(of)g(the)f(FITS)g +(\014le,)h(if)g(a)g(compressed)f(FITS)g(\014le)h(on)g(lo)s(cal)227 +2762 y(disk)f(has)g(b)s(een)g(op)s(ened.)136 2946 y Fc(\017)46 +b Fi(otherwise,)31 b(the)g(output)f(\014lename)g(is)h(ignored.)0 +3197 y(The)e(output)f(\014le)h(sp)s(eci\014er)g(is)g(useful)f(when)g +(reading)h(FTP)g(or)g(HTTP-t)m(yp)s(e)g(FITS)f(\014les)h(since)g(it)h +(can)f(b)s(e)g(used)0 3310 y(to)34 b(create)i(a)e(lo)s(cal)h(disk)e +(cop)m(y)i(of)f(the)g(\014le)f(that)i(can)f(b)s(e)f(reused)g(in)g(the)h +(future.)50 b(If)33 b(the)h(output)g(\014le)f(name)h(=)0 +3423 y(`*')i(then)e(a)i(lo)s(cal)g(\014le)f(with)g(the)g(same)g(name)g +(as)g(the)h(net)m(w)m(ork)f(\014le)g(will)h(b)s(e)e(created.)56 +b(Note)36 b(that)f(CFITSIO)0 3535 y(will)30 b(b)s(eha)m(v)m(e)g +(di\013eren)m(tly)h(dep)s(ending)d(on)i(whether)f(the)h(remote)g +(\014le)g(is)g(compressed)f(or)h(not)g(as)g(sho)m(wn)f(b)m(y)h(the)0 +3648 y(follo)m(wing)i(examples:)136 3876 y Fc(\017)46 +b Fi(`ftp://remote.mac)m(hine/tmp/m)m(y\014le.\014ts.gz\(*\)')k(-)43 +b(the)g(remote)h(compressed)f(\014le)g(is)g(copied)h(to)g(the)227 +3988 y(lo)s(cal)26 b(compressed)e(\014le)g(`m)m(y\014le.\014ts.gz',)k +(whic)m(h)c(is)g(then)h(uncompressed)e(in)h(lo)s(cal)h(memory)f(b)s +(efore)g(b)s(eing)227 4101 y(op)s(ened)30 b(and)g(passed)g(to)h(the)f +(application)i(program.)136 4286 y Fc(\017)46 b Fi(`ftp://remote.mac)m +(hine/tmp/m)m(y\014le.\014ts.gz\(m)m(y\014le.\014ts\)')d(-)37 +b(the)g(remote)g(compressed)f(\014le)h(is)f(copied)227 +4399 y(and)h(uncompressed)g(in)m(to)h(the)g(lo)s(cal)h(\014le)f(`m)m +(y\014le.\014ts'.)64 b(This)36 b(example)j(requires)e(less)h(lo)s(cal)h +(memory)227 4512 y(than)30 b(the)h(previous)f(example)h(since)g(the)f +(\014le)h(is)f(uncompressed)f(on)h(disk)g(instead)h(of)f(in)h(memory)-8 +b(.)136 4696 y Fc(\017)46 b Fi(`ftp://remote.mac)m(hine/tmp/m)m +(y\014le.\014ts\(m)m(y\014le.\014ts.gz\)')28 b(-)21 b(this)g(will)h +(usually)f(pro)s(duce)f(an)h(error)g(since)227 4809 y(CFITSIO)29 +b(itself)i(cannot)g(compress)f(\014les.)0 5036 y(The)36 +b(exact)i(b)s(eha)m(vior)e(of)h(CFITSIO)e(in)h(the)h(latter)g(case)h +(dep)s(ends)c(on)j(the)f(t)m(yp)s(e)h(of)g(ftp)f(serv)m(er)g(running)f +(on)0 5149 y(the)c(remote)g(mac)m(hine)g(and)f(ho)m(w)g(it)h(is)f +(con\014gured.)40 b(In)30 b(some)h(cases,)g(if)f(the)h(\014le)f(`m)m +(y\014le.\014ts.gz')j(exists)e(on)f(the)0 5262 y(remote)38 +b(mac)m(hine,)h(then)e(the)g(serv)m(er)g(will)h(cop)m(y)f(it)h(to)f +(the)h(lo)s(cal)g(mac)m(hine.)61 b(In)36 b(other)h(cases)h(the)f(ftp)g +(serv)m(er)0 5375 y(will)f(automatically)j(create)e(and)f(transmit)g(a) +g(compressed)g(v)m(ersion)g(of)g(the)g(\014le)g(if)g(only)g(the)g +(uncompressed)0 5488 y(v)m(ersion)27 b(exists.)41 b(This)26 +b(can)h(get)h(rather)f(confusing,)h(so)f(users)f(should)g(use)h(a)g +(certain)h(amoun)m(t)g(of)f(caution)h(when)0 5601 y(using)34 +b(the)h(output)f(\014le)h(sp)s(eci\014er)f(with)h(FTP)f(or)h(HTTP)f +(\014le)h(t)m(yp)s(es,)h(to)f(mak)m(e)h(sure)e(they)h(get)h(the)f(b)s +(eha)m(vior)0 5714 y(that)c(they)g(exp)s(ect.)p eop end +%%Page: 88 94 +TeXDict begin 88 93 bop 0 299 a Fi(88)1618 b Fg(CHAPTER)30 +b(8.)112 b(EXTENDED)30 b(FILE)h(NAME)f(SYNT)-8 b(AX)0 +555 y Fd(8.5)135 b(T)-11 b(emplate)46 b(File)g(Name)f(when)g(Creating)h +(a)g(New)f(File)0 808 y Fi(When)38 b(a)h(new)f(FITS)g(\014le)h(is)g +(created)g(with)g(a)f(call)i(to)g(\014ts)p 2101 808 28 +4 v 32 w(create)p 2369 808 V 35 w(\014le,)g(the)f(name)g(of)g(a)g +(template)h(\014le)e(ma)m(y)0 921 y(b)s(e)h(supplied)g(in)h(paren)m +(theses)g(immediately)h(follo)m(wing)g(the)g(name)f(of)g(the)g(new)f +(\014le)h(to)h(b)s(e)e(created.)71 b(This)0 1034 y(template)27 +b(is)e(used)g(to)h(de\014ne)f(the)h(structure)f(of)h(one)f(or)h(more)g +(HDUs)g(in)f(the)h(new)f(\014le.)39 b(The)25 b(template)i(\014le)e(ma)m +(y)0 1147 y(b)s(e)32 b(another)h(FITS)f(\014le,)i(in)f(whic)m(h)f(case) +i(the)f(newly)g(created)h(\014le)f(will)g(ha)m(v)m(e)h(exactly)h(the)e +(same)g(k)m(eyw)m(ords)g(in)0 1260 y(eac)m(h)25 b(HDU)g(as)g(in)f(the)g +(template)i(FITS)d(\014le,)j(but)d(all)j(the)e(data)h(units)e(will)i(b) +s(e)f(\014lled)g(with)f(zeros.)40 b(The)24 b(template)0 +1373 y(\014le)i(ma)m(y)h(also)g(b)s(e)e(an)h(ASCI)s(I)e(text)j(\014le,) +g(where)f(eac)m(h)h(line)f(\(in)g(general\))i(describ)s(es)d(one)h +(FITS)f(k)m(eyw)m(ord)i(record.)0 1486 y(The)j(format)h(of)f(the)h +(ASCI)s(I)e(template)i(\014le)g(is)f(describ)s(ed)f(b)s(elo)m(w.)0 +1833 y Fd(8.6)135 b(Image)46 b(Tile-Compression)h(Sp)t(eci\014cation)0 +2086 y Fi(When)28 b(sp)s(ecifying)g(the)h(name)g(of)f(the)h(output)f +(FITS)g(\014le)g(to)h(b)s(e)f(created,)i(the)f(user)f(can)g(indicate)i +(that)f(images)0 2198 y(should)d(b)s(e)h(written)g(in)g +(tile-compressed)h(format)g(\(see)g(section)g(5.5,)h(\\Primary)e(Arra)m +(y)h(or)f(IMA)m(GE)h(Extension)0 2311 y(I/O)f(Routines"\))i(b)m(y)e +(enclosing)h(the)g(compression)f(parameters)h(in)f(square)g(brac)m(k)m +(ets)i(follo)m(wing)g(the)f(ro)s(ot)f(disk)0 2424 y(\014le)j(name.)41 +b(Here)31 b(are)g(some)g(examples)g(of)f(the)h(syn)m(tax)g(for)f(sp)s +(ecifying)g(tile-compressed)i(output)e(images:)191 2695 +y Fe(myfile.fit[compress])185 b(-)48 b(use)f(Rice)f(algorithm)g(and)h +(default)e(tile)i(size)191 2921 y(myfile.fit[compress)42 +b(GZIP])47 b(-)g(use)g(the)g(specified)e(compression)g(algorithm;)191 +3034 y(myfile.fit[compress)d(Rice])238 b(only)46 b(the)h(first)g +(letter)f(of)h(the)g(algorithm)191 3147 y(myfile.fit[compress)42 +b(PLIO])238 b(name)46 b(is)i(required.)191 3373 y(myfile.fit[compress) +42 b(Rice)47 b(100,100])141 b(-)48 b(use)e(100)h(x)h(100)f(pixel)f +(tile)h(size)191 3486 y(myfile.fit[compress)42 b(Rice)47 +b(100,100;2])e(-)j(as)f(above,)f(and)h(use)g(noisebits)e(=)i(2)0 +3833 y Fd(8.7)135 b(HDU)46 b(Lo)t(cation)f(Sp)t(eci\014cation)0 +4086 y Fi(The)c(optional)h(HDU)h(lo)s(cation)g(sp)s(eci\014er)d +(de\014nes)h(whic)m(h)g(HDU)h(\(Header-Data)i(Unit,)h(also)d(kno)m(wn)f +(as)h(an)0 4199 y(`extension'\))36 b(within)d(the)i(FITS)e(\014le)h(to) +h(initially)h(op)s(en.)51 b(It)34 b(m)m(ust)g(immediately)i(follo)m(w)f +(the)f(base)h(\014le)f(name)0 4312 y(\(or)g(the)g(output)g(\014le)g +(name)f(if)h(presen)m(t\).)52 b(If)33 b(it)h(is)g(not)g(sp)s(eci\014ed) +g(then)f(the)h(\014rst)f(HDU)i(\(the)f(primary)f(arra)m(y\))0 +4425 y(is)g(op)s(ened.)46 b(The)32 b(HDU)h(lo)s(cation)h(sp)s +(eci\014er)e(is)h(required)f(if)g(the)h(colFilter,)i(ro)m(wFilter,)g +(or)e(binSp)s(ec)e(sp)s(eci\014ers)0 4538 y(are)f(presen)m(t,)f(b)s +(ecause)h(the)f(primary)f(arra)m(y)i(is)f(not)h(a)f(v)-5 +b(alid)30 b(HDU)g(for)f(these)g(op)s(erations.)41 b(The)29 +b(HDU)h(ma)m(y)g(b)s(e)0 4650 y(sp)s(eci\014ed)e(either)i(b)m(y)e +(absolute)i(p)s(osition)f(n)m(um)m(b)s(er,)f(starting)i(with)e(0)i(for) +e(the)h(primary)f(arra)m(y)-8 b(,)31 b(or)e(b)m(y)f(reference)0 +4763 y(to)h(the)g(HDU)g(name,)g(and)f(optionally)-8 b(,)31 +b(the)e(v)m(ersion)g(n)m(um)m(b)s(er)e(and)h(the)h(HDU)g(t)m(yp)s(e)g +(of)f(the)h(desired)f(extension.)0 4876 y(The)k(lo)s(cation)h(of)f(an)g +(image)i(within)d(a)i(single)f(cell)i(of)e(a)g(binary)g(table)h(ma)m(y) +f(also)h(b)s(e)f(sp)s(eci\014ed,)g(as)g(describ)s(ed)0 +4989 y(b)s(elo)m(w.)0 5149 y(The)26 b(absolute)h(p)s(osition)f(of)g +(the)h(extension)g(is)f(sp)s(eci\014ed)f(either)i(b)m(y)f(enclosed)h +(the)g(n)m(um)m(b)s(er)e(in)h(square)f(brac)m(k)m(ets)0 +5262 y(\(e.g.,)k(`[1]')g(=)d(the)h(\014rst)f(extension)h(follo)m(wing)i +(the)e(primary)e(arra)m(y\))j(or)f(b)m(y)f(preceded)h(the)g(n)m(um)m(b) +s(er)e(with)i(a)g(plus)0 5375 y(sign)37 b(\(`+1'\).)63 +b(T)-8 b(o)38 b(sp)s(ecify)f(the)g(HDU)h(b)m(y)g(name,)h(giv)m(e)g(the) +e(name)h(of)f(the)h(desired)f(HDU)h(\(the)f(v)-5 b(alue)38 +b(of)g(the)0 5488 y(EXTNAME)e(or)g(HDUNAME)h(k)m(eyw)m(ord\))g(and)f +(optionally)h(the)f(extension)h(v)m(ersion)f(n)m(um)m(b)s(er)f(\(v)-5 +b(alue)37 b(of)f(the)0 5601 y(EXTVER)27 b(k)m(eyw)m(ord\))i(and)e(the)h +(extension)h(t)m(yp)s(e)e(\(v)-5 b(alue)29 b(of)f(the)g(XTENSION)f(k)m +(eyw)m(ord:)40 b(IMA)m(GE,)29 b(ASCI)s(I)d(or)0 5714 +y(T)-8 b(ABLE,)36 b(or)f(BINT)-8 b(ABLE\),)36 b(separated)f(b)m(y)g +(commas)h(and)e(all)i(enclosed)g(in)f(square)g(brac)m(k)m(ets.)56 +b(If)34 b(the)h(v)-5 b(alue)p eop end +%%Page: 89 95 +TeXDict begin 89 94 bop 0 299 a Fg(8.8.)72 b(IMA)m(GE)31 +b(SECTION)2835 b Fi(89)0 555 y(of)34 b(EXTVER)f(and)f(XTENSION)h(are)h +(not)f(sp)s(eci\014ed,)h(then)f(the)h(\014rst)e(extension)j(with)e(the) +g(correct)i(v)-5 b(alue)34 b(of)0 668 y(EXTNAME)39 b(is)g(op)s(ened.)67 +b(The)38 b(extension)i(name)f(and)f(t)m(yp)s(e)i(are)f(not)h(case)g +(sensitiv)m(e,)j(and)38 b(the)h(extension)0 781 y(t)m(yp)s(e)29 +b(ma)m(y)g(b)s(e)f(abbreviated)h(to)g(a)g(single)g(letter)h(\(e.g.,)h +(I)d(=)g(IMA)m(GE)i(extension)f(or)f(primary)g(arra)m(y)-8 +b(,)30 b(A)f(or)f(T)g(=)0 894 y(ASCI)s(I)d(table)i(extension,)h(and)e +(B)h(=)f(binary)g(table)h(BINT)-8 b(ABLE)27 b(extension\).)41 +b(If)26 b(the)g(HDU)h(lo)s(cation)i(sp)s(eci\014er)0 +1007 y(is)h(equal)h(to)g(`[PRIMAR)-8 b(Y]')32 b(or)f(`[P]',)g(then)f +(the)h(primary)e(arra)m(y)i(\(the)g(\014rst)f(HDU\))h(will)g(b)s(e)f +(op)s(ened.)0 1167 y(FITS)k(images)i(are)f(most)h(commonly)f(stored)g +(in)g(the)g(primary)f(arra)m(y)h(or)g(an)g(image)h(extension,)h(but)d +(images)0 1280 y(can)d(also)h(b)s(e)e(stored)h(as)h(a)f(v)m(ector)h(in) +f(a)g(single)h(cell)g(of)f(a)h(binary)e(table)i(\(i.e.)43 +b(eac)m(h)32 b(ro)m(w)f(of)g(the)h(v)m(ector)g(column)0 +1393 y(con)m(tains)d(a)g(di\013eren)m(t)f(image\).)42 +b(Suc)m(h)27 b(an)h(image)i(can)e(b)s(e)g(op)s(ened)f(with)h(CFITSIO)e +(b)m(y)i(sp)s(ecifying)g(the)g(desired)0 1506 y(column)k(name)g(and)f +(the)h(ro)m(w)g(n)m(um)m(b)s(er)f(after)h(the)g(binary)f(table)i(HDU)g +(sp)s(eci\014er)e(as)h(sho)m(wn)g(in)f(the)h(follo)m(wing)0 +1619 y(examples.)71 b(The)40 b(column)g(name)h(is)f(separated)h(from)f +(the)h(HDU)g(sp)s(eci\014er)f(b)m(y)g(a)h(semicolon)g(and)f(the)h(ro)m +(w)0 1732 y(n)m(um)m(b)s(er)29 b(is)h(enclosed)h(in)e(paren)m(theses.) +41 b(In)30 b(this)g(case)h(CFITSIO)d(copies)j(the)f(image)i(from)d(the) +i(table)g(cell)g(in)m(to)0 1844 y(a)h(temp)s(orary)e(primary)h(arra)m +(y)g(b)s(efore)g(it)h(is)f(op)s(ened.)43 b(The)30 b(application)j +(program)e(then)g(just)g(sees)g(the)h(image)0 1957 y(in)i(the)h +(primary)e(arra)m(y)-8 b(,)37 b(without)d(an)m(y)h(extensions.)53 +b(The)34 b(particular)g(ro)m(w)h(to)g(b)s(e)e(op)s(ened)h(ma)m(y)h(b)s +(e)f(sp)s(eci\014ed)0 2070 y(either)28 b(b)m(y)f(giving)h(an)f +(absolute)h(in)m(teger)h(ro)m(w)f(n)m(um)m(b)s(er)e(\(starting)i(with)f +(1)h(for)f(the)g(\014rst)g(ro)m(w\),)i(or)e(b)m(y)g(sp)s(ecifying)0 +2183 y(a)33 b(b)s(o)s(olean)f(expression)g(that)h(ev)-5 +b(aluates)34 b(to)f(TR)m(UE)g(for)f(the)g(desired)g(ro)m(w.)47 +b(The)32 b(\014rst)f(ro)m(w)i(that)g(satis\014es)g(the)0 +2296 y(expression)28 b(will)g(b)s(e)g(used.)39 b(The)28 +b(ro)m(w)g(selection)i(expression)e(has)g(the)g(same)g(syn)m(tax)h(as)f +(describ)s(ed)f(in)h(the)g(Ro)m(w)0 2409 y(Filter)k(Sp)s(eci\014er)d +(section,)j(b)s(elo)m(w.)0 2569 y(Examples:)143 2811 +y Fe(myfile.fits[3])44 b(-)k(open)e(the)h(3rd)g(HDU)g(following)e(the)i +(primary)f(array)143 2924 y(myfile.fits+3)92 b(-)48 b(same)e(as)h +(above,)f(but)h(using)g(the)g(FTOOLS-style)d(notation)143 +3037 y(myfile.fits[EVENTS])f(-)k(open)g(the)g(extension)e(that)i(has)g +(EXTNAME)e(=)j('EVENTS')143 3150 y(myfile.fits[EVENTS,)43 +b(2])95 b(-)47 b(same)g(as)g(above,)f(but)h(also)g(requires)e(EXTVER)h +(=)i(2)143 3263 y(myfile.fits[events,2,b])42 b(-)47 b(same,)f(but)h +(also)g(requires)f(XTENSION)f(=)j('BINTABLE')143 3376 +y(myfile.fits[3;)c(images\(17\)])h(-)i(opens)g(the)g(image)f(in)h(row)g +(17)g(of)g(the)g('images')1527 3489 y(column)f(in)i(the)e(3rd)h +(extension)f(of)h(the)g(file.)143 3602 y(myfile.fits[3;)d +(images\(exposure)g(>)j(100\)])g(-)g(as)g(above,)f(but)h(opens)g(the)f +(image)907 3714 y(in)h(the)g(first)f(row)h(that)g(has)g(an)g +('exposure')e(column)h(value)907 3827 y(greater)g(than)g(100.)0 +4158 y Fd(8.8)135 b(Image)46 b(Section)0 4408 y Fi(A)41 +b(virtual)g(\014le)f(con)m(taining)i(a)f(rectangular)h(subsection)e(of) +h(an)g(image)g(can)g(b)s(e)f(extracted)i(and)e(op)s(ened)g(b)m(y)0 +4521 y(sp)s(ecifying)32 b(the)h(range)g(of)g(pixels)g(\(start:end\))g +(along)h(eac)m(h)g(axis)f(to)g(b)s(e)f(extracted)i(from)e(the)h +(original)g(image.)0 4634 y(One)d(can)h(also)h(sp)s(ecify)e(an)h +(optional)h(pixel)f(incremen)m(t)g(\(start:end:step\))h(for)f(eac)m(h)h +(axis)f(of)g(the)g(input)e(image.)0 4747 y(A)f(pixel)f(step)h(=)f(1)h +(will)g(b)s(e)f(assumed)f(if)i(it)g(is)f(not)h(sp)s(eci\014ed.)39 +b(If)27 b(the)h(start)g(pixel)g(is)f(larger)i(then)e(the)h(end)e +(pixel,)0 4860 y(then)32 b(the)g(image)h(will)f(b)s(e)f(\015ipp)s(ed)f +(\(pro)s(ducing)h(a)h(mirror)g(image\))h(along)g(that)f(dimension.)45 +b(An)32 b(asterisk,)h('*',)0 4973 y(ma)m(y)39 b(b)s(e)e(used)h(to)h(sp) +s(ecify)f(the)g(en)m(tire)h(range)g(of)f(an)h(axis,)i(and)c('-*')j +(will)e(\015ip)g(the)g(en)m(tire)h(axis.)65 b(The)38 +b(input)0 5086 y(image)31 b(can)f(b)s(e)f(in)g(the)h(primary)f(arra)m +(y)-8 b(,)31 b(in)e(an)g(image)i(extension,)g(or)f(con)m(tained)g(in)g +(a)g(v)m(ector)h(cell)g(of)f(a)g(binary)0 5199 y(table.)40 +b(In)25 b(the)h(later)h(2)f(cases)h(the)f(extension)h(name)f(or)f(n)m +(um)m(b)s(er)g(m)m(ust)h(b)s(e)f(sp)s(eci\014ed)g(b)s(efore)h(the)g +(image)h(section)0 5312 y(sp)s(eci\014er.)0 5472 y(Examples:)95 +5714 y Fe(myfile.fits[1:512:2,)43 b(2:512:2])i(-)95 b(open)47 +b(a)h(256x256)d(pixel)i(image)p eop end +%%Page: 90 96 +TeXDict begin 90 95 bop 0 299 a Fi(90)1618 b Fg(CHAPTER)30 +b(8.)112 b(EXTENDED)30 b(FILE)h(NAME)f(SYNT)-8 b(AX)668 +555 y Fe(consisting)45 b(of)i(the)g(odd)g(numbered)f(columns)g(\(1st)g +(axis\))h(and)668 668 y(the)g(even)g(numbered)e(rows)i(\(2nd)g(axis\))f +(of)h(the)g(image)f(in)i(the)668 781 y(primary)e(array)g(of)i(the)e +(file.)95 1007 y(myfile.fits[*,)e(512:256])i(-)h(open)g(an)g(image)g +(consisting)e(of)i(all)g(the)g(columns)668 1120 y(in)g(the)g(input)g +(image,)f(but)h(only)f(rows)h(256)g(through)f(512.)668 +1233 y(The)h(image)f(will)h(be)g(flipped)f(along)g(the)h(2nd)g(axis)g +(since)668 1346 y(the)g(starting)f(pixel)g(is)h(greater)f(than)h(the)g +(ending)f(pixel.)95 1571 y(myfile.fits[*:2,)e(512:256:2])h(-)i(same)g +(as)g(above)f(but)h(keeping)f(only)668 1684 y(every)h(other)f(row)h +(and)g(column)f(in)h(the)g(input)f(image.)95 1910 y(myfile.fits[-*,)e +(*])j(-)h(copy)e(the)h(entire)f(image,)g(flipping)g(it)h(along)668 +2023 y(the)g(first)f(axis.)95 2249 y(myfile.fits[3][1:256,1:256)o(])c +(-)47 b(opens)g(a)g(subsection)e(of)i(the)g(image)g(that)668 +2362 y(is)g(in)h(the)e(3rd)h(extension)f(of)h(the)g(file.)95 +2588 y(myfile.fits[4;)d(images\(12\)][1:10,1:10])e(-)48 +b(open)e(an)h(image)g(consisting)286 2700 y(of)h(the)e(first)h(10)g +(pixels)f(in)h(both)g(dimensions.)e(The)i(original)286 +2813 y(image)g(resides)f(in)h(the)g(12th)f(row)h(of)g(the)g('images')f +(vector)286 2926 y(column)g(in)i(the)f(table)f(in)h(the)g(4th)g +(extension)e(of)i(the)g(file.)0 3203 y Fi(When)23 b(CFITSIO)f(op)s(ens) +h(an)g(image)h(section)h(it)f(\014rst)f(creates)h(a)g(temp)s(orary)f +(\014le)h(con)m(taining)h(the)e(image)i(section)0 3315 +y(plus)32 b(a)i(cop)m(y)g(of)g(an)m(y)g(other)f(HDUs)h(in)f(the)h +(\014le.)50 b(This)32 b(temp)s(orary)h(\014le)h(is)f(then)g(op)s(ened)g +(b)m(y)g(the)h(application)0 3428 y(program,)28 b(so)g(it)g(is)f(not)h +(p)s(ossible)f(to)h(write)g(to)g(or)g(mo)s(dify)f(the)g(input)g(\014le) +g(when)g(sp)s(ecifying)g(an)h(image)h(section.)0 3541 +y(Note)39 b(that)f(CFITSIO)e(automatically)k(up)s(dates)d(the)g(w)m +(orld)h(co)s(ordinate)g(system)g(k)m(eyw)m(ords)f(in)g(the)h(header)0 +3654 y(of)33 b(the)h(image)g(section,)h(if)e(they)h(exist,)h(so)e(that) +h(the)f(co)s(ordinate)h(asso)s(ciated)h(with)e(eac)m(h)h(pixel)f(in)g +(the)h(image)0 3767 y(section)e(will)e(b)s(e)g(computed)g(correctly)-8 +b(.)0 4120 y Fd(8.9)135 b(Image)46 b(T)-11 b(ransform)45 +b(Filters)0 4374 y Fi(CFITSIO)33 b(can)h(apply)g(a)h(user-sp)s +(eci\014ed)e(mathematical)j(function)e(to)h(the)g(v)-5 +b(alue)34 b(of)h(ev)m(ery)g(pixel)f(in)g(a)h(FITS)0 4487 +y(image,)29 b(th)m(us)e(creating)h(a)g(new)e(virtual)h(image)i(in)d +(computer)h(memory)g(that)h(is)f(then)f(op)s(ened)h(and)f(read)h(b)m(y) +g(the)0 4600 y(application)32 b(program.)40 b(The)30 +b(original)i(FITS)d(image)j(is)e(not)h(mo)s(di\014ed)e(b)m(y)h(this)h +(pro)s(cess.)0 4760 y(The)20 b(image)j(transformation)e(sp)s(eci\014er) +f(is)h(app)s(ended)e(to)j(the)f(input)f(FITS)h(\014le)g(name)g(and)f +(is)h(enclosed)h(in)e(square)0 4873 y(brac)m(k)m(ets.)42 +b(It)29 b(b)s(egins)f(with)h(the)g(letters)i('PIX')e(to)h(distinguish)e +(it)i(from)e(other)i(t)m(yp)s(es)f(of)g(FITS)f(\014le)h(\014lters)g +(that)0 4986 y(are)36 b(recognized)i(b)m(y)e(CFITSIO.)e(The)i(image)h +(transforming)f(function)f(ma)m(y)i(use)f(an)m(y)g(of)g(the)h +(mathematical)0 5099 y(op)s(erators)44 b(listed)h(in)f(the)h(follo)m +(wing)h('Ro)m(w)f(Filtering)g(Sp)s(eci\014cation')g(section)h(of)e +(this)h(do)s(cumen)m(t.)82 b(Some)0 5212 y(examples)31 +b(of)f(image)i(transform)e(\014lters)g(are:)48 5488 y +Fe([pix)46 b(X)i(*)f(2.0])715 b(-)48 b(multiply)d(each)i(pixel)f(by)h +(2.0)48 5601 y([pix)f(sqrt\(X\)])714 b(-)48 b(take)e(the)h(square)f +(root)h(of)g(each)g(pixel)48 5714 y([pix)f(X)i(+)f(#ZEROPT)571 +b(-)48 b(add)e(the)h(value)g(of)g(the)g(ZEROPT)f(keyword)p +eop end +%%Page: 91 97 +TeXDict begin 91 96 bop 0 299 a Fg(8.9.)72 b(IMA)m(GE)31 +b(TRANSF)m(ORM)g(FIL)-8 b(TERS)2237 b Fi(91)48 555 y +Fe([pix)46 b(X>0)h(?)h(log10\(X\))d(:)j(-99.])e(-)i(if)f(the)g(pixel)f +(value)g(is)i(greater)1480 668 y(than)e(0,)h(compute)f(the)h(base)g(10) +g(log,)1480 781 y(else)f(set)h(the)g(pixel)f(=)i(-99.)0 +1013 y Fi(Use)24 b(the)g(letter)h('X')f(in)f(the)h(expression)g(to)g +(represen)m(t)g(the)g(curren)m(t)f(pixel)h(v)-5 b(alue)24 +b(in)f(the)h(image.)40 b(The)23 b(expression)0 1126 y(is)38 +b(ev)-5 b(aluated)39 b(indep)s(enden)m(tly)e(for)g(eac)m(h)i(pixel)f +(in)g(the)g(image)h(and)e(ma)m(y)h(b)s(e)g(a)g(function)f(of)h(1\))h +(the)f(original)0 1239 y(pixel)32 b(v)-5 b(alue,)32 b(2\))g(the)f(v)-5 +b(alue)32 b(of)f(other)h(pixels)f(in)g(the)g(image)i(at)f(a)f(giv)m(en) +i(relativ)m(e)g(o\013set)f(from)f(the)g(p)s(osition)h(of)0 +1352 y(the)d(pixel)f(that)h(is)g(b)s(eing)f(ev)-5 b(aluated,)30 +b(and)e(3\))h(the)g(v)-5 b(alue)29 b(of)f(an)m(y)h(header)f(k)m(eyw)m +(ords.)41 b(Header)29 b(k)m(eyw)m(ord)g(v)-5 b(alues)0 +1465 y(are)31 b(represen)m(ted)f(b)m(y)g(the)h(name)f(of)h(the)f(k)m +(eyw)m(ord)h(preceded)f(b)m(y)h(the)f('#')h(sign.)0 1625 +y(T)-8 b(o)35 b(access)h(the)f(the)g(v)-5 b(alue)35 b(of)g(adjacen)m(t) +h(pixels)f(in)f(the)h(image,)i(sp)s(ecify)e(the)g(\(1-D\))h(o\013set)g +(from)e(the)h(curren)m(t)0 1738 y(pixel)c(in)f(curly)g(brac)m(k)m(ets.) +42 b(F)-8 b(or)31 b(example)48 1970 y Fe([pix)94 b(\(x{-1})46 +b(+)i(x)f(+)h(x{+1}\))e(/)h(3])0 2202 y Fi(will)25 b(replace)g(eac)m(h) +h(pixel)f(v)-5 b(alue)25 b(with)f(the)h(running)e(mean)i(of)f(the)h(v) +-5 b(alues)25 b(of)g(that)g(pixel)g(and)f(it's)h(2)g(neigh)m(b)s(oring) +0 2314 y(pixels.)40 b(Note)30 b(that)g(in)e(this)g(notation)i(the)f +(image)h(is)f(treated)g(as)g(a)g(1-D)h(arra)m(y)-8 b(,)30 +b(where)e(eac)m(h)i(ro)m(w)f(of)g(the)g(image)0 2427 +y(\(or)c(higher)f(dimensional)g(cub)s(e\))h(is)f(app)s(ended)f(one)h +(after)h(another)g(in)f(one)h(long)g(arra)m(y)g(of)f(pixels.)39 +b(It)25 b(is)f(p)s(ossible)0 2540 y(to)35 b(refer)f(to)h(pixels)f(in)g +(the)g(ro)m(ws)g(ab)s(o)m(v)m(e)h(or)g(b)s(elo)m(w)f(the)g(curren)m(t)g +(pixel)h(b)m(y)f(using)f(the)h(v)-5 b(alue)35 b(of)f(the)h(NAXIS1)0 +2653 y(header)30 b(k)m(eyw)m(ord.)41 b(F)-8 b(or)32 b(example)48 +2885 y Fe([pix)46 b(\(x{-#NAXIS1})f(+)i(x)h(+)f(x{#NAXIS1}\))e(/)i(3])0 +3117 y Fi(will)34 b(compute)f(the)h(mean)f(of)g(eac)m(h)i(image)f +(pixel)g(and)e(the)i(pixels)f(immediately)i(ab)s(o)m(v)m(e)f(and)f(b)s +(elo)m(w)g(it)h(in)f(the)0 3230 y(adjacen)m(t)27 b(ro)m(ws)f(of)g(the)f +(image.)41 b(The)25 b(follo)m(wing)i(more)f(complex)h(example)f +(creates)h(a)f(smo)s(othed)g(virtual)g(image)0 3343 y(where)k(eac)m(h)h +(pixel)g(is)g(a)f(3)h(x)f(3)h(b)s(o)m(xcar)g(a)m(v)m(erage)i(of)d(the)h +(input)e(image)j(pixels:)95 3575 y Fe([pix)47 b(\(X)g(+)h(X{-1})e(+)i +(X{+1})286 3688 y(+)g(X{-#NAXIS1})d(+)i(X{-#NAXIS1)e(-)i(1})h(+)f +(X{-#NAXIS1)e(+)j(1})286 3801 y(+)g(X{#NAXIS1})d(+)i(X{#NAXIS1)f(-)h +(1})g(+)h(X{#NAXIS1)d(+)i(1}\))g(/)h(9.])0 4033 y Fi(If)31 +b(the)h(pixel)g(o\013set)h(extends)f(b)s(ey)m(ond)f(the)h(\014rst)f(or) +h(last)h(pixel)f(in)f(the)h(image,)i(the)e(function)g(will)g(ev)-5 +b(aluate)33 b(to)0 4145 y(unde\014ned,)28 b(or)j(NULL.)0 +4306 y(F)-8 b(or)39 b(complex)g(or)g(commonly)g(used)e(image)j +(\014ltering)f(op)s(erations,)i(one)d(can)h(write)g(the)f(expression)h +(in)m(to)g(an)0 4419 y(external)i(text)h(\014le)f(and)f(then)g(imp)s +(ort)g(it)h(in)m(to)h(the)e(\014lter)h(using)f(the)h(syn)m(tax)g('[pix) +g(@\014lename.txt]'.)72 b(The)0 4531 y(mathematical)29 +b(expression)e(can)g(extend)g(o)m(v)m(er)i(m)m(ultiple)e(lines)g(of)h +(text)g(in)e(the)h(\014le.)40 b(An)m(y)27 b(lines)g(in)g(the)g +(external)0 4644 y(text)h(\014le)e(that)i(b)s(egin)e(with)g(2)h(slash)f +(c)m(haracters)i(\('//'\))h(will)e(b)s(e)f(ignored)h(and)f(ma)m(y)h(b)s +(e)f(used)g(to)h(add)f(commen)m(ts)0 4757 y(in)m(to)31 +b(the)g(\014le.)0 4917 y(By)c(default,)g(the)f(datat)m(yp)s(e)i(of)e +(the)g(resulting)h(image)g(will)g(b)s(e)e(the)i(same)f(as)h(the)f +(original)i(image,)g(but)e(one)g(ma)m(y)0 5030 y(force)31 +b(a)g(di\013eren)m(t)g(datat)m(yp)s(e)g(b)m(y)f(app)s(ended)f(a)h(co)s +(de)h(letter)h(to)f(the)f('pix')h(k)m(eyw)m(ord:)286 +5262 y Fe(pixb)95 b(-)g(8-bit)46 b(byte)190 b(image)46 +b(with)h(BITPIX)f(=)143 b(8)286 5375 y(pixi)95 b(-)47 +b(16-bit)f(integer)g(image)g(with)h(BITPIX)f(=)95 b(16)286 +5488 y(pixj)g(-)47 b(32-bit)f(integer)g(image)g(with)h(BITPIX)f(=)95 +b(32)286 5601 y(pixr)g(-)47 b(32-bit)f(float)142 b(image)46 +b(with)h(BITPIX)f(=)i(-32)286 5714 y(pixd)95 b(-)47 b(64-bit)f(float) +142 b(image)46 b(with)h(BITPIX)f(=)i(-64)p eop end +%%Page: 92 98 +TeXDict begin 92 97 bop 0 299 a Fi(92)1618 b Fg(CHAPTER)30 +b(8.)112 b(EXTENDED)30 b(FILE)h(NAME)f(SYNT)-8 b(AX)0 +555 y Fi(Also)23 b(b)m(y)f(default,)j(an)m(y)d(other)h(HDUs)g(in)f(the) +g(input)g(\014le)g(will)h(b)s(e)e(copied)i(without)g(c)m(hange)g(to)g +(the)g(output)f(virtual)0 668 y(FITS)k(\014le,)h(but)f(one)g(ma)m(y)h +(discard)f(the)h(other)f(HDUs)h(b)m(y)f(adding)g(the)h(n)m(um)m(b)s(er) +e('1')i(to)g(the)g('pix')f(k)m(eyw)m(ord)h(\(and)0 781 +y(follo)m(wing)32 b(an)m(y)f(optional)g(datat)m(yp)s(e)g(co)s(de)g +(letter\).)42 b(F)-8 b(or)32 b(example:)239 1044 y Fe +(myfile.fits[3][pixr1)90 b(sqrt\(X\)])0 1307 y Fi(will)23 +b(create)i(a)e(virtual)g(FITS)f(\014le)h(con)m(taining)h(only)f(a)g +(primary)f(arra)m(y)i(image)g(with)e(32-bit)i(\015oating)g(p)s(oin)m(t) +f(pixels)0 1420 y(that)29 b(ha)m(v)m(e)h(a)f(v)-5 b(alue)30 +b(equal)f(to)g(the)g(square)g(ro)s(ot)g(of)g(the)g(pixels)f(in)h(the)g +(image)h(that)f(is)g(in)f(the)h(3rd)f(extension)i(of)0 +1533 y(the)h('m)m(y\014le.\014ts')g(\014le.)0 1870 y +Fd(8.10)136 b(Column)45 b(and)f(Keyw)l(ord)i(Filtering)g(Sp)t +(eci\014cation)0 2121 y Fi(The)27 b(optional)i(column/k)m(eyw)m(ord)g +(\014ltering)f(sp)s(eci\014er)f(is)h(used)f(to)i(mo)s(dify)e(the)h +(column)g(structure)f(and/or)h(the)0 2234 y(header)38 +b(k)m(eyw)m(ords)h(in)f(the)h(HDU)g(that)h(w)m(as)f(selected)h(with)e +(the)h(previous)f(HDU)h(lo)s(cation)h(sp)s(eci\014er.)65 +b(This)0 2347 y(\014ltering)42 b(sp)s(eci\014er)f(m)m(ust)h(b)s(e)f +(enclosed)i(in)e(square)h(brac)m(k)m(ets)h(and)e(can)h(b)s(e)f +(distinguished)g(from)h(a)g(general)0 2460 y(ro)m(w)d(\014lter)g(sp)s +(eci\014er)f(\(describ)s(ed)g(b)s(elo)m(w\))h(b)m(y)g(the)g(fact)h +(that)f(it)g(b)s(egins)f(with)h(the)g(string)g('col)h(')f(and)f(is)h +(not)0 2573 y(immediately)30 b(follo)m(w)m(ed)g(b)m(y)e(an)g(equals)h +(sign.)40 b(The)28 b(original)h(\014le)f(is)h(not)f(c)m(hanged)h(b)m(y) +f(this)h(\014ltering)f(op)s(eration,)0 2686 y(and)40 +b(instead)h(the)g(mo)s(di\014cations)g(are)g(made)f(on)h(a)g(cop)m(y)g +(of)g(the)g(input)f(FITS)g(\014le)g(\(usually)h(in)f(memory\),)0 +2799 y(whic)m(h)33 b(also)h(con)m(tains)g(a)f(cop)m(y)h(of)f(all)h(the) +g(other)f(HDUs)h(in)e(the)h(\014le.)49 b(This)33 b(temp)s(orary)f +(\014le)h(is)g(passed)g(to)h(the)0 2912 y(application)f(program)f(and)f +(will)h(p)s(ersist)f(only)h(un)m(til)g(the)g(\014le)g(is)g(closed)h(or) +f(un)m(til)g(the)g(program)f(exits,)j(unless)0 3025 y(the)d(out\014le)f +(sp)s(eci\014er)g(\(see)h(ab)s(o)m(v)m(e\))h(is)f(also)g(supplied.)0 +3185 y(The)g(column/k)m(eyw)m(ord)h(\014lter)f(can)g(b)s(e)g(used)f(to) +i(p)s(erform)e(the)i(follo)m(wing)g(op)s(erations.)44 +b(More)32 b(than)f(one)g(op)s(er-)0 3298 y(ation)g(ma)m(y)g(b)s(e)f(sp) +s(eci\014ed)g(b)m(y)g(separating)h(them)f(with)h(commas)f(or)h +(semi-colons.)136 3561 y Fc(\017)46 b Fi(Cop)m(y)36 b(only)g(a)g(sp)s +(eci\014ed)g(list)g(of)g(columns)g(columns)f(to)i(the)f(\014ltered)g +(input)f(\014le.)57 b(The)36 b(list)g(of)g(column)227 +3673 y(name)c(should)f(b)s(e)h(separated)g(b)m(y)g(semi-colons.)48 +b(Wild)32 b(card)g(c)m(haracters)i(ma)m(y)e(b)s(e)g(used)f(in)h(the)g +(column)227 3786 y(names)37 b(to)h(matc)m(h)g(m)m(ultiple)g(columns.)61 +b(If)37 b(the)g(expression)g(con)m(tains)i(b)s(oth)d(a)i(list)f(of)h +(columns)f(to)h(b)s(e)227 3899 y(included)h(and)f(columns)h(to)g(b)s(e) +g(deleted,)j(then)c(all)i(the)f(columns)g(in)g(the)g(original)h(table)g +(except)g(the)227 4012 y(explicitly)32 b(deleted)f(columns)f(will)g +(app)s(ear)g(in)g(the)g(\014ltered)g(table)h(\(i.e.,)h(there)e(is)h(no) +f(need)f(to)i(explicitly)227 4125 y(list)g(the)g(columns)f(to)h(b)s(e)f +(included)f(if)i(an)m(y)f(columns)g(are)h(b)s(eing)f(deleted\).)136 +4316 y Fc(\017)46 b Fi(Delete)32 b(a)d(column)g(or)g(k)m(eyw)m(ord)h(b) +m(y)f(listing)h(the)f(name)g(preceded)g(b)m(y)g(a)g(min)m(us)g(sign)g +(or)g(an)g(exclamation)227 4429 y(mark)c(\(!\),)h(e.g.,)i('-TIME')d +(will)g(delete)h(the)e(TIME)h(column)f(if)g(it)i(exists,)g(otherwise)f +(the)g(TIME)f(k)m(eyw)m(ord.)227 4542 y(An)35 b(error)f(is)h(returned)e +(if)i(neither)f(a)i(column)e(nor)g(k)m(eyw)m(ord)h(with)g(this)f(name)h +(exists.)54 b(Note)36 b(that)g(the)227 4655 y(exclamation)27 +b(p)s(oin)m(t,)g(')10 b(!',)27 b(is)e(a)g(sp)s(ecial)h(UNIX)f(c)m +(haracter,)j(so)d(if)g(it)h(is)f(used)f(on)h(the)g(command)g(line)g +(rather)227 4768 y(than)33 b(en)m(tered)h(at)g(a)g(task)g(prompt,)f(it) +h(m)m(ust)f(b)s(e)g(preceded)g(b)m(y)g(a)h(bac)m(kslash)g(to)g(force)g +(the)f(UNIX)h(shell)227 4881 y(to)d(ignore)g(it.)136 +5071 y Fc(\017)46 b Fi(Rename)29 b(an)g(existing)g(column)f(or)h(k)m +(eyw)m(ord)g(with)f(the)h(syn)m(tax)g('NewName)h(==)e(OldName'.)40 +b(An)28 b(error)227 5184 y(is)j(returned)e(if)h(neither)h(a)f(column)g +(nor)g(k)m(eyw)m(ord)h(with)f(this)h(name)f(exists.)136 +5375 y Fc(\017)46 b Fi(App)s(end)37 b(a)j(new)f(column)f(or)i(k)m(eyw)m +(ord)f(to)h(the)f(table.)68 b(T)-8 b(o)40 b(create)g(a)g(column,)h(giv) +m(e)g(the)e(new)g(name,)227 5488 y(optionally)e(follo)m(w)m(ed)g(b)m(y) +e(the)g(datat)m(yp)s(e)h(in)f(paren)m(theses,)i(follo)m(w)m(ed)g(b)m(y) +e(a)h(single)g(equals)f(sign)g(and)g(an)227 5601 y(expression)g(to)h(b) +s(e)e(used)g(to)i(compute)f(the)g(v)-5 b(alue)35 b(\(e.g.,)j('new)m +(col\(1J\))f(=)e(0')g(will)h(create)g(a)f(new)g(32-bit)227 +5714 y(in)m(teger)k(column)e(called)i('new)m(col')f(\014lled)g(with)f +(zeros\).)62 b(The)37 b(datat)m(yp)s(e)h(is)g(sp)s(eci\014ed)e(using)h +(the)h(same)p eop end +%%Page: 93 99 +TeXDict begin 93 98 bop 0 299 a Fg(8.10.)73 b(COLUMN)30 +b(AND)h(KEYW)m(ORD)g(FIL)-8 b(TERING)30 b(SPECIFICA)-8 +b(TION)1075 b Fi(93)227 555 y(syn)m(tax)28 b(that)h(is)e(allo)m(w)m(ed) +j(for)d(the)h(v)-5 b(alue)28 b(of)g(the)g(FITS)f(TF)m(ORMn)g(k)m(eyw)m +(ord)h(\(e.g.,)i('I',)f('J',)f('E',)g('D',)h(etc.)227 +668 y(for)37 b(binary)f(tables,)k(and)c('I8',)k(F12.3',)h('E20.12',)g +(etc.)62 b(for)37 b(ASCI)s(I)e(tables\).)62 b(If)37 b(the)g(datat)m(yp) +s(e)h(is)f(not)227 781 y(sp)s(eci\014ed)24 b(then)f(an)h(appropriate)h +(datat)m(yp)s(e)g(will)f(b)s(e)g(c)m(hosen)g(dep)s(ending)f(on)h(the)g +(form)g(of)g(the)g(expression)227 894 y(\(ma)m(y)f(b)s(e)d(a)i(c)m +(haracter)h(string,)h(logical,)h(bit,)f(long)e(in)m(teger,)j(or)c +(double)g(column\).)38 b(An)21 b(appropriate)g(v)m(ector)227 +1007 y(coun)m(t)31 b(\(in)g(the)f(case)i(of)e(binary)g(tables\))h(will) +g(also)g(b)s(e)f(added)g(if)g(not)h(explicitly)h(sp)s(eci\014ed.)227 +1156 y(When)26 b(creating)h(a)f(new)f(k)m(eyw)m(ord,)j(the)e(k)m(eyw)m +(ord)g(name)g(m)m(ust)g(b)s(e)f(preceded)g(b)m(y)h(a)g(p)s(ound)e(sign) +h('#',)j(and)227 1269 y(the)h(expression)f(m)m(ust)g(ev)-5 +b(aluate)30 b(to)f(a)g(scalar)g(\(i.e.,)h(cannot)f(ha)m(v)m(e)h(a)f +(column)f(name)g(in)g(the)h(expression\).)227 1382 y(The)j(commen)m(t)i +(string)f(for)f(the)h(k)m(eyw)m(ord)h(ma)m(y)f(b)s(e)f(sp)s(eci\014ed)g +(in)g(paren)m(theses)h(immediately)h(follo)m(wing)227 +1495 y(the)29 b(k)m(eyw)m(ord)f(name)g(\(instead)h(of)f(supplying)f(a)h +(datat)m(yp)s(e)h(as)g(in)e(the)i(case)g(of)f(creating)h(a)g(new)f +(column\).)227 1608 y(If)c(the)h(k)m(eyw)m(ord)g(name)f(ends)g(with)g +(a)h(p)s(ound)d(sign)i('#',)i(then)e(c\014tsio)i(will)e(substitute)h +(the)f(n)m(um)m(b)s(er)f(of)i(the)227 1720 y(most)31 +b(recen)m(tly)h(referenced)e(column)h(for)f(the)h(#)f(c)m(haracter)i(.) +41 b(This)29 b(is)i(esp)s(ecially)h(useful)d(when)h(writing)227 +1833 y(a)c(column-related)g(k)m(eyw)m(ord)g(lik)m(e)g(TUNITn)e(for)h(a) +h(newly)f(created)h(column,)g(as)g(sho)m(wn)e(in)h(the)g(follo)m(wing) +227 1946 y(examples.)136 2131 y Fc(\017)46 b Fi(Recompute)f(\(o)m(v)m +(erwrite\))i(the)d(v)-5 b(alues)44 b(in)g(an)g(existing)i(column)e(or)g +(k)m(eyw)m(ord)g(b)m(y)g(giving)i(the)e(name)227 2244 +y(follo)m(w)m(ed)32 b(b)m(y)f(an)f(equals)h(sign)f(and)g(an)g +(arithmetic)i(expression.)0 2497 y(The)23 b(expression)g(that)i(is)e +(used)g(when)g(app)s(ending)f(or)h(recomputing)h(columns)f(or)h(k)m +(eyw)m(ords)g(can)g(b)s(e)f(arbitrarily)0 2610 y(complex)36 +b(and)g(ma)m(y)g(b)s(e)f(a)h(function)g(of)g(other)g(header)g(k)m(eyw)m +(ord)g(v)-5 b(alues)36 b(and)f(other)h(columns)g(\(in)g(the)g(same)0 +2723 y(ro)m(w\).)63 b(The)37 b(full)g(syn)m(tax)i(and)e(a)m(v)-5 +b(ailable)40 b(functions)d(for)g(the)h(expression)f(are)h(describ)s(ed) +f(b)s(elo)m(w)h(in)f(the)h(ro)m(w)0 2836 y(\014lter)30 +b(sp)s(eci\014cation)i(section.)0 2996 y(If)27 b(the)h(expression)g +(con)m(tains)g(b)s(oth)f(a)h(list)h(of)f(columns)f(to)h(b)s(e)g +(included)e(and)i(columns)f(to)h(b)s(e)f(deleted,)j(then)d(all)0 +3109 y(the)34 b(columns)g(in)g(the)g(original)h(table)g(except)g(the)f +(explicitly)i(deleted)f(columns)e(will)i(app)s(ear)e(in)h(the)g +(\014ltered)0 3222 y(table.)40 b(If)26 b(no)g(columns)f(to)i(b)s(e)f +(deleted)g(are)h(sp)s(eci\014ed,)f(then)g(only)g(the)h(columns)e(that)i +(are)f(explicitly)i(listed)f(will)0 3335 y(b)s(e)k(included)g(in)g(the) +h(\014ltered)f(output)h(table.)45 b(T)-8 b(o)32 b(include)f(all)i(the)e +(columns,)h(add)f(the)h('*')g(wildcard)g(sp)s(eci\014er)0 +3447 y(at)f(the)g(end)e(of)i(the)f(list,)i(as)e(sho)m(wn)g(in)g(the)h +(examples.)0 3608 y(F)-8 b(or)30 b(complex)h(or)e(commonly)h(used)f(op) +s(erations,)i(one)e(can)h(also)h(place)g(the)e(op)s(erations)h(in)m(to) +h(an)e(external)i(text)0 3720 y(\014le)g(and)f(imp)s(ort)g(it)h(in)m +(to)h(the)f(column)g(\014lter)f(using)h(the)g(syn)m(tax)g('[col)h +(@\014lename.txt]'.)43 b(The)31 b(op)s(erations)g(can)0 +3833 y(extend)26 b(o)m(v)m(er)i(m)m(ultiple)f(lines)g(of)f(the)h +(\014le,)h(but)d(m)m(ultiple)i(op)s(erations)g(m)m(ust)f(still)i(b)s(e) +d(separated)i(b)m(y)g(semicolons.)0 3946 y(An)m(y)h(lines)h(in)f(the)g +(external)i(text)f(\014le)f(that)h(b)s(egin)f(with)g(2)h(slash)f(c)m +(haracters)i(\('//'\))g(will)f(b)s(e)e(ignored)i(and)e(ma)m(y)0 +4059 y(b)s(e)j(used)f(to)i(add)f(commen)m(ts)h(in)m(to)h(the)e(\014le.) +0 4219 y(Examples:)143 4472 y Fe([col)47 b(Time;)f(rate])667 +b(-)47 b(only)g(the)g(Time)g(and)g(rate)f(columns)g(will)1670 +4585 y(appear)h(in)g(the)g(filtered)e(input)i(file.)143 +4811 y([col)g(Time,)f(*raw])667 b(-)47 b(include)f(the)h(Time)g(column) +f(and)h(any)g(other)1670 4924 y(columns)f(whose)h(name)f(ends)h(with)g +('raw'.)143 5149 y([col)g(-TIME;)f(Good)h(==)g(STATUS])141 +b(-)47 b(deletes)f(the)h(TIME)g(column)f(and)1670 5262 +y(renames)g(the)h(status)f(column)g(to)i('Good')143 5488 +y([col)f(PI=PHA)f(*)h(1.1)g(+)h(0.2;)e(#TUNIT#\(column)e(units\))i(=)i +('counts';*])1575 5601 y(-)f(creates)f(new)h(PI)g(column)f(from)h(PHA)g +(values)1670 5714 y(and)g(also)g(writes)f(the)h(TUNITn)f(keyword)p +eop end +%%Page: 94 100 +TeXDict begin 94 99 bop 0 299 a Fi(94)1618 b Fg(CHAPTER)30 +b(8.)112 b(EXTENDED)30 b(FILE)h(NAME)f(SYNT)-8 b(AX)1670 +555 y Fe(for)47 b(the)g(new)g(column.)94 b(The)47 b(final)f('*')1670 +668 y(expression)f(means)i(preserve)e(all)i(the)1670 +781 y(columns)f(in)h(the)g(input)g(table)f(in)h(the)1670 +894 y(virtual)f(output)g(table;)94 b(without)46 b(the)h('*')1670 +1007 y(the)g(output)f(table)h(would)f(only)h(contain)1670 +1120 y(the)g(single)f('PI')h(column.)143 1346 y([col)g(rate)f(=)i +(rate/exposure;)c(TUNIT#\(&\))h(=)j('counts/s';*])1575 +1458 y(-)f(recomputes)e(the)i(rate)g(column)f(by)h(dividing)1670 +1571 y(it)h(by)f(the)g(EXPOSURE)e(keyword)h(value.)g(This)1670 +1684 y(also)h(modifies)f(the)h(value)f(of)h(the)g(TUNITn)1670 +1797 y(keyword)f(for)h(this)g(column.)f(The)h(use)f(of)i(the)1670 +1910 y('&')f(character)f(for)h(the)f(keyword)g(comment)1670 +2023 y(string)h(means)f(preserve)f(the)i(existing)1670 +2136 y(comment)f(string)g(for)h(that)g(keyword.)e(The)1670 +2249 y(final)i('*')g(preserves)e(all)i(the)g(columns)1670 +2362 y(in)h(the)f(input)f(table)g(in)h(the)g(virtual)1670 +2475 y(output)g(table.)0 2816 y Fd(8.11)136 b(Ro)l(w)45 +b(Filtering)h(Sp)t(eci\014cation)0 3068 y Fi(When)29 +b(en)m(tering)h(the)f(name)g(of)g(a)g(FITS)f(table)i(that)g(is)e(to)i +(b)s(e)e(op)s(ened)h(b)m(y)f(a)i(program,)f(an)g(optional)h(ro)m(w)f +(\014lter)0 3181 y(ma)m(y)i(b)s(e)g(sp)s(eci\014ed)f(to)h(select)h(a)g +(subset)e(of)h(the)g(ro)m(ws)f(in)h(the)g(table.)43 b(A)31 +b(temp)s(orary)f(new)g(FITS)g(\014le)h(is)g(created)0 +3294 y(on)25 b(the)h(\015y)e(whic)m(h)h(con)m(tains)h(only)g(those)g +(ro)m(ws)f(for)g(whic)m(h)g(the)g(ro)m(w)g(\014lter)h(expression)f(ev) +-5 b(aluates)26 b(to)g(true.)39 b(\(The)0 3407 y(primary)26 +b(arra)m(y)h(and)f(an)m(y)g(other)h(extensions)g(in)f(the)h(input)f +(\014le)g(are)h(also)h(copied)f(to)g(the)f(temp)s(orary)h(\014le\).)39 +b(The)0 3520 y(original)30 b(FITS)f(\014le)g(is)g(closed)h(and)e(the)i +(new)e(virtual)i(\014le)f(is)g(op)s(ened)f(b)m(y)h(the)h(application)g +(program.)40 b(The)29 b(ro)m(w)0 3633 y(\014lter)37 b(expression)g(is)h +(enclosed)g(in)f(square)g(brac)m(k)m(ets)i(follo)m(wing)g(the)e(\014le) +h(name)f(and)g(extension)h(name)f(\(e.g.,)0 3745 y('\014le.\014ts[ev)m +(en)m(ts][GRADE==50]')29 b(selects)d(only)f(those)h(ro)m(ws)f(where)f +(the)h(GRADE)h(column)f(v)-5 b(alue)25 b(equals)g(50\).)0 +3858 y(When)33 b(dealing)h(with)f(tables)g(where)g(eac)m(h)h(ro)m(w)f +(has)g(an)g(asso)s(ciated)i(time)f(and/or)f(2D)g(spatial)i(p)s +(osition,)f(the)0 3971 y(ro)m(w)e(\014lter)h(expression)e(can)i(also)g +(b)s(e)f(used)f(to)i(select)h(ro)m(ws)e(based)g(on)g(the)g(times)h(in)f +(a)g(Go)s(o)s(d)g(Time)g(In)m(terv)-5 b(als)0 4084 y(\(GTI\))31 +b(extension,)g(or)f(on)h(spatial)g(p)s(osition)g(as)f(giv)m(en)i(in)e +(a)g(SA)m(O-st)m(yle)i(region)f(\014le.)0 4383 y Fb(8.11.1)113 +b(General)38 b(Syn)m(tax)0 4603 y Fi(The)32 b(ro)m(w)h(\014ltering)g +(expression)g(can)g(b)s(e)f(an)h(arbitrarily)g(complex)g(series)g(of)g +(op)s(erations)g(p)s(erformed)f(on)g(con-)0 4716 y(stan)m(ts,)39 +b(k)m(eyw)m(ord)e(v)-5 b(alues,)38 b(and)e(column)g(data)i(tak)m(en)f +(from)f(the)h(sp)s(eci\014ed)e(FITS)h(T)-8 b(ABLE)37 +b(extension.)59 b(The)0 4829 y(expression)37 b(m)m(ust)h(ev)-5 +b(aluate)39 b(to)g(a)f(b)s(o)s(olean)g(v)-5 b(alue)38 +b(for)f(eac)m(h)i(ro)m(w)f(of)g(the)f(table,)k(where)c(a)h(v)-5 +b(alue)39 b(of)e(F)-10 b(ALSE)0 4942 y(means)30 b(that)h(the)g(ro)m(w)f +(will)h(b)s(e)f(excluded.)0 5102 y(F)-8 b(or)34 b(complex)g(or)g +(commonly)f(used)g(\014lters,)h(one)g(can)g(place)g(the)g(expression)f +(in)m(to)h(a)g(text)g(\014le)g(and)f(imp)s(ort)f(it)0 +5215 y(in)m(to)38 b(the)e(ro)m(w)h(\014lter)g(using)f(the)h(syn)m(tax)g +('[@\014lename.txt]'.)61 b(The)36 b(expression)h(can)f(b)s(e)g +(arbitrarily)h(complex)0 5328 y(and)27 b(extend)i(o)m(v)m(er)g(m)m +(ultiple)g(lines)f(of)g(the)h(\014le.)40 b(An)m(y)28 +b(lines)g(in)g(the)g(external)h(text)g(\014le)f(that)h(b)s(egin)f(with) +g(2)g(slash)0 5441 y(c)m(haracters)k(\('//'\))g(will)f(b)s(e)f(ignored) +g(and)g(ma)m(y)h(b)s(e)f(used)f(to)i(add)f(commen)m(ts)h(in)m(to)h(the) +e(\014le.)0 5601 y(Keyw)m(ord)37 b(and)f(column)g(data)i(are)f +(referenced)g(b)m(y)g(name.)60 b(An)m(y)37 b(string)f(of)h(c)m +(haracters)i(not)e(surrounded)d(b)m(y)0 5714 y(quotes)41 +b(\(ie,)j(a)d(constan)m(t)h(string\))f(or)f(follo)m(w)m(ed)i(b)m(y)f +(an)f(op)s(en)g(paren)m(theses)h(\(ie,)j(a)d(function)f(name\))h(will)g +(b)s(e)p eop end +%%Page: 95 101 +TeXDict begin 95 100 bop 0 299 a Fg(8.11.)73 b(R)m(O)m(W)31 +b(FIL)-8 b(TERING)30 b(SPECIFICA)-8 b(TION)2027 b Fi(95)0 +555 y(initially)38 b(in)m(terpreted)e(as)h(a)g(column)f(name)g(and)g +(its)h(con)m(ten)m(ts)h(for)e(the)h(curren)m(t)f(ro)m(w)g(inserted)g +(in)m(to)i(the)e(ex-)0 668 y(pression.)k(If)28 b(no)h(suc)m(h)g(column) +g(exists,)h(a)g(k)m(eyw)m(ord)f(of)h(that)f(name)g(will)h(b)s(e)e +(searc)m(hed)i(for)f(and)f(its)i(v)-5 b(alue)29 b(used,)0 +781 y(if)36 b(found.)55 b(T)-8 b(o)36 b(force)g(the)g(name)g(to)h(b)s +(e)e(in)m(terpreted)h(as)g(a)g(k)m(eyw)m(ord)g(\(in)g(case)g(there)g +(is)g(b)s(oth)f(a)h(column)g(and)0 894 y(k)m(eyw)m(ord)41 +b(with)e(the)i(same)f(name\),)j(precede)d(the)h(k)m(eyw)m(ord)f(name)g +(with)g(a)h(single)f(p)s(ound)e(sign,)43 b('#',)g(as)d(in)0 +1007 y('#NAXIS2'.)g(Due)27 b(to)g(the)f(generalities)j(of)d(FITS)g +(column)g(and)g(k)m(eyw)m(ord)h(names,)g(if)f(the)h(column)f(or)g(k)m +(eyw)m(ord)0 1120 y(name)33 b(con)m(tains)h(a)f(space)h(or)f(a)g(c)m +(haracter)h(whic)m(h)f(migh)m(t)h(app)s(ear)e(as)h(an)g(arithmetic)h +(term)f(then)g(enclose)h(the)0 1233 y(name)c(in)g('$')i(c)m(haracters)g +(as)e(in)g($MAX)i(PHA$)f(or)f(#$MAX-PHA$.)43 b(Names)31 +b(are)f(case)i(insensitiv)m(e.)0 1393 y(T)-8 b(o)32 b(access)g(a)g +(table)g(en)m(try)g(in)f(a)h(ro)m(w)f(other)h(than)f(the)g(curren)m(t)g +(one,)h(follo)m(w)h(the)e(column's)h(name)f(with)g(a)h(ro)m(w)0 +1506 y(o\013set)37 b(within)e(curly)g(braces.)57 b(F)-8 +b(or)36 b(example,)i('PHA)p Fc(f)p Fi(-3)p Fc(g)p Fi(')g(will)e(ev)-5 +b(aluate)38 b(to)e(the)g(v)-5 b(alue)36 b(of)g(column)f(PHA,)i(3)0 +1619 y(ro)m(ws)28 b(ab)s(o)m(v)m(e)i(the)e(ro)m(w)h(curren)m(tly)f(b)s +(eing)g(pro)s(cessed.)40 b(One)28 b(cannot)h(sp)s(ecify)f(an)g +(absolute)h(ro)m(w)f(n)m(um)m(b)s(er,)g(only)h(a)0 1732 +y(relativ)m(e)j(o\013set.)42 b(Ro)m(ws)31 b(that)g(fall)g(outside)g +(the)f(table)h(will)g(b)s(e)f(treated)h(as)g(unde\014ned,)d(or)j +(NULLs.)0 1892 y(Bo)s(olean)h(op)s(erators)f(can)g(b)s(e)f(used)f(in)i +(the)f(expression)h(in)f(either)h(their)g(F)-8 b(ortran)31 +b(or)f(C)h(forms.)40 b(The)30 b(follo)m(wing)0 2005 y(b)s(o)s(olean)g +(op)s(erators)h(are)g(a)m(v)-5 b(ailable:)191 2247 y +Fe("equal")428 b(.eq.)46 b(.EQ.)h(==)95 b("not)46 b(equal")476 +b(.ne.)94 b(.NE.)h(!=)191 2360 y("less)46 b(than")238 +b(.lt.)46 b(.LT.)h(<)143 b("less)46 b(than/equal")188 +b(.le.)94 b(.LE.)h(<=)47 b(=<)191 2473 y("greater)e(than")95 +b(.gt.)46 b(.GT.)h(>)143 b("greater)45 b(than/equal")g(.ge.)94 +b(.GE.)h(>=)47 b(=>)191 2585 y("or")572 b(.or.)46 b(.OR.)h(||)95 +b("and")762 b(.and.)46 b(.AND.)h(&&)191 2698 y("negation")236 +b(.not.)46 b(.NOT.)h(!)95 b("approx.)45 b(equal\(1e-7\)")92 +b(~)0 2940 y Fi(Note)32 b(that)g(the)f(exclamation)i(p)s(oin)m(t,)e(') +10 b(!',)33 b(is)e(a)g(sp)s(ecial)g(UNIX)h(c)m(haracter,)h(so)e(if)g +(it)g(is)g(used)f(on)h(the)g(command)0 3053 y(line)i(rather)f(than)h +(en)m(tered)g(at)g(a)g(task)g(prompt,)g(it)g(m)m(ust)f(b)s(e)g +(preceded)h(b)m(y)f(a)h(bac)m(kslash)g(to)h(force)f(the)g(UNIX)0 +3166 y(shell)e(to)g(ignore)g(it.)0 3326 y(The)h(expression)g(ma)m(y)i +(also)f(include)f(arithmetic)i(op)s(erators)f(and)f(functions.)47 +b(T)-8 b(rigonometric)34 b(functions)e(use)0 3439 y(radians,)23 +b(not)g(degrees.)38 b(The)22 b(follo)m(wing)h(arithmetic)g(op)s +(erators)g(and)e(functions)g(can)i(b)s(e)e(used)g(in)h(the)g +(expression)0 3552 y(\(function)38 b(names)f(are)h(case)g(insensitiv)m +(e\).)64 b(A)37 b(n)m(ull)h(v)-5 b(alue)38 b(will)f(b)s(e)g(returned)g +(in)g(case)h(of)g(illegal)i(op)s(erations)0 3665 y(suc)m(h)30 +b(as)h(divide)f(b)m(y)g(zero,)i(sqrt\(negativ)m(e\))h(log\(negativ)m +(e\),)h(log10\(negativ)m(e\),)i(arccos\(.gt.)43 b(1\),)32 +b(arcsin\(.gt.)42 b(1\).)191 3907 y Fe("addition")522 +b(+)477 b("subtraction")d(-)191 4020 y("multiplication")234 +b(*)477 b("division")618 b(/)191 4133 y("negation")522 +b(-)477 b("exponentiation")330 b(**)143 b(^)191 4246 +y("absolute)45 b(value")237 b(abs\(x\))g("cosine")714 +b(cos\(x\))191 4359 y("sine")g(sin\(x\))237 b("tangent")666 +b(tan\(x\))191 4472 y("arc)47 b(cosine")427 b(arccos\(x\))93 +b("arc)47 b(sine")619 b(arcsin\(x\))191 4585 y("arc)47 +b(tangent")379 b(arctan\(x\))93 b("arc)47 b(tangent")475 +b(arctan2\(y,x\))191 4698 y("hyperbolic)45 b(cos")237 +b(cosh\(x\))189 b("hyperbolic)45 b(sin")333 b(sinh\(x\))191 +4811 y("hyperbolic)45 b(tan")237 b(tanh\(x\))189 b("round)46 +b(to)h(nearest)f(int")h(round\(x\))191 4924 y("round)f(down)h(to)g +(int")94 b(floor\(x\))141 b("round)46 b(up)h(to)h(int")285 +b(ceil\(x\))191 5036 y("exponential")378 b(exp\(x\))237 +b("square)46 b(root")476 b(sqrt\(x\))191 5149 y("natural)45 +b(log")381 b(log\(x\))237 b("common)46 b(log")524 b(log10\(x\))191 +5262 y("modulus")570 b(x)48 b(\045)f(y)286 b("random)46 +b(#)h([0.0,1.0\)")141 b(random\(\))191 5375 y("random)46 +b(Gaussian")188 b(randomn\(\))93 b("random)46 b(Poisson")332 +b(randomp\(x\))191 5488 y("minimum")570 b(min\(x,y\))141 +b("maximum")666 b(max\(x,y\))191 5601 y("cumulative)45 +b(sum")237 b(accum\(x\))93 b("sequential)45 b(difference")g +(seqdiff\(x\))191 5714 y("if-then-else")330 b(b?x:y)p +eop end +%%Page: 96 102 +TeXDict begin 96 101 bop 0 299 a Fi(96)1618 b Fg(CHAPTER)30 +b(8.)112 b(EXTENDED)30 b(FILE)h(NAME)f(SYNT)-8 b(AX)191 +555 y Fe("angular)45 b(separation")93 b(angsep\(ra1,dec1,ra2,de2\))41 +b(\(all)47 b(in)g(degrees\))191 668 y("substring")283 +b(strmid\(s,p,n\))44 b("string)i(search")428 b(strstr\(s,r\))0 +907 y Fi(Three)30 b(di\013eren)m(t)h(random)f(n)m(um)m(b)s(er)f +(functions)h(are)h(pro)m(vided:)41 b(random\(\),)30 b(with)h(no)f +(argumen)m(ts,)h(pro)s(duces)f(a)0 1020 y(uniform)g(random)f(deviate)k +(b)s(et)m(w)m(een)e(0)g(and)f(1;)i(randomn\(\),)e(also)i(with)e(no)h +(argumen)m(ts,)g(pro)s(duces)f(a)h(normal)0 1133 y(\(Gaussian\))k +(random)e(deviate)j(with)e(zero)h(mean)f(and)g(unit)f(standard)h +(deviation;)j(randomp\(x\))d(pro)s(duces)f(a)0 1245 y(P)m(oisson)27 +b(random)f(deviate)h(whose)f(exp)s(ected)h(n)m(um)m(b)s(er)e(of)h(coun) +m(ts)h(is)g(X.)f(X)h(ma)m(y)g(b)s(e)e(an)m(y)i(p)s(ositiv)m(e)g(real)g +(n)m(um)m(b)s(er)0 1358 y(of)k(exp)s(ected)f(coun)m(ts,)h(including)f +(fractional)i(v)-5 b(alues,)31 b(but)f(the)g(return)g(v)-5 +b(alue)31 b(is)f(an)g(in)m(teger.)0 1519 y(When)d(the)g(random)g +(functions)f(are)i(used)e(in)h(a)h(v)m(ector)g(expression,)g(b)m(y)f +(default)h(the)f(same)h(random)e(v)-5 b(alue)28 b(will)0 +1631 y(b)s(e)g(used)f(when)h(ev)-5 b(aluating)30 b(eac)m(h)f(elemen)m +(t)h(of)f(the)g(v)m(ector.)41 b(If)28 b(di\013eren)m(t)h(random)f(n)m +(um)m(b)s(ers)f(are)i(desired,)f(then)0 1744 y(the)37 +b(name)g(of)g(a)g(v)m(ector)i(column)e(should)e(b)s(e)i(supplied)e(as)i +(the)h(single)f(argumen)m(t)g(to)h(the)f(random)f(function)0 +1857 y(\(e.g.,)31 b("\015ux)c(+)h(0.1)h(*)g(random\(\015ux\)",)f(where) +g("\015ux')g(is)g(the)g(name)h(of)f(a)h(v)m(ector)h(column\).)40 +b(This)27 b(will)i(create)h(a)0 1970 y(v)m(ector)d(of)f(random)f(n)m +(um)m(b)s(ers)f(that)i(will)g(b)s(e)f(used)f(in)i(sequence)g(when)e(ev) +-5 b(aluating)27 b(eac)m(h)g(elemen)m(t)g(of)f(the)f(v)m(ector)0 +2083 y(expression.)0 2243 y(An)31 b(alternate)i(syn)m(tax)f(for)f(the)g +(min)g(and)g(max)g(functions)g(has)g(only)g(a)h(single)g(argumen)m(t)g +(whic)m(h)f(should)f(b)s(e)h(a)0 2356 y(v)m(ector)g(v)-5 +b(alue)30 b(\(see)g(b)s(elo)m(w\).)41 b(The)29 b(result)g(will)h(b)s(e) +e(the)i(minim)m(um/maxim)m(um)f(elemen)m(t)h(con)m(tained)h(within)e +(the)0 2469 y(v)m(ector.)0 2629 y(The)35 b(accum\(x\))i(function)f +(forms)f(the)h(cum)m(ulativ)m(e)i(sum)d(of)h(x,)h(elemen)m(t)h(b)m(y)e +(elemen)m(t.)58 b(V)-8 b(ector)38 b(columns)e(are)0 2742 +y(supp)s(orted)h(simply)h(b)m(y)g(p)s(erforming)f(the)i(summation)g +(pro)s(cess)f(through)f(all)j(the)f(v)-5 b(alues.)65 +b(Null)39 b(v)-5 b(alues)39 b(are)0 2855 y(treated)30 +b(as)f(0.)41 b(The)29 b(seqdi\013\(x\))h(function)e(forms)h(the)g +(sequen)m(tial)i(di\013erence)e(of)h(x,)f(elemen)m(t)i(b)m(y)e(elemen)m +(t.)41 b(The)0 2968 y(\014rst)36 b(v)-5 b(alue)38 b(of)f(seqdi\013)g +(is)g(the)g(\014rst)g(v)-5 b(alue)37 b(of)g(x.)61 b(A)37 +b(single)h(n)m(ull)f(v)-5 b(alue)38 b(in)e(x)h(causes)h(a)f(pair)g(of)g +(n)m(ulls)g(in)g(the)0 3081 y(output.)55 b(The)35 b(seqdi\013)g(and)g +(accum)g(functions)g(are)h(functional)f(in)m(v)m(erses,)j(i.e.,)g +(seqdi\013\(accum\(x\)\))f(==)e(x)g(as)0 3194 y(long)c(as)g(no)f(n)m +(ull)g(v)-5 b(alues)31 b(are)g(presen)m(t.)0 3354 y(In)36 +b(the)h(if-then-else)i(expression,)f("b?x:y",)i(b)c(is)h(an)g(explicit) +h(b)s(o)s(olean)f(v)-5 b(alue)37 b(or)g(expression.)61 +b(There)36 b(is)h(no)0 3467 y(automatic)d(t)m(yp)s(e)e(con)m(v)m +(ersion)h(from)e(n)m(umeric)h(to)g(b)s(o)s(olean)g(v)-5 +b(alues,)33 b(so)f(one)g(needs)f(to)i(use)e("iV)-8 b(al!=0")35 +b(instead)0 3580 y(of)30 b(merely)g("iV)-8 b(al")32 b(as)e(the)g(b)s(o) +s(olean)g(argumen)m(t.)41 b(x)30 b(and)f(y)h(can)g(b)s(e)f(an)m(y)h +(scalar)h(data)g(t)m(yp)s(e)f(\(including)f(string\).)0 +3740 y(The)22 b(angsep)g(function)f(computes)i(the)f(angular)g +(separation)h(in)e(degrees)i(b)s(et)m(w)m(een)g(2)f(celestial)j(p)s +(ositions,)e(where)0 3853 y(the)36 b(\014rst)f(2)h(parameters)g(giv)m +(e)h(the)f(RA-lik)m(e)i(and)d(Dec-lik)m(e)j(co)s(ordinates)f(\(in)f +(decimal)g(degrees\))h(of)f(the)g(\014rst)0 3966 y(p)s(osition,)31 +b(and)e(the)i(3rd)f(and)g(4th)g(parameters)h(giv)m(e)h(the)e(co)s +(ordinates)i(of)e(the)h(second)f(p)s(osition.)0 4126 +y(The)38 b(substring)f(function)i(strmid\(S,P)-8 b(,N\))39 +b(extracts)g(a)g(substring)f(from)g(S,)g(starting)h(at)g(string)g(p)s +(osition)f(P)-8 b(,)0 4239 y(with)33 b(a)h(substring)f(length)h(N.)g +(The)f(\014rst)g(c)m(haracter)j(p)s(osition)d(in)h(S)f(is)h(lab)s(eled) +g(as)g(1.)51 b(If)33 b(P)g(is)h(0,)h(or)f(refers)f(to)0 +4352 y(a)i(p)s(osition)g(b)s(ey)m(ond)f(the)h(end)e(of)i(S,)g(then)f +(the)h(extracted)h(substring)d(will)i(b)s(e)f(NULL.)h(S,)f(P)-8 +b(,)36 b(and)e(N)g(ma)m(y)i(b)s(e)0 4465 y(functions)30 +b(of)g(other)h(columns.)0 4625 y(The)39 b(string)h(searc)m(h)h +(function)e(strstr\(S,R\))h(searc)m(hes)h(for)f(the)g(\014rst)f(o)s +(ccurrence)h(of)g(the)g(substring)f(R)h(in)f(S.)0 4738 +y(The)c(result)h(is)f(an)h(in)m(teger,)i(indicating)f(the)e(c)m +(haracter)i(p)s(osition)f(of)g(the)g(\014rst)e(matc)m(h)j(\(where)e(1)h +(is)g(the)g(\014rst)0 4851 y(c)m(haracter)c(p)s(osition)e(of)h(S\).)f +(If)g(no)h(matc)m(h)g(is)f(found,)g(then)g(strstr\(\))g(returns)f(a)i +(NULL)f(v)-5 b(alue.)0 5011 y(The)37 b(follo)m(wing)i(t)m(yp)s(e)f +(casting)g(op)s(erators)g(are)g(a)m(v)-5 b(ailable,)42 +b(where)37 b(the)h(enclosing)g(paren)m(theses)g(are)g(required)0 +5124 y(and)30 b(tak)m(en)h(from)f(the)h(C)f(language)h(usage.)42 +b(Also,)31 b(the)g(in)m(teger)g(to)h(real)f(casts)g(v)-5 +b(alues)30 b(to)i(double)e(precision:)764 5362 y Fe("real)46 +b(to)h(integer")189 b(\(int\))46 b(x)239 b(\(INT\))46 +b(x)764 5475 y("integer)f(to)i(real")190 b(\(float\))46 +b(i)143 b(\(FLOAT\))45 b(i)0 5714 y Fi(In)30 b(addition,)g(sev)m(eral)i +(constan)m(ts)g(are)f(built)f(in)g(for)g(use)g(in)g(n)m(umerical)h +(expressions:)p eop end +%%Page: 97 103 +TeXDict begin 97 102 bop 0 299 a Fg(8.11.)73 b(R)m(O)m(W)31 +b(FIL)-8 b(TERING)30 b(SPECIFICA)-8 b(TION)2027 b Fi(97)382 +555 y Fe(#pi)667 b(3.1415...)284 b(#e)620 b(2.7182...)382 +668 y(#deg)f(#pi/180)380 b(#row)524 b(current)46 b(row)h(number)382 +781 y(#null)428 b(undefined)45 b(value)142 b(#snull)428 +b(undefined)45 b(string)0 1040 y Fi(A)40 b(string)f(constan)m(t)i(m)m +(ust)e(b)s(e)g(enclosed)h(in)g(quotes)g(as)f(in)h('Crab'.)67 +b(The)39 b("n)m(ull")i(constan)m(ts)f(are)g(useful)f(for)0 +1153 y(conditionally)g(setting)g(table)g(v)-5 b(alues)38 +b(to)g(a)g(NULL,)g(or)g(unde\014ned,)f(v)-5 b(alue)39 +b(\(eg.,)i("col1==-99)f(?)62 b(#NULL)38 b(:)0 1266 y(col1"\).)0 +1426 y(There)27 b(is)g(also)i(a)e(function)g(for)h(testing)g(if)f(t)m +(w)m(o)i(v)-5 b(alues)28 b(are)g(close)g(to)h(eac)m(h)f(other,)h(i.e.,) +g(if)e(they)h(are)g("near")g(eac)m(h)0 1539 y(other)c(to)h(within)e(a)h +(user)g(sp)s(eci\014ed)f(tolerance.)40 b(The)24 b(argumen)m(ts,)h(v)-5 +b(alue)p 2502 1539 28 4 v 34 w(1)24 b(and)f(v)-5 b(alue)p +2979 1539 V 33 w(2)25 b(can)f(b)s(e)f(in)m(teger)i(or)f(real)0 +1652 y(and)32 b(represen)m(t)h(the)g(t)m(w)m(o)h(v)-5 +b(alues)33 b(who's)f(pro)m(ximit)m(y)i(is)f(b)s(eing)f(tested)h(to)h(b) +s(e)e(within)g(the)h(sp)s(eci\014ed)f(tolerance,)0 1765 +y(also)f(an)g(in)m(teger)g(or)g(real:)955 2023 y Fe(near\(value_1,)44 +b(value_2,)h(tolerance\))0 2282 y Fi(When)24 b(a)i(NULL,)e(or)h +(unde\014ned,)f(v)-5 b(alue)25 b(is)g(encoun)m(tered)g(in)g(the)f(FITS) +g(table,)j(the)e(expression)g(will)g(ev)-5 b(aluate)26 +b(to)0 2395 y(NULL)31 b(unless)f(the)h(unde\014ned)e(v)-5 +b(alue)31 b(is)g(not)g(actually)h(required)e(for)h(ev)-5 +b(aluation,)33 b(e.g.)43 b("TR)m(UE)31 b(.or.)43 b(NULL")0 +2508 y(ev)-5 b(aluates)32 b(to)f(TR)m(UE.)g(The)f(follo)m(wing)h(t)m(w) +m(o)h(functions)e(allo)m(w)i(some)f(NULL)f(detection)i(and)e(handling:) +430 2767 y Fe("a)47 b(null)f(value?")667 b(ISNULL\(x\))430 +2880 y("define)45 b(a)j(value)e(for)h(null")190 b(DEFNULL\(x,y\))0 +3139 y Fi(The)36 b(former)h(returns)e(a)i(b)s(o)s(olean)g(v)-5 +b(alue)37 b(of)g(TR)m(UE)g(if)g(the)g(argumen)m(t)g(x)g(is)g(NULL.)g +(The)f(later)i("de\014nes")f(a)0 3252 y(v)-5 b(alue)35 +b(to)g(b)s(e)e(substituted)h(for)g(NULL)g(v)-5 b(alues;)37 +b(it)e(returns)e(the)h(v)-5 b(alue)35 b(of)f(x)g(if)g(x)h(is)f(not)g +(NULL,)h(otherwise)f(it)0 3365 y(returns)29 b(the)i(v)-5 +b(alue)31 b(of)f(y)-8 b(.)0 3655 y Fb(8.11.2)113 b(Bit)36 +b(Masks)0 3874 y Fi(Bit)g(masks)f(can)h(b)s(e)f(used)f(to)i(select)h +(out)e(ro)m(ws)h(from)e(bit)i(columns)f(\(TF)m(ORMn)g(=)g(#X\))h(in)f +(FITS)f(\014les.)55 b(T)-8 b(o)0 3987 y(represen)m(t)30 +b(the)h(mask,)g(binary)-8 b(,)30 b(o)s(ctal,)i(and)e(hex)g(formats)g +(are)h(allo)m(w)m(ed:)811 4246 y Fe(binary:)142 b +(b0110xx1010000101xxxx00)o(01)811 4359 y(octal:)190 b(o720x1)46 +b(->)h(\(b111010000xxx001\))811 4471 y(hex:)286 b(h0FxD)94 +b(->)47 b(\(b00001111xxxx1101\))0 4730 y Fi(In)22 b(all)i(the)f +(represen)m(tations,)j(an)c(x)h(or)g(X)g(is)g(allo)m(w)m(ed)i(in)d(the) +h(mask)g(as)g(a)h(wild)e(card.)38 b(Note)25 b(that)e(the)g(x)g +(represen)m(ts)0 4843 y(a)k(di\013eren)m(t)h(n)m(um)m(b)s(er)e(of)h +(wild)f(card)h(bits)g(in)g(eac)m(h)h(represen)m(tation.)41 +b(All)27 b(represen)m(tations)h(are)g(case)g(insensitiv)m(e.)0 +5003 y(T)-8 b(o)28 b(construct)g(the)g(b)s(o)s(olean)f(expression)h +(using)f(the)h(mask)f(as)h(the)g(b)s(o)s(olean)f(equal)h(op)s(erator)g +(describ)s(ed)f(ab)s(o)m(v)m(e)0 5116 y(on)34 b(a)h(bit)g(table)h +(column.)53 b(F)-8 b(or)35 b(example,)i(if)d(y)m(ou)h(had)f(a)h(7)g +(bit)g(column)f(named)g(\015ags)h(in)f(a)h(FITS)f(table)i(and)0 +5229 y(w)m(an)m(ted)31 b(all)g(ro)m(ws)g(ha)m(ving)g(the)f(bit)h +(pattern)f(0010011,)k(the)c(selection)j(expression)d(w)m(ould)g(b)s(e:) +1336 5488 y Fe(flags)47 b(==)g(b0010011)191 5601 y(or)1336 +5714 y(flags)g(.eq.)f(b10011)p eop end +%%Page: 98 104 +TeXDict begin 98 103 bop 0 299 a Fi(98)1618 b Fg(CHAPTER)30 +b(8.)112 b(EXTENDED)30 b(FILE)h(NAME)f(SYNT)-8 b(AX)0 +555 y Fi(It)35 b(is)g(also)h(p)s(ossible)e(to)i(test)g(if)f(a)g(range)g +(of)g(bits)g(is)g(less)g(than,)h(less)f(than)g(equal,)i(greater)f(than) +e(and)h(greater)0 668 y(than)30 b(equal)h(to)g(a)g(particular)g(b)s(o)s +(olean)f(v)-5 b(alue:)1336 922 y Fe(flags)47 b(<=)g(bxxx010xx)1336 +1035 y(flags)g(.gt.)f(bxxx100xx)1336 1148 y(flags)h(.le.)f(b1xxxxxxx)0 +1402 y Fi(Notice)32 b(the)f(use)f(of)h(the)f(x)g(bit)h(v)-5 +b(alue)31 b(to)g(limit)g(the)f(range)h(of)g(bits)f(b)s(eing)g +(compared.)0 1563 y(It)i(is)h(not)f(necessary)h(to)g(sp)s(ecify)f(the)h +(leading)g(\(most)g(signi\014can)m(t\))h(zero)f(\(0\))g(bits)f(in)g +(the)h(mask,)g(as)g(sho)m(wn)e(in)0 1675 y(the)g(second)f(expression)g +(ab)s(o)m(v)m(e.)0 1836 y(Bit)44 b(wise)f(AND,)h(OR)e(and)g(NOT)h(op)s +(erations)g(are)g(also)h(p)s(ossible)e(on)h(t)m(w)m(o)h(or)f(more)g +(bit)g(\014elds)f(using)h(the)0 1949 y('&'\(AND\),)35 +b(')p Fc(j)p Fi('\(OR\),)g(and)e(the)h(')10 b(!'\(NOT\))34 +b(op)s(erators.)51 b(All)34 b(of)f(these)h(op)s(erators)g(result)f(in)h +(a)g(bit)f(\014eld)g(whic)m(h)0 2061 y(can)e(then)f(b)s(e)f(used)h +(with)g(the)h(equal)g(op)s(erator.)41 b(F)-8 b(or)31 +b(example:)1241 2316 y Fe(\(!flags\))45 b(==)j(b1101100)1241 +2429 y(\(flags)e(&)h(b1000001\))f(==)h(bx000001)0 2683 +y Fi(Bit)35 b(\014elds)f(can)g(b)s(e)f(app)s(ended)g(as)h(w)m(ell)h +(using)f(the)g('+')g(op)s(erator.)53 b(Strings)33 b(can)i(b)s(e)e +(concatenated)j(this)e(w)m(a)m(y)-8 b(,)0 2796 y(to)s(o.)0 +3086 y Fb(8.11.3)113 b(V)-9 b(ector)36 b(Columns)0 3304 +y Fi(V)-8 b(ector)37 b(columns)e(can)h(also)g(b)s(e)f(used)f(in)h +(building)g(the)g(expression.)56 b(No)36 b(sp)s(ecial)g(syn)m(tax)f(is) +h(required)e(if)i(one)0 3417 y(w)m(an)m(ts)46 b(to)f(op)s(erate)h(on)f +(all)h(elemen)m(ts)g(of)f(the)h(v)m(ector.)86 b(Simply)44 +b(use)h(the)g(column)g(name)g(as)g(for)g(a)g(scalar)0 +3530 y(column.)d(V)-8 b(ector)32 b(columns)f(can)g(b)s(e)f(freely)h(in) +m(termixed)h(with)e(scalar)i(columns)e(or)h(constan)m(ts)h(in)f +(virtually)g(all)0 3643 y(expressions.)40 b(The)29 b(result)g(will)g(b) +s(e)g(of)g(the)g(same)h(dimension)e(as)i(the)f(v)m(ector.)42 +b(Tw)m(o)29 b(v)m(ectors)i(in)e(an)g(expression,)0 3756 +y(though,)f(need)e(to)i(ha)m(v)m(e)g(the)f(same)g(n)m(um)m(b)s(er)f(of) +h(elemen)m(ts)h(and)e(ha)m(v)m(e)j(the)e(same)g(dimensions.)39 +b(The)26 b(only)h(places)0 3869 y(a)35 b(v)m(ector)h(column)e(cannot)h +(b)s(e)f(used)f(\(for)i(no)m(w,)g(an)m(yw)m(a)m(y\))h(are)f(the)g(SA)m +(O)f(region)h(functions)f(and)f(the)i(NEAR)0 3982 y(b)s(o)s(olean)30 +b(function.)0 4142 y(Arithmetic)24 b(and)e(logical)k(op)s(erations)d +(are)h(all)g(p)s(erformed)d(on)i(an)g(elemen)m(t)h(b)m(y)f(elemen)m(t)i +(basis.)38 b(Comparing)23 b(t)m(w)m(o)0 4255 y(v)m(ector)32 +b(columns,)e(eg)h("COL1)f(==)g(COL2",)g(th)m(us)g(results)g(in)g +(another)g(v)m(ector)i(of)e(b)s(o)s(olean)h(v)-5 b(alues)30 +b(indicating)0 4368 y(whic)m(h)g(elemen)m(ts)i(of)e(the)h(t)m(w)m(o)h +(v)m(ectors)f(are)g(equal.)0 4528 y(Eigh)m(t)g(functions)f(are)h(a)m(v) +-5 b(ailable)33 b(that)e(op)s(erate)g(on)f(a)h(v)m(ector)h(and)d +(return)h(a)g(scalar)i(result:)191 4782 y Fe("minimum")284 +b(MIN\(V\))475 b("maximum")714 b(MAX\(V\))191 4895 y("average")284 +b(AVERAGE\(V\))f("median")762 b(MEDIAN\(V\))191 5008 +y("summation")188 b(SUM\(V\))475 b("standard)46 b(deviation")188 +b(STDDEV\(V\))191 5121 y("#)47 b(of)g(values")94 b(NELEM\(V\))379 +b("#)48 b(of)f(non-null)e(values")94 b(NVALID\(V\))0 +5375 y Fi(where)40 b(V)h(represen)m(ts)g(the)g(name)g(of)h(a)f(v)m +(ector)h(column)f(or)g(a)h(man)m(ually)f(constructed)g(v)m(ector)i +(using)d(curly)0 5488 y(brac)m(k)m(ets)27 b(as)f(describ)s(ed)e(b)s +(elo)m(w.)39 b(The)25 b(\014rst)g(6)h(of)g(these)g(functions)f(ignore)h +(an)m(y)g(n)m(ull)f(v)-5 b(alues)26 b(in)f(the)h(v)m(ector)h(when)0 +5601 y(computing)k(the)f(result.)41 b(The)30 b(STDDEV\(\))h(function)g +(computes)f(the)h(sample)g(standard)e(deviation,)j(i.e.)42 +b(it)31 b(is)0 5714 y(prop)s(ortional)f(to)h(1/SQR)-8 +b(T\(N-1\))32 b(instead)f(of)g(1/SQR)-8 b(T\(N\),)31 +b(where)f(N)h(is)f(NV)-10 b(ALID\(V\).)p eop end +%%Page: 99 105 +TeXDict begin 99 104 bop 0 299 a Fg(8.11.)73 b(R)m(O)m(W)31 +b(FIL)-8 b(TERING)30 b(SPECIFICA)-8 b(TION)2027 b Fi(99)0 +555 y(The)32 b(SUM)h(function)f(literally)j(sums)c(all)j(the)f(elemen)m +(ts)h(in)f(x,)g(returning)f(a)h(scalar)h(v)-5 b(alue.)48 +b(If)32 b(x)h(is)g(a)g(b)s(o)s(olean)0 668 y(v)m(ector,)40 +b(SUM)c(returns)f(the)h(n)m(um)m(b)s(er)f(of)i(TR)m(UE)f(elemen)m(ts.) +60 b(The)36 b(NELEM)g(function)g(returns)f(the)h(n)m(um)m(b)s(er)0 +781 y(of)i(elemen)m(ts)h(in)f(v)m(ector)h(x)f(whereas)f(NV)-10 +b(ALID)39 b(return)d(the)i(n)m(um)m(b)s(er)f(of)h(non-n)m(ull)f(elemen) +m(ts)j(in)d(the)h(v)m(ector.)0 894 y(\(NELEM)28 b(also)h(op)s(erates)f +(on)g(bit)f(and)g(string)h(columns,)g(returning)f(their)h(column)f +(widths.\))40 b(As)27 b(an)h(example,)0 1007 y(to)42 +b(test)g(whether)f(all)h(elemen)m(ts)h(of)f(t)m(w)m(o)g(v)m(ectors)h +(satisfy)f(a)g(giv)m(en)g(logical)i(comparison,)g(one)e(can)g(use)f +(the)0 1120 y(expression)668 1374 y Fe(SUM\()47 b(COL1)f(>)i(COL2)f(\)) +g(==)g(NELEM\()f(COL1)h(\))0 1629 y Fi(whic)m(h)32 b(will)g(return)f +(TR)m(UE)h(if)g(all)h(elemen)m(ts)g(of)f(COL1)g(are)g(greater)h(than)f +(their)g(corresp)s(onding)f(elemen)m(ts)i(in)0 1742 y(COL2.)0 +1902 y(T)-8 b(o)32 b(sp)s(ecify)f(a)i(single)f(elemen)m(t)h(of)f(a)g(v) +m(ector,)i(giv)m(e)f(the)f(column)f(name)h(follo)m(w)m(ed)h(b)m(y)f(a)g +(comma-separated)h(list)0 2015 y(of)c(co)s(ordinates)g(enclosed)h(in)e +(square)h(brac)m(k)m(ets.)41 b(F)-8 b(or)30 b(example,)g(if)e(a)h(v)m +(ector)i(column)d(named)h(PHAS)f(exists)h(in)0 2128 y(the)e(table)g(as) +g(a)g(one)g(dimensional,)h(256)g(comp)s(onen)m(t)f(list)g(of)g(n)m(um)m +(b)s(ers)e(from)h(whic)m(h)h(y)m(ou)g(w)m(an)m(ted)g(to)g(select)i(the) +0 2241 y(57th)j(comp)s(onen)m(t)g(for)f(use)g(in)g(the)h(expression,)f +(then)h(PHAS[57])g(w)m(ould)f(do)h(the)f(tric)m(k.)45 +b(Higher)32 b(dimensional)0 2354 y(arra)m(ys)41 b(of)h(data)f(ma)m(y)h +(app)s(ear)f(in)f(a)i(column.)73 b(But)41 b(in)g(order)f(to)i(in)m +(terpret)f(them,)j(the)e(TDIMn)e(k)m(eyw)m(ord)0 2466 +y(m)m(ust)34 b(app)s(ear)g(in)g(the)g(header.)52 b(Assuming)34 +b(that)h(a)f(\(4,4,4,4\))k(arra)m(y)c(is)h(pac)m(k)m(ed)g(in)m(to)g +(eac)m(h)h(ro)m(w)e(of)g(a)h(column)0 2579 y(named)26 +b(ARRA)-8 b(Y4D,)28 b(the)f(\(1,2,3,4\))i(comp)s(onen)m(t)e(elemen)m(t) +g(of)g(eac)m(h)g(ro)m(w)g(is)f(accessed)i(b)m(y)e(ARRA)-8 +b(Y4D[1,2,3,4].)0 2692 y(Arra)m(ys)33 b(up)e(to)j(dimension)e(5)h(are)f +(curren)m(tly)h(supp)s(orted.)46 b(Eac)m(h)33 b(v)m(ector)h(index)e +(can)h(itself)g(b)s(e)f(an)h(expression,)0 2805 y(although)39 +b(it)g(m)m(ust)g(ev)-5 b(aluate)40 b(to)f(an)g(in)m(teger)h(v)-5 +b(alue)39 b(within)f(the)h(b)s(ounds)d(of)j(the)g(v)m(ector.)67 +b(V)-8 b(ector)40 b(columns)0 2918 y(whic)m(h)31 b(con)m(tain)h(spaces) +g(or)f(arithmetic)h(op)s(erators)g(m)m(ust)f(ha)m(v)m(e)h(their)f +(names)g(enclosed)h(in)f("$")h(c)m(haracters)h(as)0 3031 +y(with)d($ARRA)-8 b(Y-4D$[1,2,3,4].)0 3191 y(A)45 b(more)f(C-lik)m(e)i +(syn)m(tax)g(for)e(sp)s(ecifying)g(v)m(ector)j(indices)d(is)h(also)h(a) +m(v)-5 b(ailable.)85 b(The)45 b(elemen)m(t)h(used)d(in)i(the)0 +3304 y(preceding)28 b(example)h(alternativ)m(ely)i(could)d(b)s(e)g(sp)s +(eci\014ed)g(with)f(the)i(syn)m(tax)g(ARRA)-8 b(Y4D[4][3][2][1].)45 +b(Note)30 b(the)0 3417 y(rev)m(erse)40 b(order)f(of)h(indices)f(\(as)h +(in)f(C\),)h(as)f(w)m(ell)i(as)e(the)h(fact)g(that)g(the)g(v)-5 +b(alues)40 b(are)f(still)i(ones-based)e(\(as)h(in)0 3530 +y(F)-8 b(ortran)39 b({)g(adopted)g(to)g(a)m(v)m(oid)h(am)m(biguit)m(y)g +(for)f(1D)g(v)m(ectors\).)67 b(With)39 b(this)g(syn)m(tax,)i(one)e(do)s +(es)f(not)h(need)f(to)0 3643 y(sp)s(ecify)30 b(all)h(of)g(the)f +(indices.)41 b(T)-8 b(o)31 b(extract)h(a)f(3D)g(slice)g(of)g(this)f(4D) +h(arra)m(y)-8 b(,)32 b(use)e(ARRA)-8 b(Y4D[4].)0 3803 +y(V)g(ariable-length)33 b(v)m(ector)f(columns)e(are)g(not)h(supp)s +(orted.)0 3963 y(V)-8 b(ectors)24 b(can)e(b)s(e)f(man)m(ually)h +(constructed)h(within)e(the)h(expression)g(using)f(a)h(comma-separated) +i(list)f(of)f(elemen)m(ts)0 4076 y(surrounded)35 b(b)m(y)j(curly)g +(braces)h(\(')p Fc(fg)p Fi('\).)66 b(F)-8 b(or)38 b(example,)j(')p +Fc(f)p Fi(1,3,6,1)p Fc(g)p Fi(')h(is)d(a)f(4-elemen)m(t)i(v)m(ector)g +(con)m(taining)g(the)0 4189 y(v)-5 b(alues)26 b(1,)h(3,)g(6,)g(and)e +(1.)40 b(The)25 b(v)m(ector)i(can)f(con)m(tain)h(only)f(b)s(o)s(olean,) +g(in)m(teger,)j(and)c(real)h(v)-5 b(alues)26 b(\(or)g(expressions\).)0 +4302 y(The)e(elemen)m(ts)h(will)g(b)s(e)f(promoted)g(to)h(the)g +(highest)f(datat)m(yp)s(e)h(presen)m(t.)39 b(An)m(y)24 +b(elemen)m(ts)i(whic)m(h)e(are)h(themselv)m(es)0 4415 +y(v)m(ectors,)40 b(will)d(b)s(e)f(expanded)g(out)h(with)g(eac)m(h)g(of) +g(its)g(elemen)m(ts)i(b)s(ecoming)d(an)h(elemen)m(t)h(in)f(the)g +(constructed)0 4528 y(v)m(ector.)0 4818 y Fb(8.11.4)113 +b(Go)s(o)s(d)38 b(Time)g(In)m(terv)-6 b(al)37 b(Filtering)0 +5036 y Fi(A)44 b(common)g(\014ltering)h(metho)s(d)e(in)m(v)m(olv)m(es)j +(selecting)g(ro)m(ws)e(whic)m(h)f(ha)m(v)m(e)j(a)e(time)h(v)-5 +b(alue)44 b(whic)m(h)g(lies)g(within)0 5149 y(what)37 +b(is)g(called)i(a)f(Go)s(o)s(d)f(Time)g(In)m(terv)-5 +b(al)38 b(or)f(GTI.)g(The)g(time)h(in)m(terv)-5 b(als)38 +b(are)g(de\014ned)e(in)h(a)g(separate)i(FITS)0 5262 y(table)i +(extension)g(whic)m(h)e(con)m(tains)i(2)g(columns)f(giving)g(the)h +(start)f(and)g(stop)g(time)g(of)g(eac)m(h)i(go)s(o)s(d)e(in)m(terv)-5 +b(al.)0 5375 y(The)34 b(\014ltering)h(op)s(eration)h(accepts)g(only)e +(those)i(ro)m(ws)e(of)h(the)g(input)f(table)i(whic)m(h)e(ha)m(v)m(e)i +(an)f(asso)s(ciated)h(time)0 5488 y(whic)m(h)f(falls)i(within)e(one)h +(of)g(the)g(time)g(in)m(terv)-5 b(als)37 b(de\014ned)e(in)g(the)h(GTI)g +(extension.)57 b(A)36 b(high)g(lev)m(el)h(function,)0 +5601 y(gti\014lter\(a,b,c,d\),)44 b(is)c(a)m(v)-5 b(ailable)42 +b(whic)m(h)d(ev)-5 b(aluates)41 b(eac)m(h)g(ro)m(w)e(of)h(the)f(input)g +(table)h(and)f(returns)f(TR)m(UE)i(or)0 5714 y(F)-10 +b(ALSE)30 b(dep)s(ending)f(whether)h(the)g(ro)m(w)h(is)f(inside)g(or)g +(outside)h(the)g(go)s(o)s(d)f(time)h(in)m(terv)-5 b(al.)42 +b(The)30 b(syn)m(tax)h(is)p eop end +%%Page: 100 106 +TeXDict begin 100 105 bop 0 299 a Fi(100)1573 b Fg(CHAPTER)30 +b(8.)112 b(EXTENDED)30 b(FILE)h(NAME)f(SYNT)-8 b(AX)286 +555 y Fe(gtifilter\()45 b([)j("gtifile")d([,)i(expr)g([,)g("STARTCOL",) +e("STOPCOL")g(])j(])f(])g(\))0 799 y Fi(where)20 b(eac)m(h)h("[]")h +(demarks)e(optional)h(parameters.)38 b(Note)21 b(that)g(the)g(quotes)f +(around)g(the)g(gti\014le)i(and)d(ST)-8 b(AR)g(T/STOP)0 +912 y(column)33 b(are)h(required.)50 b(Either)34 b(single)g(or)g +(double)f(quotes)h(ma)m(y)g(b)s(e)f(used.)50 b(In)33 +b(cases)h(where)g(this)f(expression)0 1024 y(is)d(en)m(tered)g(on)g +(the)g(Unix)g(command)g(line,)g(enclose)h(the)f(en)m(tire)h(expression) +f(in)f(double)h(quotes,)g(and)g(then)f(use)0 1137 y(single)c(quotes)g +(within)e(the)i(expression)f(to)h(enclose)g(the)g('gti\014le')h(and)d +(other)i(terms.)38 b(It)25 b(is)f(also)h(usually)f(p)s(ossible)0 +1250 y(to)38 b(do)e(the)h(rev)m(erse,)j(and)c(enclose)i(the)f(whole)g +(expression)g(in)f(single)i(quotes)f(and)f(then)h(use)f(double)g +(quotes)0 1363 y(within)d(the)g(expression.)50 b(The)33 +b(gti\014le,)i(if)f(sp)s(eci\014ed,)f(can)h(b)s(e)f(blank)g(\(""\))i +(whic)m(h)e(will)g(mean)h(to)g(use)f(the)h(\014rst)0 +1476 y(extension)g(with)g(the)f(name)h("*GTI*")h(in)f(the)f(curren)m(t) +h(\014le,)h(a)f(plain)f(extension)h(sp)s(eci\014er)f(\(eg,)j("+2",)g +("[2]",)0 1589 y(or)30 b("[STDGTI]"\))i(whic)m(h)e(will)h(b)s(e)f(used) +f(to)j(select)g(an)e(extension)h(in)f(the)h(curren)m(t)f(\014le,)h(or)f +(a)h(regular)g(\014lename)0 1702 y(with)f(or)h(without)f(an)h +(extension)g(sp)s(eci\014er)f(whic)m(h)g(in)g(the)h(latter)h(case)f +(will)g(mean)f(to)i(use)e(the)h(\014rst)e(extension)0 +1815 y(with)37 b(an)g(extension)g(name)h("*GTI*".)62 +b(Expr)36 b(can)h(b)s(e)g(an)m(y)g(arithmetic)i(expression,)f +(including)f(simply)g(the)0 1928 y(time)f(column)g(name.)57 +b(A)36 b(v)m(ector)h(time)g(expression)e(will)h(pro)s(duce)f(a)h(v)m +(ector)h(b)s(o)s(olean)f(result.)57 b(ST)-8 b(AR)g(TCOL)0 +2041 y(and)27 b(STOPCOL)f(are)i(the)g(names)g(of)g(the)g(ST)-8 +b(AR)g(T/STOP)26 b(columns)i(in)f(the)h(GTI)g(extension.)41 +b(If)27 b(one)h(of)g(them)0 2154 y(is)i(sp)s(eci\014ed,)g(they)h(b)s +(oth)f(m)m(ust)g(b)s(e.)0 2314 y(In)21 b(its)h(simplest)g(form,)i(no)d +(parameters)h(need)g(to)h(b)s(e)e(pro)m(vided)g({)h(default)g(v)-5 +b(alues)22 b(will)h(b)s(e)e(used.)37 b(The)21 b(expression)0 +2427 y("gti\014lter\(\)")33 b(is)e(equiv)-5 b(alen)m(t)31 +b(to)334 2670 y Fe(gtifilter\()45 b("",)i(TIME,)f("*START*",)f +("*STOP*")h(\))0 2913 y Fi(This)31 b(will)g(searc)m(h)h(the)g(curren)m +(t)f(\014le)g(for)g(a)h(GTI)f(extension,)h(\014lter)g(the)f(TIME)g +(column)g(in)g(the)h(curren)m(t)f(table,)0 3026 y(using)j(ST)-8 +b(AR)g(T/STOP)34 b(times)i(tak)m(en)f(from)g(columns)f(in)h(the)g(GTI)g +(extension)g(with)g(names)f(con)m(taining)j(the)0 3139 +y(strings)32 b("ST)-8 b(AR)g(T")33 b(and)e("STOP".)46 +b(The)32 b(wildcards)f(\('*'\))j(allo)m(w)g(sligh)m(t)f(v)-5 +b(ariations)33 b(in)f(naming)g(con)m(v)m(en)m(tions)0 +3252 y(suc)m(h)38 b(as)g("TST)-8 b(AR)g(T")39 b(or)f("ST)-8 +b(AR)g(TTIME".)65 b(The)37 b(same)i(default)g(v)-5 b(alues)38 +b(apply)g(for)g(unsp)s(eci\014ed)f(parame-)0 3365 y(ters)f(when)f(the)h +(\014rst)f(one)i(or)f(t)m(w)m(o)h(parameters)f(are)h(sp)s(eci\014ed.)56 +b(The)36 b(function)f(automatically)k(searc)m(hes)e(for)0 +3478 y(TIMEZER)m(O/I/F)g(k)m(eyw)m(ords)f(in)g(the)h(curren)m(t)f(and)g +(GTI)g(extensions,)i(applying)f(a)f(relativ)m(e)j(time)e(o\013set,)i +(if)0 3591 y(necessary)-8 b(.)0 3879 y Fb(8.11.5)113 +b(Spatial)38 b(Region)g(Filtering)0 4098 y Fi(Another)g(common)g +(\014ltering)g(metho)s(d)f(selects)i(ro)m(ws)f(based)g(on)f(whether)h +(the)g(spatial)h(p)s(osition)e(asso)s(ciated)0 4211 y(with)32 +b(eac)m(h)i(ro)m(w)e(is)h(lo)s(cated)h(within)e(a)h(giv)m(en)g +(2-dimensional)g(region.)48 b(The)32 b(syn)m(tax)h(for)f(this)h +(high-lev)m(el)h(\014lter)0 4324 y(is)334 4567 y Fe(regfilter\()45 +b("regfilename")f([)k(,)f(Xexpr,)f(Yexpr)h([)g(,)h("wcs)e(cols")h(])g +(])g(\))0 4811 y Fi(where)22 b(eac)m(h)i("[]")g(demarks)e(optional)i +(parameters.)38 b(The)22 b(region)h(\014le)g(name)f(is)h(required)f +(and)g(m)m(ust)g(b)s(e)g(enclosed)0 4924 y(in)34 b(quotes.)51 +b(The)33 b(remaining)h(parameters)h(are)f(optional.)52 +b(There)33 b(are)i(2)f(supp)s(orted)e(formats)i(for)f(the)h(region)0 +5036 y(\014le:)62 b(ASCI)s(I)39 b(\014le)h(or)h(FITS)f(binary)g(table.) +73 b(The)40 b(region)h(\014le)g(con)m(tains)h(a)f(list)g(of)g(one)g(or) +g(more)g(geometric)0 5149 y(shap)s(es)30 b(\(circle,)j(ellipse,)g(b)s +(o)m(x,)e(etc.\))44 b(whic)m(h)31 b(de\014nes)f(a)i(region)g(on)f(the)g +(celestial)j(sphere)c(or)h(an)g(area)h(within)f(a)0 5262 +y(particular)36 b(2D)g(image.)57 b(The)35 b(region)h(\014le)f(is)g(t)m +(ypically)j(generated)e(using)f(an)g(image)i(displa)m(y)e(program)g +(suc)m(h)0 5375 y(as)e(fv/PO)m(W)g(\(distribute)f(b)m(y)h(the)f(HEASAR) +m(C\),)h(or)g(ds9)f(\(distributed)g(b)m(y)g(the)h(Smithsonian)f +(Astroph)m(ysical)0 5488 y(Observ)-5 b(atory\).)69 b(Users)39 +b(should)g(refer)g(to)h(the)g(do)s(cumen)m(tation)h(pro)m(vided)e(with) +g(these)h(programs)f(for)h(more)0 5601 y(details)29 b(on)f(the)g(syn)m +(tax)h(used)e(in)h(the)h(region)f(\014les.)40 b(The)28 +b(FITS)f(region)i(\014le)f(format)h(is)f(de\014ned)f(in)h(a)g(do)s +(cumen)m(t)0 5714 y(a)m(v)-5 b(ailable)33 b(from)d(the)g(FITS)g(Supp)s +(ort)e(O\016ce)j(at)g(h)m(ttp://\014ts.gsfc.nasa.go)m(v/)k(registry/)c +(region.h)m(tml)p eop end +%%Page: 101 107 +TeXDict begin 101 106 bop 0 299 a Fg(8.11.)73 b(R)m(O)m(W)31 +b(FIL)-8 b(TERING)30 b(SPECIFICA)-8 b(TION)1982 b Fi(101)0 +555 y(In)21 b(its)h(simplest)g(form,)i(\(e.g.,)h +(reg\014lter\("region.reg"\))h(\))c(the)g(co)s(ordinates)g(in)g(the)g +(default)g('X')h(and)e('Y')h(columns)0 668 y(will)43 +b(b)s(e)g(used)f(to)i(determine)f(if)g(eac)m(h)h(ro)m(w)f(is)g(inside)g +(or)g(outside)g(the)g(area)h(sp)s(eci\014ed)e(in)h(the)g(region)h +(\014le.)0 781 y(Alternate)32 b(p)s(osition)e(column)g(names,)h(or)f +(expressions,)h(ma)m(y)g(b)s(e)e(en)m(tered)i(if)g(needed,)f(as)h(in) +382 1039 y Fe(regfilter\("region.reg",)41 b(XPOS,)47 +b(YPOS\))0 1297 y Fi(Region)37 b(\014ltering)f(can)g(b)s(e)f(applied)g +(most)h(unam)m(biguously)f(if)h(the)g(p)s(ositions)g(in)f(the)h(region) +g(\014le)g(and)f(in)h(the)0 1410 y(table)g(to)g(b)s(e)e(\014ltered)h +(are)h(b)s(oth)e(giv)m(e)j(in)e(terms)g(of)g(absolute)h(celestial)i(co) +s(ordinate)e(units.)54 b(In)35 b(this)g(case)h(the)0 +1523 y(lo)s(cations)26 b(and)d(sizes)i(of)g(the)f(geometric)i(shap)s +(es)e(in)g(the)g(region)h(\014le)f(are)h(sp)s(eci\014ed)f(in)g(angular) +g(units)g(on)g(the)g(sky)0 1636 y(\(e.g.,)32 b(p)s(ositions)e(giv)m(en) +i(in)e(R.A.)g(and)g(Dec.)42 b(and)30 b(sizes)h(in)f(arcseconds)g(or)h +(arcmin)m(utes\).)41 b(Similarly)-8 b(,)31 b(eac)m(h)h(ro)m(w)0 +1749 y(of)h(the)h(\014ltered)f(table)h(will)f(ha)m(v)m(e)i(a)e +(celestial)j(co)s(ordinate)e(asso)s(ciated)g(with)f(it.)50 +b(This)32 b(asso)s(ciation)j(is)e(usually)0 1862 y(implemen)m(ted)39 +b(using)e(a)i(set)g(of)f(so-called)i('W)-8 b(orld)39 +b(Co)s(ordinate)g(System')f(\(or)h(W)m(CS\))f(FITS)g(k)m(eyw)m(ords)g +(that)0 1975 y(de\014ne)27 b(the)g(co)s(ordinate)h(transformation)g +(that)g(m)m(ust)f(b)s(e)f(applied)h(to)h(the)g(v)-5 b(alues)27 +b(in)g(the)h('X')g(and)e('Y')i(columns)0 2088 y(to)j(calculate)i(the)d +(co)s(ordinate.)0 2248 y(Alternativ)m(ely)-8 b(,)30 b(one)d(can)g(p)s +(erform)e(spatial)j(\014ltering)e(using)g(unitless)h('pixel')g(co)s +(ordinates)h(for)e(the)h(regions)g(and)0 2361 y(ro)m(w)33 +b(p)s(ositions.)49 b(In)33 b(this)g(case)h(the)f(user)g(m)m(ust)g(b)s +(e)f(careful)h(to)h(ensure)f(that)g(the)h(p)s(ositions)f(in)g(the)g(2)g +(\014les)h(are)0 2474 y(self-consisten)m(t.)54 b(A)34 +b(t)m(ypical)i(problem)d(is)h(that)h(the)f(region)h(\014le)f(ma)m(y)h +(b)s(e)e(generated)j(using)d(a)i(binned)d(image,)0 2587 +y(but)g(the)h(un)m(binned)e(co)s(ordinates)i(are)g(giv)m(en)h(in)e(the) +h(ev)m(en)m(t)i(table.)48 b(The)32 b(R)m(OSA)-8 b(T)33 +b(ev)m(en)m(ts)h(\014les,)g(for)e(example,)0 2700 y(ha)m(v)m(e)f(X)f +(and)f(Y)g(pixel)h(co)s(ordinates)g(that)h(range)f(from)f(1)h(-)g +(15360.)42 b(These)30 b(co)s(ordinates)g(are)g(t)m(ypically)h(binned)0 +2812 y(b)m(y)i(a)h(factor)g(of)f(32)h(to)g(pro)s(duce)e(a)i(480x480)i +(pixel)d(image.)51 b(If)32 b(one)i(then)f(uses)g(a)g(region)h(\014le)f +(generated)h(from)0 2925 y(this)c(image)i(\(in)f(image)g(pixel)g +(units\))g(to)g(\014lter)f(the)h(R)m(OSA)-8 b(T)30 b(ev)m(en)m(ts)i +(\014le,)f(then)f(the)h(X)g(and)f(Y)g(column)h(v)-5 b(alues)0 +3038 y(m)m(ust)30 b(b)s(e)g(con)m(v)m(erted)i(to)f(corresp)s(onding)e +(pixel)i(units)f(as)g(in:)382 3296 y Fe(regfilter\("rosat.reg",)42 +b(X/32.+.5,)j(Y/32.+.5\))0 3555 y Fi(Note)h(that)f(this)f(binning)f +(con)m(v)m(ersion)j(is)e(not)h(necessary)g(if)f(the)h(region)g(\014le)f +(is)h(sp)s(eci\014ed)e(using)h(celestial)0 3668 y(co)s(ordinate)h +(units)f(instead)g(of)g(pixel)h(units)f(b)s(ecause)g(CFITSIO)e(is)j +(then)e(able)i(to)g(directly)g(compare)g(the)0 3780 y(celestial)30 +b(co)s(ordinate)f(of)e(eac)m(h)i(ro)m(w)f(in)f(the)h(table)g(with)g +(the)f(celestial)k(co)s(ordinates)d(in)f(the)h(region)g(\014le)g +(without)0 3893 y(ha)m(ving)j(to)g(kno)m(w)f(an)m(ything)h(ab)s(out)f +(ho)m(w)h(the)f(image)i(ma)m(y)f(ha)m(v)m(e)g(b)s(een)f(binned.)0 +4054 y(The)f(last)h("w)m(cs)g(cols")h(parameter)f(should)e(rarely)h(b)s +(e)g(needed.)40 b(If)29 b(supplied,)f(this)i(string)f(con)m(tains)i +(the)e(names)0 4166 y(of)37 b(the)g(2)h(columns)f(\(space)h(or)f(comma) +g(separated\))h(whic)m(h)f(ha)m(v)m(e)h(the)g(asso)s(ciated)g(W)m(CS)f +(k)m(eyw)m(ords.)61 b(If)37 b(not)0 4279 y(supplied,)f(the)g(\014lter)g +(will)h(scan)f(the)g(X)g(and)f(Y)h(expressions)g(for)g(column)f(names.) +58 b(If)35 b(only)h(one)h(is)f(found)e(in)0 4392 y(eac)m(h)e +(expression,)e(those)h(columns)f(will)h(b)s(e)e(used,)h(otherwise)h(an) +f(error)g(will)h(b)s(e)f(returned.)0 4552 y(These)g(region)h(shap)s(es) +f(are)g(supp)s(orted)f(\(names)h(are)h(case)h(insensitiv)m(e\):)334 +4811 y Fe(Point)428 b(\()48 b(X1,)f(Y1)g(\))715 b(<-)48 +b(One)f(pixel)f(square)g(region)334 4924 y(Line)476 b(\()48 +b(X1,)f(Y1,)g(X2,)f(Y2)i(\))333 b(<-)48 b(One)f(pixel)f(wide)h(region) +334 5036 y(Polygon)332 b(\()48 b(X1,)f(Y1,)g(X2,)f(Y2,)h(...)g(\))95 +b(<-)48 b(Rest)e(are)h(interiors)e(with)334 5149 y(Rectangle)236 +b(\()48 b(X1,)f(Y1,)g(X2,)f(Y2,)h(A)h(\))334 b(|)47 b(boundaries)e +(considered)334 5262 y(Box)524 b(\()48 b(Xc,)f(Yc,)g(Wdth,)f(Hght,)g(A) +i(\))143 b(V)47 b(within)f(the)h(region)334 5375 y(Diamond)332 +b(\()48 b(Xc,)f(Yc,)g(Wdth,)f(Hght,)g(A)i(\))334 5488 +y(Circle)380 b(\()48 b(Xc,)f(Yc,)g(R)g(\))334 5601 y(Annulus)332 +b(\()48 b(Xc,)f(Yc,)g(Rin,)f(Rout)h(\))334 5714 y(Ellipse)332 +b(\()48 b(Xc,)f(Yc,)g(Rx,)f(Ry,)h(A)h(\))p eop end +%%Page: 102 108 +TeXDict begin 102 107 bop 0 299 a Fi(102)1573 b Fg(CHAPTER)30 +b(8.)112 b(EXTENDED)30 b(FILE)h(NAME)f(SYNT)-8 b(AX)334 +555 y Fe(Elliptannulus)44 b(\()k(Xc,)f(Yc,)g(Rinx,)f(Riny,)g(Routx,)g +(Routy,)g(Ain,)h(Aout)g(\))334 668 y(Sector)380 b(\()48 +b(Xc,)f(Yc,)g(Amin,)f(Amax)h(\))0 1111 y Fi(where)28 +b(\(Xc,Yc\))j(is)d(the)h(co)s(ordinate)h(of)e(the)h(shap)s(e's)f(cen)m +(ter;)j(\(X#,Y#\))e(are)g(the)g(co)s(ordinates)g(of)g(the)g(shap)s(e's) +0 1224 y(edges;)36 b(Rxxx)d(are)h(the)f(shap)s(es')g(v)-5 +b(arious)34 b(Radii)f(or)h(semi-ma)5 b(jor/minor)34 b(axes;)i(and)d +(Axxx)g(are)h(the)f(angles)i(of)0 1337 y(rotation)e(\(or)e(b)s(ounding) +f(angles)i(for)f(Sector\))h(in)f(degrees.)44 b(F)-8 b(or)32 +b(rotated)h(shap)s(es,)e(the)g(rotation)i(angle)f(can)g(b)s(e)0 +1450 y(left)g(o\013,)h(indicating)f(no)f(rotation.)46 +b(Common)31 b(alternate)i(names)e(for)h(the)f(regions)h(can)g(also)h(b) +s(e)d(used:)43 b(rotb)s(o)m(x)0 1563 y(=)29 b(b)s(o)m(x;)g +(rotrectangle)i(=)e(rectangle;)i(\(rot\)rhom)m(bus)e(=)f +(\(rot\)diamond;)j(and)d(pie)h(=)f(sector.)42 b(When)28 +b(a)i(shap)s(e's)0 1676 y(name)e(is)g(preceded)f(b)m(y)h(a)g(min)m(us)g +(sign,)g('-',)i(the)e(de\014ned)e(region)j(is)f(instead)g(the)g(area)h +(*outside*)g(its)f(b)s(oundary)0 1789 y(\(ie,)36 b(the)e(region)h(is)f +(in)m(v)m(erted\).)53 b(All)34 b(the)g(shap)s(es)f(within)h(a)g(single) +h(region)f(\014le)h(are)f(OR'd)f(together)j(to)e(create)0 +1902 y(the)29 b(region,)i(and)d(the)i(order)f(is)g(signi\014can)m(t.)41 +b(The)29 b(o)m(v)m(erall)i(w)m(a)m(y)g(of)e(lo)s(oking)h(at)g(region)g +(\014les)f(is)g(that)h(if)f(the)h(\014rst)0 2015 y(region)f(is)g(an)g +(excluded)g(region)g(then)f(a)i(dumm)m(y)d(included)h(region)i(of)f +(the)g(whole)g(detector)h(is)f(inserted)f(in)h(the)0 +2128 y(fron)m(t.)40 b(Then)25 b(eac)m(h)j(region)f(sp)s(eci\014cation)h +(as)f(it)g(is)g(pro)s(cessed)f(o)m(v)m(errides)h(an)m(y)g(selections)i +(inside)d(of)h(that)g(region)0 2240 y(sp)s(eci\014ed)36 +b(b)m(y)g(previous)g(regions.)59 b(Another)37 b(w)m(a)m(y)g(of)g +(thinking)f(ab)s(out)g(this)g(is)h(that)g(if)f(a)h(previous)f(excluded) +0 2353 y(region)31 b(is)f(completely)i(inside)f(of)f(a)h(subsequen)m(t) +e(included)h(region)h(the)g(excluded)f(region)h(is)f(ignored.)0 +2514 y(The)44 b(p)s(ositional)i(co)s(ordinates)g(ma)m(y)f(b)s(e)g(giv)m +(en)h(either)f(in)g(pixel)g(units,)j(decimal)e(degrees)g(or)f +(hh:mm:ss.s,)0 2626 y(dd:mm:ss.s)25 b(units.)38 b(The)26 +b(shap)s(e)f(sizes)i(ma)m(y)f(b)s(e)g(giv)m(en)h(in)e(pixels,)j +(degrees,)f(arcmin)m(utes,)h(or)e(arcseconds.)40 b(Lo)s(ok)0 +2739 y(at)31 b(examples)g(of)f(region)h(\014le)g(pro)s(duced)d(b)m(y)i +(fv/PO)m(W)h(or)g(ds9)f(for)g(further)f(details)i(of)g(the)f(region)h +(\014le)f(format.)0 2900 y(There)37 b(are)g(three)g(functions)g(that)g +(are)h(primarily)f(for)f(use)h(with)g(SA)m(O)g(region)g(\014les)g(and)g +(the)g(FSA)m(OI)g(task,)0 3012 y(but)e(they)h(can)h(b)s(e)e(used)g +(directly)-8 b(.)59 b(They)36 b(return)f(a)h(b)s(o)s(olean)g(true)g(or) +g(false)g(dep)s(ending)f(on)h(whether)f(a)i(t)m(w)m(o)0 +3125 y(dimensional)31 b(p)s(oin)m(t)f(is)g(in)g(the)h(region)g(or)f +(not:)191 3569 y Fe("point)46 b(in)h(a)h(circular)d(region")477 +3681 y(circle\(xcntr,ycntr,radius)o(,Xco)o(lumn)o(,Yc)o(olum)o(n\))191 +3907 y("point)h(in)h(an)g(elliptical)e(region")430 4020 +y(ellipse\(xcntr,ycntr,xhl)o(f_w)o(dth,)o(yhlf)o(_wd)o(th,r)o(otat)o +(ion)o(,Xco)o(lumn)o(,Yc)o(olum)o(n\))191 4246 y("point)h(in)h(a)h +(rectangular)c(region")620 4359 y(box\(xcntr,ycntr,xfll_wdth,)o(yfll)o +(_wd)o(th,r)o(otat)o(ion)o(,Xco)o(lumn)o(,Yc)o(olum)o(n\))191 +4585 y(where)334 4698 y(\(xcntr,ycntr\))g(are)j(the)g(\(x,y\))f +(position)g(of)h(the)g(center)f(of)h(the)g(region)334 +4811 y(\(xhlf_wdth,yhlf_wdth\))42 b(are)47 b(the)g(\(x,y\))f(half)h +(widths)f(of)h(the)g(region)334 4924 y(\(xfll_wdth,yfll_wdth\))42 +b(are)47 b(the)g(\(x,y\))f(full)h(widths)f(of)h(the)g(region)334 +5036 y(\(radius\))f(is)h(half)f(the)h(diameter)f(of)h(the)g(circle)334 +5149 y(\(rotation\))e(is)i(the)g(angle\(degrees\))d(that)j(the)g +(region)f(is)h(rotated)f(with)620 5262 y(respect)g(to)h +(\(xcntr,ycntr\))334 5375 y(\(Xcoord,Ycoord\))d(are)j(the)g(\(x,y\))f +(coordinates)f(to)i(test,)f(usually)g(column)620 5488 +y(names)334 5601 y(NOTE:)g(each)h(parameter)e(can)i(itself)f(be)i(an)f +(expression,)d(not)j(merely)f(a)620 5714 y(column)h(name)f(or)h +(constant.)p eop end +%%Page: 103 109 +TeXDict begin 103 108 bop 0 299 a Fg(8.11.)73 b(R)m(O)m(W)31 +b(FIL)-8 b(TERING)30 b(SPECIFICA)-8 b(TION)1982 b Fi(103)0 +555 y Fb(8.11.6)113 b(Example)38 b(Ro)m(w)f(Filters)191 +859 y Fe([)47 b(binary)f(&&)i(mag)f(<=)g(5.0])380 b(-)48 +b(Extract)e(all)h(binary)f(stars)g(brighter)1766 972 +y(than)94 b(fifth)47 b(magnitude)e(\(note)h(that)1766 +1085 y(the)h(initial)f(space)g(is)h(necessary)e(to)1766 +1197 y(prevent)h(it)h(from)g(being)f(treated)g(as)h(a)1766 +1310 y(binning)f(specification\))191 1536 y([#row)g(>=)h(125)g(&&)h +(#row)e(<=)h(175])142 b(-)48 b(Extract)e(row)h(numbers)e(125)i(through) +f(175)191 1762 y([IMAGE[4,5])f(.gt.)h(100])476 b(-)48 +b(Extract)e(all)h(rows)f(that)h(have)g(the)1766 1875 +y(\(4,5\))f(component)g(of)h(the)g(IMAGE)f(column)1766 +1988 y(greater)g(than)g(100)191 2214 y([abs\(sin\(theta)e(*)j(#deg\)\)) +f(<)i(0.5])e(-)i(Extract)e(all)h(rows)f(having)g(the)1766 +2327 y(absolute)f(value)i(of)g(the)g(sine)g(of)g(theta)1766 +2439 y(less)94 b(than)47 b(a)g(half)g(where)f(the)h(angles)1766 +2552 y(are)g(tabulated)e(in)i(degrees)191 2778 y([SUM\()f(SPEC)h(>)g +(3*BACKGRND)e(\)>=1])94 b(-)48 b(Extract)e(all)h(rows)f(containing)f(a) +1766 2891 y(spectrum,)g(held)i(in)g(vector)f(column)1766 +3004 y(SPEC,)g(with)h(at)g(least)f(one)h(value)g(3)1766 +3117 y(times)f(greater)g(than)h(the)g(background)1766 +3230 y(level)f(held)h(in)g(a)h(keyword,)d(BACKGRND)191 +3456 y([VCOL=={1,4,2}])759 b(-)48 b(Extract)e(all)h(rows)f(whose)h +(vector)f(column)1766 3569 y(VCOL)h(contains)e(the)i(3-elements)e(1,)i +(4,)g(and)1766 3681 y(2.)191 3907 y([@rowFilter.txt])711 +b(-)48 b(Extract)e(rows)g(using)h(the)g(expression)1766 +4020 y(contained)e(within)h(the)h(text)g(file)1766 4133 +y(rowFilter.txt)191 4359 y([gtifilter\(\)])855 b(-)48 +b(Search)e(the)h(current)f(file)g(for)h(a)h(GTI)239 4472 +y(extension,)92 b(filter)i(the)47 b(TIME)239 4585 y(column)f(in)h(the)g +(current)f(table,)g(using)239 4698 y(START/STOP)f(times)h(taken)g(from) +239 4811 y(columns)f(in)j(the)f(GTI)94 b(extension)191 +5036 y([regfilter\("pow.reg"\)])423 b(-)48 b(Extract)e(rows)g(which)h +(have)f(a)i(coordinate)1766 5149 y(\(as)f(given)f(in)h(the)g(X)h(and)f +(Y)g(columns\))1766 5262 y(within)f(the)h(spatial)f(region)g(specified) +1766 5375 y(in)h(the)g(pow.reg)f(region)g(file.)191 5601 +y([regfilter\("pow.reg",)c(Xs,)47 b(Ys\)])f(-)i(Same)f(as)g(above,)f +(except)g(that)h(the)1766 5714 y(Xs)g(and)g(Ys)g(columns)f(will)h(be)g +(used)f(to)p eop end +%%Page: 104 110 +TeXDict begin 104 109 bop 0 299 a Fi(104)1573 b Fg(CHAPTER)30 +b(8.)112 b(EXTENDED)30 b(FILE)h(NAME)f(SYNT)-8 b(AX)1766 +555 y Fe(determine)45 b(the)i(coordinate)e(of)i(each)1766 +668 y(row)g(in)g(the)g(table.)0 1001 y Fd(8.12)180 b(Binning)45 +b(or)g(Histogramming)i(Sp)t(eci\014cation)0 1252 y Fi(The)22 +b(optional)i(binning)e(sp)s(eci\014er)g(is)h(enclosed)h(in)f(square)f +(brac)m(k)m(ets)j(and)d(can)h(b)s(e)f(distinguished)g(from)h(a)g +(general)0 1365 y(ro)m(w)32 b(\014lter)h(sp)s(eci\014cation)g(b)m(y)f +(the)h(fact)g(that)g(it)g(b)s(egins)f(with)g(the)g(k)m(eyw)m(ord)h +('bin')f(not)h(immediately)g(follo)m(w)m(ed)0 1477 y(b)m(y)41 +b(an)f(equals)i(sign.)72 b(When)41 b(binning)e(is)i(sp)s(eci\014ed,)i +(a)e(temp)s(orary)g(N-dimensional)g(FITS)f(primary)g(arra)m(y)0 +1590 y(is)j(created)h(b)m(y)f(computing)h(the)f(histogram)h(of)f(the)g +(v)-5 b(alues)44 b(in)e(the)i(sp)s(eci\014ed)e(columns)h(of)g(a)h(FITS) +e(table)0 1703 y(extension.)f(After)30 b(the)f(histogram)h(is)g +(computed)f(the)h(input)e(FITS)h(\014le)h(con)m(taining)h(the)e(table)i +(is)e(then)g(closed)0 1816 y(and)34 b(the)h(temp)s(orary)f(FITS)g +(primary)g(arra)m(y)h(is)g(op)s(ened)f(and)g(passed)g(to)h(the)g +(application)h(program.)54 b(Th)m(us,)0 1929 y(the)39 +b(application)h(program)f(nev)m(er)g(sees)g(the)g(original)h(FITS)e +(table)i(and)e(only)h(sees)h(the)f(image)h(in)e(the)h(new)0 +2042 y(temp)s(orary)32 b(\014le)h(\(whic)m(h)g(has)f(no)h(additional)g +(extensions\).)49 b(Ob)m(viously)-8 b(,)34 b(the)f(application)h +(program)e(m)m(ust)h(b)s(e)0 2155 y(exp)s(ecting)e(to)g(op)s(en)f(a)h +(FITS)e(image)j(and)e(not)g(a)h(FITS)f(table)h(in)f(this)g(case.)0 +2315 y(The)g(data)h(t)m(yp)s(e)f(of)h(the)f(FITS)g(histogram)g(image)i +(ma)m(y)f(b)s(e)f(sp)s(eci\014ed)f(b)m(y)h(app)s(ending)f('b')h(\(for)h +(8-bit)g(b)m(yte\),)g('i')0 2428 y(\(for)g(16-bit)g(in)m(tegers\),)h +('j')f(\(for)g(32-bit)g(in)m(teger\),)i('r')d(\(for)h(32-bit)g +(\015oating)h(p)s(oin)m(ts\),)e(or)h('d')f(\(for)h(64-bit)g(double)0 +2541 y(precision)d(\015oating)h(p)s(oin)m(t\))g(to)f(the)h('bin')e(k)m +(eyw)m(ord)i(\(e.g.)41 b('[binr)28 b(X]')g(creates)i(a)e(real)h +(\015oating)g(p)s(oin)m(t)f(image\).)41 b(If)0 2654 y(the)26 +b(datat)m(yp)s(e)h(is)f(not)g(explicitly)i(sp)s(eci\014ed)d(then)h(a)g +(32-bit)h(in)m(teger)h(image)f(will)f(b)s(e)f(created)i(b)m(y)f +(default,)i(unless)0 2767 y(the)h(w)m(eigh)m(ting)h(option)f(is)g(also) +h(sp)s(eci\014ed)e(in)g(whic)m(h)h(case)g(the)g(image)h(will)f(ha)m(v)m +(e)h(a)f(32-bit)h(\015oating)g(p)s(oin)m(t)e(data)0 2880 +y(t)m(yp)s(e)j(b)m(y)f(default.)0 3040 y(The)24 b(histogram)g(image)i +(ma)m(y)f(ha)m(v)m(e)g(from)f(1)g(to)h(4)g(dimensions)e(\(axes\),)k +(dep)s(ending)c(on)h(the)g(n)m(um)m(b)s(er)f(of)h(columns)0 +3153 y(that)31 b(are)g(sp)s(eci\014ed.)40 b(The)30 b(general)h(form)f +(of)g(the)h(binning)e(sp)s(eci\014cation)i(is:)48 3410 +y Fe([bin{bijrd})92 b(Xcol=min:max:binsize,)42 b(Ycol=)47 +b(...,)f(Zcol=...,)f(Tcol=...;)h(weight])0 3668 y Fi(in)39 +b(whic)m(h)g(up)f(to)i(4)g(columns,)h(eac)m(h)f(corresp)s(onding)e(to)i +(an)g(axis)f(of)h(the)f(image,)k(are)d(listed.)67 b(The)39 +b(column)0 3781 y(names)27 b(are)h(case)h(insensitiv)m(e,)g(and)e(the)h +(column)f(n)m(um)m(b)s(er)f(ma)m(y)i(b)s(e)f(giv)m(en)h(instead)g(of)g +(the)g(name,)g(preceded)f(b)m(y)0 3894 y(a)32 b(p)s(ound)e(sign)i +(\(e.g.,)i([bin)d(#4=1:512]\).)47 b(If)31 b(the)h(column)g(name)g(is)f +(not)h(sp)s(eci\014ed,)g(then)f(CFITSIO)g(will)h(\014rst)0 +4007 y(try)37 b(to)h(use)f(the)g('preferred)f(column')i(as)f(sp)s +(eci\014ed)g(b)m(y)g(the)g(CPREF)g(k)m(eyw)m(ord)h(if)f(it)g(exists)h +(\(e.g.,)j('CPREF)0 4120 y(=)i('DETX,DETY'\),)h(otherwise)g(column)f +(names)g('X',)h('Y',)g('Z',)f(and)f('T')i(will)f(b)s(e)f(assumed)h(for) +g(eac)m(h)h(of)0 4233 y(the)37 b(4)h(axes,)i(resp)s(ectiv)m(ely)-8 +b(.)62 b(In)37 b(cases)h(where)e(the)i(column)f(name)g(could)g(b)s(e)f +(confused)h(with)g(an)g(arithmetic)0 4346 y(expression,)30 +b(enclose)i(the)f(column)f(name)g(in)g(paren)m(theses)h(to)g(force)g +(the)f(name)h(to)g(b)s(e)f(in)m(terpreted)g(literally)-8 +b(.)0 4506 y(Eac)m(h)33 b(column)f(name)g(ma)m(y)h(b)s(e)f(follo)m(w)m +(ed)h(b)m(y)g(an)f(equals)g(sign)h(and)e(then)h(the)g(lo)m(w)m(er)i +(and)e(upp)s(er)e(range)i(of)h(the)0 4619 y(histogram,)f(and)e(the)h +(size)h(of)f(the)g(histogram)h(bins,)e(separated)h(b)m(y)g(colons.)43 +b(Spaces)31 b(are)g(allo)m(w)m(ed)i(b)s(efore)e(and)0 +4731 y(after)e(the)g(equals)g(sign)f(but)g(not)h(within)f(the)h +('min:max:binsize')g(string.)40 b(The)29 b(min,)f(max)h(and)f(binsize)h +(v)-5 b(alues)0 4844 y(ma)m(y)32 b(b)s(e)e(in)m(teger)i(or)f +(\015oating)h(p)s(oin)m(t)f(n)m(um)m(b)s(ers,)f(or)h(they)g(ma)m(y)g(b) +s(e)g(the)g(names)g(of)g(k)m(eyw)m(ords)g(in)g(the)g(header)g(of)0 +4957 y(the)g(table.)41 b(If)30 b(the)h(latter,)h(then)e(the)g(v)-5 +b(alue)31 b(of)g(that)g(k)m(eyw)m(ord)f(is)h(substituted)f(in)m(to)h +(the)g(expression.)0 5117 y(Default)37 b(v)-5 b(alues)36 +b(for)g(the)g(min,)h(max)f(and)g(binsize)g(quan)m(tities)h(will)f(b)s +(e)f(used)h(if)f(not)i(explicitly)g(giv)m(en)g(in)f(the)0 +5230 y(binning)29 b(expression)h(as)h(sho)m(wn)f(in)g(these)h +(examples:)191 5488 y Fe([bin)47 b(x)g(=)g(:512:2])94 +b(-)47 b(use)g(default)f(minimum)g(value)191 5601 y([bin)h(x)g(=)g +(1::2])190 b(-)47 b(use)g(default)f(maximum)g(value)191 +5714 y([bin)h(x)g(=)g(1:512])142 b(-)47 b(use)g(default)f(bin)h(size)p +eop end +%%Page: 105 111 +TeXDict begin 105 110 bop 0 299 a Fg(8.12.)113 b(BINNING)31 +b(OR)f(HISTOGRAMMING)h(SPECIFICA)-8 b(TION)1268 b Fi(105)191 +555 y Fe([bin)47 b(x)g(=)g(1:])286 b(-)47 b(use)g(default)f(maximum)g +(value)g(and)h(bin)g(size)191 668 y([bin)g(x)g(=)g(:512])190 +b(-)47 b(use)g(default)f(minimum)g(value)g(and)h(bin)g(size)191 +781 y([bin)g(x)g(=)g(2])334 b(-)47 b(use)g(default)f(minimum)g(and)h +(maximum)f(values)191 894 y([bin)h(x])524 b(-)47 b(use)g(default)f +(minimum,)g(maximum)g(and)g(bin)h(size)191 1007 y([bin)g(4])524 +b(-)47 b(default)f(2-D)h(image,)f(bin)h(size)g(=)g(4)h(in)f(both)g +(axes)191 1120 y([bin])619 b(-)47 b(default)f(2-D)h(image)0 +1374 y Fi(CFITSIO)31 b(will)i(use)f(the)h(v)-5 b(alue)33 +b(of)g(the)g(TLMINn,)f(TLMAXn,)h(and)f(TDBINn)h(k)m(eyw)m(ords,)h(if)e +(they)h(exist,)h(for)0 1487 y(the)j(default)f(min,)i(max,)g(and)e +(binsize,)i(resp)s(ectiv)m(ely)-8 b(.)61 b(If)36 b(they)h(do)f(not)h +(exist)g(then)f(CFITSIO)f(will)i(use)f(the)0 1600 y(actual)d(minim)m +(um)e(and)h(maxim)m(um)g(v)-5 b(alues)32 b(in)g(the)g(column)f(for)h +(the)g(histogram)h(min)e(and)h(max)g(v)-5 b(alues.)45 +b(The)0 1713 y(default)34 b(binsize)f(will)h(b)s(e)f(set)h(to)h(1,)g +(or)e(\(max)h(-)g(min\))f(/)h(10.,)i(whic)m(hev)m(er)e(is)g(smaller,)h +(so)e(that)i(the)e(histogram)0 1826 y(will)e(ha)m(v)m(e)g(at)g(least)h +(10)f(bins)f(along)h(eac)m(h)h(axis.)0 1986 y(A)41 b(shortcut)g +(notation)h(is)f(allo)m(w)m(ed)i(if)e(all)h(the)f(columns/axes)h(ha)m +(v)m(e)g(the)f(same)g(binning)f(sp)s(eci\014cation.)74 +b(In)0 2099 y(this)33 b(case)g(all)h(the)f(column)f(names)h(ma)m(y)g(b) +s(e)f(listed)h(within)f(paren)m(theses,)i(follo)m(w)m(ed)h(b)m(y)d(the) +h(\(single\))h(binning)0 2212 y(sp)s(eci\014cation,)d(as)g(in:)191 +2466 y Fe([bin)47 b(\(X,Y\)=1:512:2])191 2579 y([bin)g(\(X,Y\))f(=)h +(5])0 2834 y Fi(The)31 b(optional)i(w)m(eigh)m(ting)h(factor)e(is)g +(the)g(last)g(item)h(in)e(the)h(binning)f(sp)s(eci\014er)g(and,)h(if)f +(presen)m(t,)i(is)e(separated)0 2947 y(from)38 b(the)g(list)h(of)f +(columns)g(b)m(y)g(a)h(semi-colon.)65 b(As)39 b(the)f(histogram)h(is)f +(accum)m(ulated,)k(this)c(w)m(eigh)m(t)i(is)e(used)0 +3059 y(to)d(incremen)m(ted)f(the)g(v)-5 b(alue)35 b(of)f(the)g +(appropriated)f(bin)h(in)f(the)h(histogram.)52 b(If)34 +b(the)g(w)m(eigh)m(ting)i(factor)f(is)f(not)0 3172 y(sp)s(eci\014ed,)24 +b(then)f(the)g(default)g(w)m(eigh)m(t)i(=)d(1)i(is)f(assumed.)37 +b(The)23 b(w)m(eigh)m(ting)i(factor)f(ma)m(y)f(b)s(e)g(a)g(constan)m(t) +i(in)m(teger)f(or)0 3285 y(\015oating)30 b(p)s(oin)m(t)f(n)m(um)m(b)s +(er,)f(or)h(the)g(name)g(of)g(a)g(k)m(eyw)m(ord)h(con)m(taining)g(the)g +(w)m(eigh)m(ting)g(v)-5 b(alue.)41 b(Or)28 b(the)h(w)m(eigh)m(ting)0 +3398 y(factor)g(ma)m(y)g(b)s(e)e(the)h(name)g(of)h(a)f(table)h(column)f +(in)g(whic)m(h)f(case)j(the)e(v)-5 b(alue)28 b(in)g(that)h(column,)f +(on)g(a)h(ro)m(w)f(b)m(y)g(ro)m(w)0 3511 y(basis,)i(will)h(b)s(e)f +(used.)0 3671 y(In)35 b(some)h(cases,)i(the)d(column)h(or)f(k)m(eyw)m +(ord)h(ma)m(y)g(giv)m(e)h(the)f(recipro)s(cal)g(of)g(the)g(actual)h(w)m +(eigh)m(t)g(v)-5 b(alue)36 b(that)g(is)0 3784 y(needed.)49 +b(In)32 b(this)h(case,)i(precede)e(the)h(w)m(eigh)m(t)g(k)m(eyw)m(ord)g +(or)f(column)g(name)g(b)m(y)g(a)g(slash)g('/')h(to)g(tell)g(CFITSIO)0 +3897 y(to)d(use)f(the)h(recipro)s(cal)g(of)f(the)h(v)-5 +b(alue)31 b(when)e(constructing)i(the)g(histogram.)0 +4057 y(F)-8 b(or)35 b(complex)f(or)g(commonly)g(used)f(histograms,)j +(one)e(can)g(also)h(place)g(its)f(description)g(in)m(to)h(a)f(text)h +(\014le)f(and)0 4170 y(imp)s(ort)44 b(it)g(in)m(to)i(the)e(binning)f +(sp)s(eci\014cation)i(using)f(the)h(syn)m(tax)f('[bin)g +(@\014lename.txt]'.)84 b(The)44 b(\014le's)g(con-)0 4283 +y(ten)m(ts)37 b(can)e(extend)h(o)m(v)m(er)h(m)m(ultiple)f(lines,)i +(although)e(it)g(m)m(ust)f(still)i(conform)f(to)g(the)g(no-spaces)g +(rule)f(for)h(the)0 4396 y(min:max:binsize)h(syn)m(tax)h(and)e(eac)m(h) +i(axis)g(sp)s(eci\014cation)g(m)m(ust)f(still)g(b)s(e)g +(comma-separated.)62 b(An)m(y)37 b(lines)g(in)0 4509 +y(the)32 b(external)h(text)g(\014le)f(that)h(b)s(egin)e(with)h(2)g +(slash)g(c)m(haracters)h(\('//'\))h(will)e(b)s(e)g(ignored)g(and)f(ma)m +(y)i(b)s(e)e(used)g(to)0 4622 y(add)f(commen)m(ts)h(in)m(to)g(the)g +(\014le.)0 4782 y(Examples:)191 5036 y Fe([bini)46 b(detx,)h(dety])762 +b(-)47 b(2-D,)g(16-bit)f(integer)g(histogram)1861 5149 +y(of)i(DETX)e(and)h(DETY)g(columns,)e(using)1861 5262 +y(default)h(values)g(for)h(the)g(histogram)1861 5375 +y(range)g(and)g(binsize)191 5601 y([bin)g(\(detx,)f(dety\)=16;)f +(/exposure])g(-)i(2-D,)g(32-bit)f(real)h(histogram)e(of)i(DETX)1861 +5714 y(and)g(DETY)g(columns)f(with)g(a)i(bin)f(size)f(=)i(16)p +eop end +%%Page: 106 112 +TeXDict begin 106 111 bop 0 299 a Fi(106)1573 b Fg(CHAPTER)30 +b(8.)112 b(EXTENDED)30 b(FILE)h(NAME)f(SYNT)-8 b(AX)1861 +555 y Fe(in)48 b(both)e(axes.)h(The)f(histogram)g(values)1861 +668 y(are)h(divided)f(by)h(the)g(EXPOSURE)f(keyword)1861 +781 y(value.)191 1007 y([bin)h(time=TSTART:TSTOP:0.1])280 +b(-)47 b(1-D)g(lightcurve,)e(range)h(determined)f(by)1861 +1120 y(the)i(TSTART)f(and)h(TSTOP)g(keywords,)1861 1233 +y(with)g(0.1)g(unit)g(size)f(bins.)191 1458 y([bin)h(pha,)f +(time=8000.:8100.:0.1])90 b(-)47 b(2-D)g(image)g(using)f(default)g +(binning)1861 1571 y(of)i(the)e(PHA)h(column)f(for)h(the)g(X)h(axis,) +1861 1684 y(and)f(1000)g(bins)g(in)g(the)g(range)1861 +1797 y(8000.)g(to)g(8100.)f(for)h(the)g(Y)h(axis.)191 +2023 y([bin)f(@binFilter.txt])616 b(-)47 b(Use)g(the)g(contents)f(of)h +(the)g(text)f(file)1861 2136 y(binFilter.txt)f(for)h(the)h(binning)1861 +2249 y(specifications.)p eop end +%%Page: 107 113 +TeXDict begin 107 112 bop 0 1225 a Ff(Chapter)65 b(9)0 +1687 y Fl(T)-19 b(emplate)76 b(Files)0 2180 y Fi(When)38 +b(a)h(new)f(FITS)g(\014le)h(is)g(created)g(with)g(a)f(call)i(to)g +(\014ts)p 2101 2180 28 4 v 32 w(create)p 2369 2180 V +35 w(\014le,)g(the)f(name)g(of)g(a)g(template)h(\014le)e(ma)m(y)0 +2293 y(b)s(e)h(supplied)g(in)h(paren)m(theses)g(immediately)h(follo)m +(wing)g(the)g(name)f(of)g(the)g(new)f(\014le)h(to)h(b)s(e)e(created.)71 +b(This)0 2406 y(template)27 b(is)e(used)g(to)h(de\014ne)f(the)h +(structure)f(of)h(one)f(or)h(more)g(HDUs)g(in)f(the)h(new)f(\014le.)39 +b(The)25 b(template)i(\014le)e(ma)m(y)0 2518 y(b)s(e)32 +b(another)h(FITS)f(\014le,)i(in)f(whic)m(h)f(case)i(the)f(newly)g +(created)h(\014le)f(will)g(ha)m(v)m(e)h(exactly)h(the)e(same)g(k)m(eyw) +m(ords)g(in)0 2631 y(eac)m(h)25 b(HDU)g(as)g(in)f(the)g(template)i +(FITS)d(\014le,)j(but)d(all)j(the)e(data)h(units)e(will)i(b)s(e)f +(\014lled)g(with)f(zeros.)40 b(The)24 b(template)0 2744 +y(\014le)i(ma)m(y)h(also)g(b)s(e)e(an)h(ASCI)s(I)e(text)j(\014le,)g +(where)f(eac)m(h)h(line)f(\(in)g(general\))i(describ)s(es)d(one)h(FITS) +f(k)m(eyw)m(ord)i(record.)0 2857 y(The)j(format)h(of)f(the)h(ASCI)s(I)e +(template)i(\014le)g(is)f(describ)s(ed)f(in)i(the)f(follo)m(wing)i +(sections.)0 3188 y Fd(9.1)135 b(Detailed)47 b(T)-11 +b(emplate)46 b(Line)f(F)-11 b(ormat)0 3438 y Fi(The)30 +b(format)h(of)f(eac)m(h)i(ASCI)s(I)c(template)k(line)f(closely)h(follo) +m(ws)f(the)g(format)g(of)f(a)h(FITS)f(k)m(eyw)m(ord)g(record:)95 +3682 y Fe(KEYWORD)46 b(=)i(KEYVALUE)d(/)j(COMMENT)0 3926 +y Fi(except)22 b(that)g(free)g(format)f(ma)m(y)h(b)s(e)f(used)f +(\(e.g.,)25 b(the)d(equals)f(sign)h(ma)m(y)f(app)s(ear)g(at)h(an)m(y)g +(p)s(osition)f(in)g(the)h(line\))g(and)0 4039 y(T)-8 +b(AB)34 b(c)m(haracters)g(are)g(allo)m(w)m(ed)h(and)e(are)g(treated)h +(the)g(same)f(as)h(space)f(c)m(haracters.)51 b(The)33 +b(KEYV)-10 b(ALUE)33 b(and)0 4152 y(COMMENT)d(\014elds)g(are)h +(optional.)43 b(The)30 b(equals)h(sign)f(c)m(haracter)j(is)d(also)i +(optional,)g(but)e(it)h(is)f(recommended)0 4264 y(that)42 +b(it)f(b)s(e)g(included)f(for)h(clarit)m(y)-8 b(.)75 +b(An)m(y)41 b(template)i(line)e(that)h(b)s(egins)f(with)f(the)i(p)s +(ound)d('#')i(c)m(haracter)i(is)0 4377 y(ignored)30 b(b)m(y)h(the)f +(template)i(parser)e(and)g(ma)m(y)h(b)s(e)e(use)h(to)h(insert)g(commen) +m(ts)g(in)m(to)g(the)g(template)h(\014le)e(itself.)0 +4538 y(The)c(KEYW)m(ORD)g(name)g(\014eld)g(is)g(limited)h(to)g(8)f(c)m +(haracters)h(in)f(length)h(and)e(only)h(the)g(letters)i(A-Z,)e(digits)h +(0-9,)0 4650 y(and)h(the)g(h)m(yphen)f(and)h(underscore)g(c)m +(haracters)h(ma)m(y)g(b)s(e)f(used,)g(without)h(an)m(y)f(em)m(b)s +(edded)g(spaces.)40 b(Lo)m(w)m(ercase)0 4763 y(letters)22 +b(in)f(the)h(template)g(k)m(eyw)m(ord)g(name)f(will)g(b)s(e)g(con)m(v)m +(erted)i(to)f(upp)s(ercase.)36 b(Leading)22 b(spaces)f(in)g(the)h +(template)0 4876 y(line)k(preceding)g(the)f(k)m(eyw)m(ord)h(name)g(are) +g(generally)h(ignored,)g(except)f(if)g(the)g(\014rst)f(8)h(c)m +(haracters)h(of)f(a)g(template)0 4989 y(line)f(are)h(all)g(blank,)g +(then)f(the)g(en)m(tire)h(line)g(is)f(treated)h(as)f(a)h(FITS)e(commen) +m(t)i(k)m(eyw)m(ord)g(\(with)f(a)h(blank)e(k)m(eyw)m(ord)0 +5102 y(name\))31 b(and)f(is)g(copied)h(v)m(erbatim)g(in)m(to)g(the)g +(FITS)e(header.)0 5262 y(The)37 b(KEYV)-10 b(ALUE)37 +b(\014eld)g(ma)m(y)h(ha)m(v)m(e)g(an)m(y)g(allo)m(w)m(ed)h(FITS)e(data) +h(t)m(yp)s(e:)54 b(c)m(haracter)39 b(string,)h(logical,)h(in)m(teger,)0 +5375 y(real,)34 b(complex)f(in)m(teger,)i(or)d(complex)i(real.)47 +b(The)32 b(c)m(haracter)j(string)d(v)-5 b(alues)33 b(need)f(not)h(b)s +(e)f(enclosed)h(in)f(single)0 5488 y(quote)d(c)m(haracters)h(unless)e +(they)g(are)h(necessary)g(to)g(distinguish)e(the)i(string)f(from)g(a)h +(di\013eren)m(t)g(data)g(t)m(yp)s(e)f(\(e.g.)0 5601 y(2.0)h(is)e(a)h +(real)h(but)e('2.0')i(is)f(a)g(string\).)40 b(The)27 +b(k)m(eyw)m(ord)h(has)f(an)h(unde\014ned)d(\(n)m(ull\))j(v)-5 +b(alue)29 b(if)e(the)h(template)h(record)0 5714 y(only)h(con)m(tains)i +(blanks)e(follo)m(wing)i(the)e("=")h(or)g(b)s(et)m(w)m(een)g(the)f("=") +h(and)f(the)g("/")i(commen)m(t)g(\014eld)d(delimiter.)1882 +5942 y(107)p eop end +%%Page: 108 114 +TeXDict begin 108 113 bop 0 299 a Fi(108)2295 b Fg(CHAPTER)30 +b(9.)71 b(TEMPLA)-8 b(TE)30 b(FILES)0 555 y Fi(String)c(k)m(eyw)m(ord)h +(v)-5 b(alues)27 b(longer)g(than)f(68)h(c)m(haracters)h(\(the)f(maxim)m +(um)f(length)h(that)g(will)g(\014t)f(in)g(a)h(single)g(FITS)0 +668 y(k)m(eyw)m(ord)41 b(record\))g(are)g(p)s(ermitted)f(using)g(the)h +(CFITSIO)e(long)i(string)g(con)m(v)m(en)m(tion.)74 b(They)40 +b(can)h(either)g(b)s(e)0 781 y(sp)s(eci\014ed)28 b(as)i(a)f(single)h +(long)f(line)h(in)e(the)i(template,)h(or)e(b)m(y)f(using)h(m)m(ultiple) +h(lines)f(where)f(the)i(con)m(tin)m(uing)g(lines)0 894 +y(con)m(tain)i(the)e('CONTINUE')g(k)m(eyw)m(ord,)h(as)g(in)f(this)g +(example:)95 1139 y Fe(LONGKEY)46 b(=)i('This)e(is)h(a)h(long)e(string) +g(value)h(that)f(is)i(contin&')95 1252 y(CONTINUE)94 +b('ued)46 b(over)h(2)g(records')f(/)h(comment)f(field)h(goes)f(here)0 +1497 y Fi(The)29 b(format)h(of)g(template)h(lines)e(with)h(CONTINUE)e +(k)m(eyw)m(ord)i(is)g(v)m(ery)g(strict:)41 b(3)30 b(spaces)g(m)m(ust)f +(follo)m(w)i(CON-)0 1610 y(TINUE)f(and)g(the)g(rest)h(of)f(the)h(line)g +(is)f(copied)h(v)m(erbatim)g(to)g(the)g(FITS)e(\014le.)0 +1771 y(The)i(start)h(of)g(the)f(optional)i(COMMENT)e(\014eld)g(m)m(ust) +h(b)s(e)e(preceded)i(b)m(y)f("/",)i(whic)m(h)e(is)h(used)f(to)h +(separate)g(it)0 1883 y(from)e(the)g(k)m(eyw)m(ord)h(v)-5 +b(alue)30 b(\014eld.)41 b(Exceptions)30 b(are)h(if)f(the)h(KEYW)m(ORD)g +(name)f(\014eld)g(con)m(tains)h(COMMENT,)0 1996 y(HISTOR)-8 +b(Y,)30 b(CONTINUE,)g(or)g(if)g(the)h(\014rst)f(8)g(c)m(haracters)i(of) +f(the)f(template)i(line)f(are)g(blanks.)0 2157 y(More)c(than)f(one)h +(Header-Data)i(Unit)e(\(HDU\))g(ma)m(y)g(b)s(e)f(de\014ned)f(in)h(the)h +(template)h(\014le.)39 b(The)26 b(start)h(of)g(an)f(HDU)0 +2269 y(de\014nition)k(is)g(denoted)h(with)f(a)h(SIMPLE)e(or)i(XTENSION) +e(template)j(line:)0 2430 y(1\))i(SIMPLE)f(b)s(egins)g(a)h(Primary)g +(HDU)g(de\014nition.)50 b(SIMPLE)33 b(ma)m(y)h(only)g(app)s(ear)f(as)h +(the)g(\014rst)f(k)m(eyw)m(ord)h(in)0 2543 y(the)e(template)i(\014le.) +45 b(If)32 b(the)g(template)i(\014le)e(b)s(egins)f(with)h(XTENSION)f +(instead)h(of)g(SIMPLE,)g(then)f(a)i(default)0 2655 y(empt)m(y)d +(Primary)e(HDU)i(is)g(created,)h(and)d(the)i(template)h(is)e(then)g +(assumed)f(to)i(de\014ne)f(the)h(k)m(eyw)m(ords)f(starting)0 +2768 y(with)h(the)h(\014rst)e(extension)i(follo)m(wing)h(the)f(Primary) +f(HDU.)0 2928 y(2\))35 b(XTENSION)e(marks)g(the)i(b)s(eginning)e(of)h +(a)h(new)e(extension)i(HDU)f(de\014nition.)52 b(The)33 +b(previous)h(HDU)h(will)0 3041 y(b)s(e)30 b(closed)h(at)g(this)f(p)s +(oin)m(t)h(and)e(pro)s(cessing)i(of)f(the)h(next)f(extension)h(b)s +(egins.)0 3373 y Fd(9.2)135 b(Auto-indexing)45 b(of)h(Keyw)l(ords)0 +3623 y Fi(If)31 b(a)h(template)g(k)m(eyw)m(ord)g(name)f(ends)g(with)g +(a)g("#")h(c)m(haracter,)i(it)e(is)f(said)g(to)h(b)s(e)f +('auto-indexed'.)44 b(Eac)m(h)32 b("#")0 3736 y(c)m(haracter)i(will)f +(b)s(e)f(replaced)i(b)m(y)e(the)h(curren)m(t)g(in)m(teger)h(index)e(v) +-5 b(alue,)34 b(whic)m(h)f(gets)g(reset)h(=)e(1)h(at)h(the)e(start)i +(of)0 3849 y(eac)m(h)h(new)f(HDU)g(in)g(the)g(\014le)g(\(or)g(7)h(in)e +(the)h(sp)s(ecial)h(case)g(of)f(a)g(GR)m(OUP)h(de\014nition\).)51 +b(The)33 b(FIRST)g(indexed)0 3962 y(k)m(eyw)m(ord)c(in)f(eac)m(h)h +(template)h(HDU)f(de\014nition)f(is)g(used)f(as)i(the)f('incremen)m +(tor';)j(eac)m(h)e(subsequen)m(t)f(o)s(ccurrence)0 4075 +y(of)k(this)f(SAME)g(k)m(eyw)m(ord)h(will)g(cause)g(the)g(index)f(v)-5 +b(alue)32 b(to)g(b)s(e)f(incremen)m(ted.)44 b(This)31 +b(b)s(eha)m(vior)g(can)h(b)s(e)f(rather)0 4188 y(subtle,)d(as)g +(illustrated)h(in)e(the)h(follo)m(wing)h(examples)f(in)f(whic)m(h)h +(the)g(TTYPE)e(k)m(eyw)m(ord)i(is)g(the)g(incremen)m(tor)g(in)0 +4300 y(b)s(oth)i(cases:)95 4546 y Fe(TTYPE#)47 b(=)g(TIME)95 +4659 y(TFORM#)g(=)g(1D)95 4772 y(TTYPE#)g(=)g(RATE)95 +4884 y(TFORM#)g(=)g(1E)0 5130 y Fi(will)26 b(create)i(TTYPE1,)e(TF)m +(ORM1,)i(TTYPE2,)f(and)e(TF)m(ORM2)i(k)m(eyw)m(ords.)40 +b(But)26 b(if)g(the)g(template)h(lo)s(oks)f(lik)m(e,)95 +5375 y Fe(TTYPE#)47 b(=)g(TIME)95 5488 y(TTYPE#)g(=)g(RATE)95 +5601 y(TFORM#)g(=)g(1D)95 5714 y(TFORM#)g(=)g(1E)p eop +end +%%Page: 109 115 +TeXDict begin 109 114 bop 0 299 a Fg(9.3.)72 b(TEMPLA)-8 +b(TE)30 b(P)-8 b(ARSER)30 b(DIRECTIVES)2028 b Fi(109)0 +555 y(this)31 b(results)f(in)h(a)g(FITS)f(\014les)h(with)f(TTYPE1,)h +(TTYPE2,)g(TF)m(ORM2,)h(and)e(TF)m(ORM2,)i(whic)m(h)f(is)g(probably)0 +668 y(not)g(what)f(w)m(as)h(in)m(tended!)0 1000 y Fd(9.3)135 +b(T)-11 b(emplate)46 b(P)l(arser)g(Directiv)l(es)0 1251 +y Fi(In)29 b(addition)i(to)f(the)g(template)i(lines)e(whic)m(h)g +(de\014ne)f(individual)h(k)m(eyw)m(ords,)g(the)g(template)i(parser)d +(recognizes)0 1363 y(3)h(sp)s(ecial)h(directiv)m(es)g(whic)m(h)f(are)g +(eac)m(h)h(preceded)f(b)m(y)f(the)h(bac)m(kslash)h(c)m(haracter:)90 +b Fe(\\include,)45 b(\\group)p Fi(,)29 b(and)48 1476 +y Fe(\\end)p Fi(.)0 1637 y(The)37 b('include')h(directiv)m(e)i(m)m(ust) +d(b)s(e)h(follo)m(w)m(ed)h(b)m(y)f(a)g(\014lename.)63 +b(It)38 b(forces)g(the)g(parser)f(to)i(temp)s(orarily)f(stop)0 +1749 y(reading)d(the)g(curren)m(t)g(template)h(\014le)f(and)f(b)s(egin) +h(reading)g(the)g(include)f(\014le.)55 b(Once)35 b(the)g(parser)f(reac) +m(hes)i(the)0 1862 y(end)f(of)h(the)g(include)f(\014le)h(it)g(con)m +(tin)m(ues)g(parsing)g(the)f(curren)m(t)h(template)h(\014le.)56 +b(Include)35 b(\014les)h(can)g(b)s(e)f(nested,)0 1975 +y(and)30 b(HDU)h(de\014nitions)f(can)g(span)g(m)m(ultiple)h(template)h +(\014les.)0 2135 y(The)f(start)h(of)g(a)g(GR)m(OUP)h(de\014nition)e(is) +h(denoted)g(with)f(the)h('group')g(directiv)m(e,)h(and)f(the)f(end)h +(of)f(a)i(GR)m(OUP)0 2248 y(de\014nition)k(is)h(denoted)f(with)g(the)h +('end')f(directiv)m(e.)63 b(Eac)m(h)39 b(GR)m(OUP)e(con)m(tains)i(0)f +(or)f(more)h(mem)m(b)s(er)f(blo)s(c)m(ks)0 2361 y(\(HDUs)44 +b(or)f(GR)m(OUPs\).)79 b(Mem)m(b)s(er)42 b(blo)s(c)m(ks)i(of)f(t)m(yp)s +(e)g(GR)m(OUP)g(can)g(con)m(tain)h(their)f(o)m(wn)g(mem)m(b)s(er)f(blo) +s(c)m(ks.)0 2474 y(The)32 b(GR)m(OUP)g(de\014nition)g(itself)h(o)s +(ccupies)g(one)f(FITS)g(\014le)g(HDU)h(of)f(sp)s(ecial)h(t)m(yp)s(e)f +(\(GR)m(OUP)h(HDU\),)h(so)e(if)h(a)0 2587 y(template)f(sp)s(eci\014es)e +(1)h(group)e(with)h(1)h(mem)m(b)s(er)f(HDU)h(lik)m(e:)0 +2838 y Fe(\\group)0 2951 y(grpdescr)46 b(=)h('demo')0 +3064 y(xtension)f(bintable)0 3177 y(#)h(this)g(bintable)f(has)h(0)g +(cols,)f(0)i(rows)0 3290 y(\\end)0 3541 y Fi(then)30 +b(the)h(parser)e(creates)j(a)f(FITS)f(\014le)g(with)g(3)h(HDUs)g(:)0 +3792 y Fe(1\))47 b(dummy)g(PHDU)0 3905 y(2\))g(GROUP)g(HDU)f(\(has)h(1) +h(member,)d(which)i(is)g(bintable)e(in)j(HDU)f(number)f(3\))0 +4018 y(3\))h(bintable)f(\(member)g(of)h(GROUP)f(in)h(HDU)g(number)f +(2\))0 4269 y Fi(T)-8 b(ec)m(hnically)32 b(sp)s(eaking,)e(the)f(GR)m +(OUP)i(HDU)f(is)g(a)g(BINT)-8 b(ABLE)30 b(with)g(6)g(columns.)40 +b(Applications)31 b(can)f(de\014ne)0 4382 y(additional)23 +b(columns)f(in)f(a)i(GR)m(OUP)f(HDU)h(using)f(TF)m(ORMn)f(and)h(TTYPEn) +f(\(where)g(n)h(is)g(7,)i(8,)h(....\))39 b(k)m(eyw)m(ords)0 +4494 y(or)30 b(their)h(auto-indexing)g(equiv)-5 b(alen)m(ts.)0 +4655 y(F)d(or)26 b(a)f(more)g(complicated)h(example)f(of)g(a)h +(template)g(\014le)f(using)f(the)h(group)f(directiv)m(es,)k(lo)s(ok)d +(at)g(the)g(sample.tpl)0 4767 y(\014le)30 b(that)h(is)g(included)e(in)i +(the)f(CFITSIO)f(distribution.)0 5100 y Fd(9.4)135 b(F)-11 +b(ormal)46 b(T)-11 b(emplate)45 b(Syn)l(tax)0 5350 y +Fi(The)30 b(template)i(syn)m(tax)f(can)f(formally)h(b)s(e)f(de\014ned)f +(as)i(follo)m(ws:)191 5601 y Fe(TEMPLATE)45 b(=)j(BLOCK)e([)i(BLOCK)e +(...)h(])p eop end +%%Page: 110 116 +TeXDict begin 110 115 bop 0 299 a Fi(110)2295 b Fg(CHAPTER)30 +b(9.)71 b(TEMPLA)-8 b(TE)30 b(FILES)334 555 y Fe(BLOCK)46 +b(=)i({)f(HDU)g(|)h(GROUP)e(})334 781 y(GROUP)g(=)i(\\GROUP)e([)h +(BLOCK)g(...)g(])g(\\END)430 1007 y(HDU)f(=)i(XTENSION)d([)j(LINE)f +(...)f(])i({)f(XTENSION)f(|)h(\\GROUP)f(|)i(\\END)f(|)g(EOF)g(})382 +1233 y(LINE)f(=)i([)f(KEYWORD)f([)i(=)f(])h(])f([)g(VALUE)g(])g([)h(/)f +(COMMENT)f(])191 1458 y(X)h(...)238 b(-)48 b(X)f(can)g(be)g(present)f +(1)h(or)h(more)e(times)191 1571 y({)h(X)h(|)f(Y)h(})f(-)h(X)f(or)g(Y) +191 1684 y([)g(X)h(])238 b(-)48 b(X)f(is)g(optional)0 +1937 y Fi(A)m(t)34 b(the)f(topmost)g(lev)m(el,)i(the)e(template)i +(de\014nes)c(1)j(or)e(more)h(template)h(blo)s(c)m(ks.)49 +b(Blo)s(c)m(ks)34 b(can)f(b)s(e)f(either)h(HDU)0 2050 +y(\(Header)27 b(Data)h(Unit\))g(or)e(a)h(GR)m(OUP)-8 +b(.)28 b(F)-8 b(or)27 b(eac)m(h)g(blo)s(c)m(k)g(the)g(parser)f(creates) +i(1)f(\(or)g(more)f(for)h(GR)m(OUPs\))g(FITS)0 2163 y(\014le)j(HDUs.)0 +2495 y Fd(9.5)135 b(Errors)0 2745 y Fi(In)24 b(general)h(the)f(\014ts)p +692 2745 28 4 v 33 w(execute)p 1019 2745 V 34 w(template\(\))i +(function)e(tries)h(to)g(b)s(e)f(as)g(atomic)i(as)f(p)s(ossible,)g(so)f +(either)h(ev)m(erything)0 2858 y(is)f(done)g(or)g(nothing)f(is)h(done.) +39 b(If)23 b(an)h(error)f(o)s(ccurs)h(during)f(parsing)g(of)h(the)g +(template,)j(\014ts)p 3125 2858 V 33 w(execute)p 3452 +2858 V 34 w(template\(\))0 2971 y(will)k(\(try)g(to\))h(delete)g(the)f +(top)g(lev)m(el)h(BLOCK)e(\(with)h(all)g(its)h(c)m(hildren)e(if)h(an)m +(y\))g(in)g(whic)m(h)f(the)h(error)f(o)s(ccurred,)0 3084 +y(then)g(it)h(will)g(stop)f(reading)h(the)f(template)i(\014le)e(and)g +(it)h(will)g(return)e(with)h(an)g(error.)0 3417 y Fd(9.6)135 +b(Examples)0 3667 y Fi(1.)54 b(This)34 b(template)i(\014le)f(will)g +(create)h(a)f(200)h(x)e(300)i(pixel)f(image,)j(with)c(4-b)m(yte)i(in)m +(teger)g(pixel)f(v)-5 b(alues,)36 b(in)f(the)0 3780 y(primary)29 +b(HDU:)95 4032 y Fe(SIMPLE)47 b(=)g(T)95 4145 y(BITPIX)g(=)g(32)95 +4258 y(NAXIS)g(=)g(2)239 b(/)47 b(number)f(of)h(dimensions)95 +4371 y(NAXIS1)g(=)g(100)95 b(/)47 b(length)f(of)h(first)g(axis)95 +4484 y(NAXIS2)g(=)g(200)95 b(/)47 b(length)f(of)h(second)f(axis)95 +4597 y(OBJECT)h(=)g(NGC)g(253)g(/)g(name)g(of)g(observed)f(object)0 +4850 y Fi(The)35 b(allo)m(w)m(ed)i(v)-5 b(alues)36 b(of)f(BITPIX)g(are) +h(8,)h(16,)h(32,)g(-32,)g(or)d(-64,)j(represen)m(ting,)f(resp)s(ectiv)m +(ely)-8 b(,)39 b(8-bit)d(in)m(teger,)0 4962 y(16-bit)c(in)m(teger,)g +(32-bit)f(in)m(teger,)h(32-bit)g(\015oating)f(p)s(oin)m(t,)g(or)f(64)h +(bit)g(\015oating)g(p)s(oin)m(t)f(pixels.)0 5123 y(2.)39 +b(T)-8 b(o)23 b(create)h(a)f(FITS)e(table,)26 b(the)c(template)i +(\014rst)e(needs)g(to)i(include)e(XTENSION)g(=)g(T)-8 +b(ABLE)23 b(or)f(BINT)-8 b(ABLE)0 5235 y(to)31 b(de\014ne)e(whether)g +(it)h(is)g(an)f(ASCI)s(I)g(or)g(binary)g(table,)i(and)f(NAXIS2)g(to)g +(de\014ne)f(the)h(n)m(um)m(b)s(er)f(of)h(ro)m(ws)f(in)h(the)0 +5348 y(table.)50 b(Tw)m(o)34 b(template)g(lines)g(are)g(then)f(needed)f +(to)i(de\014ne)f(the)g(name)h(\(TTYPEn\))e(and)h(FITS)g(data)h(format)0 +5461 y(\(TF)m(ORMn\))d(of)f(the)h(columns,)f(as)h(in)f(this)g(example:) +95 5714 y Fe(xtension)46 b(=)h(bintable)p eop end +%%Page: 111 117 +TeXDict begin 111 116 bop 0 299 a Fg(9.6.)72 b(EXAMPLES)3039 +b Fi(111)95 555 y Fe(naxis2)47 b(=)g(40)95 668 y(ttype#)g(=)g(Name)95 +781 y(tform#)g(=)g(10a)95 894 y(ttype#)g(=)g(Npoints)95 +1007 y(tform#)g(=)g(j)95 1120 y(ttype#)g(=)g(Rate)95 +1233 y(tunit#)g(=)g(counts/s)95 1346 y(tform#)g(=)g(e)0 +1605 y Fi(The)26 b(ab)s(o)m(v)m(e)j(example)e(de\014nes)f(a)i(n)m(ull)f +(primary)f(arra)m(y)h(follo)m(w)m(ed)i(b)m(y)e(a)g(40-ro)m(w)h(binary)e +(table)i(extension)g(with)f(3)0 1718 y(columns)h(called)h('Name',)h +('Np)s(oin)m(ts',)f(and)f('Rate',)i(with)e(data)h(formats)f(of)g('10A') +i(\(ASCI)s(I)d(c)m(haracter)i(string\),)0 1831 y('1J')k(\(in)m(teger\)) +i(and)d('1E')i(\(\015oating)f(p)s(oin)m(t\),)h(resp)s(ectiv)m(ely)-8 +b(.)50 b(Note)34 b(that)f(the)g(other)g(required)f(FITS)g(k)m(eyw)m +(ords)0 1944 y(\(BITPIX,)37 b(NAXIS,)g(NAXIS1,)h(PCOUNT,)e(GCOUNT,)h +(TFIELDS,)f(and)g(END\))h(do)g(not)g(need)f(to)h(b)s(e)f(ex-)0 +2057 y(plicitly)j(de\014ned)d(in)i(the)f(template)i(b)s(ecause)f(their) +g(v)-5 b(alues)38 b(can)g(b)s(e)f(inferred)f(from)i(the)f(other)h(k)m +(eyw)m(ords)g(in)0 2170 y(the)d(template.)55 b(This)34 +b(example)i(also)g(illustrates)f(that)h(the)f(templates)h(are)f +(generally)h(case-insensitiv)m(e)h(\(the)0 2283 y(k)m(eyw)m(ord)29 +b(names)g(and)g(TF)m(ORMn)f(v)-5 b(alues)30 b(are)f(con)m(v)m(erted)i +(to)e(upp)s(er-case)g(in)f(the)h(FITS)g(\014le\))g(and)f(that)i(string) +0 2396 y(k)m(eyw)m(ord)h(v)-5 b(alues)31 b(generally)g(do)f(not)h(need) +f(to)h(b)s(e)f(enclosed)h(in)f(quotes.)p eop end +%%Page: 112 118 +TeXDict begin 112 117 bop 0 299 a Fi(112)2295 b Fg(CHAPTER)30 +b(9.)71 b(TEMPLA)-8 b(TE)30 b(FILES)p eop end +%%Page: 113 119 +TeXDict begin 113 118 bop 0 1225 a Ff(Chapter)65 b(10)0 +1687 y Fl(Summary)76 b(of)i(all)f(FITSIO)0 1937 y(User-In)-6 +b(terface)77 b(Subroutines)0 2429 y Fi(Error)29 b(Status)i(Routines)f +(page)h(29)382 2696 y Fe(FTVERS\()46 b(>)h(version\))382 +2809 y(FTGERR\(status,)d(>)j(errtext\))382 2922 y(FTGMSG\()f(>)h +(errmsg\))382 3035 y(FTRPRT)f(\(stream,)f(>)j(status\))382 +3147 y(FTPMSG\(errmsg\))382 3260 y(FTPMRK)382 3373 y(FTCMSG)382 +3486 y(FTCMRK)0 3753 y Fi(FITS)30 b(File)h(Op)s(en)e(and)h(Close)h +(Subroutines:)39 b(page)31 b(35)382 4020 y Fe +(FTOPEN\(unit,filename,rwm)o(ode)o(,)42 b(>)47 b(blocksize,status\))382 +4133 y(FTDKOPEN\(unit,filename,r)o(wmo)o(de,)41 b(>)48 +b(blocksize,status\))382 4246 y(FTNOPN\(unit,filename,rwm)o(ode)o(,)42 +b(>)47 b(status\))382 4359 y(FTDOPN\(unit,filename,rwm)o(ode)o(,)42 +b(>)47 b(status\))382 4472 y(FTTOPN\(unit,filename,rwm)o(ode)o(,)42 +b(>)47 b(status\))382 4585 y(FTIOPN\(unit,filename,rwm)o(ode)o(,)42 +b(>)47 b(status\))382 4698 y(FTREOPEN\(unit,)d(>)j(newunit,)f(status\)) +382 4811 y(FTINIT\(unit,filename,blo)o(cks)o(ize,)41 +b(>)48 b(status\))382 4924 y(FTDKINIT\(unit,filename,b)o(loc)o(ksiz)o +(e,)42 b(>)47 b(status\))382 5036 y(FTTPLT\(unit,)d(filename,)i +(tplfilename,)e(>)j(status\))382 5149 y(FTFLUS\(unit,)d(>)k(status\)) +382 5262 y(FTCLOS\(unit,)c(>)k(status\))382 5375 y(FTDELT\(unit,)c(>)k +(status\))382 5488 y(FTGIOU\()e(>)h(iounit,)f(status\))382 +5601 y(FTFIOU\(iounit,)e(>)j(status\))0 5714 y(CFITS2Unit\(fitsfile)c +(*ptr\))141 b(\(C)48 b(routine\))1882 5942 y Fi(113)p +eop end +%%Page: 114 120 +TeXDict begin 114 119 bop 0 299 a Fi(114)281 b Fg(CHAPTER)30 +b(10.)112 b(SUMMAR)-8 b(Y)32 b(OF)e(ALL)g(FITSIO)f(USER-INTERF)-10 +b(A)m(CE)30 b(SUBR)m(OUTINES)382 555 y Fe(CUnit2FITS\(int)44 +b(unit\))380 b(\(C)47 b(routine\))382 668 y(FTEXTN\(filename,)c(>)48 +b(nhdu,)e(status\))382 781 y(FTFLNM\(unit,)e(>)k(filename,)d(status\)) +382 894 y(FTFLMD\(unit,)f(>)k(iomode,)e(status\))382 +1007 y(FFURLT\(unit,)e(>)k(urltype,)d(status\))382 1120 +y(FTIURL\(filename,)e(>)48 b(filetype,)d(infile,)h(outfile,)f(extspec,) +h(filter,)716 1233 y(binspec,)f(colspec,)h(status\))382 +1346 y(FTRTNM\(filename,)d(>)48 b(rootname,)d(status\))382 +1458 y(FTEXIST\(filename,)e(>)k(exist,)f(status\))0 1695 +y Fi(HDU-Lev)m(el)33 b(Op)s(erations:)40 b(page)31 b(38)382 +1932 y Fe(FTMAHD\(unit,nhdu,)43 b(>)k(hdutype,status\))382 +2045 y(FTMRHD\(unit,nmove,)c(>)k(hdutype,status\))382 +2158 y(FTGHDN\(unit,)d(>)k(nhdu\))382 2271 y(FTMNHD\(unit,)c(hdutype,)i +(extname,)f(extver,)h(>)i(status\))382 2384 y(FTGHDT\(unit,)c(>)k +(hdutype,)d(status\))382 2497 y(FTTHDU\(unit,)f(>)k(hdunum,)e(status\)) +382 2610 y(FTCRHD\(unit,)e(>)k(status\))382 2723 y +(FTIIMG\(unit,bitpix,naxis)o(,na)o(xes,)41 b(>)48 b(status\))382 +2836 y(FTITAB\(unit,rowlen,nrows)o(,tf)o(ield)o(s,tt)o(ype)o(,tbc)o +(ol,t)o(for)o(m,tu)o(nit,)o(ext)o(name)o(,)42 b(>)716 +2949 y(status\))382 3061 y(FTIBIN\(unit,nrows,tfield)o(s,t)o(type)o +(,tfo)o(rm,)o(tuni)o(t,ex)o(tna)o(me,v)o(arid)o(at)f(>)48 +b(status\))382 3174 y(FTRSIM\(unit,bitpix,naxis)o(,na)o(xes,)o(stat)o +(us\))382 3287 y(FTDHDU\(unit,)c(>)k(hdutype,status\))382 +3400 y(FTCPFL\(iunit,ounit,previ)o(ous)o(,)42 b(current,)j(following,)g +(>)j(status\))382 3513 y(FTCOPY\(iunit,ounit,morek)o(eys)o(,)42 +b(>)47 b(status\))382 3626 y(FTCPHD\(inunit,)d(outunit,)h(>)j(status\)) +382 3739 y(FTCPDT\(iunit,ounit,)42 b(>)48 b(status\))0 +3976 y Fi(Subroutines)29 b(to)i(sp)s(ecify)f(or)g(mo)s(dify)g(the)g +(structure)g(of)h(the)f(CHDU:)h(page)h(41)382 4213 y +Fe(FTRDEF\(unit,)44 b(>)k(status\))93 b(\(DEPRECATED\))382 +4326 y(FTPDEF\(unit,bitpix,naxis)o(,na)o(xes,)o(pcou)o(nt,)o(gcou)o +(nt,)41 b(>)48 b(status\))93 b(\(DEPRECATED\))382 4439 +y(FTADEF\(unit,rowlen,tfiel)o(ds,)o(tbco)o(l,tf)o(orm)o(,nro)o(ws)42 +b(>)47 b(status\))94 b(\(DEPRECATED\))382 4551 y +(FTBDEF\(unit,tfields,tfor)o(m,v)o(arid)o(at,n)o(row)o(s)42 +b(>)47 b(status\))94 b(\(DEPRECATED\))382 4664 y(FTDDEF\(unit,bytlen,) +42 b(>)48 b(status\))93 b(\(DEPRECATED\))382 4777 y +(FTPTHP\(unit,theap,)43 b(>)k(status\))0 5014 y Fi(Header)31 +b(Space)f(and)g(P)m(osition)i(Subroutines:)39 b(page)31 +b(43)382 5251 y Fe(FTHDEF\(unit,morekeys,)42 b(>)47 b(status\))382 +5364 y(FTGHSP\(iunit,)d(>)j(keysexist,keysadd,status\))382 +5477 y(FTGHPS\(iunit,)d(>)j(keysexist,key_no,status\))0 +5714 y Fi(Read)31 b(or)f(W)-8 b(rite)32 b(Standard)d(Header)i +(Subroutines:)39 b(page)31 b(43)p eop end +%%Page: 115 121 +TeXDict begin 115 120 bop 3764 299 a Fi(115)382 555 y +Fe(FTPHPS\(unit,bitpix,naxis)o(,na)o(xes,)41 b(>)48 b(status\))382 +668 y(FTPHPR\(unit,simple,bitpi)o(x,n)o(axis)o(,nax)o(es,)o(pcou)o +(nt,g)o(cou)o(nt,e)o(xten)o(d,)41 b(>)48 b(status\))382 +781 y(FTGHPR\(unit,maxdim,)42 b(>)48 b(simple,bitpix,naxis,naxe)o(s,p)o +(coun)o(t,gc)o(oun)o(t,ex)o(tend)o(,)716 894 y(status\))382 +1007 y(FTPHTB\(unit,rowlen,nrows)o(,tf)o(ield)o(s,tt)o(ype)o(,tbc)o +(ol,t)o(for)o(m,tu)o(nit,)o(ext)o(name)o(,)42 b(>)716 +1120 y(status\))382 1233 y(FTGHTB\(unit,maxdim,)g(>)48 +b(rowlen,nrows,tfields,tty)o(pe,)o(tbco)o(l,tf)o(orm)o(,tun)o(it,)716 +1346 y(extname,status\))382 1458 y(FTPHBN\(unit,nrows,tfield)o(s,t)o +(type)o(,tfo)o(rm,)o(tuni)o(t,ex)o(tna)o(me,v)o(arid)o(at)41 +b(>)48 b(status\))382 1571 y(FTGHBN\(unit,maxdim,)42 +b(>)48 b(nrows,tfields,ttype,tfor)o(m,t)o(unit)o(,ext)o(nam)o(e,va)o +(rida)o(t,)716 1684 y(status\))0 1942 y Fi(W)-8 b(rite)32 +b(Keyw)m(ord)e(Subroutines:)39 b(page)31 b(45)382 2199 +y Fe(FTPREC\(unit,card,)43 b(>)k(status\))382 2312 y +(FTPCOM\(unit,comment,)42 b(>)48 b(status\))382 2425 +y(FTPHIS\(unit,history,)42 b(>)48 b(status\))382 2538 +y(FTPDAT\(unit,)c(>)k(status\))382 2651 y(FTPKY[JKLS]\(unit,keyword)o +(,ke)o(yval)o(,com)o(men)o(t,)42 b(>)47 b(status\))382 +2764 y(FTPKY[EDFG]\(unit,keyword)o(,ke)o(yval)o(,dec)o(ima)o(ls,c)o +(omme)o(nt,)41 b(>)48 b(status\))382 2877 y(FTPKLS\(unit,keyword,keyv)o +(al,)o(comm)o(ent,)41 b(>)47 b(status\))382 2990 y(FTPLSW\(unit,)d(>)k +(status\))382 3103 y(FTPKYU\(unit,keyword,comm)o(ent)o(,)42 +b(>)47 b(status\))382 3216 y(FTPKN[JKLS]\(unit,keyroot)o(,st)o(artn)o +(o,no)o(_ke)o(ys,k)o(eyva)o(ls,)o(comm)o(ents)o(,)42 +b(>)47 b(status\))382 3329 y(FTPKN[EDFG]\(unit,keyroot)o(,st)o(artn)o +(o,no)o(_ke)o(ys,k)o(eyva)o(ls,)o(deci)o(mals)o(,co)o(mmen)o(ts,)41 +b(>)907 3441 y(status\))382 3554 y(FTCPKYinunit,)j(outunit,)i(innum,)g +(outnum,)f(keyroot,)h(>)h(status\))382 3667 y +(FTPKYT\(unit,keyword,intv)o(al,)o(dblv)o(al,c)o(omm)o(ent,)41 +b(>)48 b(status\))382 3780 y(FTPKTP\(unit,)c(filename,)i(>)h(status\)) +382 3893 y(FTPUNT\(unit,keyword,unit)o(s,)41 b(>)48 b(status\))0 +4151 y Fi(Insert)30 b(Keyw)m(ord)g(Subroutines:)39 b(page)31 +b(47)382 4408 y Fe(FTIREC\(unit,key_no,card,)41 b(>)47 +b(status\))382 4521 y(FTIKY[JKLS]\(unit,keyword)o(,ke)o(yval)o(,com)o +(men)o(t,)42 b(>)47 b(status\))382 4634 y(FTIKLS\(unit,keyword,keyv)o +(al,)o(comm)o(ent,)41 b(>)47 b(status\))382 4747 y +(FTIKY[EDFG]\(unit,keyword)o(,ke)o(yval)o(,dec)o(ima)o(ls,c)o(omme)o +(nt,)41 b(>)48 b(status\))382 4860 y(FTIKYU\(unit,keyword,comm)o(ent)o +(,)42 b(>)47 b(status\))0 5118 y Fi(Read)31 b(Keyw)m(ord)f +(Subroutines:)39 b(page)31 b(47)382 5375 y Fe(FTGREC\(unit,key_no,)42 +b(>)48 b(card,status\))382 5488 y(FTGKYN\(unit,key_no,)42 +b(>)48 b(keyword,value,comment,st)o(atu)o(s\))382 5601 +y(FTGCRD\(unit,keyword,)42 b(>)48 b(card,status\))382 +5714 y(FTGNXK\(unit,inclist,ninc)o(,ex)o(clis)o(t,ne)o(xc,)41 +b(>)48 b(card,status\))p eop end +%%Page: 116 122 +TeXDict begin 116 121 bop 0 299 a Fi(116)281 b Fg(CHAPTER)30 +b(10.)112 b(SUMMAR)-8 b(Y)32 b(OF)e(ALL)g(FITSIO)f(USER-INTERF)-10 +b(A)m(CE)30 b(SUBR)m(OUTINES)382 555 y Fe(FTGKEY\(unit,keyword,)42 +b(>)48 b(value,comment,status\))382 668 y(FTGKY[EDJKLS]\(unit,keywo)o +(rd,)41 b(>)48 b(keyval,comment,status\))382 781 y +(FTGKN[EDJKLS]\(unit,keyro)o(ot,)o(star)o(tno,)o(max)o(_key)o(s,)42 +b(>)47 b(keyvals,nfound,status\))382 894 y(FTGKYT\(unit,keyword,)42 +b(>)48 b(intval,dblval,comment,s)o(tat)o(us\))382 1007 +y(FTGUNT\(unit,keyword,)42 b(>)48 b(units,status\))0 +1263 y Fi(Mo)s(dify)30 b(Keyw)m(ord)g(Subroutines:)39 +b(page)31 b(49)382 1519 y Fe(FTMREC\(unit,key_no,card,)41 +b(>)47 b(status\))382 1632 y(FTMCRD\(unit,keyword,card)o(,)42 +b(>)47 b(status\))382 1745 y(FTMNAM\(unit,oldkey,keywo)o(rd,)41 +b(>)48 b(status\))382 1858 y(FTMCOM\(unit,keyword,comm)o(ent)o(,)42 +b(>)47 b(status\))382 1971 y(FTMKY[JKLS]\(unit,keyword)o(,ke)o(yval)o +(,com)o(men)o(t,)42 b(>)47 b(status\))382 2084 y +(FTMKLS\(unit,keyword,keyv)o(al,)o(comm)o(ent,)41 b(>)47 +b(status\))382 2197 y(FTMKY[EDFG]\(unit,keyword)o(,ke)o(yval)o(,dec)o +(ima)o(ls,c)o(omme)o(nt,)41 b(>)48 b(status\))382 2310 +y(FTMKYU\(unit,keyword,comm)o(ent)o(,)42 b(>)47 b(status\))0 +2566 y Fi(Up)s(date)30 b(Keyw)m(ord)g(Subroutines:)39 +b(page)32 b(50)382 2822 y Fe(FTUCRD\(unit,keyword,card)o(,)42 +b(>)47 b(status\))382 2935 y(FTUKY[JKLS]\(unit,keyword)o(,ke)o(yval)o +(,com)o(men)o(t,)42 b(>)47 b(status\))382 3048 y +(FTUKLS\(unit,keyword,keyv)o(al,)o(comm)o(ent,)41 b(>)47 +b(status\))382 3161 y(FTUKY[EDFG]\(unit,keyword)o(,ke)o(yval)o(,dec)o +(ima)o(ls,c)o(omme)o(nt,)41 b(>)48 b(status\))382 3274 +y(FTUKYU\(unit,keyword,comm)o(ent)o(,)42 b(>)47 b(status\))0 +3530 y Fi(Delete)33 b(Keyw)m(ord)d(Subroutines:)39 b(page)31 +b(50)382 3786 y Fe(FTDREC\(unit,key_no,)42 b(>)48 b(status\))382 +3899 y(FTDKEY\(unit,keyword,)42 b(>)48 b(status\))0 4155 +y Fi(De\014ne)31 b(Data)h(Scaling)f(P)m(arameters)g(and)f(Unde\014ned)f +(Pixel)i(Flags:)42 b(page)31 b(51)382 4411 y Fe +(FTPSCL\(unit,bscale,bzero)o(,)42 b(>)47 b(status\))382 +4524 y(FTTSCL\(unit,colnum,tscal)o(,tz)o(ero,)41 b(>)48 +b(status\))382 4637 y(FTPNUL\(unit,blank,)43 b(>)k(status\))382 +4750 y(FTSNUL\(unit,colnum,snull)41 b(>)47 b(status\))382 +4863 y(FTTNUL\(unit,colnum,tnull)41 b(>)47 b(status\))0 +5119 y Fi(FITS)30 b(Primary)f(Arra)m(y)i(or)f(IMA)m(GE)i(Extension)e +(I/O)h(Subroutines:)39 b(page)31 b(52)382 5375 y Fe(FTGIDT\(unit,)44 +b(>)k(bitpix,status\))382 5488 y(FTGIET\(unit,)c(>)k(bitpix,status\)) +382 5601 y(FTGIDM\(unit,)c(>)k(naxis,status\))382 5714 +y(FTGISZ\(unit,)c(maxdim,)i(>)i(naxes,status\))p eop +end +%%Page: 117 123 +TeXDict begin 117 122 bop 3764 299 a Fi(117)382 555 y +Fe(FTGIPR\(unit,)44 b(maxdim,)i(>)i(bitpix,naxis,naxes,stat)o(us\))382 +668 y(FTPPR[BIJKED]\(unit,group)o(,fp)o(ixel)o(,nel)o(eme)o(nts,)o +(valu)o(es,)41 b(>)48 b(status\))382 781 y(FTPPN[BIJKED]\(unit,group)o +(,fp)o(ixel)o(,nel)o(eme)o(nts,)o(valu)o(es,)o(null)o(val)41 +b(>)48 b(status\))382 894 y(FTPPRU\(unit,group,fpixel)o(,ne)o(leme)o +(nts,)41 b(>)47 b(status\))382 1007 y(FTGPV[BIJKED]\(unit,group)o(,fp)o +(ixel)o(,nel)o(eme)o(nts,)o(null)o(val)o(,)42 b(>)47 +b(values,anyf,status\))382 1120 y(FTGPF[BIJKED]\(unit,group)o(,fp)o +(ixel)o(,nel)o(eme)o(nts,)41 b(>)48 b(values,flagvals,anyf,st)o(atu)o +(s\))382 1233 y(FTPGP[BIJKED]\(unit,group)o(,fp)o(arm,)o(npar)o(m,v)o +(alue)o(s,)42 b(>)47 b(status\))382 1346 y(FTGGP[BIJKED]\(unit,group)o +(,fp)o(arm,)o(npar)o(m,)41 b(>)48 b(values,status\))382 +1458 y(FTP2D[BIJKED]\(unit,group)o(,di)o(m1,n)o(axis)o(1,n)o(axis)o +(2,im)o(age)o(,)42 b(>)47 b(status\))382 1571 y +(FTP3D[BIJKED]\(unit,group)o(,di)o(m1,d)o(im2,)o(nax)o(is1,)o(naxi)o +(s2,)o(naxi)o(s3,c)o(ube)o(,)42 b(>)47 b(status\))382 +1684 y(FTG2D[BIJKED]\(unit,group)o(,nu)o(llva)o(l,di)o(m1,)o(naxi)o +(s1,n)o(axi)o(s2,)41 b(>)48 b(image,anyf,status\))382 +1797 y(FTG3D[BIJKED]\(unit,group)o(,nu)o(llva)o(l,di)o(m1,)o(dim2)o +(,nax)o(is1)o(,nax)o(is2,)o(nax)o(is3,)41 b(>)1002 1910 +y(cube,anyf,status\))382 2023 y(FTPSS[BIJKED]\(unit,group)o(,na)o(xis,) +o(naxe)o(s,f)o(pixe)o(ls,l)o(pix)o(els,)o(arra)o(y,)g(>)48 +b(status\))382 2136 y(FTGSV[BIJKED]\(unit,group)o(,na)o(xis,)o(naxe)o +(s,f)o(pixe)o(ls,l)o(pix)o(els,)o(incs)o(,nu)o(llva)o(l,)42 +b(>)1002 2249 y(array,anyf,status\))382 2362 y +(FTGSF[BIJKED]\(unit,group)o(,na)o(xis,)o(naxe)o(s,f)o(pixe)o(ls,l)o +(pix)o(els,)o(incs)o(,)g(>)1002 2475 y(array,flagvals,anyf,statu)o(s\)) +0 2739 y Fi(T)-8 b(able)31 b(Column)e(Information)i(Subroutines:)39 +b(page)31 b(55)382 3003 y Fe(FTGNRW\(unit,)44 b(>)k(nrows,)e(status\)) +382 3115 y(FTGNCL\(unit,)e(>)k(ncols,)e(status\))382 +3228 y(FTGCNO\(unit,casesen,colt)o(emp)o(late)o(,)c(>)47 +b(colnum,status\))382 3341 y(FTGCNN\(unit,casesen,colt)o(emp)o(late)o +(,)42 b(>)47 b(colnam,colnum,status\))382 3454 y(FTGTCL\(unit,colnum,) +42 b(>)48 b(datacode,repeat,width,st)o(atu)o(s\))382 +3567 y(FTEQTY\(unit,colnum,)42 b(>)48 b(datacode,repeat,width,st)o(atu) +o(s\))382 3680 y(FTGCDW\(unit,colnum,)42 b(>)48 b(dispwidth,status\)) +382 3793 y(FTGACL\(unit,colnum,)42 b(>)716 3906 y +(ttype,tbcol,tunit,tform,)o(tsca)o(l,t)o(zero)o(,snu)o(ll,)o(tdis)o +(p,st)o(atu)o(s\))382 4019 y(FTGBCL\(unit,colnum,)g(>)716 +4132 y(ttype,tunit,datatype,rep)o(eat,)o(tsc)o(al,t)o(zero)o(,tn)o +(ull,)o(tdis)o(p,s)o(tatu)o(s\))382 4245 y(FTPTDM\(unit,colnum,naxis)o +(,na)o(xes,)f(>)48 b(status\))382 4357 y(FTGTDM\(unit,colnum,maxdi)o +(m,)41 b(>)48 b(naxis,naxes,status\))382 4470 y +(FTDTDM\(unit,tdimstr,coln)o(um,)o(maxd)o(im,)41 b(>)48 +b(naxis,naxes,)c(status\))382 4583 y(FFGRSZ\(unit,)g(>)k +(nrows,status\))0 4847 y Fi(Lo)m(w-Lev)m(el)32 b(T)-8 +b(able)31 b(Access)h(Subroutines:)39 b(page)31 b(58)382 +5111 y Fe(FTGTBS\(unit,frow,startch)o(ar,)o(ncha)o(rs,)41 +b(>)48 b(string,status\))382 5224 y(FTPTBS\(unit,frow,startch)o(ar,)o +(ncha)o(rs,s)o(tri)o(ng,)41 b(>)48 b(status\))382 5337 +y(FTGTBB\(unit,frow,startch)o(ar,)o(ncha)o(rs,)41 b(>)48 +b(array,status\))382 5450 y(FTPTBB\(unit,frow,startch)o(ar,)o(ncha)o +(rs,a)o(rra)o(y,)42 b(>)47 b(status\))0 5714 y Fi(Edit)30 +b(Ro)m(ws)h(or)f(Columns)g(page)h(58)p eop end +%%Page: 118 124 +TeXDict begin 118 123 bop 0 299 a Fi(118)281 b Fg(CHAPTER)30 +b(10.)112 b(SUMMAR)-8 b(Y)32 b(OF)e(ALL)g(FITSIO)f(USER-INTERF)-10 +b(A)m(CE)30 b(SUBR)m(OUTINES)382 555 y Fe(FTIROW\(unit,frow,nrows,)41 +b(>)48 b(status\))382 668 y(FTDROW\(unit,frow,nrows,)41 +b(>)48 b(status\))382 781 y(FTDRRG\(unit,rowrange,)42 +b(>)47 b(status\))382 894 y(FTDRWS\(unit,rowlist,nrow)o(s,)41 +b(>)48 b(status\))382 1007 y(FTICOL\(unit,colnum,ttype)o(,tf)o(orm,)41 +b(>)48 b(status\))382 1120 y(FTICLS\(unit,colnum,ncols)o(,tt)o(ype,)o +(tfor)o(m,)41 b(>)48 b(status\))382 1233 y(FTMVEC\(unit,colnum,newve)o +(cle)o(n,)42 b(>)47 b(status\))382 1346 y(FTDCOL\(unit,colnum,)42 +b(>)48 b(status\))382 1458 y(FTCPCL\(inunit,outunit,in)o(col)o(num,)o +(outc)o(oln)o(um,c)o(reat)o(eco)o(l,)42 b(>)47 b(status\);)0 +1716 y Fi(Read)31 b(and)e(W)-8 b(rite)32 b(Column)e(Data)i(Routines)e +(page)h(60)382 1974 y Fe(FTPCL[SLBIJKEDCM]\(unit,c)o(oln)o(um,f)o(row,) +o(fel)o(em,n)o(elem)o(ent)o(s,va)o(lues)o(,)42 b(>)47 +b(status\))382 2087 y(FTPCN[BIJKED]\(unit,colnu)o(m,f)o(row,)o(fele)o +(m,n)o(elem)o(ents)o(,va)o(lues)o(,nul)o(lva)o(l)42 b(>)47 +b(status\))382 2199 y(FTPCLX\(unit,colnum,frow,)o(fbi)o(t,nb)o(it,l)o +(ray)o(,)42 b(>)47 b(status\))382 2312 y(FTPCLU\(unit,colnum,frow,)o +(fel)o(em,n)o(elem)o(ent)o(s,)42 b(>)47 b(status\))382 +2425 y(FTGCL\(unit,colnum,frow,f)o(ele)o(m,ne)o(leme)o(nts)o(,)42 +b(>)47 b(values,status\))382 2538 y(FTGCV[SBIJKEDCM]\(unit,co)o(lnu)o +(m,fr)o(ow,f)o(ele)o(m,ne)o(leme)o(nts)o(,nul)o(lval)o(,)42 +b(>)1098 2651 y(values,anyf,status\))382 2764 y +(FTGCF[SLBIJKEDCM]\(unit,c)o(oln)o(um,f)o(row,)o(fel)o(em,n)o(elem)o +(ent)o(s,)g(>)1193 2877 y(values,flagvals,anyf,stat)o(us\))382 +2990 y(FTGSV[BIJKED]\(unit,colnu)o(m,n)o(axis)o(,nax)o(es,)o(fpix)o +(els,)o(lpi)o(xels)o(,inc)o(s,n)o(ullv)o(al,)f(>)1002 +3103 y(array,anyf,status\))382 3216 y(FTGSF[BIJKED]\(unit,colnu)o(m,n)o +(axis)o(,nax)o(es,)o(fpix)o(els,)o(lpi)o(xels)o(,inc)o(s,)g(>)1002 +3329 y(array,flagvals,anyf,statu)o(s\))382 3441 y +(FTGCX\(unit,colnum,frow,f)o(bit)o(,nbi)o(t,)h(>)47 b(lray,status\))382 +3554 y(FTGCX[IJD]\(unit,colnum,f)o(row)o(,nro)o(ws,f)o(bit)o(,nbi)o(t,) +42 b(>)47 b(array,status\))382 3667 y(FTGDES\(unit,colnum,rownu)o(m,)41 +b(>)48 b(nelements,offset,status\))382 3780 y +(FTPDES\(unit,colnum,rownu)o(m,n)o(elem)o(ents)o(,of)o(fset)o(,)42 +b(>)47 b(status\))0 4038 y Fi(Ro)m(w)31 b(Selection)h(and)d(Calculator) +j(Routines:)41 b(page)31 b(64)382 4295 y Fe(FTFROW\(unit,expr,firstro)o +(w,)41 b(nrows,)47 b(>)g(n_good_rows,)d(row_status,)h(status\))382 +4408 y(FTFFRW\(unit,)f(expr,)j(>)g(rownum,)f(status\))382 +4521 y(FTSROW\(inunit,)e(outunit,)h(expr,)i(>)g(status)f(\))382 +4634 y(FTCROW\(unit,datatype,exp)o(r,f)o(irst)o(row,)o(nel)o(emen)o +(ts,n)o(ulv)o(al,)41 b(>)620 4747 y(array,anynul,status\))382 +4860 y(FTCALC\(inunit,)j(expr,)i(outunit,)g(parName,)f(parInfo,)h(>)h +(status\))382 4973 y(FTCALC_RNG\(inunit,)c(expr,)j(outunit,)g(parName,) +f(parInfo,)573 5086 y(nranges,)g(firstrow,)h(lastrow,)f(>)j(status\)) +382 5199 y(FTTEXP\(unit,)c(expr,)j(>)g(datatype,)e(nelem,)h(naxis,)h +(naxes,)f(status\))0 5456 y Fi(Celestial)32 b(Co)s(ordinate)f(System)f +(Subroutines:)39 b(page)31 b(65)382 5714 y Fe(FTGICS\(unit,)44 +b(>)k(xrval,yrval,xrpix,yrpix)o(,xin)o(c,yi)o(nc,)o(rot,)o(coor)o(dty)o +(pe,s)o(tatu)o(s\))p eop end +%%Page: 119 125 +TeXDict begin 119 124 bop 3764 299 a Fi(119)382 555 y +Fe(FTGTCS\(unit,xcol,ycol,)42 b(>)716 668 y(xrval,yrval,xrpix,yrpix,)o +(xinc)o(,yi)o(nc,r)o(ot,c)o(oor)o(dtyp)o(e,st)o(atu)o(s\))382 +781 y(FTWLDP\(xpix,ypix,xrval,y)o(rva)o(l,xr)o(pix,)o(yrp)o(ix,x)o +(inc,)o(yin)o(c,ro)o(t,)1241 894 y(coordtype,)j(>)i(xpos,ypos,status\)) +382 1007 y(FTXYPX\(xpos,ypos,xrval,y)o(rva)o(l,xr)o(pix,)o(yrp)o(ix,x)o +(inc,)o(yin)o(c,ro)o(t,)1241 1120 y(coordtype,)e(>)i +(xpix,ypix,status\))0 1340 y Fi(File)32 b(Chec)m(ksum)d(Subroutines:)40 +b(page)31 b(67)382 1560 y Fe(FTPCKS\(unit,)44 b(>)k(status\))382 +1673 y(FTUCKS\(unit,)c(>)k(status\))382 1785 y(FTVCKS\(unit,)c(>)k +(dataok,hduok,status\))382 1898 y(FTGCKS\(unit,)c(>)k +(datasum,hdusum,status\))382 2011 y(FTESUM\(sum,complement,)42 +b(>)47 b(checksum\))382 2124 y(FTDSUM\(checksum,compleme)o(nt,)41 +b(>)48 b(sum\))0 2457 y Fi(Time)30 b(and)g(Date)i(Utilit)m(y)h +(Subroutines:)39 b(page)31 b(68)382 2677 y Fe(FTGSDT\()46 +b(>)h(day,)g(month,)f(year,)g(status)g(\))382 2790 y(FTGSTM\(>)f +(datestr,)h(timeref,)f(status\))382 2903 y(FTDT2S\()h(year,)g(month,)g +(day,)h(>)g(datestr,)f(status\))382 3016 y(FTTM2S\()g(year,)g(month,)g +(day,)h(hour,)f(minute,)g(second,)g(decimals,)764 3129 +y(>)h(datestr,)f(status\))382 3242 y(FTS2DT\(datestr,)d(>)48 +b(year,)e(month,)g(day,)h(status\))382 3354 y(FTS2TM\(datestr,)c(>)48 +b(year,)e(month,)g(day,)h(hour,)f(minute,)g(second,)g(status\))0 +3574 y Fi(General)31 b(Utilit)m(y)i(Subroutines:)39 b(page)31 +b(69)382 3794 y Fe(FTGHAD\(unit,)44 b(>)k(curaddr,nextaddr\))382 +3907 y(FTUPCH\(string\))382 4020 y(FTCMPS\(str_template,stri)o(ng,)o +(case)o(sen,)41 b(>)47 b(match,exact\))382 4133 y(FTTKEY\(keyword,)c(>) +48 b(status\))382 4246 y(FTTREC\(card,)c(>)k(status\))382 +4359 y(FTNCHK\(unit,)c(>)k(status\))382 4472 y(FTGKNM\(unit,)c(>)k +(keyword,)d(keylength,)g(status\))382 4585 y(FTPSVC\(card,)f(>)k +(value,comment,status\))382 4698 y(FTKEYN\(keyroot,seq_no,)42 +b(>)47 b(keyword,status\))382 4811 y(FTNKEY\(seq_no,keyroot,)42 +b(>)47 b(keyword,status\))382 4924 y(FTDTYP\(value,)d(>)j +(dtype,status\))382 5036 y(class)f(=)i(FTGKCL\(card\))382 +5149 y(FTASFM\(tform,)c(>)j(datacode,width,decimals,st)o(atus)o(\))382 +5262 y(FTBNFM\(tform,)d(>)j(datacode,repeat,width,stat)o(us\))382 +5375 y(FTGABC\(tfields,tform,spa)o(ce,)41 b(>)48 b +(rowlen,tbcol,status\))382 5488 y(FTGTHD\(template,)43 +b(>)48 b(card,hdtype,status\))382 5601 y(FTRWRG\(rowlist,)43 +b(maxrows,)j(maxranges,)f(>)i(numranges,)e(rangemin,)716 +5714 y(rangemax,)g(status\))p eop end +%%Page: 120 126 +TeXDict begin 120 125 bop 0 299 a Fi(120)281 b Fg(CHAPTER)30 +b(10.)112 b(SUMMAR)-8 b(Y)32 b(OF)e(ALL)g(FITSIO)f(USER-INTERF)-10 +b(A)m(CE)30 b(SUBR)m(OUTINES)p eop end +%%Page: 121 127 +TeXDict begin 121 126 bop 0 1225 a Ff(Chapter)65 b(11)0 +1687 y Fl(P)-6 b(arameter)77 b(De\014nitions)0 2180 y +Fe(anyf)47 b(-)g(\(logical\))e(set)i(to)g(TRUE)g(if)g(any)g(of)g(the)g +(returned)f(data)g(values)h(are)f(undefined)0 2293 y(array)g(-)i(\(any) +e(datatype)g(except)g(character\))f(array)h(of)i(bytes)e(to)h(be)g +(read)g(or)g(written.)0 2406 y(bitpix)f(-)i(\(integer\))d(bits)h(per)h +(pixel:)f(8,)i(16,)f(32,)f(-32,)h(or)g(-64)0 2518 y(blank)f(-)i +(\(integer\))d(value)h(used)h(for)g(undefined)e(pixels)h(in)i(integer)d +(primary)h(array)0 2631 y(blank)g(-)i(\(integer*8\))d(value)h(used)h +(for)f(undefined)g(pixels)g(in)h(integer)f(primary)g(array)0 +2744 y(blocksize)f(-)j(\(integer\))d(2880-byte)g(logical)h(record)g +(blocking)g(factor)477 2857 y(\(if)h(0)h(<)f(blocksize)e(<)j(11\))f(or) +g(the)g(actual)f(block)g(size)h(in)g(bytes)477 2970 y(\(if)g(10)g(<)h +(blocksize)d(<)j(28800\).)93 b(As)47 b(of)g(version)f(3.3)h(of)g +(FITSIO,)477 3083 y(blocksizes)e(greater)h(than)h(2880)f(are)h(no)g +(longer)g(supported.)0 3196 y(bscale)f(-)i(\(double)d(precision\))g +(scaling)h(factor)g(for)h(the)g(primary)f(array)0 3309 +y(bytlen)g(-)i(\(integer\))d(length)h(of)h(the)g(data)g(unit,)f(in)h +(bytes)0 3422 y(bzero)f(-)i(\(double)e(precision\))f(zero)h(point)h +(for)g(primary)e(array)i(scaling)0 3535 y(card)g(-)g(\(character*80\))d +(header)i(record)g(to)h(be)h(read)e(or)h(written)0 3648 +y(casesen)f(-)h(\(logical\))f(will)g(string)g(matching)g(be)h(case)g +(sensitive?)0 3760 y(checksum)f(-)h(\(character*16\))d(encoded)i +(checksum)f(string)0 3873 y(colname)h(-)h(\(character\))e(ASCII)h(name) +h(of)g(the)g(column)0 3986 y(colnum)f(-)i(\(integer\))d(number)h(of)h +(the)g(column)f(\(first)g(column)g(=)i(1\))0 4099 y(coltemplate)d(-)i +(\(character\))e(template)g(string)i(to)g(be)g(matched)f(to)h(column)f +(names)0 4212 y(comment)g(-)h(\(character\))e(the)i(keyword)f(comment)g +(field)0 4325 y(comments)g(-)h(\(character)e(array\))h(keyword)g +(comment)g(fields)0 4438 y(compid)g(-)i(\(integer\))d(the)i(type)f(of)i +(computer)d(that)i(the)g(program)e(is)j(running)d(on)0 +4551 y(complement)g(-)i(\(logical\))f(should)g(the)h(checksum)e(be)i +(complemented?)0 4664 y(coordtype)e(-)j(\(character\))c(type)j(of)g +(coordinate)e(projection)g(\(-SIN,)h(-TAN,)h(-ARC,)477 +4777 y(-NCP,)g(-GLS,)f(-MER,)g(or)i(-AIT\))0 4890 y(cube)f(-)g(3D)g +(data)g(cube)g(of)g(the)g(appropriate)d(datatype)0 5002 +y(curaddr)i(-)h(\(integer\))f(starting)f(address)h(\(in)h(bytes\))f(of) +h(the)g(CHDU)0 5115 y(current)f(-)h(\(integer\))f(if)h(not)g(equal)f +(to)h(0,)g(copy)g(the)g(current)f(HDU)0 5228 y(datacode)g(-)h +(\(integer\))e(symbolic)h(code)g(of)i(the)f(binary)f(table)g(column)g +(datatype)0 5341 y(dataok)g(-)i(\(integer\))d(was)i(the)g(data)f(unit)h +(verification)d(successful)h(\(=1\))i(or)430 5454 y(not)f(\(=)i(-1\).) +94 b(Equals)46 b(zero)h(if)g(the)g(DATASUM)f(keyword)f(is)j(not)f +(present.)0 5567 y(datasum)f(-)h(\(double)f(precision\))f(32-bit)h(1's) +h(complement)e(checksum)h(for)h(the)f(data)h(unit)0 5680 +y(datatype)f(-)h(\(character\))e(datatype)g(\(format\))h(of)h(the)g +(binary)f(table)g(column)1882 5942 y Fi(121)p eop end +%%Page: 122 128 +TeXDict begin 122 127 bop 0 299 a Fi(122)1779 b Fg(CHAPTER)30 +b(11.)112 b(P)-8 b(ARAMETER)30 b(DEFINITIONS)0 555 y +Fe(datestr)94 b(-)47 b(\(string\))f(FITS)g(date/time)f(string:)h +('YYYY-MM-DDThh:mm:ss.ddd')o(,)525 668 y('YYYY-MM-dd',)e(or)j +('dd/mm/yy')0 781 y(day)g(-)g(\(integer\))f(current)f(day)i(of)h(the)e +(month)0 894 y(dblval)g(-)i(\(double)d(precision\))g(fractional)g(part) +i(of)g(the)g(keyword)f(value)0 1007 y(decimals)g(-)h(\(integer\))e +(number)h(of)i(decimal)d(places)h(to)i(be)f(displayed)0 +1120 y(dim1)g(-)g(\(integer\))e(actual)h(size)h(of)g(the)g(first)g +(dimension)e(of)i(the)g(image)f(or)h(cube)g(array)0 1233 +y(dim2)g(-)g(\(integer\))e(actual)h(size)h(of)g(the)g(second)f +(dimension)g(of)h(the)g(cube)f(array)0 1346 y(dispwidth)f(-)j +(\(integer\))d(-)i(the)g(display)f(width)h(\(length)e(of)j(string\))d +(for)i(a)h(column)0 1458 y(dtype)e(-)i(\(character\))d(datatype)g(of)i +(the)g(keyword)f(\('C',)g('L',)h('I',)94 b(or)48 b('F'\))764 +1571 y(C)f(=)h(character)d(string)764 1684 y(L)i(=)h(logical)764 +1797 y(I)f(=)h(integer)764 1910 y(F)f(=)h(floating)d(point)h(number)0 +2023 y(errmsg)g(-)i(\(character*80\))43 b(oldest)k(error)f(message)g +(on)h(the)g(internal)e(stack)0 2136 y(errtext)h(-)h(\(character*30\))d +(descriptive)h(error)h(message)g(corresponding)e(to)j(error)g(number)0 +2249 y(casesen)f(-)h(\(logical\))f(true)g(if)h(column)f(name)h +(matching)f(is)h(case)f(sensitive)0 2362 y(exact)g(-)i(\(logical\))d +(do)i(the)g(strings)f(match)g(exactly,)g(or)h(were)g(wildcards)e(used?) +0 2475 y(exclist)94 b(\(character)45 b(array\))h(list)g(of)h(names)g +(to)g(be)g(excluded)f(from)g(search)0 2588 y(exists)142 +b(-)47 b(flag)g(indicating)e(whether)g(the)i(file)g(or)g(compressed)e +(file)i(exists)f(on)h(disk)0 2700 y(extend)f(-)i(\(logical\))d(true)h +(if)i(there)e(may)h(be)g(extensions)e(following)g(the)i(primary)f(data) +0 2813 y(extname)g(-)h(\(character\))e(value)h(of)i(the)e(EXTNAME)g +(keyword)g(\(if)h(not)g(blank\))0 2926 y(fbit)g(-)g(\(integer\))e +(first)i(bit)g(in)g(the)g(field)f(to)h(be)g(read)g(or)g(written)0 +3039 y(felem)f(-)i(\(integer\))d(first)h(pixel)h(of)g(the)g(element)f +(vector)g(\(ignored)f(for)i(ASCII)g(tables\))0 3152 y(filename)f(-)h +(\(character\))e(name)h(of)i(the)e(FITS)h(file)0 3265 +y(flagvals)f(-)h(\(logical)f(array\))g(True)g(if)h(corresponding)e +(data)h(element)g(is)h(undefined)0 3378 y(following)e(-)j(\(integer\))d +(if)i(not)g(equal)f(to)i(0,)f(copy)f(all)h(following)f(HDUs)g(in)h(the) +g(input)g(file)0 3491 y(fparm)f(-)i(\(integer\))d(sequence)h(number)g +(of)h(the)g(first)f(group)h(parameter)e(to)i(read)g(or)g(write)0 +3604 y(fpixel)f(-)i(\(integer\))d(the)i(first)f(pixel)g(position)0 +3717 y(fpixels)g(-)h(\(integer)f(array\))g(the)h(first)f(included)g +(pixel)g(in)h(each)g(dimension)0 3830 y(frow)g(-)g(\(integer\))e +(beginning)h(row)h(number)f(\(first)g(row)h(of)g(table)f(=)i(1\))0 +3942 y(frowll)e(-)i(\(integer*8\))c(beginning)i(row)g(number)h(\(first) +f(row)h(of)g(table)f(=)i(1\))0 4055 y(gcount)e(-)i(\(integer\))d(value) +h(of)h(the)g(GCOUNT)f(keyword)g(\(usually)g(=)h(1\))0 +4168 y(group)f(-)i(\(integer\))d(sequence)h(number)g(of)h(the)g(data)f +(group)h(\(=0)g(for)g(non-grouped)d(data\))0 4281 y(hdtype)i(-)i +(\(integer\))d(header)h(record)g(type:)g(-1=delete;)93 +b(0=append)46 b(or)h(replace;)907 4394 y(1=append;)e(2=this)h(is)h(the) +g(END)g(keyword)0 4507 y(hduok)f(-)i(\(integer\))d(was)i(the)g(HDU)g +(verification)d(successful)h(\(=1\))i(or)430 4620 y(not)f(\(=)i(-1\).) +94 b(Equals)46 b(zero)h(if)g(the)g(CHECKSUM)e(keyword)h(is)h(not)g +(present.)0 4733 y(hdusum)f(-)i(\(double)d(precision\))g(32)j(bit)e +(1's)h(complement)e(checksum)h(for)h(the)g(entire)f(CHDU)0 +4846 y(hdutype)g(-)h(\(integer\))f(type)g(of)h(HDU:)g(0)g(=)h(primary)e +(array)g(or)h(IMAGE,)f(1)i(=)f(ASCII)g(table,)907 4959 +y(2)g(=)h(binary)e(table,)g(-1)h(=)h(any)e(HDU)h(type)g(or)g(unknown)f +(type)0 5072 y(history)g(-)h(\(character\))e(the)i(HISTORY)f(keyword)g +(comment)f(string)0 5185 y(hour)i(-)g(\(integer\))e(hour)i(from)g(0)g +(-)h(23)0 5297 y(image)e(-)i(2D)f(image)f(of)i(the)e(appropriate)f +(datatype)0 5410 y(inclist)94 b(\(character)45 b(array\))h(list)g(of)h +(names)g(to)g(be)g(included)f(in)h(search)0 5523 y(incs)g(-)g +(\(integer)f(array\))g(sampling)f(interval)h(for)h(pixels)f(in)h(each)g +(FITS)f(dimension)0 5636 y(intval)g(-)i(\(integer\))d(integer)h(part)g +(of)h(the)g(keyword)f(value)p eop end +%%Page: 123 129 +TeXDict begin 123 128 bop 3764 299 a Fi(123)0 555 y Fe(iounit)46 +b(-)i(\(integer\))d(value)h(of)h(an)h(unused)e(I/O)h(unit)f(number)0 +668 y(iunit)g(-)i(\(integer\))d(logical)h(unit)h(number)f(associated)f +(with)h(the)h(input)g(FITS)f(file,)h(1-199)0 781 y(key_no)f(-)i +(\(integer\))d(sequence)g(number)h(\(starting)g(with)g(1\))i(of)f(the)g +(keyword)e(record)0 894 y(keylength)g(-)j(\(integer\))d(length)h(of)h +(the)g(keyword)f(name)0 1007 y(keyroot)g(-)h(\(character\))e(root)i +(string)f(for)h(the)g(keyword)e(name)0 1120 y(keysadd)h(-\(integer\))f +(number)h(of)h(new)g(keyword)f(records)g(which)g(can)h(fit)g(in)g(the)g +(CHU)0 1233 y(keysexist)e(-)j(\(integer\))d(number)h(of)h(existing)f +(keyword)g(records)f(in)j(the)f(CHU)0 1346 y(keyval)f(-)i(value)e(of)h +(the)g(keyword)f(in)h(the)g(appropriate)e(datatype)0 +1458 y(keyvals)h(-)h(\(array\))f(value)g(of)i(the)f(keywords)e(in)i +(the)g(appropriate)e(datatype)0 1571 y(keyword)h(-)h(\(character*8\))d +(name)j(of)g(a)h(keyword)0 1684 y(lray)f(-)g(\(logical)f(array\))g +(array)g(of)h(logical)f(values)g(corresponding)e(to)k(the)e(bit)h +(array)0 1797 y(lpixels)f(-)h(\(integer)f(array\))g(the)h(last)f +(included)g(pixel)g(in)i(each)e(dimension)0 1910 y(match)g(-)i +(\(logical\))d(do)i(the)g(2)h(strings)d(match?)0 2023 +y(maxdim)h(-)i(\(integer\))d(dimensioned)g(size)h(of)h(the)g(NAXES,)f +(TTYPE,)g(TFORM)h(or)g(TUNIT)f(arrays)0 2136 y(max_keys)g(-)h +(\(integer\))e(maximum)h(number)g(of)h(keywords)f(to)h(search)f(for)0 +2249 y(minute)g(-)i(\(integer\))d(minute)h(of)h(an)g(hour)g(\(0)g(-)h +(59\))0 2362 y(month)e(-)i(\(integer\))d(current)h(month)g(of)h(the)g +(year)g(\(1)g(-)h(12\))0 2475 y(morekeys)e(-)h(\(integer\))e(will)i +(leave)f(space)h(in)g(the)g(header)f(for)h(this)f(many)h(more)g +(keywords)0 2588 y(naxes)f(-)i(\(integer)d(array\))h(size)h(of)g(each)g +(dimension)e(in)i(the)g(FITS)g(array)0 2700 y(naxesll)f(-)h +(\(integer*8)e(array\))h(size)h(of)g(each)g(dimension)e(in)i(the)g +(FITS)g(array)0 2813 y(naxis)f(-)i(\(integer\))d(number)h(of)h +(dimensions)e(in)j(the)e(FITS)h(array)0 2926 y(naxis1)f(-)i +(\(integer\))d(length)h(of)h(the)g(X/first)f(axis)g(of)i(the)f(FITS)f +(array)0 3039 y(naxis2)g(-)i(\(integer\))d(length)h(of)h(the)g +(Y/second)f(axis)g(of)h(the)g(FITS)g(array)0 3152 y(naxis3)f(-)i +(\(integer\))d(length)h(of)h(the)g(Z/third)f(axis)g(of)i(the)f(FITS)f +(array)0 3265 y(nbit)h(-)g(\(integer\))e(number)h(of)i(bits)e(in)h(the) +g(field)g(to)g(read)g(or)g(write)0 3378 y(nchars)f(-)i(\(integer\))d +(number)h(of)h(characters)e(to)i(read)g(and)g(return)0 +3491 y(ncols)f(-)i(\(integer\))d(number)h(of)h(columns)0 +3604 y(nelements)e(-)j(\(integer\))d(number)h(of)h(data)g(elements)e +(to)j(read)e(or)h(write)0 3717 y(nelementsll)e(-)i(\(integer*8\))e +(number)h(of)h(data)g(elements)e(to)j(read)e(or)h(write)0 +3830 y(nexc)142 b(\(integer\))93 b(number)46 b(of)h(names)g(in)g(the)g +(exclusion)e(list)i(\(may)f(=)i(0\))0 3942 y(nhdu)f(-)g(\(integer\))e +(absolute)h(number)g(of)h(the)g(HDU)g(\(1st)g(HDU)g(=)g(1\))0 +4055 y(ninc)142 b(\(integer\))93 b(number)46 b(of)h(names)g(in)g(the)g +(inclusion)e(list)0 4168 y(nmove)h(-)i(\(integer\))d(number)h(of)h +(HDUs)g(to)g(move)g(\(+)g(or)g(-\),)g(relative)f(to)h(current)f +(position)0 4281 y(nfound)g(-)i(\(integer\))d(number)h(of)h(keywords)f +(found)g(\(highest)g(keyword)f(number\))0 4394 y(no_keys)h(-)h +(\(integer\))f(number)g(of)h(keywords)e(to)j(write)e(in)h(the)g +(sequence)0 4507 y(nparm)f(-)i(\(integer\))d(number)h(of)h(group)g +(parameters)e(to)i(read)g(or)g(write)0 4620 y(nrows)f(-)i(\(integer\))d +(number)h(of)h(rows)g(in)g(the)g(table)0 4733 y(nrowsll)f(-)h +(\(integer*8\))e(number)h(of)h(rows)g(in)g(the)g(table)0 +4846 y(nullval)f(-)h(value)g(to)g(represent)e(undefined)g(pixels,)h(of) +h(the)g(appropriate)e(datatype)0 4959 y(nextaddr)h(-)h(\(integer\))e +(starting)h(address)g(\(in)h(bytes\))f(of)h(the)g(HDU)g(following)e +(the)i(CHDU)0 5072 y(offset)f(-)i(\(integer\))d(byte)h(offset)h(in)g +(the)g(heap)f(to)h(the)g(first)g(element)f(of)h(the)g(array)0 +5185 y(offsetll)f(-)h(\(integer*8\))e(byte)h(offset)g(in)i(the)f(heap)f +(to)h(the)g(first)g(element)e(of)j(the)f(array)0 5297 +y(oldkey)f(-)i(\(character\))c(old)j(name)g(of)g(keyword)f(to)h(be)g +(modified)0 5410 y(ounit)f(-)i(\(integer\))d(logical)h(unit)h(number)f +(associated)f(with)h(the)h(output)f(FITS)h(file)g(1-199)0 +5523 y(pcount)f(-)i(\(integer\))d(value)h(of)h(the)g(PCOUNT)f(keyword)g +(\(usually)g(=)h(0\))0 5636 y(previous)f(-)h(\(integer\))e(if)i(not)g +(equal)g(to)g(0,)g(copy)g(all)g(previous)e(HDUs)i(in)g(the)g(input)f +(file)p eop end +%%Page: 124 130 +TeXDict begin 124 129 bop 0 299 a Fi(124)1779 b Fg(CHAPTER)30 +b(11.)112 b(P)-8 b(ARAMETER)30 b(DEFINITIONS)0 555 y +Fe(repeat)46 b(-)i(\(integer\))d(length)h(of)h(element)f(vector)g +(\(e.g.)g(12J\);)h(ignored)f(for)g(ASCII)h(table)0 668 +y(rot)g(-)g(\(double)f(precision\))f(celestial)g(coordinate)g(rotation) +h(angle)g(\(degrees\))0 781 y(rowlen)g(-)i(\(integer\))d(length)h(of)h +(a)h(table)e(row,)h(in)g(characters)e(or)i(bytes)0 894 +y(rowlenll)f(-)h(\(integer*8\))e(length)h(of)h(a)g(table)g(row,)f(in)i +(characters)d(or)i(bytes)0 1007 y(rowlist)f(-)h(\(integer)f(array\))g +(list)h(of)g(row)g(numbers)e(to)j(be)f(deleted)f(in)h(increasing)e +(order)0 1120 y(rownum)h(-)i(\(integer\))d(number)h(of)h(the)g(row)g +(\(first)f(row)h(=)g(1\))0 1233 y(rowrange-)e(\(string\))h(list)g(of)i +(rows)e(or)h(row)g(ranges)f(to)i(be)f(deleted)0 1346 +y(rwmode)f(-)i(\(integer\))d(file)h(access)h(mode:)f(0)h(=)h(readonly,) +d(1)j(=)f(readwrite)0 1458 y(second)142 b(\(double\)-)45 +b(second)h(within)g(minute)g(\(0)h(-)h(60.9999999999\))c(\(leap)i +(second!\))0 1571 y(seq_no)g(-)i(\(integer\))d(the)i(sequence)e(number) +h(to)i(append)e(to)h(the)g(keyword)f(root)g(name)0 1684 +y(simple)g(-)i(\(logical\))d(does)h(the)h(FITS)g(file)g(conform)e(to)j +(all)f(the)f(FITS)h(standards)0 1797 y(snull)f(-)i(\(character\))d +(value)h(used)h(to)g(represent)e(undefined)g(values)h(in)i(ASCII)e +(table)0 1910 y(space)g(-)i(\(integer\))d(number)h(of)h(blank)g(spaces) +f(to)h(leave)f(between)g(ASCII)h(table)f(columns)0 2023 +y(startchar)f(-)j(\(integer\))d(first)h(character)g(in)h(the)g(row)g +(to)g(be)g(read)0 2136 y(startno)f(-)h(\(integer\))f(value)g(of)h(the)g +(first)f(keyword)g(sequence)g(number)g(\(usually)f(1\))0 +2249 y(status)h(-)i(\(integer\))d(returned)g(error)i(status)f(code)g +(\(0)i(=)f(OK\))0 2362 y(str_template)d(\(character\))h(template)h +(string)g(to)h(be)g(matched)f(to)h(reference)e(string)0 +2475 y(stream)h(-)i(\(character\))c(output)i(stream)g(for)h(the)g +(report:)f(either)g('STDOUT')g(or)h('STDERR')0 2588 y(string)f(-)i +(\(character\))c(character)i(string)0 2700 y(sum)h(-)g(\(double)f +(precision\))f(32)i(bit)g(unsigned)f(checksum)f(value)0 +2813 y(tbcol)h(-)i(\(integer)d(array\))h(column)h(number)f(of)h(the)g +(first)f(character)f(in)j(the)e(field\(s\))0 2926 y(tdisp)g(-)i +(\(character\))d(Fortran)g(type)i(display)f(format)g(for)h(the)g(table) +f(column)0 3039 y(template-\(character\))c(template)k(string)g(for)h(a) +g(FITS)g(header)f(record)0 3152 y(tfields)g(-)h(\(integer\))f(number)g +(of)h(fields)f(\(columns\))f(in)i(the)g(table)0 3265 +y(tform)f(-)i(\(character)d(array\))h(format)g(of)h(the)g(column\(s\);) +e(allowed)h(values)g(are:)430 3378 y(For)g(ASCII)h(tables:)93 +b(Iw,)47 b(Aw,)g(Fww.dd,)f(Eww.dd,)g(or)h(Dww.dd)430 +3491 y(For)f(binary)h(tables:)e(rL,)i(rX,)g(rB,)g(rI,)g(rJ,)g(rA,)g +(rAw,)f(rE,)h(rD,)g(rC,)g(rM)430 3604 y(where)f('w'=width)f(of)i(the)g +(field,)f('d'=no.)g(of)h(decimals,)f('r'=repeat)f(count)430 +3717 y(Note)h(that)h(the)g('rAw')f(form)h(is)g(non-standard)d +(extension)i(to)h(the)430 3830 y(TFORM)f(keyword)g(syntax)g(that)g(is)i +(not)f(specifically)d(defined)i(in)h(the)430 3942 y(Binary)f(Tables)g +(definition)f(document.)0 4055 y(theap)h(-)i(\(integer\))d(zero)i +(indexed)f(byte)g(offset)g(of)h(starting)f(address)g(of)h(the)g(heap) +430 4168 y(relative)e(to)i(the)g(beginning)e(of)j(the)f(binary)f(table) +g(data)0 4281 y(tnull)g(-)i(\(integer\))d(value)h(used)h(to)g +(represent)f(undefined)f(values)h(in)h(binary)f(table)0 +4394 y(tnullll)g(-)h(\(integer*8\))e(value)h(used)h(to)g(represent)e +(undefined)h(values)g(in)h(binary)f(table)0 4507 y(ttype)g(-)i +(\(character)d(array\))h(label)g(for)h(table)g(column\(s\))0 +4620 y(tscal)f(-)i(\(double)e(precision\))f(scaling)g(factor)i(for)f +(table)h(column)0 4733 y(tunit)f(-)i(\(character)d(array\))h(physical)f +(unit)i(for)g(table)f(column\(s\))0 4846 y(tzero)g(-)i(\(double)e +(precision\))f(scaling)g(zero)i(point)f(for)h(table)g(column)0 +4959 y(unit)94 b(-)48 b(\(integer\))d(logical)h(unit)h(number)f +(associated)f(with)h(the)h(FITS)g(file)f(\(1-199\))0 +5072 y(units)g(-)i(\(character\))d(the)h(keyword)g(units)h(string)f +(\(e.g.,)g('km/s'\))0 5185 y(value)g(-)i(\(character\))d(the)h(keyword) +g(value)h(string)0 5297 y(values)f(-)i(array)e(of)h(data)g(values)f(of) +h(the)g(appropriate)e(datatype)0 5410 y(varidat)h(-)h(\(integer\))f +(size)g(in)h(bytes)g(of)g(the)g('variable)e(length)h(data)h(area')525 +5523 y(following)e(the)i(binary)f(table)h(data)f(\(usually)g(=)h(0\))0 +5636 y(version)f(-)h(\(real\))f(current)g(revision)g(number)g(of)h(the) +g(library)p eop end +%%Page: 125 131 +TeXDict begin 125 130 bop 3764 299 a Fi(125)0 555 y Fe(width)46 +b(-)i(\(integer\))d(width)h(of)i(the)f(character)e(string)h(field)0 +668 y(xcol)h(-)g(\(integer\))e(number)h(of)i(the)f(column)f(containing) +f(the)i(X)g(coordinate)e(values)0 781 y(xinc)i(-)g(\(double)f +(precision\))f(X)i(axis)g(coordinate)e(increment)g(at)i(reference)f +(pixel)g(\(deg\))0 894 y(xpix)h(-)g(\(double)f(precision\))f(X)i(axis)g +(pixel)f(location)0 1007 y(xpos)h(-)g(\(double)f(precision\))f(X)i +(axis)g(celestial)e(coordinate)g(\(usually)h(RA\))h(\(deg\))0 +1120 y(xrpix)f(-)i(\(double)e(precision\))f(X)i(axis)g(reference)e +(pixel)h(array)h(location)0 1233 y(xrval)f(-)i(\(double)e(precision\))f +(X)i(axis)g(coordinate)e(value)h(at)h(the)g(reference)e(pixel)i +(\(deg\))0 1346 y(ycol)g(-)g(\(integer\))e(number)h(of)i(the)f(column)f +(containing)f(the)i(X)g(coordinate)e(values)0 1458 y(year)i(-)g +(\(integer\))e(last)i(2)g(digits)g(of)g(the)g(year)f(\(00)h(-)h(99\))0 +1571 y(yinc)f(-)g(\(double)f(precision\))f(Y)i(axis)g(coordinate)e +(increment)g(at)i(reference)f(pixel)g(\(deg\))0 1684 +y(ypix)h(-)g(\(double)f(precision\))f(y)i(axis)g(pixel)f(location)0 +1797 y(ypos)h(-)g(\(double)f(precision\))f(y)i(axis)g(celestial)e +(coordinate)g(\(usually)h(DEC\))g(\(deg\))0 1910 y(yrpix)g(-)i +(\(double)e(precision\))f(Y)i(axis)g(reference)e(pixel)h(array)h +(location)0 2023 y(yrval)f(-)i(\(double)e(precision\))f(Y)i(axis)g +(coordinate)e(value)h(at)h(the)g(reference)e(pixel)i(\(deg\))p +eop end +%%Page: 126 132 +TeXDict begin 126 131 bop 0 299 a Fi(126)1779 b Fg(CHAPTER)30 +b(11.)112 b(P)-8 b(ARAMETER)30 b(DEFINITIONS)p eop end +%%Page: 127 133 +TeXDict begin 127 132 bop 0 1225 a Ff(Chapter)65 b(12)0 +1687 y Fl(FITSIO)76 b(Error)h(Status)h(Co)6 b(des)0 2180 +y Fe(Status)46 b(codes)g(in)i(the)f(range)f(-99)h(to)g(-999)94 +b(and)47 b(1)h(to)f(999)g(are)g(reserved)e(for)i(future)0 +2293 y(FITSIO)f(use.)95 2518 y(0)96 b(OK,)47 b(no)g(error)0 +2631 y(101)95 b(input)46 b(and)h(output)f(files)g(are)h(the)g(same)0 +2744 y(103)95 b(too)47 b(many)f(FITS)h(files)f(open)h(at)g(once;)f(all) +h(internal)f(buffers)g(full)0 2857 y(104)95 b(error)46 +b(opening)g(existing)f(file)0 2970 y(105)95 b(error)46 +b(creating)g(new)g(FITS)h(file;)f(\(does)h(a)g(file)g(with)g(this)f +(name)h(already)f(exist?\))0 3083 y(106)95 b(error)46 +b(writing)g(record)g(to)h(FITS)g(file)0 3196 y(107)95 +b(end-of-file)44 b(encountered)h(while)h(reading)g(record)g(from)h +(FITS)g(file)0 3309 y(108)95 b(error)46 b(reading)g(record)g(from)h +(file)0 3422 y(110)95 b(error)46 b(closing)g(FITS)g(file)0 +3535 y(111)95 b(internal)45 b(array)i(dimensions)e(exceeded)0 +3648 y(112)95 b(Cannot)46 b(modify)g(file)g(with)h(readonly)f(access)0 +3760 y(113)95 b(Could)46 b(not)h(allocate)e(memory)0 +3873 y(114)95 b(illegal)45 b(logical)h(unit)h(number;)f(must)g(be)i +(between)d(1)j(-)f(199,)g(inclusive)0 3986 y(115)95 b(NULL)46 +b(input)h(pointer)e(to)j(routine)0 4099 y(116)95 b(error)46 +b(seeking)g(position)f(in)j(file)0 4325 y(121)95 b(invalid)45 +b(URL)i(prefix)f(on)i(file)e(name)0 4438 y(122)95 b(tried)46 +b(to)h(register)f(too)h(many)f(IO)h(drivers)0 4551 y(123)95 +b(driver)46 b(initialization)e(failed)0 4664 y(124)95 +b(matching)45 b(driver)h(is)h(not)g(registered)0 4777 +y(125)95 b(failed)46 b(to)h(parse)f(input)h(file)f(URL)0 +4890 y(126)95 b(parse)46 b(error)g(in)i(range)e(list)0 +5115 y(151)95 b(bad)47 b(argument)e(in)i(shared)f(memory)g(driver)0 +5228 y(152)95 b(null)46 b(pointer)g(passed)g(as)h(an)h(argument)0 +5341 y(153)95 b(no)47 b(more)f(free)h(shared)f(memory)g(handles)0 +5454 y(154)95 b(shared)46 b(memory)g(driver)g(is)h(not)g(initialized)0 +5567 y(155)95 b(IPC)47 b(error)f(returned)f(by)j(a)f(system)f(call)0 +5680 y(156)95 b(no)47 b(memory)f(in)h(shared)f(memory)g(driver)1882 +5942 y Fi(127)p eop end +%%Page: 128 134 +TeXDict begin 128 133 bop 0 299 a Fi(128)1613 b Fg(CHAPTER)30 +b(12.)112 b(FITSIO)30 b(ERR)m(OR)g(ST)-8 b(A)g(TUS)30 +b(CODES)0 555 y Fe(157)95 b(resource)45 b(deadlock)h(would)g(occur)0 +668 y(158)95 b(attempt)45 b(to)j(open/create)c(lock)j(file)g(failed)0 +781 y(159)95 b(shared)46 b(memory)g(block)g(cannot)g(be)h(resized)f(at) +h(the)g(moment)0 1120 y(201)95 b(header)46 b(not)h(empty;)f(can't)g +(write)g(required)g(keywords)0 1233 y(202)95 b(specified)45 +b(keyword)h(name)g(was)h(not)g(found)g(in)g(the)g(header)0 +1346 y(203)95 b(specified)45 b(header)h(record)g(number)g(is)h(out)g +(of)g(bounds)0 1458 y(204)95 b(keyword)45 b(value)i(field)f(is)h(blank) +0 1571 y(205)95 b(keyword)45 b(value)i(string)f(is)h(missing)f(the)h +(closing)f(quote)g(character)0 1684 y(206)95 b(illegal)45 +b(indexed)h(keyword)g(name)h(\(e.g.)f('TFORM1000'\))0 +1797 y(207)95 b(illegal)45 b(character)h(in)h(keyword)f(name)g(or)i +(header)e(record)0 1910 y(208)95 b(keyword)45 b(does)i(not)g(have)g +(expected)e(name.)i(Keyword)e(out)i(of)g(sequence?)0 +2023 y(209)95 b(keyword)45 b(does)i(not)g(have)g(expected)e(integer)h +(value)0 2136 y(210)95 b(could)46 b(not)h(find)g(the)f(required)g(END)h +(header)f(keyword)0 2249 y(211)95 b(illegal)45 b(BITPIX)i(keyword)e +(value)0 2362 y(212)95 b(illegal)45 b(NAXIS)i(keyword)f(value)0 +2475 y(213)95 b(illegal)45 b(NAXISn)i(keyword)e(value:)h(must)h(be)g(0) +h(or)f(positive)e(integer)0 2588 y(214)95 b(illegal)45 +b(PCOUNT)i(keyword)e(value)0 2700 y(215)95 b(illegal)45 +b(GCOUNT)i(keyword)e(value)0 2813 y(216)95 b(illegal)45 +b(TFIELDS)h(keyword)g(value)0 2926 y(217)95 b(negative)45 +b(ASCII)i(or)g(binary)f(table)g(width)h(value)f(\(NAXIS1\))0 +3039 y(218)95 b(negative)45 b(number)h(of)h(rows)g(in)g(ASCII)g(or)g +(binary)f(table)g(\(NAXIS2\))0 3152 y(219)95 b(column)46 +b(name)g(\(TTYPE)g(keyword\))g(not)h(found)0 3265 y(220)95 +b(illegal)45 b(SIMPLE)i(keyword)e(value)0 3378 y(221)95 +b(could)46 b(not)h(find)g(the)f(required)g(SIMPLE)g(header)g(keyword)0 +3491 y(222)95 b(could)46 b(not)h(find)g(the)f(required)g(BITPIX)g +(header)g(keyword)0 3604 y(223)95 b(could)46 b(not)h(find)g(the)f +(required)g(NAXIS)g(header)g(keyword)0 3717 y(224)95 +b(could)46 b(not)h(find)g(all)f(the)h(required)f(NAXISn)g(keywords)g +(in)h(the)g(header)0 3830 y(225)95 b(could)46 b(not)h(find)g(the)f +(required)g(XTENSION)g(header)g(keyword)0 3942 y(226)95 +b(the)47 b(CHDU)f(is)h(not)g(an)g(ASCII)g(table)f(extension)0 +4055 y(227)95 b(the)47 b(CHDU)f(is)h(not)g(a)h(binary)e(table)g +(extension)0 4168 y(228)95 b(could)46 b(not)h(find)g(the)f(required)g +(PCOUNT)g(header)g(keyword)0 4281 y(229)95 b(could)46 +b(not)h(find)g(the)f(required)g(GCOUNT)g(header)g(keyword)0 +4394 y(230)95 b(could)46 b(not)h(find)g(the)f(required)g(TFIELDS)g +(header)g(keyword)0 4507 y(231)95 b(could)46 b(not)h(find)g(all)f(the)h +(required)f(TBCOLn)g(keywords)g(in)h(the)g(header)0 4620 +y(232)95 b(could)46 b(not)h(find)g(all)f(the)h(required)f(TFORMn)g +(keywords)g(in)h(the)g(header)0 4733 y(233)95 b(the)47 +b(CHDU)f(is)h(not)g(an)g(IMAGE)g(extension)0 4846 y(234)95 +b(illegal)45 b(TBCOL)i(keyword)f(value;)g(out)h(of)g(range)0 +4959 y(235)95 b(this)46 b(operation)g(only)g(allowed)g(for)h(ASCII)f +(or)h(BINARY)g(table)f(extension)0 5072 y(236)95 b(column)46 +b(is)h(too)g(wide)f(to)i(fit)f(within)f(the)h(specified)e(width)h(of)h +(the)g(ASCII)g(table)0 5185 y(237)95 b(the)47 b(specified)e(column)h +(name)h(template)e(matched)h(more)h(than)f(one)h(column)f(name)0 +5297 y(241)95 b(binary)46 b(table)g(row)h(width)f(is)i(not)e(equal)h +(to)g(the)g(sum)g(of)g(the)g(field)f(widths)0 5410 y(251)95 +b(unrecognizable)44 b(type)i(of)h(FITS)g(extension)0 +5523 y(252)95 b(unrecognizable)44 b(FITS)i(record)0 5636 +y(253)95 b(END)47 b(keyword)e(contains)h(non-blank)f(characters)g(in)i +(columns)f(9-80)p eop end +%%Page: 129 135 +TeXDict begin 129 134 bop 3764 299 a Fi(129)0 555 y Fe(254)95 +b(Header)46 b(fill)g(area)h(contains)f(non-blank)f(characters)0 +668 y(255)95 b(Data)46 b(fill)h(area)g(contains)e(non-blank)g(on)j +(non-zero)d(values)0 781 y(261)95 b(unable)46 b(to)h(parse)f(the)h +(TFORM)g(keyword)e(value)i(string)0 894 y(262)95 b(unrecognizable)44 +b(TFORM)i(datatype)f(code)0 1007 y(263)95 b(illegal)45 +b(TDIMn)i(keyword)f(value)0 1233 y(301)95 b(illegal)45 +b(HDU)i(number;)f(less)h(than)f(1)i(or)f(greater)f(than)h(internal)e +(buffer)h(size)0 1346 y(302)95 b(column)46 b(number)g(out)h(of)g(range) +f(\(1)h(-)h(999\))0 1458 y(304)95 b(attempt)45 b(to)j(move)e(to)h +(negative)f(file)h(record)f(number)0 1571 y(306)95 b(attempted)45 +b(to)i(read)g(or)g(write)f(a)i(negative)d(number)h(of)i(bytes)e(in)h +(the)g(FITS)g(file)0 1684 y(307)95 b(illegal)45 b(starting)h(row)h +(number)f(for)h(table)f(read)h(or)g(write)f(operation)0 +1797 y(308)95 b(illegal)45 b(starting)h(element)g(number)g(for)h(table) +f(read)h(or)g(write)f(operation)0 1910 y(309)95 b(attempted)45 +b(to)i(read)g(or)g(write)f(character)g(string)g(in)h(non-character)d +(table)i(column)0 2023 y(310)95 b(attempted)45 b(to)i(read)g(or)g +(write)f(logical)g(value)g(in)i(non-logical)c(table)j(column)0 +2136 y(311)95 b(illegal)45 b(ASCII)i(table)f(TFORM)h(format)f(code)g +(for)h(attempted)e(operation)0 2249 y(312)95 b(illegal)45 +b(binary)i(table)f(TFORM)g(format)g(code)h(for)g(attempted)e(operation) +0 2362 y(314)95 b(value)46 b(for)h(undefined)e(pixels)h(has)h(not)g +(been)g(defined)0 2475 y(317)95 b(attempted)45 b(to)i(read)g(or)g +(write)f(descriptor)f(in)i(a)h(non-descriptor)c(field)0 +2588 y(320)95 b(number)46 b(of)h(array)f(dimensions)f(out)i(of)g(range) +0 2700 y(321)95 b(first)46 b(pixel)g(number)g(is)i(greater)d(than)i +(the)g(last)g(pixel)f(number)0 2813 y(322)95 b(attempt)45 +b(to)j(set)f(BSCALE)f(or)h(TSCALn)f(scaling)g(parameter)f(=)i(0)0 +2926 y(323)95 b(illegal)45 b(axis)i(length)f(less)h(than)f(1)0 +3152 y(340)h(NOT_GROUP_TABLE)d(340)142 b(Grouping)45 +b(function)h(error)0 3265 y(341)95 b(HDU_ALREADY_MEMBER)0 +3378 y(342)47 b(MEMBER_NOT_FOUND)0 3491 y(343)g(GROUP_NOT_FOUND)0 +3604 y(344)g(BAD_GROUP_ID)0 3717 y(345)g(TOO_MANY_HDUS_TRACKED)0 +3830 y(346)g(HDU_ALREADY_TRACKED)0 3942 y(347)g(BAD_OPTION)0 +4055 y(348)g(IDENTICAL_POINTERS)0 4168 y(349)g(BAD_GROUP_ATTACH)0 +4281 y(350)g(BAD_GROUP_DETACH)0 4507 y(360)g(NGP_NO_MEMORY)665 +b(malloc)46 b(failed)0 4620 y(361)h(NGP_READ_ERR)713 +b(read)46 b(error)h(from)f(file)0 4733 y(362)h(NGP_NUL_PTR)761 +b(null)46 b(pointer)g(passed)g(as)h(an)g(argument.)1575 +4846 y(Passing)f(null)g(pointer)g(as)h(a)h(name)f(of)1575 +4959 y(template)f(file)g(raises)g(this)h(error)0 5072 +y(363)g(NGP_EMPTY_CURLINE)473 b(line)46 b(read)h(seems)f(to)h(be)h +(empty)e(\(used)1575 5185 y(internally\))0 5297 y(364)h +(NGP_UNREAD_QUEUE_FULL)281 b(cannot)46 b(unread)g(more)g(then)h(1)g +(line)g(\(or)g(single)1575 5410 y(line)g(twice\))0 5523 +y(365)g(NGP_INC_NESTING)569 b(too)46 b(deep)h(include)f(file)h(nesting) +e(\(infinite)1575 5636 y(loop,)h(template)g(includes)f(itself)i(?\))p +eop end +%%Page: 130 136 +TeXDict begin 130 135 bop 0 299 a Fi(130)1613 b Fg(CHAPTER)30 +b(12.)112 b(FITSIO)30 b(ERR)m(OR)g(ST)-8 b(A)g(TUS)30 +b(CODES)0 555 y Fe(366)47 b(NGP_ERR_FOPEN)665 b(fopen\(\))45 +b(failed,)h(cannot)g(open)h(template)e(file)0 668 y(367)i(NGP_EOF)953 +b(end)46 b(of)i(file)e(encountered)f(and)i(not)g(expected)0 +781 y(368)g(NGP_BAD_ARG)761 b(bad)46 b(arguments)g(passed.)g(Usually)f +(means)1575 894 y(internal)h(parser)g(error.)g(Should)g(not)h(happen)0 +1007 y(369)g(NGP_TOKEN_NOT_EXPECT)329 b(token)46 b(not)h(expected)e +(here)0 1233 y(401)95 b(error)46 b(attempting)f(to)i(convert)f(an)h +(integer)f(to)h(a)h(formatted)d(character)g(string)0 +1346 y(402)95 b(error)46 b(attempting)f(to)i(convert)f(a)h(real)g +(value)f(to)i(a)f(formatted)e(character)h(string)0 1458 +y(403)95 b(cannot)46 b(convert)g(a)h(quoted)f(string)g(keyword)g(to)h +(an)g(integer)0 1571 y(404)95 b(attempted)45 b(to)i(read)g(a)g +(non-logical)e(keyword)h(value)g(as)h(a)h(logical)e(value)0 +1684 y(405)95 b(cannot)46 b(convert)g(a)h(quoted)f(string)g(keyword)g +(to)h(a)h(real)e(value)0 1797 y(406)95 b(cannot)46 b(convert)g(a)h +(quoted)f(string)g(keyword)g(to)h(a)h(double)e(precision)f(value)0 +1910 y(407)95 b(error)46 b(attempting)f(to)i(read)g(character)e(string) +h(as)h(an)h(integer)0 2023 y(408)95 b(error)46 b(attempting)f(to)i +(read)g(character)e(string)h(as)h(a)h(real)e(value)0 +2136 y(409)95 b(error)46 b(attempting)f(to)i(read)g(character)e(string) +h(as)h(a)h(double)e(precision)f(value)0 2249 y(410)95 +b(bad)47 b(keyword)e(datatype)h(code)0 2362 y(411)95 +b(illegal)45 b(number)i(of)g(decimal)f(places)g(while)g(formatting)f +(floating)h(point)g(value)0 2475 y(412)95 b(numerical)45 +b(overflow)g(during)i(implicit)e(datatype)h(conversion)0 +2588 y(413)95 b(error)46 b(compressing)f(image)0 2700 +y(414)95 b(error)46 b(uncompressing)e(image)0 2813 y(420)95 +b(error)46 b(in)h(date)g(or)g(time)g(conversion)0 3039 +y(431)95 b(syntax)46 b(error)g(in)h(parser)f(expression)0 +3152 y(432)95 b(expression)45 b(did)i(not)f(evaluate)g(to)h(desired)f +(type)0 3265 y(433)95 b(vector)46 b(result)g(too)h(large)f(to)h(return) +f(in)i(array)0 3378 y(434)95 b(data)46 b(parser)g(failed)g(not)h(sent)g +(an)g(out)g(column)0 3491 y(435)95 b(bad)47 b(data)f(encounter)f(while) +i(parsing)f(column)0 3604 y(436)95 b(parse)46 b(error:)g(output)g(file) +h(not)g(of)g(proper)f(type)0 3830 y(501)95 b(celestial)45 +b(angle)h(too)h(large)g(for)f(projection)0 3942 y(502)95 +b(bad)47 b(celestial)e(coordinate)g(or)i(pixel)f(value)0 +4055 y(503)95 b(error)46 b(in)h(celestial)e(coordinate)g(calculation)0 +4168 y(504)95 b(unsupported)44 b(type)j(of)g(celestial)e(projection)0 +4281 y(505)95 b(required)45 b(celestial)g(coordinate)g(keywords)h(not)h +(found)0 4394 y(506)95 b(approximate)44 b(wcs)j(keyword)f(values)g +(were)h(returned)p eop end +%%Trailer + +userdict /end-hook known{end-hook}if +%%EOF diff --git a/external/cfitsio/fitsio.tex b/external/cfitsio/fitsio.tex new file mode 100644 index 0000000..b025558 --- /dev/null +++ b/external/cfitsio/fitsio.tex @@ -0,0 +1,7688 @@ +\documentclass[11pt]{book} +\input{html.sty} +\htmladdtonavigation + {\begin{rawhtml} + FITSIO Home + \end{rawhtml}} +%\oddsidemargin=0.25in +\oddsidemargin=0.00in +\evensidemargin=0.00in +\textwidth=6.5in +%\topmargin=0.0in +\textheight=8.75in +\parindent=0cm +\parskip=0.2cm +\begin{document} +\pagenumbering{roman} + +\begin{titlepage} +\normalsize +\vspace*{4.6cm} +\begin{center} +{\Huge \bf FITSIO User's Guide}\\ +\end{center} +\medskip +\medskip +\begin{center} +{\LARGE \bf A Subroutine Interface to FITS Format Files}\\ +\end{center} +\begin{center} +{\LARGE \bf for Fortran Programmers}\\ +\end{center} +\medskip +\medskip +\begin{center} +{\Large Version 3.0\\} +\end{center} +\bigskip +\vskip 2.5cm +\begin{center} +{HEASARC\\ +Code 662\\ +Goddard Space Flight Center\\ +Greenbelt, MD 20771\\ +USA} +\end{center} + +\vfill +\bigskip +\begin{center} +{\Large May 2011\\} +\end{center} +\vfill +\end{titlepage} + +\clearpage + +\tableofcontents + +\chapter{Introduction } +\pagenumbering{arabic} + +This document describes the Fortran-callable subroutine interface that +is provided as part of the CFITSIO library (which is written in ANSI +C). This is a companion document to the CFITSIO User's Guide which +should be consulted for further information about the underlying +CFITSIO library. In the remainder of this document, the terms FITSIO +and CFITSIO are interchangeable and refer to the same library. + +FITSIO/CFITSIO is a machine-independent library of routines for reading +and writing data files in the FITS (Flexible Image Transport System) +data format. It can also read IRAF format image files and raw binary +data arrays by converting them on the fly into a virtual FITS format +file. This library was written to provide a powerful yet simple +interface for accessing FITS files which will run on most commonly used +computers and workstations. FITSIO supports all the features described +in the official NOST definition of the FITS format and can read and +write all the currently defined types of extensions, including ASCII +tables (TABLE), Binary tables (BINTABLE) and IMAGE extensions. The +FITSIO subroutines insulate the programmer from having to deal with the +complicated formatting details in the FITS file, however, it is assumed +that users have a general knowledge about the structure and usage of +FITS files. + +The CFITSIO package was initially developed by the HEASARC (High Energy +Astrophysics Science Archive Research Center) at the NASA Goddard Space +Flight Center to convert various existing and newly acquired +astronomical data sets into FITS format and to further analyze data +already in FITS format. New features continue to be added to CFITSIO +in large part due to contributions of ideas or actual code from users +of the package. The Integral Science Data Center in Switzerland, and +the XMM/ESTEC project in The Netherlands made especially significant +contributions that resulted in many of the new features that appeared +in v2.0 of CFITSIO. + +The latest version of the CFITSIO source code, documentation, and +example programs are available on the World-Wide Web or via anonymous +ftp from: + +\begin{verbatim} + http://heasarc.gsfc.nasa.gov/fitsio + ftp://legacy.gsfc.nasa.gov/software/fitsio/c +\end{verbatim} +\newpage +Any questions, bug reports, or suggested enhancements related to the CFITSIO +package should be sent to the primary author: + +\begin{verbatim} + Dr. William Pence Telephone: (301) 286-4599 + HEASARC, Code 662 E-mail: William.D.Pence@nasa.gov + NASA/Goddard Space Flight Center + Greenbelt, MD 20771, USA +\end{verbatim} +This User's Guide assumes that readers already have a general +understanding of the definition and structure of FITS format files. +Further information about FITS formats is available from the FITS Support +Office at {\tt http://fits.gsfc.nasa.gov}. In particular, the +'NOST FITS Standard' gives the authoritative definition of the FITS data +format, and the `FITS User's Guide' provides additional historical background +and practical advice on using FITS files. + +CFITSIO users may also be interested in the FTOOLS package of programs +that can be used to manipulate and analyze FITS format files. +Information about FTOOLS can be obtained on the Web or via anonymous +ftp at: + +\begin{verbatim} + http://heasarc.gsfc.nasa.gov/ftools + ftp://legacy.gsfc.nasa.gov/software/ftools/release +\end{verbatim} + +\chapter{ Creating FITSIO/CFITSIO } + + +\section{Building the Library} + +To use the FITSIO subroutines one must first build the CFITSIO library, +which requires a C compiler. gcc is ideal, or most other ANSI-C +compilers will also work. The CFITSIO code is contained in about 40 C +source files (*.c) and header files (*.h). On VAX/VMS systems 2 +assembly-code files (vmsieeed.mar and vmsieeer.mar) are also needed. + +The Fortran interface subroutines to the C CFITSIO routines are located +in the f77\_wrap1.c, through f77\_wrap4.c files. These are relatively simple +'wrappers' that translate the arguments in the Fortran subroutine into +the appropriate format for the corresponding C routine. This +translation is performed transparently to the user by a set of C macros +located in the cfortran.h file. Unfortunately cfortran.h does not +support every combination of C and Fortran compilers so the Fortran +interface is not supported on all platforms. (see further notes below). + +A standard combination of C and Fortran compilers will be assumed by +default, but one may also specify a particular Fortran compiler by +doing: + +\begin{verbatim} + > setenv CFLAGS -DcompilerName=1 +\end{verbatim} +(where 'compilerName' is the name of the compiler) before running +the configure command. The currently recognized compiler +names are: + +\begin{verbatim} + g77Fortran + IBMR2Fortran + CLIPPERFortran + pgiFortran + NAGf90Fortran + f2cFortran + hpuxFortran + apolloFortran + sunFortran + CRAYFortran + mipsFortran + DECFortran + vmsFortran + CONVEXFortran + PowerStationFortran + AbsoftUNIXFortran + AbsoftProFortran + SXFortran +\end{verbatim} +Alternatively, one may edit the CFLAGS line in the Makefile to add the +'-DcompilerName' flag after running the './configure' command. + +The CFITSIO library is built on Unix systems by typing: + +\begin{verbatim} + > ./configure [--prefix=/target/installation/path] + [--enable-sse2] [--enable-ssse3] + > make (or 'make shared') + > make install (this step is optional) +\end{verbatim} +at the operating system prompt. The configure command customizes the +Makefile for the particular system, then the `make' command compiles the +source files and builds the library. Type `./configure' and not simply +`configure' to ensure that the configure script in the current directory +is run and not some other system-wide configure script. The optional +'prefix' argument to configure gives the path to the directory where +the CFITSIO library and include files should be installed via the later +'make install' command. For example, + +\begin{verbatim} + > ./configure --prefix=/usr1/local +\end{verbatim} +will cause the 'make install' command to copy the CFITSIO libcfitsio file +to /usr1/local/lib and the necessary include files to /usr1/local/include +(assuming of course that the process has permission to write to these +directories). + +The optional --enable-sse2 and --enable-ssse3 flags will cause configure to +attempt to build CFITSIO using faster byte-swapping algorithms. +See the "Optimizing Programs" section of this manual for +more information about these options. + +By default, the Makefile will be configured to build the set of Fortran-callable +wrapper routines whose calling sequences are described later in this +document. + +The 'make shared' option builds a shared or dynamic version of the +CFITSIO library. When using the shared library the executable code is +not copied into your program at link time and instead the program +locates the necessary library code at run time, normally through +LD\_LIBRARY\_PATH or some other method. The advantages of using a shared +library are: + +\begin{verbatim} + 1. Less disk space if you build more than 1 program + 2. Less memory if more than one copy of a program using the shared + library is running at the same time since the system is smart + enough to share copies of the shared library at run time. + 3. Possibly easier maintenance since a new version of the shared + library can be installed without relinking all the software + that uses it (as long as the subroutine names and calling + sequences remain unchanged). + 4. No run-time penalty. +\end{verbatim} +The disadvantages are: + +\begin{verbatim} + 1. More hassle at runtime. You have to either build the programs + specially or have LD_LIBRARY_PATH set right. + 2. There may be a slight start up penalty, depending on where you are + reading the shared library and the program from and if your CPU is + either really slow or really heavily loaded. +\end{verbatim} + +On HP/UX systems, the environment variable CFLAGS should be set +to -Ae before running configure to enable "extended ANSI" features. + +It may not be possible to statically link programs that use CFITSIO on +some platforms (namely, on Solaris 2.6) due to the network drivers +(which provide FTP and HTTP access to FITS files). It is possible to +make both a dynamic and a static version of the CFITSIO library, but +network file access will not be possible using the static version. + +On VAX/VMS and ALPHA/VMS systems the make\_gfloat.com command file may +be executed to build the cfitsio.olb object library using the default +G-floating point option for double variables. The make\_dfloat.com and +make\_ieee.com files may be used instead to build the library with the +other floating point options. Note that the getcwd function that is +used in the group.c module may require that programs using CFITSIO be +linked with the ALPHA\$LIBRARY:VAXCRTL.OLB library. See the example +link line in the next section of this document. + +On Windows IBM-PC type platforms the situation is more complicated +because of the wide variety of Fortran compilers that are available and +because of the inherent complexities of calling the CFITSIO C routines +from Fortran. Two different versions of the CFITSIO dll library are +available, compiled with the Borland C++ compiler and the Microsoft +Visual C++ compiler, respectively, in the files +cfitsiodll\_2xxx\_borland.zip and cfitsiodll\_3xxx\_vcc.zip, where +'3xxx' represents the current release number. Both these dll libraries +contain a set of Fortran wrapper routines which may be compatible with +some, but probably not all, available Fortran compilers. To test if +they are compatible, compile the program testf77.f and try linking to +these dll libraries. If these libraries do not work with a particular +Fortran compiler, then there are 2 possible solutions. The first +solution would be to modify the file cfortran.h for that particular +combination of C and Fortran compilers, and then rebuild the CFITSIO +dll library. This will require, however, a some expertise in +mixed language programming. +The other solution is to use the older v5.03 Fortran-77 implementation +of FITSIO that is still available from the FITSIO web-site. This +version is no longer supported, but it does provide the basic functions +for reading and writing FITS files and should be compatible with most +Fortran compilers. + +CFITSIO has currently been tested on the following platforms: + +\begin{verbatim} + OPERATING SYSTEM COMPILER + Sun OS gcc and cc (3.0.1) + Sun Solaris gcc and cc + Silicon Graphics IRIX gcc and cc + Silicon Graphics IRIX64 MIPS + Dec Alpha OSF/1 gcc and cc + DECstation Ultrix gcc + Dec Alpha OpenVMS cc + DEC VAX/VMS gcc and cc + HP-UX gcc + IBM AIX gcc + Linux gcc + MkLinux DR3 + Windows 95/98/NT Borland C++ V4.5 + Windows 95/98/NT/ME/XP Microsoft/Compaq Visual C++ v5.0, v6.0 + Windows 95/98/NT Cygwin gcc + OS/2 gcc + EMX + MacOS 7.1 or greater Metrowerks 10.+ +\end{verbatim} +CFITSIO will probably run on most other Unix platforms. Cray +supercomputers are currently not supported. + + +\section{Testing the Library} + +The CFITSIO library should be tested by building and running +the testprog.c program that is included with the release. +On Unix systems type: + +\begin{verbatim} + % make testprog + % testprog > testprog.lis + % diff testprog.lis testprog.out + % cmp testprog.fit testprog.std +\end{verbatim} + On VMS systems, +(assuming cc is the name of the C compiler command), type: + +\begin{verbatim} + $ cc testprog.c + $ link testprog, cfitsio/lib, alpha$library:vaxcrtl/lib + $ run testprog +\end{verbatim} +The testprog program should produce a FITS file called `testprog.fit' +that is identical to the `testprog.std' FITS file included with this +release. The diagnostic messages (which were piped to the file +testprog.lis in the Unix example) should be identical to the listing +contained in the file testprog.out. The 'diff' and 'cmp' commands +shown above should not report any differences in the files. (There +may be some minor formatting differences, such as the presence or +absence of leading zeros, or 3 digit exponents in numbers, +which can be ignored). + +The Fortran wrappers in CFITSIO may be tested with the testf77 +program. On Unix systems the fortran compilation and link command +may be called 'f77' or 'g77', depending on the system. + +\begin{verbatim} + % f77 -o testf77 testf77.f -L. -lcfitsio -lnsl -lsocket + or + % f77 -f -o testf77 testf77.f -L. -lcfitsio (under SUN O/S) + or + % f77 -o testf77 testf77.f -Wl,-L. -lcfitsio -lm -lnsl -lsocket (HP/UX) + or + % g77 -o testf77 -s testf77.f -lcfitsio -lcc_dynamic -lncurses (Mac OS-X) + + % testf77 > testf77.lis + % diff testf77.lis testf77.out + % cmp testf77.fit testf77.std +\end{verbatim} +On machines running SUN O/S, Fortran programs must be compiled with the +'-f' option to force double precision variables to be aligned on 8-byte +boundaries to make the fortran-declared variables compatible with C. A +similar compiler option may be required on other platforms. Failing to +use this option may cause the program to crash on FITSIO routines that +read or write double precision variables. + +On Windows platforms, linking Fortran programs with a C library +often depends on the particular compilers involved. Some users have +found the following commands work when using the Intel Fortran compiler: + +\begin{verbatim} +ifort /libs.dll cfitsio.lib /MD testf77.f /Gm + +or possibly, + +ifort /libs:dll cfitsio.lib /MD /fpp /extfpp:cfortran.h,fitsio.h + /iface:cvf testf77.f +\end{verbatim} +Also note that on some systems the output listing of the testf77 +program may differ slightly from the testf77.std template if leading +zeros are not printed by default before the decimal point when using F +format. + +A few other utility programs are included with CFITSIO: + +\begin{verbatim} + speed - measures the maximum throughput (in MB per second) + for writing and reading FITS files with CFITSIO + + listhead - lists all the header keywords in any FITS file + + fitscopy - copies any FITS file (especially useful in conjunction + with the CFITSIO's extended input filename syntax) + + cookbook - a sample program that performs common read and + write operations on a FITS file. + + iter_a, iter_b, iter_c - examples of the CFITSIO iterator routine +\end{verbatim} + +The first 4 of these utility programs can be compiled and linked by typing + +\begin{verbatim} + % make program_name +\end{verbatim} + + +\section{Linking Programs with FITSIO} + +When linking applications software with the FITSIO library, several system libraries usually need to be specified on the link comman +Unix systems, the most reliable way to determine what libraries are required +is to type 'make testprog' and see what libraries the configure script has +added. The typical libraries that may need to be added are -lm (the math +library) and -lnsl and -lsocket (needed only for FTP and HTTP file access). +These latter 2 libraries are not needed on VMS and Windows platforms, +because FTP file access is not currently supported on those platforms. + +Note that when upgrading to a newer version of CFITSIO it is usually +necessary to recompile, as well as relink, the programs that use CFITSIO, +because the definitions in fitsio.h often change. + + +\section{Getting Started with FITSIO} + +In order to effectively use the FITSIO library as quickly as possible, +it is recommended that new users follow these steps: + +1. Read the following `FITS Primer' chapter for a brief +overview of the structure of FITS files. This is especially important +for users who have not previously dealt with the FITS table and image +extensions. + +2. Write a simple program to read or write a FITS file using the Basic +Interface routines. + +3. Refer to the cookbook.f program that is included with this release +for examples of routines that perform various common FITS file +operations. + +4. Read Chapters 4 and 5 to become familiar with the conventions and +advanced features of the FITSIO interface. + +5. Scan through the more extensive set of routines that are provided +in the `Advanced Interface'. These routines perform more specialized +functions than are provided by the Basic Interface routines. + + +\section{Example Program} + +The following listing shows an example of how to use the FITSIO +routines in a Fortran program. Refer to the cookbook.f program that +is included with the FITSIO distribution for examples of other +FITS programs. + +\begin{verbatim} + program writeimage + +C Create a FITS primary array containing a 2-D image + + integer status,unit,blocksize,bitpix,naxis,naxes(2) + integer i,j,group,fpixel,nelements,array(300,200) + character filename*80 + logical simple,extend + + status=0 +C Name of the FITS file to be created: + filename='ATESTFILE.FITS' + +C Get an unused Logical Unit Number to use to create the FITS file + call ftgiou(unit,status) + +C create the new empty FITS file + blocksize=1 + call ftinit(unit,filename,blocksize,status) + +C initialize parameters about the FITS image (300 x 200 16-bit integers) + simple=.true. + bitpix=16 + naxis=2 + naxes(1)=300 + naxes(2)=200 + extend=.true. + +C write the required header keywords + call ftphpr(unit,simple,bitpix,naxis,naxes,0,1,extend,status) + +C initialize the values in the image with a linear ramp function + do j=1,naxes(2) + do i=1,naxes(1) + array(i,j)=i+j + end do + end do + +C write the array to the FITS file + group=1 + fpixel=1 + nelements=naxes(1)*naxes(2) + call ftpprj(unit,group,fpixel,nelements,array,status) + +C write another optional keyword to the header + call ftpkyj(unit,'EXPOSURE',1500,'Total Exposure Time',status) + +C close the file and free the unit number + call ftclos(unit, status) + call ftfiou(unit, status) + end +\end{verbatim} + + +\section{Legal Stuff} + +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." + + +\section{Acknowledgments} + +The development of many of the powerful features in CFITSIO was made +possible through collaborations with many people or organizations from +around the world. The following, in particular, have made especially +significant contributions: + +Programmers from the Integral Science Data Center, Switzerland (namely, +Jurek Borkowski, Bruce O'Neel, and Don Jennings), designed the concept +for the plug-in I/O drivers that was introduced with CFITSIO 2.0. The +use of `drivers' greatly simplified the low-level I/O, which in turn +made other new features in CFITSIO (e.g., support for compressed FITS +files and support for IRAF format image files) much easier to +implement. Jurek Borkowski wrote the Shared Memory driver, and Bruce +O'Neel wrote the drivers for accessing FITS files over the network +using the FTP, HTTP, and ROOT protocols. + +The ISDC also provided the template parsing routines (written by Jurek +Borkowski) and the hierarchical grouping routines (written by Don +Jennings). The ISDC DAL (Data Access Layer) routines are layered on +top of CFITSIO and make extensive use of these features. + +Uwe Lammers (XMM/ESA/ESTEC, The Netherlands) designed the +high-performance lexical parsing algorithm that is used to do +on-the-fly filtering of FITS tables. This algorithm essentially +pre-compiles the user-supplied selection expression into a form that +can be rapidly evaluated for each row. Peter Wilson (RSTX, NASA/GSFC) +then wrote the parsing routines used by CFITSIO based on Lammers' +design, combined with other techniques such as the CFITSIO iterator +routine to further enhance the data processing throughput. This effort +also benefited from a much earlier lexical parsing routine that was +developed by Kent Blackburn (NASA/GSFC). More recently, Craig Markwardt +(NASA/GSFC) implemented additional functions (median, average, stddev) +and other enhancements to the lexical parser. + +The CFITSIO iterator function is loosely based on similar ideas +developed for the XMM Data Access Layer. + +Peter Wilson (RSTX, NASA/GSFC) wrote the complete set of +Fortran-callable wrappers for all the CFITSIO routines, which in turn +rely on the CFORTRAN macro developed by Burkhard Burow. + +The syntax used by CFITSIO for filtering or binning input FITS files is +based on ideas developed for the AXAF Science Center Data Model by +Jonathan McDowell, Antonella Fruscione, Aneta Siemiginowska and Bill +Joye. See http://heasarc.gsfc.nasa.gov/docs/journal/axaf7.html for +further description of the AXAF Data Model. + +The file decompression code were taken directly from the gzip (GNU zip) +program developed by Jean-loup Gailly and others. + +Doug Mink, SAO, provided the routines for converting IRAF format +images into FITS format. + +Martin Reinecke (Max Planck Institute, Garching)) provided the modifications to +cfortran.h that are necessary to support 64-bit integer values when calling +C routines from fortran programs. The cfortran.h macros were originally developed +by Burkhard Burow (CERN). + +Julian Taylor (ESO, Garching) provided the fast byte-swapping algorithms +that use the SSE2 and SSSE3 machine instructions available on x86\_64 CPUs. + +In addition, many other people have made valuable contributions to the +development of CFITSIO. These include (with apologies to others that may +have inadvertently been omitted): + +Steve Allen, Carl Akerlof, Keith Arnaud, Morten Krabbe Barfoed, Kent +Blackburn, G Bodammer, Romke Bontekoe, Lucio Chiappetti, Keith Costorf, +Robin Corbet, John Davis, Richard Fink, Ning Gan, Emily Greene, Joe +Harrington, Cheng Ho, Phil Hodge, Jim Ingham, Yoshitaka Ishisaki, Diab +Jerius, Mark Levine, Todd Karakaskian, Edward King, Scott Koch, Claire +Larkin, Rob Managan, Eric Mandel, John Mattox, Carsten Meyer, Emi +Miyata, Stefan Mochnacki, Mike Noble, Oliver Oberdorf, Clive Page, +Arvind Parmar, Jeff Pedelty, Tim Pearson, Maren Purves, Scott Randall, +Chris Rogers, Arnold Rots, Barry Schlesinger, Robin Stebbins, Andrew +Szymkowiak, Allyn Tennant, Peter Teuben, James Theiler, Doug Tody, +Shiro Ueno, Steve Walton, Archie Warnock, Alan Watson, Dan Whipple, Wim +Wimmers, Peter Young, Jianjun Xu, and Nelson Zarate. + + +\chapter{ A FITS Primer } + +This section gives a brief overview of the structure of FITS files. +Users should refer to the documentation available from the NOST, as +described in the introduction, for more detailed information on FITS +formats. + +FITS was first developed in the late 1970's as a standard data +interchange format between various astronomical observatories. Since +then FITS has become the defacto standard data format supported by most +astronomical data analysis software packages. + +A FITS file consists of one or more Header + Data Units (HDUs), where +the first HDU is called the `Primary HDU', or `Primary Array'. The +primary array contains an N-dimensional array of pixels, such as a 1-D +spectrum, a 2-D image, or a 3-D data cube. Six different primary +datatypes are supported: Unsigned 8-bit bytes, 16, 32, and 64-bit signed +integers, and 32 and 64-bit floating point reals. FITS also has a +convention for storing unsigned integers (see the later +section entitled `Unsigned Integers' for more details). The primary HDU +may also consist of only a header with a null array containing no +data pixels. + +Any number of additional HDUs may follow the primary array; these +additional HDUs are called FITS `extensions'. There are currently 3 +types of extensions defined by the FITS standard: + +\begin{itemize} +\item + Image Extension - a N-dimensional array of pixels, like in a primary array +\item + ASCII Table Extension - rows and columns of data in ASCII character format +\item + Binary Table Extension - rows and columns of data in binary representation +\end{itemize} + +In each case the HDU consists of an ASCII Header Unit followed by an optional +Data Unit. For historical reasons, each Header or Data unit must be an +exact multiple of 2880 8-bit bytes long. Any unused space is padded +with fill characters (ASCII blanks or zeros). + +Each Header Unit consists of any number of 80-character keyword records +or `card images' which have the general form: + +\begin{verbatim} + KEYNAME = value / comment string + NULLKEY = / comment: This keyword has no value +\end{verbatim} +The keyword names may be up to 8 characters long and can only contain +uppercase letters, the digits 0-9, the hyphen, and the underscore +character. The keyword name is (usually) followed by an equals sign and +a space character (= ) in columns 9 - 10 of the record, followed by the +value of the keyword which may be either an integer, a floating point +number, a character string (enclosed in single quotes), or a boolean +value (the letter T or F). A keyword may also have a null or undefined +value if there is no specified value string, as in the second example. + +The last keyword in the header is always the `END' keyword which has no +value or comment fields. There are many rules governing the exact +format of a keyword record (see the NOST FITS Standard) so it is better +to rely on standard interface software like FITSIO to correctly +construct or to parse the keyword records rather than try to deal +directly with the raw FITS formats. + +Each Header Unit begins with a series of required keywords which depend +on the type of HDU. These required keywords specify the size and +format of the following Data Unit. The header may contain other +optional keywords to describe other aspects of the data, such as the +units or scaling values. Other COMMENT or HISTORY keywords are also +frequently added to further document the data file. + +The optional Data Unit immediately follows the last 2880-byte block in +the Header Unit. Some HDUs do not have a Data Unit and only consist of +the Header Unit. + +If there is more than one HDU in the FITS file, then the Header Unit of +the next HDU immediately follows the last 2880-byte block of the +previous Data Unit (or Header Unit if there is no Data Unit). + +The main required keywords in FITS primary arrays or image extensions are: +\begin{itemize} +\item +BITPIX -- defines the datatype of the array: 8, 16, 32, 64, -32, -64 for +unsigned 8--bit byte, 16--bit signed integer, 32--bit signed integer, +64--bit signed integer, +32--bit IEEE floating point, and 64--bit IEEE double precision floating +point, respectively. +\item +NAXIS -- the number of dimensions in the array, usually 0, 1, 2, 3, or 4. +\item +NAXISn -- (n ranges from 1 to NAXIS) defines the size of each dimension. +\end{itemize} + +FITS tables start with the keyword XTENSION = `TABLE' (for ASCII +tables) or XTENSION = `BINTABLE' (for binary tables) and have the +following main keywords: +\begin{itemize} +\item +TFIELDS -- number of fields or columns in the table +\item +NAXIS2 -- number of rows in the table +\item +TTYPEn -- for each column (n ranges from 1 to TFIELDS) gives the +name of the column +\item +TFORMn -- the datatype of the column +\item +TUNITn -- the physical units of the column (optional) +\end{itemize} + +Users should refer to the FITS Support Office at {\tt http://fits.gsfc.nasa.gov} +for further information about the FITS format and related software +packages. + + + +\chapter{FITSIO Conventions and Guidelines } + + +\section{CFITSIO Size Limitations} + +CFITSIO places few restrictions on the size of FITS files that it +reads or writes. There are a few limits, however, which may affect +some extreme cases: + +1. The maximum number of FITS files that may be simultaneously opened +by CFITSIO is set by NMAXFILES as defined in fitsio2.h. It is currently +set = 300 by default. CFITSIO will allocate about 80 * NMAXFILES bytes +of memory for internal use. Note that the underlying C compiler or +operating system, may have a smaller limit on the number of opened files. +The C symbolic constant FOPEN\_MAX is intended to define the maximum +number of files that may open at once (including any other text or +binary files that may be open, not just FITS files). On some systems it +has been found that gcc supports a maximum of 255 opened files. + +2. By default, CFITSIO can handle FITS files up to 2.1 GB in size (2**31 +bytes). This file size limit is often imposed by 32-bit operating +systems. More recently, as 64-bit operating systems become more common, an +industry-wide standard (at least on Unix systems) has been developed to +support larger sized files (see http://ftp.sas.com/standards/large.file/). +Starting with version 2.1 of CFITSIO, larger FITS files up to 6 terabytes +in size may be read and written on supported platforms. In order +to support these larger files, CFITSIO must be compiled with the +'-D\_LARGEFILE\_SOURCE' and `-D\_FILE\_OFFSET\_BITS=64' compiler flags. +Some platforms may also require the `-D\_LARGE\_FILES' compiler flag. + This causes the compiler to allocate 8-bytes instead of +4-bytes for the `off\_t' datatype which is used to store file offset +positions. It appears that in most cases it is not necessary to +also include these compiler flags when compiling programs that link to +the CFITSIO library. + +If CFITSIO is compiled with the -D\_LARGEFILE\_SOURCE +and -D\_FILE\_OFFSET\_BITS=64 flags on a +platform that supports large files, then it can read and write FITS +files that contain up to 2**31 2880-byte FITS records, or approximately +6 terabytes in size. It is still required that the value of the NAXISn +and PCOUNT keywords in each extension be within the range of a signed +4-byte integer (max value = 2,147,483,648). Thus, each dimension of an +image (given by the NAXISn keywords), the total width of a table +(NAXIS1 keyword), the number of rows in a table (NAXIS2 keyword), and +the total size of the variable-length array heap in binary tables +(PCOUNT keyword) must be less than this limit. + +Currently, support for large files within CFITSIO has been tested +on the Linux, Solaris, and IBM AIX operating systems. + + +\section{Multiple Access to the Same FITS File} + +CFITSIO supports simultaneous read and write access to multiple HDUs in +the same FITS file. Thus, one can open the same FITS file twice within +a single program and move to 2 different HDUs in the file, and then +read and write data or keywords to the 2 extensions just as if one were +accessing 2 completely separate FITS files. Since in general it is +not possible to physically open the same file twice and then expect to +be able to simultaneously (or in alternating succession) write to 2 +different locations in the file, CFITSIO recognizes when the file to be +opened (in the call to fits\_open\_file) has already been opened and +instead of actually opening the file again, just logically links the +new file to the old file. (This only applies if the file is opened +more than once within the same program, and does not prevent the same +file from being simultaneously opened by more than one program). Then +before CFITSIO reads or writes to either (logical) file, it makes sure +that any modifications made to the other file have been completely +flushed from the internal buffers to the file. Thus, in principle, one +could open a file twice, in one case pointing to the first extension +and in the other pointing to the 2nd extension and then write data to +both extensions, in any order, without danger of corrupting the file, +There may be some efficiency penalties in doing this however, since +CFITSIO has to flush all the internal buffers related to one file +before switching to the other, so it would still be prudent to +minimize the number of times one switches back and forth between doing +I/O to different HDUs in the same file. + + +\section{Current Header Data Unit (CHDU)} + +In general, a FITS file can contain multiple Header Data Units, also +called extensions. CFITSIO only operates within one HDU at any given +time, and the currently selected HDU is called the Current Header Data +Unit (CHDU). When a FITS file is first created or opened the CHDU is +automatically defined to be the first HDU (i.e., the primary array). +CFITSIO routines are provided to move to and open any other existing +HDU within the FITS file or to append or insert a new HDU in the FITS +file which then becomes the CHDU. + + +\section{Subroutine Names} + +All FITSIO subroutine names begin with the letters 'ft' to distinguish +them from other subroutines and are 5 or 6 characters long. Users should +not name their own subroutines beginning with 'ft' to avoid conflicts. +(The SPP interface routines all begin with 'fs'). Subroutines which read +or get information from the FITS file have names beginning with +'ftg...'. Subroutines which write or put information into the FITS file +have names beginning with 'ftp...'. + + +\section{Subroutine Families and Datatypes} + +Many of the subroutines come in families which differ only in the +datatype of the associated parameter(s) . The datatype of these +subroutines is indicated by the last letter of the subroutine name +(e.g., 'j' in 'ftpkyj') as follows: + +\begin{verbatim} + x - bit + b - character*1 (unsigned byte) + i - short integer (I*2) + j - integer (I*4, 32-bit integer) + k - long long integer (I*8, 64-bit integer) + e - real exponential floating point (R*4) + f - real fixed-format floating point (R*4) + d - double precision real floating-point (R*8) + g - double precision fixed-format floating point (R*8) + c - complex reals (pairs of R*4 values) + m - double precision complex (pairs of R*8 values) + l - logical (L*4) + s - character string +\end{verbatim} + +When dealing with the FITS byte datatype, it is important to remember +that the raw values (before any scaling by the BSCALE and BZERO, or +TSCALn and TZEROn keyword values) in byte arrays (BITPIX = 8) or byte +columns (TFORMn = 'B') are interpreted as unsigned bytes with values +ranging from 0 to 255. Some Fortran compilers support a non-standard +byte datatype such as INTEGER*1, LOGICAL*1, or BYTE, which can sometimes +be used instead of CHARACTER*1 variables. Many machines permit passing a +numeric datatype (such as INTEGER*1) to the FITSIO subroutines which are +expecting a CHARACTER*1 datatype, but this technically violates the +Fortran-77 standard and is not supported on all machines (e.g., on a VAX/VMS +machine one must use the VAX-specific \%DESCR function). + +One feature of the CFITSIO routines is that they can operate on a `X' +(bit) column in a binary table as though it were a `B' (byte) column. +For example a `11X' datatype column can be interpreted the same as a +`2B' column (i.e., 2 unsigned 8-bit bytes). In some instances, it can +be more efficient to read and write whole bytes at a time, rather than +reading or writing each individual bit. + +The double precision complex datatype is not a standard Fortran-77 +datatype. If a particular Fortran compiler does not directly support +this datatype, then one may instead pass an array of pairs of double +precision values to these subroutines. The first value in each pair +is the real part, and the second is the imaginary part. + + +\section{Implicit Data Type Conversion} + +The FITSIO routines that read and write numerical data can perform +implicit data type conversion. This means that the data type of the +variable or array in the program does not need to be the same as the +data type of the value in the FITS file. Data type conversion is +supported for numerical and string data types (if the string contains a +valid number enclosed in quotes) when reading a FITS header keyword +value and for numeric values when reading or writing values in the +primary array or a table column. CFITSIO returns status = +NUM\_OVERFLOW if the converted data value exceeds the range of the +output data type. Implicit data type conversion is not supported +within binary tables for string, logical, complex, or double complex +data types. + +In addition, any table column may be read as if it contained string values. +In the case of numeric columns the returned string will be formatted +using the TDISPn display format if it exists. + + +\section{Data Scaling} + +When reading numerical data values in the primary array or a +table column, the values will be scaled automatically by the BSCALE and +BZERO (or TSCALn and TZEROn) header keyword values if they are +present in the header. The scaled data that is returned to the reading +program will have + +\begin{verbatim} + output value = (FITS value) * BSCALE + BZERO +\end{verbatim} +(a corresponding formula using TSCALn and TZEROn is used when reading +from table columns). In the case of integer output values the floating +point scaled value is truncated to an integer (not rounded to the +nearest integer). The ftpscl and fttscl subroutines may be used to +override the scaling parameters defined in the header (e.g., to turn +off the scaling so that the program can read the raw unscaled values +from the FITS file). + +When writing numerical data to the primary array or to a table +column the data values will generally be automatically inversely scaled +by the value of the BSCALE and BZERO (or TSCALn and TZEROn) header +keyword values if they they exist in the header. These keywords must +have been written to the header before any data is written for them to +have any effect. Otherwise, one may use the ftpscl and fttscl +subroutines to define or override the scaling keywords in the header +(e.g., to turn off the scaling so that the program can write the raw +unscaled values into the FITS file). If scaling is performed, the +inverse scaled output value that is written into the FITS file will +have + +\begin{verbatim} + FITS value = ((input value) - BZERO) / BSCALE +\end{verbatim} +(a corresponding formula using TSCALn and TZEROn is used when +writing to table columns). Rounding to the nearest integer, rather +than truncation, is performed when writing integer datatypes to the +FITS file. + + +\section{Error Status Values and the Error Message Stack} + +The last parameter in nearly every FITSIO subroutine is the error +status value which is both an input and an output parameter. A +returned positive value for this parameter indicates an error was +detected. A listing of all the FITSIO status code values is given at +the end of this document. + +The FITSIO library uses an `inherited status' convention for the status +parameter which means that if a subroutine is called with a positive +input value of the status parameter, then the subroutine will exit +immediately without changing the value of the status parameter. Thus, +if one passes the status value returned from each FITSIO routine as +input to the next FITSIO subroutine, then whenever an error is detected +all further FITSIO processing will cease. This convention can simplify +the error checking in application programs because it is not necessary +to check the value of the status parameter after every single FITSIO +subroutine call. If a program contains a sequence of several FITSIO +calls, one can just check the status value after the last call. Since +the returned status values are generally distinctive, it should be +possible to determine which subroutine originally returned the error +status. + +FITSIO also maintains an internal stack of error messages (80-character +maximum length) which in many cases provide a more detailed explanation +of the cause of the error than is provided by the error status number +alone. It is recommended that the error message stack be printed out +whenever a program detects a FITSIO error. To do this, call the FTGMSG +routine repeatedly to get the successive messages on the stack. When the +stack is empty FTGMSG will return a blank string. Note that this is a +`First In -- First Out' stack, so the oldest error message is returned +first by ftgmsg. + + +\section{Variable-Length Array Facility in Binary Tables} + +FITSIO provides easy-to-use support for reading and writing data in +variable length fields of a binary table. The variable length columns +have TFORMn keyword values of the form `1Pt(len)' or `1Qt(len)' where `t' is the +datatype code (e.g., I, J, E, D, etc.) and `len' is an integer +specifying the maximum length of the vector in the table. If the value +of `len' is not specified when the table is created (e.g., if the TFORM +keyword value is simply specified as '1PE' instead of '1PE(400) ), then +FITSIO will automatically scan the table when it is closed to +determine the maximum length of the vector and will append this value +to the TFORMn value. + +The same routines which read and write data in an ordinary fixed length +binary table extension are also used for variable length fields, +however, the subroutine parameters take on a slightly different +interpretation as described below. + +All the data in a variable length field is written into an area called +the `heap' which follows the main fixed-length FITS binary table. The +size of the heap, in bytes, is specified with the PCOUNT keyword in the +FITS header. When creating a new binary table, the initial value of +PCOUNT should usually be set to zero. FITSIO will recompute the size +of the heap as the data is written and will automatically update the +PCOUNT keyword value when the table is closed. When writing variable +length data to a table, CFITSIO will automatically extend the size +of the heap area if necessary, so that any following HDUs do not +get overwritten. + +By default the heap data area starts immediately after the last row of +the fixed-length table. This default starting location may be +overridden by the THEAP keyword, but this is not recommended. +If additional rows of data are added to the table, CFITSIO will +automatically shift the the heap down to make room for the new +rows, but it is obviously be more efficient to initially +create the table with the necessary number of blank rows, so that +the heap does not needed to be constantly moved. + +When writing to a variable length field, the entire array of values for +a given row of the table must be written with a single call to FTPCLx. +The total length of the array is calculated from (NELEM+FELEM-1). One +cannot append more elements to an existing field at a later time; any +attempt to do so will simply overwrite all the data which was previously +written. Note also that the new data will be written to a new area of +the heap and the heap space used by the previous write cannot be +reclaimed. For this reason it is advised that each row of a variable +length field only be written once. An exception to this general rule +occurs when setting elements of an array as undefined. One must first +write a dummy value into the array with FTPCLx, and then call FTPCLU to +flag the desired elements as undefined. (Do not use the FTPCNx family +of routines with variable length fields). Note that the rows of a table, +whether fixed or variable length, do not have to be written +consecutively and may be written in any order. + +When writing to a variable length ASCII character field (e.g., TFORM = +'1PA') only a single character string written. FTPCLS writes the whole +length of the input string (minus any trailing blank characters), thus +the NELEM and FELEM parameters are ignored. If the input string is +completely blank then FITSIO will write one blank character to the FITS +file. Similarly, FTGCVS and FTGCFS read the entire string (truncated +to the width of the character string argument in the subroutine call) +and also ignore the NELEM and FELEM parameters. + +The FTPDES subroutine is useful in situations where multiple rows of a +variable length column have the identical array of values. One can +simply write the array once for the first row, and then use FTPDES to +write the same descriptor values into the other rows (use the FTGDES +routine to read the first descriptor value); all the rows will then +point to the same storage location thus saving disk space. + +When reading from a variable length array field one can only read as +many elements as actually exist in that row of the table; reading does +not automatically continue with the next row of the table as occurs +when reading an ordinary fixed length table field. Attempts to read +more than this will cause an error status to be returned. One can +determine the number of elements in each row of a variable column with +the FTGDES subroutine. + + +\section{Support for IEEE Special Values} + +The ANSI/IEEE-754 floating-point number standard defines certain +special values that are used to represent such quantities as +Not-a-Number (NaN), denormalized, underflow, overflow, and infinity. +(See the Appendix in the NOST FITS standard or the NOST FITS User's +Guide for a list of these values). The FITSIO subroutines that read +floating point data in FITS files recognize these IEEE special values +and by default interpret the overflow and infinity values as being +equivalent to a NaN, and convert the underflow and denormalized values +into zeros. In some cases programmers may want access to the raw IEEE +values, without any modification by FITSIO. This can be done by +calling the FTGPVx or FTGCVx routines while specifying 0.0 as the value +of the NULLVAL parameter. This will force FITSIO to simply pass the +IEEE values through to the application program, without any +modification. This does not work for double precision values on +VAX/VMS machines, however, where there is no easy way to bypass the +default interpretation of the IEEE special values. This is also not +supported when reading floating-point images that have been compressed +with the FITS tiled image compression convention that is discussed in +section 5.6; the pixels values in tile compressed images are +represented by scaled integers, and a reserved integer value +(not a NaN) is used to represent undefined pixels. + + + +\section{When the Final Size of the FITS HDU is Unknown} + +It is not required to know the total size of a FITS data array or table +before beginning to write the data to the FITS file. In the case of +the primary array or an image extension, one should initially create +the array with the size of the highest dimension (largest NAXISn +keyword) set to a dummy value, such as 1. Then after all the data have +been written and the true dimensions are known, then the NAXISn value +should be updated using the fits\_ update\_key routine before moving to +another extension or closing the FITS file. + +When writing to FITS tables, CFITSIO automatically keeps track of the +highest row number that is written to, and will increase the size of +the table if necessary. CFITSIO will also automatically insert space +in the FITS file if necessary, to ensure that the data 'heap', if it +exists, and/or any additional HDUs that follow the table do not get +overwritten as new rows are written to the table. + +As a general rule it is best to specify the initial number of rows = 0 +when the table is created, then let CFITSIO keep track of the number of +rows that are actually written. The application program should not +manually update the number of rows in the table (as given by the NAXIS2 +keyword) since CFITSIO does this automatically. If a table is +initially created with more than zero rows, then this will usually be +considered as the minimum size of the table, even if fewer rows are +actually written to the table. Thus, if a table is initially created +with NAXIS2 = 20, and CFITSIO only writes 10 rows of data before +closing the table, then NAXIS2 will remain equal to 20. If however, 30 +rows of data are written to this table, then NAXIS2 will be increased +from 20 to 30. The one exception to this automatic updating of the +NAXIS2 keyword is if the application program directly modifies the +value of NAXIS2 (up or down) itself just before closing the table. In this +case, CFITSIO does not update NAXIS2 again, since it assumes that the +application program must have had a good reason for changing the value +directly. This is not recommended, however, and is only provided for +backward compatibility with software that initially creates a table +with a large number of rows, than decreases the NAXIS2 value to the +actual smaller value just before closing the table. + + +\section{Local FITS Conventions supported by FITSIO} + +CFITSIO supports several local FITS conventions which are not +defined in the official NOST FITS standard and which are not +necessarily recognized or supported by other FITS software packages. +Programmers should be cautious about using these features, especially +if the FITS files that are produced are expected to be processed by +other software systems which do not use the CFITSIO interface. + + +\subsection{Support for Long String Keyword Values.} + +The length of a standard FITS string keyword is limited to 68 +characters because it must fit entirely within a single FITS header +keyword record. In some instances it is necessary to encode strings +longer than this limit, so FITSIO supports a local convention in which +the string value is continued over multiple keywords. This +continuation convention uses an ampersand character at the end of each +substring to indicate that it is continued on the next keyword, and the +continuation keywords all have the name CONTINUE without an equal sign +in column 9. The string value may be continued in this way over as many +additional CONTINUE keywords as is required. The following lines +illustrate this continuation convention which is used in the value of +the STRKEY keyword: + +\begin{verbatim} +LONGSTRN= 'OGIP 1.0' / The OGIP Long String Convention may be used. +STRKEY = 'This is a very long string keyword&' / Optional Comment +CONTINUE ' value that is continued over 3 keywords in the & ' +CONTINUE 'FITS header.' / This is another optional comment. +\end{verbatim} +It is recommended that the LONGSTRN keyword, as shown +here, always be included in any HDU that uses this longstring +convention. A subroutine called FTPLSW +has been provided in CFITSIO to write this keyword if it does not +already exist. + +This long string convention is supported by the following FITSIO +subroutines that deal with string-valued keywords: + +\begin{verbatim} + ftgkys - read a string keyword + ftpkls - write (append) a string keyword + ftikls - insert a string keyword + ftmkls - modify the value of an existing string keyword + ftukls - update an existing keyword, or write a new keyword + ftdkey - delete a keyword +\end{verbatim} +These routines will transparently read, write, or delete a long string +value in the FITS file, so programmers in general do not have to be +concerned about the details of the convention that is used to encode +the long string in the FITS header. When reading a long string, one +must ensure that the character string parameter used in these +subroutine calls has been declared long enough to hold the entire +string, otherwise the returned string value will be truncated. + +Note that the more commonly used FITSIO subroutine to write string +valued keywords (FTPKYS) does NOT support this long string convention +and only supports strings up to 68 characters in length. This has been +done deliberately to prevent programs from inadvertently writing +keywords using this non-standard convention without the explicit intent +of the programmer or user. The FTPKLS subroutine must be called +instead to write long strings. This routine can also be used to write +ordinary string values less than 68 characters in length. + + +\subsection{Arrays of Fixed-Length Strings in Binary Tables} + +CFITSIO supports 2 ways to specify that a character column in a binary +table contains an array of fixed-length strings. The first way, which +is officially supported by the FITS Standard document, uses the TDIMn keyword. +For example, if TFORMn = '60A' and TDIMn = '(12,5)' then that +column will be interpreted as containing an array of 5 strings, each 12 +characters long. + +FITSIO also supports a +local convention for the format of the TFORMn keyword value of the form +'rAw' where 'r' is an integer specifying the total width in characters +of the column, and 'w' is an integer specifying the (fixed) length of +an individual unit string within the vector. For example, TFORM1 = +'120A10' would indicate that the binary table column is 120 characters +wide and consists of 12 10-character length strings. This convention +is recognized by the FITSIO subroutines that read or write strings in +binary tables. The Binary Table definition document specifies that +other optional characters may follow the datatype code in the TFORM +keyword, so this local convention is in compliance with the +FITS standard, although other FITS readers are not required to +recognize this convention. + +The Binary Table definition document that was approved by the IAU in +1994 contains an appendix describing an alternate convention for +specifying arrays of fixed or variable length strings in a binary table +character column (with the form 'rA:SSTRw/nnn)'. This appendix was not +officially voted on by the IAU and hence is still provisional. FITSIO +does not currently support this proposal. + + +\subsection{Keyword Units Strings} + +One deficiency of the current FITS Standard is that it does not define +a specific convention for recording the physical units of a keyword +value. The TUNITn keyword can be used to specify the physical units of +the values in a table column, but there is no analogous convention for +keyword values. The comment field of the keyword is often used for +this purpose, but the units are usually not specified in a well defined +format that FITS readers can easily recognize and extract. + +To solve this deficiency, FITSIO uses a local convention in which the +keyword units are enclosed in square brackets as the first token in the +keyword comment field; more specifically, the opening square bracket +immediately follows the slash '/' comment field delimiter and a single +space character. The following examples illustrate keywords that use +this convention: + + +\begin{verbatim} +EXPOSURE= 1800.0 / [s] elapsed exposure time +V_HELIO = 16.23 / [km s**(-1)] heliocentric velocity +LAMBDA = 5400. / [angstrom] central wavelength +FLUX = 4.9033487787637465E-30 / [J/cm**2/s] average flux +\end{verbatim} + +In general, the units named in the IAU(1988) Style Guide are +recommended, with the main exception that the preferred unit for angle +is 'deg' for degrees. + +The FTPUNT and FTGUNT subroutines in FITSIO write and read, +respectively, the keyword unit strings in an existing keyword. + + +\subsection{HIERARCH Convention for Extended Keyword Names} + +CFITSIO supports the HIERARCH keyword convention which allows keyword +names that are longer then 8 characters and may contain the full range +of printable ASCII text characters. This convention +was developed at the European Southern Observatory (ESO) to support +hierarchical FITS keyword such as: + +\begin{verbatim} +HIERARCH ESO INS FOCU POS = -0.00002500 / Focus position +\end{verbatim} +Basically, this convention uses the FITS keyword 'HIERARCH' to indicate +that this convention is being used, then the actual keyword name +({\tt'ESO INS FOCU POS'} in this example) begins in column 10 and can +contain any printable ASCII text characters, including spaces. The +equals sign marks the end of the keyword name and is followed by the +usual value and comment fields just as in standard FITS keywords. +Further details of this convention are described at +http://arcdev.hq.eso.org/dicb/dicd/dic-1-1.4.html (search for +HIERARCH). + +This convention allows a much broader range of keyword names +than is allowed by the FITS Standard. Here are more examples +of such keywords: + +\begin{verbatim} +HIERARCH LongKeyword = 47.5 / Keyword has > 8 characters, and mixed case +HIERARCH XTE$TEMP = 98.6 / Keyword contains the '$' character +HIERARCH Earth is a star = F / Keyword contains embedded spaces +\end{verbatim} +CFITSIO will transparently read and write these keywords, so application +programs do not in general need to know anything about the specific +implementation details of the HIERARCH convention. In particular, +application programs do not need to specify the `HIERARCH' part of the +keyword name when reading or writing keywords (although it +may be included if desired). When writing a keyword, CFITSIO first +checks to see if the keyword name is legal as a standard FITS keyword +(no more than 8 characters long and containing only letters, digits, or +a minus sign or underscore). If so it writes it as a standard FITS +keyword, otherwise it uses the hierarch convention to write the +keyword. The maximum keyword name length is 67 characters, which +leaves only 1 space for the value field. A more practical limit is +about 40 characters, which leaves enough room for most keyword values. +CFITSIO returns an error if there is not enough room for both the +keyword name and the keyword value on the 80-character card, except for +string-valued keywords which are simply truncated so that the closing +quote character falls in column 80. In the current implementation, +CFITSIO preserves the case of the letters when writing the keyword +name, but it is case-insensitive when reading or searching for a +keyword. The current implementation allows any ASCII text character +(ASCII 32 to ASCII 126) in the keyword name except for the '=' +character. A space is also required on either side of the equal sign. + + +\section{Optimizing Code for Maximum Processing Speed} + +CFITSIO has been carefully designed to obtain the highest possible +speed when reading and writing FITS files. In order to achieve the +best performance, however, application programmers must be careful to +call the CFITSIO routines appropriately and in an efficient sequence; +inappropriate usage of CFITSIO routines can greatly slow down the +execution speed of a program. + +The maximum possible I/O speed of CFITSIO depends of course on the type +of computer system that it is running on. As a rough guide, the +current generation of workstations can achieve speeds of 2 -- 10 MB/s +when reading or writing FITS images and similar, or slightly slower +speeds with FITS binary tables. Reading of FITS files can occur at +even higher rates (30MB/s or more) if the FITS file is still cached in +system memory following a previous read or write operation on the same +file. To more accurately predict the best performance that is possible +on any particular system, a diagnostic program called ``speed.c'' is +included with the CFITSIO distribution which can be run to +approximately measure the maximum possible speed of writing and reading +a test FITS file. + +The following 2 sections provide some background on how CFITSIO +internally manages the data I/O and describes some strategies that may +be used to optimize the processing speed of software that uses +CFITSIO. + + +\subsection{Background Information: How CFITSIO Manages Data I/O} + +Many CFITSIO operations involve transferring only a small number of +bytes to or from the FITS file (e.g, reading a keyword, or writing a +row in a table); it would be very inefficient to physically read or +write such small blocks of data directly in the FITS file on disk, +therefore CFITSIO maintains a set of internal Input--Output (IO) +buffers in RAM memory that each contain one FITS block (2880 bytes) of +data. Whenever CFITSIO needs to access data in the FITS file, it first +transfers the FITS block containing those bytes into one of the IO +buffers in memory. The next time CFITSIO needs to access bytes in the +same block it can then go to the fast IO buffer rather than using a +much slower system disk access routine. The number of available IO +buffers is determined by the NIOBUF parameter (in fitsio2.h) and is +currently set to 40. + +Whenever CFITSIO reads or writes data it first checks to see if that +block of the FITS file is already loaded into one of the IO buffers. +If not, and if there is an empty IO buffer available, then it will load +that block into the IO buffer (when reading a FITS file) or will +initialize a new block (when writing to a FITS file). If all the IO +buffers are already full, it must decide which one to reuse (generally +the one that has been accessed least recently), and flush the contents +back to disk if it has been modified before loading the new block. + +The one major exception to the above process occurs whenever a large +contiguous set of bytes are accessed, as might occur when reading or +writing a FITS image. In this case CFITSIO bypasses the internal IO +buffers and simply reads or writes the desired bytes directly in the +disk file with a single call to a low-level file read or write +routine. The minimum threshold for the number of bytes to read or +write this way is set by the MINDIRECT parameter and is currently set +to 3 FITS blocks = 8640 bytes. This is the most efficient way to read +or write large chunks of data and can achieve IO transfer rates of +5 -- 10MB/s or greater. Note that this fast direct IO process is not +applicable when accessing columns of data in a FITS table because the +bytes are generally not contiguous since they are interleaved by the +other columns of data in the table. This explains why the speed for +accessing FITS tables is generally slower than accessing +FITS images. + +Given this background information, the general strategy for efficiently +accessing FITS files should now be apparent: when dealing with FITS +images, read or write large chunks of data at a time so that the direct +IO mechanism will be invoked; when accessing FITS headers or FITS +tables, on the other hand, once a particular FITS block has been +loading into one of the IO buffers, try to access all the needed +information in that block before it gets flushed out of the IO buffer. +It is important to avoid the situation where the same FITS block is +being read then flushed from a IO buffer multiple times. + +The following section gives more specific suggestions for optimizing +the use of CFITSIO. + + +\subsection{Optimization Strategies} + +1. Because the data in FITS files is always stored in "big-endian" byte order, +where the first byte of numeric values contains the most significant bits and the +last byte contains the least significant bits, CFITSIO must swap the order of the bytes +when reading or writing FITS files when running on little-endian machines (e.g., +Linux and Microsoft Windows operating systems running on PCs with x86 CPUs). + +On fairly new CPUs that support "SSSE3" machine instructions +(e.g., starting with Intel Core 2 CPUs in 2007, and in AMD CPUs +beginning in 2011) significantly faster 4-byte and 8-byte swapping +algorithms are available. These faster byte swapping functions are +not used by default in CFITSIO (because of the potential code +portablility issues), but users can enable them on supported +platforms by adding the appropriate compiler flags (-mssse3 with gcc +or icc on linux) when compiling the swapproc.c source file, which will +allow the compiler to generate code using the SSSE3 instruction set. +A convenient way to do this is to configure the CFITSIO library +with the following command: + +\begin{verbatim} + > ./configure --enable-ssse3 +\end{verbatim} +Note, however, that a binary executable file that is +created using these faster functions will only run on +machines that support the SSSE3 machine instructions. It will +crash on machines that do not support them. + +For faster 2-byte swaps on virtually all x86-64 CPUs (even those that +do not support SSSE3), a variant using only SSE2 instructions exists. +SSE2 is enabled by default on x86\_64 CPUs with 64-bit operating systems +(and is also automatically enabled by the --enable-ssse3 flag). +When running on x86\_64 CPUs with 32-bit operating systems, these faster +2-byte swapping algorithms are not used by default in CFITSIO, but can be +enabled explicitly with: + +\begin{verbatim} +./configure --enable-sse2 +\end{verbatim} +Preliminary testing indicates that these SSSE3 and SSE2 based +byte-swapping algorithms can boost the CFITSIO performance when +reading or writing FITS images by 20\% - 30\% or more. +It is important to note, however, that compiler optimization must be +turned on (e.g., by using the -O1 or -O2 flags in gcc) when building +programs that use these fast byte-swapping algorithms in order +to reap the full benefit of the SSSE3 and SSE2 instructions; without +optimization, the code may actually run slower than when using +more traditional byte-swapping techniques. + +2. When dealing with a FITS primary array or IMAGE extension, it is +more efficient to read or write large chunks of the image at a time +(at least 3 FITS blocks = 8640 bytes) so that the direct IO mechanism +will be used as described in the previous section. Smaller chunks of +data are read or written via the IO buffers, which is somewhat less +efficient because of the extra copy operation and additional +bookkeeping steps that are required. In principle it is more efficient +to read or write as big an array of image pixels at one time as +possible, however, if the array becomes so large that the operating +system cannot store it all in RAM, then the performance may be degraded +because of the increased swapping of virtual memory to disk. + +3. When dealing with FITS tables, the most important efficiency factor +in the software design is to read or write the data in the FITS file in +a single pass through the file. An example of poor program design +would be to read a large, 3-column table by sequentially reading the +entire first column, then going back to read the 2nd column, and +finally the 3rd column; this obviously requires 3 passes through the +file which could triple the execution time of an I/O limited program. +For small tables this is not important, but when reading multi-megabyte +sized tables these inefficiencies can become significant. The more +efficient procedure in this case is to read or write only as many rows +of the table as will fit into the available internal I/O buffers, then +access all the necessary columns of data within that range of rows. +Then after the program is completely finished with the data in those +rows it can move on to the next range of rows that will fit in the +buffers, continuing in this way until the entire file has been +processed. By using this procedure of accessing all the columns of a +table in parallel rather than sequentially, each block of the FITS file +will only be read or written once. + +The optimal number of rows to read or write at one time in a given +table depends on the width of the table row, on the number of I/O +buffers that have been allocated in FITSIO, and also on the number of +other FITS files that are open at the same time (since one I/O buffer +is always reserved for each open FITS file). Fortunately, a FITSIO +routine is available that will return the optimal number of rows for a +given table: call ftgrsz(unit, nrows, status). It is not critical to +use exactly the value of nrows returned by this routine, as long as one +does not exceed it. Using a very small value however can also lead to +poor performance because of the overhead from the larger number of +subroutine calls. + +The optimal number of rows returned by ftgrsz is valid only as long as +the application program is only reading or writing data in the +specified table. Any other calls to access data in the table header +would cause additional blocks of data to be +loaded into the I/O buffers displacing data from the original table, +and should be avoided during the critical period while the table is +being read or written. + +4. Use binary table extensions rather than ASCII table +extensions for better efficiency when dealing with tabular data. The +I/O to ASCII tables is slower because of the overhead in formatting or +parsing the ASCII data fields, and because ASCII tables are about twice +as large as binary tables with the same information content. + +5. Design software so that it reads the FITS header keywords in the +same order in which they occur in the file. When reading keywords, +FITSIO searches forward starting from the position of the last keyword +that was read. If it reaches the end of the header without finding the +keyword, it then goes back to the start of the header and continues the +search down to the position where it started. In practice, as long as +the entire FITS header can fit at one time in the available internal I/O +buffers, then the header keyword access will be very fast and it makes +little difference which order they are accessed. + +6. Avoid the use of scaling (by using the BSCALE and BZERO or TSCAL and +TZERO keywords) in FITS files since the scaling operations add to the +processing time needed to read or write the data. In some cases it may +be more efficient to temporarily turn off the scaling (using ftpscl or +fttscl) and then read or write the raw unscaled values in the FITS +file. + +7. Avoid using the 'implicit datatype conversion' capability in +FITSIO. For instance, when reading a FITS image with BITPIX = -32 +(32-bit floating point pixels), read the data into a single precision +floating point data array in the program. Forcing FITSIO to convert +the data to a different datatype can significantly slow the program. + +8. Where feasible, design FITS binary tables using vector column +elements so that the data are written as a contiguous set of bytes, +rather than as single elements in multiple rows. For example, it is +faster to access the data in a table that contains a single row +and 2 columns with TFORM keywords equal to '10000E' and '10000J', than +it is to access the same amount of data in a table with 10000 rows +which has columns with the TFORM keywords equal to '1E' and '1J'. In +the former case the 10000 floating point values in the first column are +all written in a contiguous block of the file which can be read or +written quickly, whereas in the second case each floating point value +in the first column is interleaved with the integer value in the second +column of the same row so CFITSIO has to explicitly move to the +position of each element to be read or written. + +9. Avoid the use of variable length vector columns in binary tables, +since any reading or writing of these data requires that CFITSIO first +look up or compute the starting address of each row of data in the +heap. In practice, this is probably not a significant efficiency issue. + +10. When copying data from one FITS table to another, it is faster to +transfer the raw bytes instead of reading then writing each column of +the table. The FITSIO subroutines FTGTBS and FTPTBS (for ASCII +tables), and FTGTBB and FTPTBB (for binary tables) will perform +low-level reads or writes of any contiguous range of bytes in a table +extension. These routines can be used to read or write a whole row (or +multiple rows) of a table with a single subroutine call. These +routines are fast because they bypass all the usual data scaling, error +checking and machine dependent data conversion that is normally done by +FITSIO, and they allow the program to write the data to the output file +in exactly the same byte order. For these same reasons, use of these +routines can be somewhat risky because no validation or machine +dependent conversion is performed by these routines. In general these +routines are only recommended for optimizing critical pieces of code +and should only be used by programmers who thoroughly understand the +internal byte structure of the FITS tables they are reading or +writing. + +11. Another strategy for improving the speed of writing a FITS table, +similar to the previous one, is to directly construct the entire byte +stream for a whole table row (or multiple rows) within the application +program and then write it to the FITS file with +ftptbb. This avoids all the overhead normally present +in the column-oriented CFITSIO write routines. This technique should +only be used for critical applications, because it makes the code more +difficult to understand and maintain, and it makes the code more system +dependent (e.g., do the bytes need to be swapped before writing to the +FITS file?). + +12. Finally, external factors such as the type of magnetic disk +controller (SCSI or IDE), the size of the disk cache, the average seek +speed of the disk, the amount of disk fragmentation, and the amount of +RAM available on the system can all have a significant impact on +overall I/O efficiency. For critical applications, a system +administrator should review the proposed system hardware to identify any +potential I/O bottlenecks. + + + +\chapter{ Basic Interface Routines } + +This section defines a basic set of subroutines that can be +used to perform the most common types of read and write operations +on FITS files. New users should start with these subroutines and +then, as needed, explore the more advance routines described in +the following chapter to perform more complex or specialized operations. + +A right arrow symbol ($>$) is used to separate the input parameters from +the output parameters in the definition of each routine. This symbol +is not actually part of the calling sequence. Note that +the status parameter is both an input and an output parameter +and must be initialized = 0 prior to calling the FITSIO subroutines. + +Refer to Chapter 9 for the definition of all the parameters +used by these interface routines. + + +\section{FITSIO Error Status Routines \label{FTVERS}} + + +\begin{description} +\item[1 ] Return the current version number of the fitsio library. + The version number will be incremented with each new + release of CFITSIO. +\end{description} + +\begin{verbatim} + FTVERS( > version) +\end{verbatim} + +\begin{description} +\item[2 ] Return the descriptive text string corresponding to a FITSIO error + status code. The 30-character length string contains a brief + description of the cause of the error. +\end{description} + +\begin{verbatim} + FTGERR(status, > errtext) +\end{verbatim} + +\begin{description} +\item[3 ] Return the top (oldest) 80-character error message from the + internal FITSIO stack of error messages and shift any remaining + messages on the stack up one level. Any FITSIO error will + generate one or more messages on the stack. Call this routine + repeatedly to get each message in sequence. The error stack is empty + when a blank string is returned. +\end{description} + +\begin{verbatim} + FTGMSG( > errmsg) +\end{verbatim} + +\begin{description} +\item[4 ]The FTPMRK routine puts an invisible marker on the + CFITSIO error stack. The FTCMRK routine can then be + used to delete any more recent error messages on the stack, back to + the position of the marker. This preserves any older error messages + on the stack. FTCMSG simply clears the entire error message stack. + These routines are called without any arguments. +\end{description} + +\begin{verbatim} + FTPMRK + FTCMRK + FTCMSG +\end{verbatim} + + +\begin{description} +\item[5 ] Print out the error message corresponding to the input status + value and all the error messages on the FITSIO stack to the specified + file stream (stream can be either the string 'STDOUT' or 'STDERR'). + If the input status value = 0 then this routine does nothing. +\end{description} + +\begin{verbatim} + FTRPRT (stream, > status) +\end{verbatim} + +\begin{description} +\item[6 ] Write an 80-character message to the FITSIO error stack. Application + programs should not normally write to the stack, but there may be + some situations where this is desirable. +\end{description} + +\begin{verbatim} + FTPMSG(errmsg) +\end{verbatim} + + +\section{File I/O Routines} + + +\begin{description} +\item[1 ]Open an existing FITS file with readonly or readwrite access. + This routine always opens the primary array (the first HDU) of + the file, and does not move to a following extension, if one was + specified as part of the filename. Use the FTNOPN routine to + automatically move to the extension. This routine will also + open IRAF images (.imh format files) and raw binary data arrays + with READONLY access by first converting them on the fly into + virtual FITS images. See the `Extended File Name Syntax' chapter + for more details. The FTDKOPEN routine simply opens the specified + file without trying to interpret the filename using the extended + filename syntax. +\end{description} + +\begin{verbatim} + FTOPEN(unit,filename,rwmode, > blocksize,status) + FTDKOPEN(unit,filename,rwmode, > blocksize,status) +\end{verbatim} + +\begin{description} +\item[2 ]Open an existing FITS file with readonly or readwrite access + and move to a following extension, if one was specified as + part of the filename. (e.g., 'filename.fits+2' or + 'filename.fits[2]' will move to the 3rd HDU in the file). + Note that this routine differs from FTOPEN in that it does not + have the redundant blocksize argument. +\end{description} + +\begin{verbatim} + FTNOPN(unit,filename,rwmode, > status) +\end{verbatim} + +\begin{description} +\item[3 ]Open an existing FITS file with readonly or readwrite access + and then move to the first HDU containing significant data, if a) an HDU + name or number to open was not explicitly specified as part of the + filename, and b) if the FITS file contains a null primary array (i.e., + NAXIS = 0). In this case, it will look for the first IMAGE HDU with + NAXIS > 0, or the first table that does not contain the strings `GTI' + (Good Time Interval) or `OBSTABLE' in the EXTNAME keyword value. FTTOPN + is similar, except it will move to the first significant table HDU + (skipping over any image HDUs) in the file if a specific HDU name + or number is not specified. FTIOPN will move to the first non-null + image HDU, skipping over any tables. +\end{description} + +\begin{verbatim} + FTDOPN(unit,filename,rwmode, > status) + FTTOPN(unit,filename,rwmode, > status) + FTIOPN(unit,filename,rwmode, > status) +\end{verbatim} + +\begin{description} +\item[4 ]Open and initialize a new empty FITS file. A template file may also be + specified to define the structure of the new file (see section 4.2.4). + The FTDKINIT routine simply creates the specified + file without trying to interpret the filename using the extended + filename syntax. +\end{description} + +\begin{verbatim} + FTINIT(unit,filename,blocksize, > status) + FTDKINIT(unit,filename,blocksize, > status) +\end{verbatim} + +\begin{description} +\item[5 ]Close a FITS file previously opened with ftopen or ftinit +\end{description} + +\begin{verbatim} + FTCLOS(unit, > status) +\end{verbatim} + +\begin{description} +\item[6 ] Move to a specified (absolute) HDU in the FITS file (nhdu = 1 for the + FITS primary array) +\end{description} + +\begin{verbatim} + FTMAHD(unit,nhdu, > hdutype,status) +\end{verbatim} + +\begin{description} +\item[7 ] Create a primary array (if none already exists), or insert a + new IMAGE extension immediately following the CHDU, or + insert a new Primary Array at the beginning of the file. Any + following extensions in the file will be shifted down to make room + for the new extension. If the CHDU is the last HDU in the file + then the new image extension will simply be appended to the end of + the file. One can force a new primary array to be inserted at the + beginning of the FITS file by setting status = -9 prior + to calling the routine. In this case the existing primary array will be + converted to an IMAGE extension. The new extension (or primary + array) will become the CHDU. The FTIIMGLL routine is identical + to the FTIIMG routine except that the 4th parameter (the length + of each axis) is an array of 64-bit integers rather than an array + of 32-bit integers. +\end{description} + +\begin{verbatim} + FTIIMG(unit,bitpix,naxis,naxes, > status) + FTIIMGLL(unit,bitpix,naxis,naxesll, > status) +\end{verbatim} + +\begin{description} +\item[8 ] Insert a new ASCII TABLE extension immediately following the CHDU. + Any following extensions will be shifted down to make room for + the new extension. If there are no other following extensions + then the new table extension will simply be appended to the + end of the file. The new extension will become the CHDU. The FTITABLL + routine is identical + to the FTITAB routine except that the 2nd and 3rd parameters (that give + the size of the table) are 64-bit integers rather than + 32-bit integers. Under normal circumstances, the nrows and nrowsll + paramenters should have a value of 0; CFITSIO will automatically update + the number of rows as data is written to the table. +\end{description} + +\begin{verbatim} + FTITAB(unit,rowlen,nrows,tfields,ttype,tbcol,tform,tunit,extname, > + status) + FTITABLL(unit,rowlenll,nrowsll,tfields,ttype,tbcol,tform,tunit,extname, > + status) +\end{verbatim} + +\begin{description} +\item[9 ] Insert a new binary table extension immediately following the CHDU. + Any following extensions will be shifted down to make room for + the new extension. If there are no other following extensions + then the new bintable extension will simply be appended to the + end of the file. The new extension will become the CHDU. The FTIBINLL + routine is identical + to the FTIBIN routine except that the 2nd parameter (that gives + the length of the table) is a 64-bit integer rather than + a 32-bit integer. Under normal circumstances, the nrows and nrowsll + paramenters should have a value of 0; CFITSIO will automatically update + the number of rows as data is written to the table. +\end{description} + +\begin{verbatim} + FTIBIN(unit,nrows,tfields,ttype,tform,tunit,extname,varidat > status) + FTIBINLL(unit,nrowsll,tfields,ttype,tform,tunit,extname,varidat > status) + +\end{verbatim} + +\section{Keyword I/O Routines} + + +\begin{description} +\item[1 ]Put (append) an 80-character record into the CHU. +\end{description} + +\begin{verbatim} + FTPREC(unit,card, > status) +\end{verbatim} + +\begin{description} +\item[2 ] Put (append) a new keyword of the appropriate datatype into the CHU. + The E and D versions of this routine have the added feature that + if the 'decimals' parameter is negative, then the 'G' display + format rather then the 'E' format will be used when constructing + the keyword value, taking the absolute value of 'decimals' for the + precision. This will suppress trailing zeros, and will use a + fixed format rather than an exponential format, + depending on the magnitude of the value. +\end{description} + +\begin{verbatim} + FTPKY[JKLS](unit,keyword,keyval,comment, > status) + FTPKY[EDFG](unit,keyword,keyval,decimals,comment, > status) +\end{verbatim} + +\begin{description} +\item[3 ]Get the nth 80-character header record from the CHU. The first keyword + in the header is at key\_no = 1; if key\_no = 0 then this subroutine + simple moves the internal pointer to the beginning of the header + so that subsequent keyword operations will start at the top of + the header; it also returns a blank card value in this case. +\end{description} + +\begin{verbatim} + FTGREC(unit,key_no, > card,status) +\end{verbatim} + +\begin{description} +\item[4 ] Get a keyword value (with the appropriate datatype) and comment from + the CHU +\end{description} + +\begin{verbatim} + FTGKY[EDJKLS](unit,keyword, > keyval,comment,status) +\end{verbatim} + +\begin{description} +\item[5 ] Delete an existing keyword record. +\end{description} + +\begin{verbatim} + FTDKEY(unit,keyword, > status) +\end{verbatim} + + +\section{Data I/O Routines} + +The following routines read or write data values in the current HDU of +the FITS file. Automatic datatype conversion +will be attempted for numerical datatypes if the specified datatype is +different from the actual datatype of the FITS array or table column. + + +\begin{description} +\item[1 ]Write elements into the primary data array or image extension. +\end{description} + +\begin{verbatim} + FTPPR[BIJKED](unit,group,fpixel,nelements,values, > status) +\end{verbatim} + +\begin{description} +\item[2 ] Read elements from the primary data array or image extension. + Undefined array elements will be + returned with a value = nullval, unless nullval = 0 in which case no + checks for undefined pixels will be performed. The anyf parameter is + set to true (= .true.) if any of the returned + elements were undefined. +\end{description} + +\begin{verbatim} + FTGPV[BIJKED](unit,group,fpixel,nelements,nullval, > values,anyf,status) +\end{verbatim} + +\begin{description} +\item[3 ] Write elements into an ASCII or binary table column. The `felem' + parameter applies only to vector columns in binary tables and is + ignored when writing to ASCII tables. +\end{description} + +\begin{verbatim} + FTPCL[SLBIJKEDCM](unit,colnum,frow,felem,nelements,values, > status) +\end{verbatim} + +\begin{description} +\item[4 ] Read elements from an ASCII or binary table column. Undefined + array elements will be returned with a value = nullval, unless nullval = 0 + (or = ' ' for ftgcvs) in which case no checking for undefined values will + be performed. The ANYF parameter is set to true if any of the returned + elements are undefined. + + Any column, regardless of it's intrinsic datatype, may be read as a + string. It should be noted however that reading a numeric column + as a string is 10 - 100 times slower than reading the same column + as a number due to the large overhead in constructing the formatted + strings. The display format of the returned strings will be + determined by the TDISPn keyword, if it exists, otherwise by the + datatype of the column. The length of the returned strings can be + determined with the ftgcdw routine. The following TDISPn display + formats are currently supported: + +\begin{verbatim} + Iw.m Integer + Ow.m Octal integer + Zw.m Hexadecimal integer + Fw.d Fixed floating point + Ew.d Exponential floating point + Dw.d Exponential floating point + Gw.d General; uses Fw.d if significance not lost, else Ew.d +\end{verbatim} + where w is the width in characters of the displayed values, m is the minimum + number of digits displayed, and d is the number of digits to the right of the + decimal. The .m field is optional. +\end{description} + + +\begin{verbatim} + FTGCV[SBIJKEDCM](unit,colnum,frow,felem,nelements,nullval, > + values,anyf,status) +\end{verbatim} + +\begin{description} +\item[5 ] Get the table column number and full name of the column whose name + matches the input template string. See the `Advanced Interface Routines' + chapter for a full description of this routine. +\end{description} + +\begin{verbatim} + FTGCNN(unit,casesen,coltemplate, > colname,colnum,status) +\end{verbatim} + + +\chapter{ Advanced Interface Subroutines } + +This chapter defines all the available subroutines in the FITSIO user +interface. For completeness, the basic subroutines described in the +previous chapter are also repeated here. A right arrow symbol is used +here to separate the input parameters from the output parameters in the +definition of each subroutine. This symbol is not actually part of the +calling sequence. An alphabetical list and definition of all the +parameters is given at the end of this section. + + +\section{FITS File Open and Close Subroutines: \label{FTOPEN}} + + +\begin{description} +\item[1 ]Open an existing FITS file with readonly or readwrite access. The +FTDKOPEN routine simply opens the specified file without trying to +interpret the filename using the extended filename syntax. FTDOPN opens +the file and +also moves to the first HDU containing significant data, if no specific +HDU is specified as part of the filename. FTTOPN and FTIOPN are similar +except that they will move to the first table HDU or image HDU, respectively, +if a HDU name or number is not specified as part of the filename. +\end{description} + +\begin{verbatim} + FTOPEN(unit,filename,rwmode, > blocksize,status) + FTDKOPEN(unit,filename,rwmode, > blocksize,status) + + FTDOPN(unit,filename,rwmode, > status) + FTTOPN(unit,filename,rwmode, > status) + FTIOPN(unit,filename,rwmode, > status) +\end{verbatim} + + +\begin{description} +\item[2 ]Open an existing FITS file with readonly or readwrite access + and move to a following extension, if one was specified as + part of the filename. (e.g., 'filename.fits+2' or + 'filename.fits[2]' will move to the 3rd HDU in the file). + Note that this routine differs from FTOPEN in that it does not + have the redundant blocksize argument. +\end{description} + +\begin{verbatim} + FTNOPN(unit,filename,rwmode, > status) +\end{verbatim} + +\begin{description} +\item[3 ] Reopen a FITS file that was previously opened with + FTOPEN, FTNOPN, or FTINIT. The newunit number + may then be treated as a separate file, and one may + simultaneously read or write to 2 (or more) different extensions in + the same file. The FTOPEN and FTNOPN routines (above) automatically + detects cases where a previously opened file is being opened again, + and then internally call FTREOPEN, so programs should rarely + need to explicitly call this routine. +\end{description} + +\begin{verbatim} + FTREOPEN(unit, > newunit, status) +\end{verbatim} + +\begin{description} +\item[4 ]Open and initialize a new empty FITS file. + The FTDKINIT routine simply creates the specified + file without trying to interpret the filename using the extended + filename syntax. +\end{description} + +\begin{verbatim} + FTINIT(unit,filename,blocksize, > status) + FTDKINIT(unit,filename,blocksize, > status) +\end{verbatim} + + +\begin{description} +\item[5 ] Create a new FITS file, using a template file to define its + initial size and structure. The template may be another FITS HDU + or an ASCII template file. If the input template file name + is blank, then this routine behaves the same as FTINIT. + The currently supported format of the ASCII template file is described + under the fits\_parse\_template routine (in the general Utilities + section), but this may change slightly later releases of + CFITSIO. +\end{description} + +\begin{verbatim} + FTTPLT(unit, filename, tplfilename, > status) +\end{verbatim} + +\begin{description} +\item[6 ]Flush internal buffers of data to the output FITS file + previously opened with ftopen or ftinit. The routine usually + never needs to be called, but doing so will ensure that + if the program subsequently aborts, then the FITS file will + have at least been closed properly. +\end{description} + +\begin{verbatim} + FTFLUS(unit, > status) +\end{verbatim} + +\begin{description} +\item[7 ]Close a FITS file previously opened with ftopen or ftinit +\end{description} + +\begin{verbatim} + FTCLOS(unit, > status) +\end{verbatim} + +\begin{description} +\item[8 ] Close and DELETE a FITS file previously opened with ftopen or ftinit. + This routine may be useful in cases where a FITS file is created, but + an error occurs which prevents the complete file from being written. +\end{description} + +\begin{verbatim} + FTDELT(unit, > status) +\end{verbatim} + +\begin{description} +\item[9 ] Get the value of an unused I/O unit number which may then be used + as input to FTOPEN or FTINIT. This routine searches for the first + unused unit number in the range from with 99 down to 50. This + routine just keeps an internal list of the allocated unit numbers + and does not physically check that the Fortran unit is available (to be + compatible with the SPP version of FITSIO). Thus users must not + independently allocate any unit numbers in the range 50 - 99 + if this routine is also to be used in the same program. This + routine is provided for convenience only, and it is not required + that the unit numbers used by FITSIO be allocated by this routine. +\end{description} + +\begin{verbatim} + FTGIOU( > iounit, status) +\end{verbatim} + +\begin{description} +\item[10] Free (deallocate) an I/O unit number which was previously allocated + with FTGIOU. All previously allocated unit numbers may be + deallocated at once by calling FTFIOU with iounit = -1. +\end{description} + +\begin{verbatim} + FTFIOU(iounit, > status) +\end{verbatim} + +\begin{description} +\item[11] Return the Fortran unit number that corresponds to the C fitsfile +pointer value, or vice versa. These 2 C routines may be useful in +mixed language programs where both C and Fortran subroutines need +to access the same file. For example, if a FITS file is opened +with unit 12 by a Fortran subroutine, then a C routine within the +same program could get the fitfile pointer value to access the same file +by calling 'fptr = CUnit2FITS(12)'. These routines return a value +of zero if an error occurs. +\end{description} + +\begin{verbatim} + int CFITS2Unit(fitsfile *ptr); + fitsfile* CUnit2FITS(int unit); +\end{verbatim} + + +\begin{description} +\item[11] Parse the input filename and return the HDU number that would be +moved to if the file were opened with FTNOPN. The returned HDU +number begins with 1 for the primary array, so for example, if the +input filename = `myfile.fits[2]' then hdunum = 3 will be returned. +FITSIO does not open the file to check if the extension actually exists +if an extension number is specified. If an extension *name* is included +in the file name specification (e.g. `myfile.fits[EVENTS]' then this +routine will have to open the FITS file and look for the position of +the named extension, then close file again. This is not possible if +the file is being read from the stdin stream, and an error will be +returned in this case. If the filename does not specify an explicit +extension (e.g. 'myfile.fits') then hdunum = -99 will be returned, +which is functionally equivalent to hdunum = 1. This routine is mainly +used for backward compatibility in the ftools software package and is +not recommended for general use. It is generally better and more +efficient to first open the FITS file with FTNOPN, then use FTGHDN to +determine which HDU in the file has been opened, rather than calling + FTEXTN followed by a call to FTNOPN. +\end{description} + +\begin{verbatim} + FTEXTN(filename, > nhdu, status) +\end{verbatim} + +\begin{description} +\item[12] Return the name of the opened FITS file. +\end{description} + +\begin{verbatim} + FTFLNM(unit, > filename, status) +\end{verbatim} + +\begin{description} +\item[13] Return the I/O mode of the open FITS file (READONLY = 0, READWRITE = 1). +\end{description} + +\begin{verbatim} + FTFLMD(unit, > iomode, status) +\end{verbatim} + +\begin{description} +\item[14] Return the file type of the opened FITS file (e.g. 'file://', 'ftp://', + etc.). +\end{description} + +\begin{verbatim} + FTURLT(unit, > urltype, status) +\end{verbatim} + +\begin{description} +\item[15] Parse the input filename or URL into its component parts: the file +type (file://, ftp://, http://, etc), the base input file name, the +name of the output file that the input file is to be copied to prior +to opening, the HDU or extension specification, the filtering +specifier, the binning specifier, and the column specifier. Blank +strings will be returned for any components that are not present +in the input file name. +\end{description} + +\begin{verbatim} + FTIURL(filename, > filetype, infile, outfile, extspec, filter, + binspec, colspec, status) +\end{verbatim} + +\begin{description} +\item[16] Parse the input file name and return the root file name. The root +name includes the file type if specified, (e.g. 'ftp://' or 'http://') +and the full path name, to the extent that it is specified in the input +filename. It does not include the HDU name or number, or any filtering +specifications. +\end{description} + +\begin{verbatim} + FTRTNM(filename, > rootname, status) +\end{verbatim} + + +\begin{description} +\item[16] Test if the input file or a compressed version of the file (with +a .gz, .Z, .z, or .zip extension) exists on disk. The returned value of +the 'exists' parameter will have 1 of the 4 following values: + +\begin{verbatim} + 2: the file does not exist, but a compressed version does exist + 1: the disk file does exist + 0: neither the file nor a compressed version of the file exist + -1: the input file name is not a disk file (could be a ftp, http, + smem, or mem file, or a file piped in on the STDIN stream) +\end{verbatim} + +\end{description} + +\begin{verbatim} + FTEXIST(filename, > exists, status); +\end{verbatim} + +\section{HDU-Level Operations \label{FTMAHD}} + +When a FITS file is first opened or created, the internal buffers in +FITSIO automatically point to the first HDU in the file. The following +routines may be used to move to another HDU in the file. Note that +the HDU numbering convention used in FITSIO denotes the primary array +as the first HDU, the first extension in a FITS file is the second HDU, +and so on. + + +\begin{description} +\item[1 ] Move to a specified (absolute) HDU in the FITS file (nhdu = 1 for the + FITS primary array) +\end{description} + +\begin{verbatim} + FTMAHD(unit,nhdu, > hdutype,status) +\end{verbatim} + +\begin{description} +\item[2 ]Move to a new (existing) HDU forward or backwards relative to the CHDU +\end{description} + +\begin{verbatim} + FTMRHD(unit,nmove, > hdutype,status) +\end{verbatim} + +\begin{description} +\item[3 ] Move to the (first) HDU which has the specified extension type and + EXTNAME (or HDUNAME) and EXTVER keyword values. The hdutype parameter + may have + a value of IMAGE\_HDU (0), ASCII\_TBL (1), BINARY\_TBL (2), or ANY\_HDU (-1) + where ANY\_HDU means that only the extname and extver values will be + used to locate the correct extension. If the input value of + extver is 0 then the EXTVER keyword is ignored and the first HDU + with a matching EXTNAME (or HDUNAME) keyword will be found. If no + matching HDU is found in the file then the current HDU will remain + unchanged + and a status = BAD\_HDU\_NUM (301) will be returned. +\end{description} + +\begin{verbatim} + FTMNHD(unit, hdutype, extname, extver, > status) +\end{verbatim} + +\begin{description} +\item[4 ]Get the number of the current HDU in the FITS file (primary array = 1) +\end{description} + +\begin{verbatim} + FTGHDN(unit, > nhdu) +\end{verbatim} + +\begin{description} +\item[5 ] Return the type of the current HDU in the FITS file. The possible + values for hdutype are IMAGE\_HDU (0), ASCII\_TBL (1), or BINARY\_TBL (2). +\end{description} + +\begin{verbatim} + FTGHDT(unit, > hdutype, status) +\end{verbatim} + +\begin{description} +\item[6 ] Return the total number of HDUs in the FITS file. + The CHDU remains unchanged. +\end{description} + +\begin{verbatim} + FTTHDU(unit, > hdunum, status) +\end{verbatim} + +\begin{description} +\item[7 ]Create (append) a new empty HDU following the last extension that + has been previously accessed by the program. This will overwrite + any extensions in an existing FITS file if the program has not already + moved to that (or a later) extension using the FTMAHD or FTMRHD routines. + For example, if an existing FITS file contains a primary array and 5 + extensions and a program (1) opens the FITS file, (2) moves to + extension 4, (3) moves back to the primary array, and (4) then calls + FTCRHD, then the new extension will be written following the 4th + extension, overwriting the existing 5th extension. +\end{description} + +\begin{verbatim} + FTCRHD(unit, > status) +\end{verbatim} + + +\begin{description} +\item[8 ] Create a primary array (if none already exists), or insert a + new IMAGE extension immediately following the CHDU, or + insert a new Primary Array at the beginning of the file. Any + following extensions in the file will be shifted down to make room + for the new extension. If the CHDU is the last HDU in the file + then the new image extension will simply be appended to the end of + the file. One can force a new primary array to be inserted at the + beginning of the FITS file by setting status = -9 prior + to calling the routine. In this case the existing primary array will be + converted to an IMAGE extension. The new extension (or primary + array) will become the CHDU. The FTIIMGLL routine is identical + to the FTIIMG routine except that the 4th parameter (the length + of each axis) is an array of 64-bit integers rather than an array + of 32-bit integers. +\end{description} + +\begin{verbatim} + FTIIMG(unit,bitpix,naxis,naxes, > status) + FTIIMGLL(unit,bitpix,naxis,naxesll, > status) +\end{verbatim} + +\begin{description} +\item[9 ] Insert a new ASCII TABLE extension immediately following the CHDU. + Any following extensions will be shifted down to make room for + the new extension. If there are no other following extensions + then the new table extension will simply be appended to the + end of the file. The new extension will become the CHDU. The FTITABLL + routine is identical + to the FTITAB routine except that the 2nd and 3rd parameters (that give + the size of the table) are 64-bit integers rather than + 32-bit integers. +\end{description} + +\begin{verbatim} + FTITAB(unit,rowlen,nrows,tfields,ttype,tbcol,tform,tunit,extname, > + status) + FTITABLL(unit,rowlenll,nrowsll,tfields,ttype,tbcol,tform,tunit,extname, > + status) +\end{verbatim} + + +\begin{description} +\item[10] Insert a new binary table extension immediately following the CHDU. + Any following extensions will be shifted down to make room for + the new extension. If there are no other following extensions + then the new bintable extension will simply be appended to the + end of the file. The new extension will become the CHDU. The FTIBINLL + routine is identical + to the FTIBIN routine except that the 2nd parameter (that gives + the length of the table) is a 64-bit integer rather than + a 32-bit integer. +\end{description} + +\begin{verbatim} + FTIBIN(unit,nrows,tfields,ttype,tform,tunit,extname,varidat > status) + FTIBINLL(unit,nrowsll,tfields,ttype,tform,tunit,extname,varidat > status) + +\end{verbatim} + + +\begin{description} +\item[11] Resize an image by modifing the size, dimensions, and/or datatype of the + current primary array or image extension. If the new image, as specified + by the input arguments, is larger than the current existing image + in the FITS file then zero fill data will be inserted at the end + of the current image and any following extensions will be moved + further back in the file. Similarly, if the new image is + smaller than the current image then any following extensions + will be shifted up towards the beginning of the FITS file + and the image data will be truncated to the new size. + This routine rewrites the BITPIX, NAXIS, and NAXISn keywords + with the appropriate values for new image. The FTRSIMLL routine is identical + to the FTRSIM routine except that the 4th parameter (the length + of each axis) is an array of 64-bit integers rather than an array + of 32-bit integers. +\end{description} + +\begin{verbatim} + FTRSIM(unit,bitpix,naxis,naxes,status) + FTRSIMLL(unit,bitpix,naxis,naxesll,status) +\end{verbatim} + +\begin{description} +\item[12] Delete the CHDU in the FITS file. Any following HDUs will be shifted + forward in the file, to fill in the gap created by the deleted + HDU. In the case of deleting the primary array (the first HDU in + the file) then the current primary array will be replace by a null + primary array containing the minimum set of required keywords and + no data. If there are more extensions in the file following the + one that is deleted, then the the CHDU will be redefined to point + to the following extension. If there are no following extensions + then the CHDU will be redefined to point to the previous HDU. The + output HDUTYPE parameter indicates the type of the new CHDU after + the previous CHDU has been deleted. +\end{description} + +\begin{verbatim} + FTDHDU(unit, > hdutype,status) +\end{verbatim} + +\begin{description} +\item[13] Copy all or part of the input FITS file and append it + to the end of the output FITS file. If 'previous' (an integer parameter) is + not equal to 0, then any HDUs preceding the current HDU in the input file + will be copied to the output file. Similarly, 'current' and 'following' + determine whether the current HDU, and/or any following HDUs in the input + file will be copied to the output file. If all 3 parameters are not equal + to zero, then the entire input file will be copied. On return, the current + HDU in the input file will be unchanged, and the last copied HDU will be the + current HDU in the output file. +\end{description} + +\begin{verbatim} + FTCPFL(iunit, ounit, previous, current, following, > status) +\end{verbatim} + +\begin{description} +\item[14] Copy the entire CHDU from the FITS file associated with IUNIT to the CHDU + of the FITS file associated with OUNIT. The output HDU must be empty and + not already contain any keywords. Space will be reserved for MOREKEYS + additional keywords in the output header if there is not already enough + space. +\end{description} + +\begin{verbatim} + FTCOPY(iunit,ounit,morekeys, > status) +\end{verbatim} + +\begin{description} +\item[15] Copy the header (and not the data) from the CHDU associated with inunit + to the CHDU associated with outunit. If the current output HDU + is not completely empty, then the CHDU will be closed and a new + HDU will be appended to the output file. This routine will automatically + transform the necessary keywords when copying a primary array to + and image extension, or an image extension to a primary array. + An empty output data unit will be created (all values = 0). +\end{description} + +\begin{verbatim} + FTCPHD(inunit, outunit, > status) +\end{verbatim} + +\begin{description} +\item[16] Copy just the data from the CHDU associated with IUNIT + to the CHDU associated with OUNIT. This will overwrite + any data previously in the OUNIT CHDU. This low level routine is used + by FTCOPY, but it may also be useful in certain application programs + which want to copy the data from one FITS file to another but also + want to modify the header keywords in the process. all the required + header keywords must be written to the OUNIT CHDU before calling + this routine +\end{description} + +\begin{verbatim} + FTCPDT(iunit,ounit, > status) +\end{verbatim} + + +\section{Define or Redefine the structure of the CHDU \label{FTRDEF}} + +It should rarely be necessary to call the subroutines in this section. +FITSIO internally calls these routines whenever necessary, so any calls +to these routines by application programs will likely be redundant. + + +\begin{description} +\item[1 ] This routine forces FITSIO to scan the current header keywords that + define the structure of the HDU (such as the NAXISn, PCOUNT and GCOUNT + keywords) so that it can initialize the internal buffers that describe + the HDU structure. This routine may be used instead of the more + complicated calls to ftpdef, ftadef or ftbdef. This routine is + also very useful for reinitializing the structure of an HDU, + if the number of rows in a table, as specified by the NAXIS2 keyword, + has been modified from its initial value. +\end{description} + +\begin{verbatim} + FTRDEF(unit, > status) (DEPRECATED) +\end{verbatim} + +\begin{description} +\item[2 ]Define the structure of the primary array or IMAGE extension. When + writing GROUPed FITS files that by convention set the NAXIS1 keyword + equal to 0, ftpdef must be called with naxes(1) = 1, NOT 0, otherwise + FITSIO will report an error status=308 when trying to write data + to a group. Note: it is usually simpler to call FTRDEF rather + than this routine. +\end{description} + +\begin{verbatim} + FTPDEF(unit,bitpix,naxis,naxes,pcount,gcount, > status) (DEPRECATED) +\end{verbatim} + +\begin{description} +\item[3 ] Define the structure of an ASCII table (TABLE) extension. Note: it + is usually simpler to call FTRDEF rather than this routine. +\end{description} + +\begin{verbatim} + FTADEF(unit,rowlen,tfields,tbcol,tform,nrows > status) (DEPRECATED) +\end{verbatim} + +\begin{description} +\item[4 ] Define the structure of a binary table (BINTABLE) extension. Note: it + is usually simpler to call FTRDEF rather than this routine. +\end{description} + +\begin{verbatim} + FTBDEF(unit,tfields,tform,varidat,nrows > status) (DEPRECATED) +\end{verbatim} + +\begin{description} +\item[5 ] Define the size of the Current Data Unit, overriding the length + of the data unit as previously defined by ftpdef, ftadef, or ftbdef. + This is useful if one does not know the total size of the data unit until + after the data have been written. The size (in bytes) of an ASCII or + Binary table is given by NAXIS1 * NAXIS2. (Note that to determine the + value of NAXIS1 it is often more convenient to read the value of the + NAXIS1 keyword from the output file, rather than computing the row + length directly from all the TFORM keyword values). Note: it + is usually simpler to call FTRDEF rather than this routine. +\end{description} + +\begin{verbatim} + FTDDEF(unit,bytlen, > status) (DEPRECATED) +\end{verbatim} + +\begin{description} +\item[6 ] Define the zero indexed byte offset of the 'heap' measured from + the start of the binary table data. By default the heap is assumed + to start immediately following the regular table data, i.e., at + location NAXIS1 x NAXIS2. This routine is only relevant for + binary tables which contain variable length array columns (with + TFORMn = 'Pt'). This subroutine also automatically writes + the value of theap to a keyword in the extension header. This + subroutine must be called after the required keywords have been + written (with ftphbn) and after the table structure has been defined + (with ftbdef) but before any data is written to the table. +\end{description} + +\begin{verbatim} + FTPTHP(unit,theap, > status) +\end{verbatim} + + +\section{FITS Header I/O Subroutines} + + +\subsection{Header Space and Position Routines \label{FTHDEF}} + + +\begin{description} +\item[1 ] Reserve space in the CHU for MOREKEYS more header keywords. + This subroutine may be called to reserve space for keywords which are + to be written at a later time, after the data unit or subsequent + extensions have been written to the FITS file. If this subroutine is + not explicitly called, then the initial size of the FITS header will be + limited to the space available at the time that the first data is written + to the associated data unit. FITSIO has the ability to dynamically + add more space to the header if needed, however it is more efficient + to preallocate the required space if the size is known in advance. +\end{description} + +\begin{verbatim} + FTHDEF(unit,morekeys, > status) +\end{verbatim} + +\begin{description} +\item[2 ] Return the number of existing keywords in the CHU (NOT including the + END keyword which is not considered a real keyword) and the remaining + space available to write additional keywords in the CHU. (returns + KEYSADD = -1 if the header has not yet been closed). + Note that FITSIO will attempt to dynamically add space for more + keywords if required when appending new keywords to a header. +\end{description} + +\begin{verbatim} + FTGHSP(iunit, > keysexist,keysadd,status) +\end{verbatim} + +\begin{description} +\item[3 ] Return the number of keywords in the header and the current position + in the header. This returns the number of the keyword record that + will be read next (or one greater than the position of the last keyword + that was read or written). A value of 1 is returned if the pointer is + positioned at the beginning of the header. +\end{description} + +\begin{verbatim} + FTGHPS(iunit, > keysexist,key_no,status) +\end{verbatim} + +\subsection{Read or Write Standard Header Routines \label{FTPHPR}} + +These subroutines provide a simple method of reading or writing most of +the keyword values that are normally required in a FITS files. These +subroutines are provided for convenience only and are not required to +be used. If preferred, users may call the lower-level subroutines +described in the previous section to individually read or write the +required keywords. Note that in most cases, the required keywords such +as NAXIS, TFIELD, TTYPEn, etc, which define the structure of the HDU +must be written to the header before any data can be written to the +image or table. + + +\begin{description} +\item[1 ] Put the primary header or IMAGE extension keywords into the CHU. +There are 2 available routines: The simpler FTPHPS routine is +equivalent to calling ftphpr with the default values of SIMPLE = true, +pcount = 0, gcount = 1, and EXTEND = true. PCOUNT, GCOUNT and EXTEND +keywords are not required in the primary header and are only written if +pcount is not equal to zero, gcount is not equal to zero or one, and if +extend is TRUE, respectively. When writing to an IMAGE extension, the +SIMPLE and EXTEND parameters are ignored. +\end{description} + +\begin{verbatim} + FTPHPS(unit,bitpix,naxis,naxes, > status) + + FTPHPR(unit,simple,bitpix,naxis,naxes,pcount,gcount,extend, > status) +\end{verbatim} + +\begin{description} +\item[2 ] Get primary header or IMAGE extension keywords from the CHU. When + reading from an IMAGE extension the SIMPLE and EXTEND parameters are + ignored. +\end{description} + +\begin{verbatim} + FTGHPR(unit,maxdim, > simple,bitpix,naxis,naxes,pcount,gcount,extend, + status) +\end{verbatim} + +\begin{description} +\item[3 ] Put the ASCII table header keywords into the CHU. The optional +TUNITn and EXTNAME keywords are written only if the input string +values are not blank. +\end{description} + +\begin{verbatim} + FTPHTB(unit,rowlen,nrows,tfields,ttype,tbcol,tform,tunit,extname, > + status) +\end{verbatim} + +\begin{description} +\item[4 ] Get the ASCII table header keywords from the CHU +\end{description} + +\begin{verbatim} + FTGHTB(unit,maxdim, > rowlen,nrows,tfields,ttype,tbcol,tform,tunit, + extname,status) +\end{verbatim} + +\begin{description} +\item[5 ]Put the binary table header keywords into the CHU. The optional + TUNITn and EXTNAME keywords are written only if the input string + values are not blank. The pcount parameter, which specifies the + size of the variable length array heap, should initially = 0; + FITSIO will automatically update the PCOUNT keyword value if any + variable length array data is written to the heap. The TFORM keyword + value for variable length vector columns should have the form 'Pt(len)' + or '1Pt(len)' where `t' is the data type code letter (A,I,J,E,D, etc.) + and `len' is an integer specifying the maximum length of the vectors + in that column (len must be greater than or equal to the longest + vector in the column). If `len' is not specified when the table is + created (e.g., the input TFORMn value is just '1Pt') then FITSIO will + scan the column when the table is first closed and will append the + maximum length to the TFORM keyword value. Note that if the table + is subsequently modified to increase the maximum length of the vectors + then the modifying program is responsible for also updating the TFORM + keyword value. +\end{description} + + +\begin{verbatim} + FTPHBN(unit,nrows,tfields,ttype,tform,tunit,extname,varidat, > status) +\end{verbatim} + +\begin{description} +\item[6 ]Get the binary table header keywords from the CHU +\end{description} + +\begin{verbatim} + FTGHBN(unit,maxdim, > nrows,tfields,ttype,tform,tunit,extname,varidat, + status) +\end{verbatim} + +\subsection{Write Keyword Subroutines \label{FTPREC}} + + +\begin{description} +\item[1 ]Put (append) an 80-character record into the CHU. +\end{description} + +\begin{verbatim} + FTPREC(unit,card, > status) +\end{verbatim} + +\begin{description} +\item[2 ] Put (append) a COMMENT keyword into the CHU. Multiple COMMENT keywords + will be written if the input comment string is longer than 72 characters. +\end{description} + +\begin{verbatim} + FTPCOM(unit,comment, > status) +\end{verbatim} + +\begin{description} +\item[3 ]Put (append) a HISTORY keyword into the CHU. Multiple HISTORY keywords + will be written if the input history string is longer than 72 characters. +\end{description} + +\begin{verbatim} + FTPHIS(unit,history, > status) +\end{verbatim} + +\begin{description} +\item[4 ] Put (append) the DATE keyword into the CHU. The keyword value will contain + the current system date as a character string in 'dd/mm/yy' format. If + a DATE keyword already exists in the header, then this subroutine will + simply update the keyword value in-place with the current date. +\end{description} + +\begin{verbatim} + FTPDAT(unit, > status) +\end{verbatim} + +\begin{description} +\item[5 ] Put (append) a new keyword of the appropriate datatype into the CHU. + Note that FTPKYS will only write string values up to 68 characters in + length; longer strings will be truncated. The FTPKLS routine can be + used to write longer strings, using a non-standard FITS convention. + The E and D versions of this routine have the added feature that + if the 'decimals' parameter is negative, then the 'G' display + format rather then the 'E' format will be used when constructing + the keyword value, taking the absolute value of 'decimals' for the + precision. This will suppress trailing zeros, and will use a + fixed format rather than an exponential format, + depending on the magnitude of the value. +\end{description} + +\begin{verbatim} + FTPKY[JKLS](unit,keyword,keyval,comment, > status) + FTPKY[EDFG](unit,keyword,keyval,decimals,comment, > status) +\end{verbatim} + +\begin{description} +\item[6 ] Put (append) a string valued keyword into the CHU which may be longer + than 68 characters in length. This uses the Long String Keyword + convention that is described in the "Usage Guidelines and Suggestions" + section of this document. Since this uses a non-standard FITS + convention to encode the long keyword string, programs which use + this routine should also call the FTPLSW routine to add some COMMENT + keywords to warn users of the FITS file that this convention is + being used. FTPLSW also writes a keyword called LONGSTRN to record + the version of the longstring convention that has been used, in case + a new convention is adopted at some point in the future. If the + LONGSTRN keyword is already present in the header, then FTPLSW will + simply return and will not write duplicate keywords. +\end{description} + +\begin{verbatim} + FTPKLS(unit,keyword,keyval,comment, > status) + FTPLSW(unit, > status) +\end{verbatim} + +\begin{description} +\item[7 ] Put (append) a new keyword with an undefined, or null, value into the CHU. + The value string of the keyword is left blank in this case. +\end{description} + +\begin{verbatim} + FTPKYU(unit,keyword,comment, > status) +\end{verbatim} + +\begin{description} +\item[8 ] Put (append) a numbered sequence of keywords into the CHU. One may + append the same comment to every keyword (and eliminate the need + to have an array of identical comment strings, one for each keyword) by + including the ampersand character as the last non-blank character in the + (first) COMMENTS string parameter. This same string + will then be used for the comment field in all the keywords. (Note + that the SPP version of these routines only supports a single comment + string). +\end{description} + +\begin{verbatim} + FTPKN[JKLS](unit,keyroot,startno,no_keys,keyvals,comments, > status) + FTPKN[EDFG](unit,keyroot,startno,no_keys,keyvals,decimals,comments, > + status) +\end{verbatim} + +\begin{description} +\item[9 ]Copy an indexed keyword from one HDU to another, modifying + the index number of the keyword name in the process. For example, + this routine could read the TLMIN3 keyword from the input HDU + (by giving keyroot = "TLMIN" and innum = 3) and write it to the + output HDU with the keyword name TLMIN4 (by setting outnum = 4). + If the input keyword does not exist, then this routine simply + returns without indicating an error. +\end{description} + +\begin{verbatim} + FTCPKY(inunit, outunit, innum, outnum, keyroot, > status) +\end{verbatim} + +\begin{description} +\item[10] Put (append) a 'triple precision' keyword into the CHU in F28.16 format. + The floating point keyword value is constructed by concatenating the + input integer value with the input double precision fraction value + (which must have a value between 0.0 and 1.0). The FTGKYT routine should + be used to read this keyword value, because the other keyword reading + subroutines will not preserve the full precision of the value. +\end{description} + +\begin{verbatim} + FTPKYT(unit,keyword,intval,dblval,comment, > status) +\end{verbatim} + +\begin{description} +\item[11] Write keywords to the CHDU that are defined in an ASCII template file. + The format of the template file is described under the ftgthd + routine below. +\end{description} + +\begin{verbatim} + FTPKTP(unit, filename, > status) +\end{verbatim} + +\begin{description} +\item[12] Append the physical units string to an existing keyword. This + routine uses a local convention, shown in the following example, + in which the keyword units are enclosed in square brackets in the + beginning of the keyword comment field. +\end{description} + + +\begin{verbatim} + VELOCITY= 12.3 / [km/s] orbital speed + + FTPUNT(unit,keyword,units, > status) +\end{verbatim} + +\subsection{Insert Keyword Subroutines \label{FTIREC}} + + +\begin{description} +\item[1 ] Insert a new keyword record into the CHU at the specified position + (i.e., immediately preceding the (keyno)th keyword in the header.) + This 'insert record' subroutine is somewhat less efficient + then the 'append record' subroutine (FTPREC) described above because + the remaining keywords in the header have to be shifted down one slot. +\end{description} + +\begin{verbatim} + FTIREC(unit,key_no,card, > status) +\end{verbatim} + +\begin{description} +\item[2 ] Insert a new keyword into the CHU. The new keyword is inserted + immediately following the last keyword that has been read from the header. + The FTIKLS subroutine works the same as the FTIKYS subroutine, except + it also supports long string values greater than 68 characters in length. + These 'insert keyword' subroutines are somewhat less efficient then + the 'append keyword' subroutines described above because the remaining + keywords in the header have to be shifted down one slot. +\end{description} + +\begin{verbatim} + FTIKEY(unit, card, > status) + FTIKY[JKLS](unit,keyword,keyval,comment, > status) + FTIKLS(unit,keyword,keyval,comment, > status) + FTIKY[EDFG](unit,keyword,keyval,decimals,comment, > status) +\end{verbatim} + +\begin{description} +\item[3 ] Insert a new keyword with an undefined, or null, value into the CHU. + The value string of the keyword is left blank in this case. +\end{description} + +\begin{verbatim} + FTIKYU(unit,keyword,comment, > status) +\end{verbatim} + +\subsection{Read Keyword Subroutines \label{FTGREC}} + +These routines return the value of the specified keyword(s). Wild card +characters (*, ?, or \#) may be used when specifying the name of the keyword +to be read: a '?' will match any single character at that position in the +keyword name and a '*' will match any length (including zero) string of +characters. The '\#' character will match any consecutive string of +decimal digits (0 - 9). Note that when a wild card is used in the input +keyword name, the routine will only search for a match from the current +header position to the end of the header. It will not resume the search +from the top of the header back to the original header position as is done +when no wildcards are included in the keyword name. If the desired +keyword string is 8-characters long (the maximum length of a keyword +name) then a '*' may be appended as the ninth character of the input +name to force the keyword search to stop at the end of the header +(e.g., 'COMMENT *' will search for the next COMMENT keyword). The +ffgrec routine may be used to set the starting position when doing +wild card searches. + + +\begin{description} +\item[1 ]Get the nth 80-character header record from the CHU. The first keyword + in the header is at key\_no = 1; if key\_no = 0 then this subroutine + simple moves the internal pointer to the beginning of the header + so that subsequent keyword operations will start at the top of + the header; it also returns a blank card value in this case. +\end{description} + +\begin{verbatim} + FTGREC(unit,key_no, > card,status) +\end{verbatim} + +\begin{description} +\item[2 ] Get the name, value (as a string), and comment of the nth keyword in CHU. + This routine also checks that the returned keyword name (KEYWORD) contains + only legal ASCII characters. Call FTGREC and FTPSVC to bypass this error + check. +\end{description} + +\begin{verbatim} + FTGKYN(unit,key_no, > keyword,value,comment,status) +\end{verbatim} + +\begin{description} +\item[3 ] Get the 80-character header record for the named keyword +\end{description} + +\begin{verbatim} + FTGCRD(unit,keyword, > card,status) +\end{verbatim} + +\begin{description} +\item[4 ] Get the next keyword whose name matches one of the strings in + 'inclist' but does not match any of the strings in 'exclist'. + The strings in inclist and exclist may contain wild card characters + (*, ?, and \#) as described at the beginning of this section. + This routine searches from the current header position to the + end of the header, only, and does not continue the search from + the top of the header back to the original position. The current + header position may be reset with the ftgrec routine. Note + that nexc may be set = 0 if there are no keywords to be excluded. + This routine returns status = 202 if a matching + keyword is not found. +\end{description} + +\begin{verbatim} + FTGNXK(unit,inclist,ninc,exclist,nexc, > card,status) +\end{verbatim} + +\begin{description} +\item[5 ] Get the literal keyword value as a character string. Regardless + of the datatype of the keyword, this routine simply returns the + string of characters in the value field of the keyword along with + the comment field. +\end{description} + +\begin{verbatim} + FTGKEY(unit,keyword, > value,comment,status) +\end{verbatim} + +\begin{description} +\item[6 ] Get a keyword value (with the appropriate datatype) and comment from + the CHU +\end{description} + +\begin{verbatim} + FTGKY[EDJKLS](unit,keyword, > keyval,comment,status) +\end{verbatim} + +\begin{description} +\item[7 ] Get a sequence of numbered keyword values. These + routines do not support wild card characters in the root name. +\end{description} + +\begin{verbatim} + FTGKN[EDJKLS](unit,keyroot,startno,max_keys, > keyvals,nfound,status) +\end{verbatim} + +\begin{description} +\item[8 ] Get the value of a floating point keyword, returning the integer and + fractional parts of the value in separate subroutine arguments. + This subroutine may be used to read any keyword but is especially + useful for reading the 'triple precision' keywords written by FTPKYT. +\end{description} + +\begin{verbatim} + FTGKYT(unit,keyword, > intval,dblval,comment,status) +\end{verbatim} + +\begin{description} +\item[9 ] Get the physical units string in an existing keyword. This + routine uses a local convention, shown in the following example, + in which the keyword units are + enclosed in square brackets in the beginning of the keyword comment + field. A blank string is returned if no units are defined + for the keyword. +\end{description} + +\begin{verbatim} + VELOCITY= 12.3 / [km/s] orbital speed + + FTGUNT(unit,keyword, > units,status) +\end{verbatim} + +\subsection{Modify Keyword Subroutines \label{FTMREC}} + +Wild card characters, as described in the Read Keyword section, above, +may be used when specifying the name of the keyword to be modified. + + +\begin{description} +\item[1 ] Modify (overwrite) the nth 80-character header record in the CHU +\end{description} + +\begin{verbatim} + FTMREC(unit,key_no,card, > status) +\end{verbatim} + +\begin{description} +\item[2 ] Modify (overwrite) the 80-character header record for the named keyword + in the CHU. This can be used to overwrite the name of the keyword as + well as its value and comment fields. +\end{description} + +\begin{verbatim} + FTMCRD(unit,keyword,card, > status) +\end{verbatim} + +\begin{description} +\item[3 ] Modify (overwrite) the name of an existing keyword in the CHU + preserving the current value and comment fields. +\end{description} + +\begin{verbatim} + FTMNAM(unit,oldkey,keyword, > status) +\end{verbatim} + +\begin{description} +\item[4 ] Modify (overwrite) the comment field of an existing keyword in the CHU +\end{description} + +\begin{verbatim} + FTMCOM(unit,keyword,comment, > status) +\end{verbatim} + +\begin{description} +\item[5 ] Modify the value and comment fields of an existing keyword in the CHU. + The FTMKLS subroutine works the same as the FTMKYS subroutine, except + it also supports long string values greater than 68 characters in length. + Optionally, one may modify only the value field and leave the comment + field unchanged by setting the input COMMENT parameter equal to + the ampersand character (\&). + The E and D versions of this routine have the added feature that + if the 'decimals' parameter is negative, then the 'G' display + format rather then the 'E' format will be used when constructing + the keyword value, taking the absolute value of 'decimals' for the + precision. This will suppress trailing zeros, and will use a + fixed format rather than an exponential format, + depending on the magnitude of the value. +\end{description} + +\begin{verbatim} + FTMKY[JKLS](unit,keyword,keyval,comment, > status) + FTMKLS(unit,keyword,keyval,comment, > status) + FTMKY[EDFG](unit,keyword,keyval,decimals,comment, > status) +\end{verbatim} + +\begin{description} +\item[6 ] Modify the value of an existing keyword to be undefined, or null. + The value string of the keyword is set to blank. + Optionally, one may leave the comment field unchanged by setting the + input COMMENT parameter equal to the ampersand character (\&). +\end{description} + +\begin{verbatim} + FTMKYU(unit,keyword,comment, > status) +\end{verbatim} + +\subsection{Update Keyword Subroutines \label{FTUCRD}} + + +\begin{description} +\item[1 ] Update an 80-character record in the CHU. If the specified keyword + already exists then that header record will be replaced with + the input CARD string. If it does not exist then the new record will + be added to the header. + The FTUKLS subroutine works the same as the FTUKYS subroutine, except + it also supports long string values greater than 68 characters in length. +\end{description} + +\begin{verbatim} + FTUCRD(unit,keyword,card, > status) +\end{verbatim} + +\begin{description} +\item[2 ] Update the value and comment fields of a keyword in the CHU. + The specified keyword is modified if it already exists (by calling + FTMKYx) otherwise a new keyword is created by calling FTPKYx. + The E and D versions of this routine have the added feature that + if the 'decimals' parameter is negative, then the 'G' display + format rather then the 'E' format will be used when constructing + the keyword value, taking the absolute value of 'decimals' for the + precision. This will suppress trailing zeros, and will use a + fixed format rather than an exponential format, + depending on the magnitude of the value. +\end{description} + +\begin{verbatim} + FTUKY[JKLS](unit,keyword,keyval,comment, > status) + FTUKLS(unit,keyword,keyval,comment, > status) + FTUKY[EDFG](unit,keyword,keyval,decimals,comment, > status) +\end{verbatim} + +\begin{description} +\item[3 ] Update the value of an existing keyword to be undefined, or null, + or insert a new undefined-value keyword if it doesn't already exist. + The value string of the keyword is left blank in this case. +\end{description} + +\begin{verbatim} + FTUKYU(unit,keyword,comment, > status) +\end{verbatim} + +\subsection{Delete Keyword Subroutines \label{FTDREC}} + + +\begin{description} +\item[1 ] Delete an existing keyword record. The space previously occupied by + the keyword is reclaimed by moving all the following header records up + one row in the header. The first routine deletes a keyword at a + specified position in the header (the first keyword is at position 1), + whereas the second routine deletes a specifically named keyword. + Wild card characters, as described in the Read Keyword section, above, + may be used when specifying the name of the keyword to be deleted + (be careful!). +\end{description} + +\begin{verbatim} + FTDREC(unit,key_no, > status) + FTDKEY(unit,keyword, > status) +\end{verbatim} + + +\section{Data Scaling and Undefined Pixel Parameters \label{FTPSCL}} + +These subroutines define or modify the internal parameters used by +FITSIO to either scale the data or to represent undefined pixels. +Generally FITSIO will scale the data according to the values of the BSCALE +and BZERO (or TSCALn and TZEROn) keywords, however these subroutines +may be used to override the keyword values. This may be useful when +one wants to read or write the raw unscaled values in the FITS file. +Similarly, FITSIO generally uses the value of the BLANK or TNULLn +keyword to signify an undefined pixel, but these routines may be used +to override this value. These subroutines do not create or modify the +corresponding header keyword values. + + +\begin{description} +\item[1 ] Reset the scaling factors in the primary array or image extension; does + not change the BSCALE and BZERO keyword values and only affects the + automatic scaling performed when the data elements are written/read + to/from the FITS file. When reading from a FITS file the returned + data value = (the value given in the FITS array) * BSCALE + BZERO. + The inverse formula is used when writing data values to the FITS + file. (NOTE: BSCALE and BZERO must be declared as Double Precision + variables). +\end{description} + +\begin{verbatim} + FTPSCL(unit,bscale,bzero, > status) +\end{verbatim} + +\begin{description} +\item[2 ] Reset the scaling parameters for a table column; does not change + the TSCALn or TZEROn keyword values and only affects the automatic + scaling performed when the data elements are written/read to/from + the FITS file. When reading from a FITS file the returned data + value = (the value given in the FITS array) * TSCAL + TZERO. The + inverse formula is used when writing data values to the FITS file. + (NOTE: TSCAL and TZERO must be declared as Double Precision + variables). +\end{description} + +\begin{verbatim} + FTTSCL(unit,colnum,tscal,tzero, > status) +\end{verbatim} + +\begin{description} +\item[3 ] Define the integer value to be used to signify undefined pixels in the + primary array or image extension. This is only used if BITPIX = 8, 16, + 32. or 64 This does not create or change the value of the BLANK keyword in + the header. FTPNULLL is identical to FTPNUL except that the blank + value is a 64-bit integer instead of a 32-bit integer. +\end{description} + +\begin{verbatim} + FTPNUL(unit,blank, > status) + FTPNULLL(unit,blankll, > status) +\end{verbatim} + +\begin{description} +\item[4 ] Define the string to be used to signify undefined pixels in + a column in an ASCII table. This does not create or change the value + of the TNULLn keyword. +\end{description} + +\begin{verbatim} + FTSNUL(unit,colnum,snull > status) +\end{verbatim} + +\begin{description} +\item[5 ] Define the value to be used to signify undefined pixels in + an integer column in a binary table (where TFORMn = 'B', 'I', 'J', or 'K'). + This does not create or change the value of the TNULLn keyword. + FTTNULLL is identical to FTTNUL except that the tnull + value is a 64-bit integer instead of a 32-bit integer. +\end{description} + +\begin{verbatim} + FTTNUL(unit,colnum,tnull > status) + FTTNULLL(unit,colnum,tnullll > status) +\end{verbatim} + + +\section{FITS Primary Array or IMAGE Extension I/O Subroutines \label{FTPPR}} + + These subroutines put or get data values in the primary data array +(i.e., the first HDU in the FITS file) or an IMAGE extension. The +data array is represented as a single one-dimensional array of +pixels regardless of the actual dimensionality of the array, and the +FPIXEL parameter gives the position within this 1-D array of the first +pixel to read or write. Automatic data type conversion is performed +for numeric data (except for complex data types) if the data type of +the primary array (defined by the BITPIX keyword) differs from the data +type of the array in the calling subroutine. The data values are also +scaled by the BSCALE and BZERO header values as they are being written +or read from the FITS array. The ftpscl subroutine MUST be +called to define the scaling parameters when writing data to the FITS +array or to override the default scaling value given in the header when +reading the FITS array. + + Two sets of subroutines are provided to read the data array which +differ in the way undefined pixels are handled. The first set of +routines (FTGPVx) simply return an array of data elements in which +undefined pixels are set equal to a value specified by the user in the +'nullval' parameter. An additional feature of these subroutines is +that if the user sets nullval = 0, then no checks for undefined pixels +will be performed, thus increasing the speed of the program. The +second set of routines (FTGPFx) returns the data element array and, in +addition, a logical array which defines whether the corresponding data +pixel is undefined. The latter set of subroutines may be more +convenient to use in some circumstances, however, it requires an +additional array of logical values which can be unwieldy when working +with large data arrays. Also for programmer convenience, sets of +subroutines to directly read or write 2 and 3 dimensional arrays have +been provided, as well as a set of subroutines to read or write any +contiguous rectangular subset of pixels within the n-dimensional array. + + +\begin{description} +\item[1 ] Get the data type of the image (= BITPIX value). Possible returned + values are: 8, 16, 32, 64, -32, or -64 corresponding to unsigned byte, + signed 2-byte integer, signed 4-byte integer, signed 8-byte integer, + real, and double. + + The second subroutine is similar to FTGIDT, except that if the image + pixel values are scaled, with non-default values for the BZERO and + BSCALE keywords, then this routine will return the 'equivalent' + data type that is needed to store the scaled values. For example, + if BITPIX = 16 and BSCALE = 0.1 then the equivalent data type is + floating point, and -32 will be returned. There are 2 special cases: + if the image contains unsigned 2-byte integer values, with BITPIX = + 16, BSCALE = 1, and BZERO = 32768, then this routine will return + a non-standard value of 20 for the bitpix value. Similarly if the + image contains unsigned 4-byte integers, then bitpix will + be returned with a value of 40. +\end{description} + + +\begin{verbatim} + FTGIDT(unit, > bitpix,status) + FTGIET(unit, > bitpix,status) +\end{verbatim} + +\begin{description} +\item[2 ] Get the dimension (number of axes = NAXIS) of the image +\end{description} + +\begin{verbatim} + FTGIDM(unit, > naxis,status) +\end{verbatim} + +\begin{description} +\item[3 ] Get the size of all the dimensions of the image. The FTGISZLL + routine returns an array of 64-bit integers instead of 32-bit integers. +\end{description} + +\begin{verbatim} + FTGISZ(unit, maxdim, > naxes,status) + FTGISZLL(unit, maxdim, > naxesll,status) +\end{verbatim} + +\begin{description} +\item[4 ] Get the parameters that define the type and size of the image. This + routine simply combines calls to the above 3 routines. The FTGIPRLL + routine returns an array of 64-bit integers instead of 32-bit integers. +\end{description} + + +\begin{verbatim} + FTGIPR(unit, maxdim, > bitpix, naxis, naxes, int *status) + FTGIPRLL(unit, maxdim, > bitpix, naxis, naxesll, int *status) +\end{verbatim} + +\begin{description} +\item[5 ]Put elements into the data array +\end{description} + +\begin{verbatim} + FTPPR[BIJKED](unit,group,fpixel,nelements,values, > status) +\end{verbatim} + +\begin{description} +\item[6 ]Put elements into the data array, substituting the appropriate FITS null + value for all elements which are equal to the value of NULLVAL. For + integer FITS arrays, the null value defined by the previous call to FTPNUL + will be substituted; for floating point FITS arrays (BITPIX = -32 + or -64) then the special IEEE NaN (Not-a-Number) value will be + substituted. +\end{description} + +\begin{verbatim} + FTPPN[BIJKED](unit,group,fpixel,nelements,values,nullval > status) +\end{verbatim} + +\begin{description} +\item[7 ]Set data array elements as undefined +\end{description} + +\begin{verbatim} + FTPPRU(unit,group,fpixel,nelements, > status) +\end{verbatim} + +\begin{description} +\item[8 ] Get elements from the data array. Undefined array elements will be + returned with a value = nullval, unless nullval = 0 in which case no + checks for undefined pixels will be performed. +\end{description} + +\begin{verbatim} + FTGPV[BIJKED](unit,group,fpixel,nelements,nullval, > values,anyf,status) +\end{verbatim} + +\begin{description} +\item[9 ] Get elements and nullflags from data array. + Any undefined array elements will have the corresponding flagvals element + set equal to .TRUE. +\end{description} + +\begin{verbatim} + FTGPF[BIJKED](unit,group,fpixel,nelements, > values,flagvals,anyf,status) +\end{verbatim} + +\begin{description} +\item[10] Put values into group parameters +\end{description} + +\begin{verbatim} + FTPGP[BIJKED](unit,group,fparm,nparm,values, > status) +\end{verbatim} + +\begin{description} +\item[11] Get values from group parameters +\end{description} + +\begin{verbatim} + FTGGP[BIJKED](unit,group,fparm,nparm, > values,status) +\end{verbatim} +The following 4 subroutines transfer FITS images with 2 or 3 dimensions +to or from a data array which has been declared in the calling program. +The dimensionality of the FITS image is passed by the naxis1, naxis2, +and naxis3 parameters and the declared dimensions of the program array +are passed in the dim1 and dim2 parameters. Note that the program array +does not have to have the same dimensions as the FITS array, but must +be at least as big. For example if a FITS image with NAXIS1 = NAXIS2 = 400 +is read into a program array which is dimensioned as 512 x 512 pixels, +then the image will just fill the lower left corner of the array +with pixels in the range 1 - 400 in the X an Y directions. This has +the effect of taking a contiguous set of pixel value in the FITS array +and writing them to a non-contiguous array in program memory +(i.e., there are now some blank pixels around the edge of the image +in the program array). + + +\begin{description} +\item[11] Put 2-D image into the data array +\end{description} + +\begin{verbatim} + FTP2D[BIJKED](unit,group,dim1,naxis1,naxis2,image, > status) +\end{verbatim} + +\begin{description} +\item[12] Put 3-D cube into the data array +\end{description} + +\begin{verbatim} + FTP3D[BIJKED](unit,group,dim1,dim2,naxis1,naxis2,naxis3,cube, > status) +\end{verbatim} + +\begin{description} +\item[13] Get 2-D image from the data array. Undefined + pixels in the array will be set equal to the value of 'nullval', + unless nullval=0 in which case no testing for undefined pixels will + be performed. +\end{description} + +\begin{verbatim} + FTG2D[BIJKED](unit,group,nullval,dim1,naxis1,naxis2, > image,anyf,status) +\end{verbatim} + +\begin{description} +\item[14] Get 3-D cube from the data array. Undefined + pixels in the array will be set equal to the value of 'nullval', + unless nullval=0 in which case no testing for undefined pixels will + be performed. +\end{description} + +\begin{verbatim} + FTG3D[BIJKED](unit,group,nullval,dim1,dim2,naxis1,naxis2,naxis3, > + cube,anyf,status) +\end{verbatim} + +The following subroutines transfer a rectangular subset of the pixels +in a FITS N-dimensional image to or from an array which has been +declared in the calling program. The fpixels and lpixels parameters +are integer arrays which specify the starting and ending pixels in each +dimension of the FITS image that are to be read or written. (Note that +these are the starting and ending pixels in the FITS image, not in the +declared array). The array parameter is treated simply as a large +one-dimensional array of the appropriate datatype containing the pixel +values; The pixel values in the FITS array are read/written from/to +this program array in strict sequence without any gaps; it is up to +the calling routine to correctly interpret the dimensionality of this +array. The two families of FITS reading routines (FTGSVx and FTGSFx +subroutines) also have an 'incs' parameter which defines the +data sampling interval in each dimension of the FITS array. For +example, if incs(1)=2 and incs(2)=3 when reading a 2-dimensional +FITS image, then only every other pixel in the first dimension +and every 3rd pixel in the second dimension will be returned in +the 'array' parameter. [Note: the FTGSSx family of routines which +were present in previous versions of FITSIO have been superseded +by the more general FTGSVx family of routines.] + + +\begin{description} +\item[15] Put an arbitrary data subsection into the data array. +\end{description} + +\begin{verbatim} + FTPSS[BIJKED](unit,group,naxis,naxes,fpixels,lpixels,array, > status) +\end{verbatim} + +\begin{description} +\item[16] Get an arbitrary data subsection from the data array. Undefined + pixels in the array will be set equal to the value of 'nullval', + unless nullval=0 in which case no testing for undefined pixels will + be performed. +\end{description} + +\begin{verbatim} + FTGSV[BIJKED](unit,group,naxis,naxes,fpixels,lpixels,incs,nullval, > + array,anyf,status) +\end{verbatim} + +\begin{description} +\item[17] Get an arbitrary data subsection from the data array. Any Undefined + pixels in the array will have the corresponding 'flagvals' + element set equal to .TRUE. +\end{description} + +\begin{verbatim} + FTGSF[BIJKED](unit,group,naxis,naxes,fpixels,lpixels,incs, > + array,flagvals,anyf,status) +\end{verbatim} + + +\section{FITS ASCII and Binary Table Data I/O Subroutines} + + +\subsection{Column Information Subroutines \label{FTGCNO}} + + +\begin{description} +\item[1 ] Get the number of rows or columns in the current FITS table. + The number of rows is given by the NAXIS2 keyword and the + number of columns is given by the TFIELDS keyword in the header + of the table. The FTGNRWLL routine is identical to FTGNRW except + that the number of rows is returned as a 64-bit integer rather + than a 32-bit integer. +\end{description} + +\begin{verbatim} + FTGNRW(unit, > nrows, status) + FTGNRWLL(unit, > nrowsll, status) + FTGNCL(unit, > ncols, status) +\end{verbatim} + +\begin{description} +\item[2 ] Get the table column number (and name) of the column whose name +matches an input template name. The table column names are defined by +the TTYPEn keywords in the FITS header. If a column does not have a +TTYPEn keyword, then these routines assume that the name consists of +all blank characters. These 2 subroutines perform the same function +except that FTGCNO only returns the number of the matching column whereas +FTGCNN also returns the name of the column. If CASESEN = .true. then +the column name match will be case-sensitive. + +The input column name template (COLTEMPLATE) is (1) either the exact +name of the column to be searched for, or (2) it may contain wild cards +characters (*, ?, or \#), or (3) it may contain the number of the desired +column (where the number is expressed as ASCII digits). The first 2 wild +cards behave similarly to UNIX filename matching: the '*' character matches +any sequence of characters (including zero characters) and the '?' +character matches any single character. The \# wildcard will match +any consecutive string of decimal digits (0-9). As an example, the template +strings 'AB?DE', 'AB*E', and 'AB*CDE' will all match the string +'ABCDE'. If more than one column name in the table matches the +template string, then the first match is returned and the status value +will be set to 237 as a warning that a unique match was not found. To +find the other cases that match the template, simply call the +subroutine again leaving the input status value equal to 237 and the +next matching name will then be returned. Repeat this process until a +status = 219 (column name not found) is returned. If these subroutines +fail to match the template to any of the columns in the table, they +lastly check if the template can be interpreted as a simple positive +integer (e.g., '7', or '512') and if so, they return that column +number. If no matches are found then a status = 219 error is +returned. + +Note that the FITS Standard recommends that only letters, digits, and +the underscore character be used in column names (with no embedded +spaces in the name). Trailing blank characters are not significant. +\end{description} + +\begin{verbatim} + FTGCNO(unit,casesen,coltemplate, > colnum,status) + FTGCNN(unit,casesen,coltemplate, > colname,colnum,status) +\end{verbatim} + +\begin{description} +\item[3 ] Get the datatype of a column in an ASCII or binary table. This + routine returns an integer code value corresponding to the datatype + of the column. (See the FTBNFM and FTASFM subroutines in the Utilities + section of this document for a list of the code values). The vector + repeat count (which is alway 1 for ASCII table columns) is also returned. + If the specified column has an ASCII character datatype (code = 16) then + the width of a unit string in the column is also returned. Note that + this routine supports the local convention for specifying arrays of + strings within a binary table character column, using the syntax + TFORM = 'rAw' where 'r' is the total number of characters (= the width + of the column) and 'w' is the width of a unit string within the column. + Thus if the column has TFORM = '60A12' then this routine will return + datacode = 16, repeat = 60, and width = 12. (The TDIMn + keyword may also be used to specify the unit string length; The pair + of keywords TFORMn = '60A' and TDIMn = '(12,5)' would have the + same effect as TFORMn = '60A12'). + + The second routine, FTEQTY is similar except that in + the case of scaled integer columns it returns the 'equivalent' data + type that is needed to store the scaled values, and not necessarily + the physical data type of the unscaled values as stored in the FITS + table. For example if a '1I' column in a binary table has TSCALn = + 1 and TZEROn = 32768, then this column effectively contains unsigned + short integer values, and thus the returned value of typecode will + be the code for an unsigned short integer, not a signed short integer. + Similarly, if a column has TTYPEn = '1I' + and TSCALn = 0.12, then the returned typecode + will be the code for a 'real' column. +\end{description} + +\begin{verbatim} + FTGTCL(unit,colnum, > datacode,repeat,width,status) + FTEQTY(unit,colnum, > datacode,repeat,width,status) +\end{verbatim} + +\begin{description} +\item[4 ] Return the display width of a column. This is the length + of the string that will be returned + when reading the column as a formatted string. The display width is + determined by the TDISPn keyword, if present, otherwise by the data + type of the column. +\end{description} + +\begin{verbatim} + FTGCDW(unit, colnum, > dispwidth, status) +\end{verbatim} + +\begin{description} +\item[5 ] Get information about an existing ASCII table column. (NOTE: TSCAL and + TZERO must be declared as Double Precision variables). All the + returned parameters are scalar quantities. +\end{description} + +\begin{verbatim} + FTGACL(unit,colnum, > + ttype,tbcol,tunit,tform,tscal,tzero,snull,tdisp,status) +\end{verbatim} + +\begin{description} +\item[6 ] Get information about an existing binary table column. (NOTE: TSCAL and + TZERO must be declared as Double Precision variables). DATATYPE is a + character string which returns the datatype of the column as defined + by the TFORMn keyword (e.g., 'I', 'J','E', 'D', etc.). In the case + of an ASCII character column, DATATYPE will have a value of the + form 'An' where 'n' is an integer expressing the width of the field + in characters. For example, if TFORM = '160A8' then FTGBCL will return + DATATYPE='A8' and REPEAT=20. All the returned parameters are scalar + quantities. +\end{description} + +\begin{verbatim} + FTGBCL(unit,colnum, > + ttype,tunit,datatype,repeat,tscal,tzero,tnull,tdisp,status) +\end{verbatim} + +\begin{description} +\item[7 ] Put (append) a TDIMn keyword whose value has the form '(l,m,n...)' + where l, m, n... are the dimensions of a multidimensional array + column in a binary table. +\end{description} + +\begin{verbatim} + FTPTDM(unit,colnum,naxis,naxes, > status) +\end{verbatim} + +\begin{description} +\item[8 ] Return the number of and size of the dimensions of a table column. + Normally this information is given by the TDIMn keyword, but if + this keyword is not present then this routine returns NAXIS = 1 + and NAXES(1) equal to the repeat count in the TFORM keyword. +\end{description} + +\begin{verbatim} + FTGTDM(unit,colnum,maxdim, > naxis,naxes,status) +\end{verbatim} + +\begin{description} +\item[9 ] Decode the input TDIMn keyword string (e.g. '(100,200)') and return the + number of and size of the dimensions of a binary table column. If the input + tdimstr character string is null, then this routine returns naxis = 1 + and naxes[0] equal to the repeat count in the TFORM keyword. This routine + is called by FTGTDM. +\end{description} + +\begin{verbatim} + FTDTDM(unit,tdimstr,colnum,maxdim, > naxis,naxes, status) +\end{verbatim} + +\begin{description} +\item[10] Return the optimal number of rows to read or write at one time for + maximum I/O efficiency. Refer to the ``Optimizing Code'' section + in Chapter 5 for more discussion on how to use this routine. +\end{description} + + +\begin{verbatim} + FFGRSZ(unit, > nrows,status) +\end{verbatim} + + +\subsection{Low-Level Table Access Subroutines \label{FTGTBS}} + +The following subroutines provide low-level access to the data in ASCII +or binary tables and are mainly useful as an efficient way to copy all +or part of a table from one location to another. These routines simply +read or write the specified number of consecutive bytes in an ASCII or +binary table, without regard for column boundaries or the row length in +the table. The first two subroutines read or write consecutive bytes +in a table to or from a character string variable, while the last two +subroutines read or write consecutive bytes to or from a variable +declared as a numeric data type (e.g., INTEGER, INTEGER*2, REAL, DOUBLE +PRECISION). These routines do not perform any machine dependent data +conversion or byte swapping, except that conversion to/from ASCII +format is performed by the FTGTBS and FTPTBS routines on machines which +do not use ASCII character codes in the internal data representations +(e.g., on IBM mainframe computers). + + +\begin{description} +\item[1 ] Read a consecutive string of characters from an ASCII table + into a character variable (spanning columns and multiple rows if necessary) + This routine should not be used with binary tables because of + complications related to passing string variables between C and Fortran. +\end{description} + +\begin{verbatim} + FTGTBS(unit,frow,startchar,nchars, > string,status) +\end{verbatim} + +\begin{description} +\item[2 ] Write a consecutive string of characters to an ASCII table + from a character variable (spanning columns and multiple rows if necessary) + This routine should not be used with binary tables because of + complications related to passing string variables between C and Fortran. +\end{description} + +\begin{verbatim} + FTPTBS(unit,frow,startchar,nchars,string, > status) +\end{verbatim} + +\begin{description} +\item[3 ] Read a consecutive array of bytes from an ASCII or binary table + into a numeric variable (spanning columns and multiple rows if necessary). + The array parameter may be declared as any numerical datatype as long + as the array is at least 'nchars' bytes long, e.g., if nchars = 17, + then declare the array as INTEGER*4 ARRAY(5). +\end{description} + +\begin{verbatim} + FTGTBB(unit,frow,startchar,nchars, > array,status) +\end{verbatim} + +\begin{description} +\item[4 ] Write a consecutive array of bytes to an ASCII or binary table + from a numeric variable (spanning columns and multiple rows if necessary) + The array parameter may be declared as any numerical datatype as long + as the array is at least 'nchars' bytes long, e.g., if nchars = 17, + then declare the array as INTEGER*4 ARRAY(5). +\end{description} + +\begin{verbatim} + FTPTBB(unit,frow,startchar,nchars,array, > status) +\end{verbatim} + + +\subsection{Edit Rows or Columns \label{FTIROW}} + + +\begin{description} +\item[1 ] Insert blank rows into an existing ASCII or binary table (in the CDU). + All the rows FOLLOWING row FROW are shifted down by NROWS rows. If + FROW or FROWLL equals 0 then the blank rows are inserted at the beginning of the + table. These routines modify the NAXIS2 keyword to reflect the new + number of rows in the table. Note that it is *not* necessary to insert rows in a table before + writing data to those rows (indeed, it would be inefficient to do so). + Instead, one may simply write data to any row of the table, whether that + row of data already exists or not. +\end{description} + +\begin{verbatim} + FTIROW(unit,frow,nrows, > status) + FTIROWLL(unit,frowll,nrowsll, > status) +\end{verbatim} + +\begin{description} +\item[2 ] Delete rows from an existing ASCII or binary table (in the CDU). + The NROWS (or NROWSLL) is the number of rows are deleted, starting + with row FROW (or FROWLL), and + any remaining rows in the table are shifted up to fill in the space. + These routines modify the NAXIS2 keyword to reflect the new number + of rows in the table. +\end{description} + +\begin{verbatim} + FTDROW(unit,frow,nrows, > status) + FTDROWLL(unit,frowll,nrowsll, > status) +\end{verbatim} + +\begin{description} +\item[3 ] Delete a list of rows from an ASCII or binary table (in the CDU). + In the first routine, 'rowrange' is a character string listing the + rows or row ranges to delete (e.g., '2-4, 5, 8-9'). In the second + routine, 'rowlist' is an integer array of row numbers to be deleted + from the table. nrows is the number of row numbers in the list. + The first row in the table is 1 not 0. The list of row numbers + must be sorted in ascending order. +\end{description} + +\begin{verbatim} + FTDRRG(unit,rowrange, > status) + FTDRWS(unit,rowlist,nrows, > status) +\end{verbatim} + +\begin{description} +\item[4 ] Insert a blank column (or columns) into an existing ASCII or binary + table (in the CDU). COLNUM specifies the column number that the (first) + new column should occupy in the table. NCOLS specifies how many + columns are to be inserted. Any existing columns from this position and + higher are moved over to allow room for the new column(s). + The index number on all the following keywords will be incremented + if necessary to reflect the new position of the column(s) in the table: + TBCOLn, TFORMn, TTYPEn, TUNITn, TNULLn, TSCALn, TZEROn, TDISPn, TDIMn, + TLMINn, TLMAXn, TDMINn, TDMAXn, TCTYPn, TCRPXn, TCRVLn, TCDLTn, TCROTn, + and TCUNIn. +\end{description} + +\begin{verbatim} + FTICOL(unit,colnum,ttype,tform, > status) + FTICLS(unit,colnum,ncols,ttype,tform, > status) +\end{verbatim} + +\begin{description} +\item[5 ] Modify the vector length of a binary table column (e.g., + change a column from TFORMn = '1E' to '20E'). The vector + length may be increased or decreased from the current value. +\end{description} + +\begin{verbatim} + FTMVEC(unit,colnum,newveclen, > status) +\end{verbatim} + +\begin{description} +\item[6 ] Delete a column from an existing ASCII or binary table (in the CDU). + The index number of all the keywords listed above (for FTICOL) will be + decremented if necessary to reflect the new position of the column(s) in + the table. Those index keywords that refer to the deleted column will + also be deleted. Note that the physical size of the FITS file will + not be reduced by this operation, and the empty FITS blocks if any + at the end of the file will be padded with zeros. +\end{description} + +\begin{verbatim} + FTDCOL(unit,colnum, > status) +\end{verbatim} + +\begin{description} +\item[7 ] Copy a column from one HDU to another (or to the same HDU). If + createcol = TRUE, then a new column will be inserted in the output + table, at position `outcolumn', otherwise the existing output column will + be overwritten (in which case it must have a compatible datatype). + Note that the first column in a table is at colnum = 1. +\end{description} + +\begin{verbatim} + FTCPCL(inunit,outunit,incolnum,outcolnum,createcol, > status); +\end{verbatim} + +\subsection{Read and Write Column Data Routines \label{FTPCLS}} + +These subroutines put or get data values in the current ASCII or Binary table +extension. Automatic data type conversion is performed for numerical data +types (B,I,J,E,D) if the data type of the column (defined by the TFORM keyword) +differs from the data type of the calling subroutine. The data values are also +scaled by the TSCALn and TZEROn header values as they are being written to +or read from the FITS array. The fttscl subroutine MUST be used to define the +scaling parameters when writing data to the table or to override the default +scaling values given in the header when reading from the table. +Note that it is *not* necessary to insert rows in a table before +writing data to those rows (indeed, it would be inefficient to do so). +Instead, one may simply write data to any row of the table, whether that +row of data already exists or not. + + In the case of binary tables with vector elements, the 'felem' +parameter defines the starting pixel within the element vector. This +parameter is ignored with ASCII tables. Similarly, in the case of +binary tables the 'nelements' parameter specifies the total number of +vector values read or written (continuing on subsequent rows if +required) and not the number of table elements. Two sets of +subroutines are provided to get the column data which differ in the way +undefined pixels are handled. The first set of routines (FTGCV) +simply return an array of data elements in which undefined pixels are +set equal to a value specified by the user in the 'nullval' parameter. +An additional feature of these subroutines is that if the user sets +nullval = 0, then no checks for undefined pixels will be performed, +thus increasing the speed of the program. The second set of routines +(FTGCF) returns the data element array and in addition a logical array +of flags which defines whether the corresponding data pixel is undefined. + + Any column, regardless of it's intrinsic datatype, may be read as a + string. It should be noted however that reading a numeric column + as a string is 10 - 100 times slower than reading the same column as + a number due to the large overhead in constructing the formatted + strings. The display format of the returned strings will be + determined by the TDISPn keyword, if it exists, otherwise by the + datatype of the column. The length of the returned strings can be + determined with the ftgcdw routine. The following TDISPn display + formats are currently supported: + +\begin{verbatim} + Iw.m Integer + Ow.m Octal integer + Zw.m Hexadecimal integer + Fw.d Fixed floating point + Ew.d Exponential floating point + Dw.d Exponential floating point + Gw.d General; uses Fw.d if significance not lost, else Ew.d +\end{verbatim} + where w is the width in characters of the displayed values, m is the minimum + number of digits displayed, and d is the number of digits to the right of the + decimal. The .m field is optional. + + +\begin{description} +\item[1 ] Put elements into an ASCII or binary table column (in the CDU). + (The SPP FSPCLS routine has an additional integer argument after + the VALUES character string which specifies the size of the 1st + dimension of this 2-D CHAR array). + + The alternate version of these routines, whose names end in 'LL' + after the datatype character, support large tables with more then + 2*31 rows. When calling these routines, the frow and felem parameters + *must* be 64-bit integer*8 variables, instead of normal 4-byte integers. +\end{description} + +\begin{verbatim} + FTPCL[SLBIJKEDCM](unit,colnum,frow,felem,nelements,values, > status) + FTPCL[LBIJKEDCM]LL(unit,colnum,frow,felem,nelements,values, > status) +\end{verbatim} + +\begin{description} +\item[2 ] Put elements into an ASCII or binary table column (in the CDU) + substituting the appropriate FITS null value for any elements that + are equal to NULLVAL. For ASCII TABLE extensions, the + null value defined by the previous call to FTSNUL will be substituted; + For integer FITS columns, in a binary table the null value + defined by the previous call to FTTNUL will be substituted; + For floating point FITS columns a special IEEE NaN (Not-a-Number) + value will be substituted. + + The alternate version of these routines, whose names end in 'LL' + after the datatype character, support large tables with more then + 2*31 rows. When calling these routines, the frow and felem parameters + *must* be 64-bit integer*8 variables, instead of normal 4-byte integers. +\end{description} + +\begin{verbatim} + FTPCN[SBIJKED](unit,colnum,frow,felem,nelements,values,nullval > status) + FTPCN[SBIJKED]LL(unit,colnum,(I*8) frow,(I*8) felem,nelements,values, + nullval > status) +\end{verbatim} + +\begin{description} +\item[3 ] Put bit values into a binary byte ('B') or bit ('X') table column (in the + CDU). LRAY is an array of logical values corresponding to the sequence of + bits to be written. If LRAY is true then the corresponding bit is + set to 1, otherwise the bit is set to 0. Note that in the case of + 'X' columns, FITSIO will write to all 8 bits of each byte whether + they are formally valid or not. Thus if the column is defined as + '4X', and one calls FTPCLX with fbit=1 and nbit=8, then all 8 bits + will be written into the first byte (as opposed to writing the + first 4 bits into the first row and then the next 4 bits into the + next row), even though the last 4 bits of each byte are formally + not defined. +\end{description} + +\begin{verbatim} + FTPCLX(unit,colnum,frow,fbit,nbit,lray, > status) +\end{verbatim} + +\begin{description} +\item[4 ] Set table elements in a column as undefined +\end{description} + +\begin{verbatim} + FTPCLU(unit,colnum,frow,felem,nelements, > status) +\end{verbatim} + +\begin{description} +\item[5 ] Get elements from an ASCII or binary table column (in the CDU). These + routines return the values of the table column array elements. Undefined + array elements will be returned with a value = nullval, unless nullval = 0 + (or = ' ' for ftgcvs) in which case no checking for undefined values will + be performed. The ANYF parameter is set to true if any of the returned + elements are undefined. (Note: the ftgcl routine simple gets an array + of logical data values without any checks for undefined values; use + the ftgcfl routine to check for undefined logical elements). + (The SPP FSGCVS routine has an additional integer argument after + the VALUES character string which specifies the size of the 1st + dimension of this 2-D CHAR array). + + The alternate version of these routines, whose names end in 'LL' + after the datatype character, support large tables with more then + 2*31 rows. When calling these routines, the frow and felem parameters + *must* be 64-bit integer*8 variables, instead of normal 4-byte integers. +\end{description} + +\begin{verbatim} + FTGCL(unit,colnum,frow,felem,nelements, > values,status) + FTGCV[SBIJKEDCM](unit,colnum,frow,felem,nelements,nullval, > + values,anyf,status) + FTGCV[BIJKEDCM]LL(unit,colnum,(I*8) frow, (I*8) felem, nelements, + nullval, > values,anyf,status) +\end{verbatim} + +\begin{description} +\item[6 ] Get elements and null flags from an ASCII or binary table column (in the + CHDU). These routines return the values of the table column array elements. + Any undefined array elements will have the corresponding flagvals element + set equal to .TRUE. The ANYF parameter is set to true if any of the + returned elements are undefined. + (The SPP FSGCFS routine has an additional integer argument after + the VALUES character string which specifies the size of the 1st + dimension of this 2-D CHAR array). + + The alternate version of these routines, whose names end in 'LL' + after the datatype character, support large tables with more then + 2*31 rows. When calling these routines, the frow and felem parameters + *must* be 64-bit integer*8 variables, instead of normal 4-byte integers. +\end{description} + +\begin{verbatim} + FTGCF[SLBIJKEDCM](unit,colnum,frow,felem,nelements, > + values,flagvals,anyf,status) + FTGCF[BIJKED]LL(unit,colnum, (I*8) frow, (I*8) felem,nelements, > + values,flagvals,anyf,status) +\end{verbatim} + +\begin{description} +\item[7 ] Get an arbitrary data subsection from an N-dimensional array + in a binary table vector column. Undefined pixels + in the array will be set equal to the value of 'nullval', + unless nullval=0 in which case no testing for undefined pixels will + be performed. The first and last rows in the table to be read + are specified by fpixels(naxis+1) and lpixels(naxis+1), and hence + are treated as the next higher dimension of the FITS N-dimensional + array. The INCS parameter specifies the sampling interval in + each dimension between the data elements that will be returned. +\end{description} + +\begin{verbatim} + FTGSV[BIJKED](unit,colnum,naxis,naxes,fpixels,lpixels,incs,nullval, > + array,anyf,status) +\end{verbatim} + +\begin{description} +\item[8 ] Get an arbitrary data subsection from an N-dimensional array + in a binary table vector column. Any Undefined + pixels in the array will have the corresponding 'flagvals' + element set equal to .TRUE. The first and last rows in the table + to be read are specified by fpixels(naxis+1) and lpixels(naxis+1), + and hence are treated as the next higher dimension of the FITS + N-dimensional array. The INCS parameter specifies the sampling + interval in each dimension between the data elements that will be + returned. +\end{description} + +\begin{verbatim} + FTGSF[BIJKED](unit,colnum,naxis,naxes,fpixels,lpixels,incs, > + array,flagvals,anyf,status) +\end{verbatim} + +\begin{description} +\item[9 ] Get bit values from a byte ('B') or bit (`X`) table column (in the + CDU). LRAY is an array of logical values corresponding to the + sequence of bits to be read. If LRAY is true then the + corresponding bit was set to 1, otherwise the bit was set to 0. + Note that in the case of 'X' columns, FITSIO will read all 8 bits + of each byte whether they are formally valid or not. Thus if the + column is defined as '4X', and one calls FTGCX with fbit=1 and + nbit=8, then all 8 bits will be read from the first byte (as + opposed to reading the first 4 bits from the first row and then the + first 4 bits from the next row), even though the last 4 bits of + each byte are formally not defined. +\end{description} + +\begin{verbatim} + FTGCX(unit,colnum,frow,fbit,nbit, > lray,status) +\end{verbatim} + +\begin{description} +\item[10] Read any consecutive set of bits from an 'X' or 'B' column and + interpret them as an unsigned n-bit integer. NBIT must be less than + or equal to 16 when calling FTGCXI, and less than or equal to 32 when + calling FTGCXJ; there is no limit on the value of NBIT for FTGCXD, but + the returned double precision value only has 48 bits of precision on + most 32-bit word machines. The NBITS bits are interpreted as an + unsigned integer unless NBITS = 16 (in FTGCXI) or 32 (in FTGCXJ) in which + case the string of bits are interpreted as 16-bit or 32-bit 2's + complement signed integers. If NROWS is greater than 1 then the + same set of bits will be read from sequential rows in the table + starting with row FROW. Note that the numbering convention + used here for the FBIT parameter adopts 1 for the first element of the + vector of bits; this is the Most Significant Bit of the integer value. +\end{description} + +\begin{verbatim} + FTGCX[IJD](unit,colnum,frow,nrows,fbit,nbit, > array,status) +\end{verbatim} + +\begin{description} +\item[11] Get the descriptor for a variable length column in a binary table. + The descriptor consists of 2 integer parameters: the number of elements + in the array and the starting offset relative to the start of the heap. + The first routine returns a single descriptor whereas the second routine + returns the descriptors for a range of rows in the table. +\end{description} + +\begin{verbatim} + FTGDES(unit,colnum,rownum, > nelements,offset,status) + FTGDESLL(unit,colnum,rownum, > nelementsll,offsetll,status) + + FFGDESS(unit,colnum,firstrow,nrows > nelements,offset, status) + FFGDESSLL(unit,colnum,firstrow,nrows > nelementsll,offsetll, status) +\end{verbatim} + +\begin{description} +\item[12] Write the descriptor for a variable length column in a binary table. + These subroutines can be used in conjunction with FTGDES to enable + 2 or more arrays to point to the same storage location to save + storage space if the arrays are identical. +\end{description} + +\begin{verbatim} + FTPDES(unit,colnum,rownum,nelements,offset, > status) + FTPDESLL(unit,colnum,rownum,nelementsll,offsetll, > status) +\end{verbatim} + + +\section{Row Selection and Calculator Routines \label{FTFROW}} + +These routines all parse and evaluate an input string containing a user +defined arithmetic expression. The first 3 routines select rows in a +FITS table, based on whether the expression evaluates to true (not +equal to zero) or false (zero). The other routines evaluate the +expression and calculate a value for each row of the table. The +allowed expression syntax is described in the row filter section in the +earlier `Extended File Name Syntax' chapter of this document. The +expression may also be written to a text file, and the name of the +file, prepended with a '@' character may be supplied for the 'expr' +parameter (e.g. '@filename.txt'). The expression in the file can +be arbitrarily complex and extend over multiple lines of the file. +Lines that begin with 2 slash characters ('//') will be ignored and +may be used to add comments to the file. + + +\begin{description} +\item[1 ] Evaluate a boolean expression over the indicated rows, returning an + array of flags indicating which rows evaluated to TRUE/FALSE +\end{description} + +\begin{verbatim} + FTFROW(unit,expr,firstrow, nrows, > n_good_rows, row_status, status) +\end{verbatim} + +\begin{description} +\item[2 ] Find the first row which satisfies the input boolean expression +\end{description} + +\begin{verbatim} + FTFFRW(unit, expr, > rownum, status) +\end{verbatim} + +\begin{description} +\item[3 ]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 output +table is not empty, then this routine will append the new +selected rows after the existing rows. If the +files are the same, delete the FALSE rows (preserve the TRUE rows). +\end{description} + +\begin{verbatim} + FTSROW(inunit, outunit, expr, > status) +\end{verbatim} + +\begin{description} +\item[4 ] 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 FTTEXP to obtain +the dimensions of the results. +\end{description} + +\begin{verbatim} + FTCROW(unit,datatype,expr,firstrow,nelements,nulval, > + array,anynul,status) +\end{verbatim} + +\begin{description} +\item[5 ]Evaluate an expression and write the result either to a column (if +the expression is a function of other columns in the table) or to a +keyword (if the expression evaluates to a constant and is not a +function of other columns in the table). In the former case, the +parName parameter is the name of the column (which may or may not already +exist) into which to write the results, and parInfo contains an +optional TFORM keyword value if a new column is being created. If a +TFORM value is not specified then a default format will be used, +depending on the expression. If the expression evaluates to a constant, +then the result will be written to the keyword name given by the +parName parameter, and the parInfo parameter may be used to supply an +optional comment for the keyword. If the keyword does not already +exist, then the name of the keyword must be preceded with a '\#' character, +otherwise the result will be written to a column with that name. +\end{description} + + +\begin{verbatim} + FTCALC(inunit, expr, outunit, parName, parInfo, > status) +\end{verbatim} + +\begin{description} +\item[6 ] This calculator routine is similar to the previous routine, except +that the expression is only evaluated over the specified +row ranges. nranges specifies the number of row ranges, and firstrow +and lastrow give the starting and ending row number of each range. +\end{description} + +\begin{verbatim} + FTCALC_RNG(inunit, expr, outunit, parName, parInfo, + nranges, firstrow, lastrow, > status) +\end{verbatim} + +\begin{description} +\item[7 ]Evaluate the given expression and return dimension and type information +on the result. The returned dimensions correspond to a single row entry +of the requested expression, and are equivalent to the result of fits\_read\_tdim(). +Note that strings are considered to be one element regardless of string length. +If maxdim == 0, then naxes is optional. +\end{description} + +\begin{verbatim} + FTTEXP(unit, expr, maxdim > datatype, nelem, naxis, naxes, status) +\end{verbatim} + + + +\section{Celestial Coordinate System Subroutines \label{FTGICS}} + +The FITS community has adopted a set of keyword conventions that define +the transformations needed to convert between pixel locations in an +image and the corresponding celestial coordinates on the sky, or more +generally, that define world coordinates that are to be associated with +any pixel location in an n-dimensional FITS array. CFITSIO is distributed +with a couple of self-contained World Coordinate System (WCS) routines, +however, these routines DO NOT support all the latest WCS conventions, +so it is STRONGLY RECOMMENDED that software developers use a more robust +external WCS library. Several recommended libraries are: + +\begin{verbatim} + WCSLIB - supported by Mark Calabretta + WCSTools - supported by Doug Mink + AST library - developed by the U.K. Starlink project +\end{verbatim} + +More information about the WCS keyword conventions and links to all of +these WCS libraries can be found on the FITS Support Office web site at +http://fits.gsfc.nasa.gov under the WCS link. + +The functions provided in these external WCS libraries will need access to +the WCS information contained in the FITS file headers. One convenient +way to pass this information to the external library is to use FITSIO +to copy the header keywords into one long character string, and then +pass this string to an interface routine in the external library that +will extract the necessary WCS information (e.g., see the astFitsChan +and astPutCards routines in the Starlink AST library). + +The following FITSIO routines DO NOT support the more recent WCS conventions +that have been approved as part of the FITS standard. Consequently, +the following routines ARE NOW DEPRECATED. It is STRONGLY RECOMMENDED +that software developers not use these routines, and instead use an +external WCS library, as described above. + +These routines are included mainly for backward compatibility with +existing software. They support the following standard map +projections: -SIN, -TAN, -ARC, -NCP, -GLS, -MER, and -AIT (these are the +legal values for the coordtype parameter). These routines are based +on similar functions in Classic AIPS. All the angular quantities are +given in units of degrees. + + +\begin{description} +\item[1 ] Get the values of all the standard FITS celestial coordinate system + keywords from the header of a FITS image (i.e., the primary array or + an image extension). These values may then be passed to the subroutines + that perform the coordinate transformations. If any or all of the WCS + keywords are not present, then default values will be returned. If + the first coordinate axis is the declination-like coordinate, then + this routine will swap them so that the longitudinal-like coordinate + is returned as the first axis. + + If the file uses the newer 'CDj\_i' WCS transformation matrix + keywords instead of old style 'CDELTn' and 'CROTA2' keywords, then + this routine will calculate and return the values of the equivalent + old-style keywords. Note that the conversion from the new-style + keywords to the old-style values is sometimes only an + approximation, so if the approximation is larger than an internally + defined threshold level, then CFITSIO will still return the + approximate WCS keyword values, but will also return with status = + 506, to warn the calling program that approximations have been + made. It is then up to the calling program to decide whether the + approximations are sufficiently accurate for the particular + application, or whether more precise WCS transformations must be + performed using new-style WCS keywords directly. +\end{description} + +\begin{verbatim} + FTGICS(unit, > xrval,yrval,xrpix,yrpix,xinc,yinc,rot,coordtype,status) +\end{verbatim} + +\begin{description} +\item[2 ] Get the values of all the standard FITS celestial coordinate system + keywords from the header of a FITS table where the X and Y (or RA and + DEC coordinates are stored in 2 separate columns of the table. + These values may then be passed to the subroutines that perform the + coordinate transformations. +\end{description} + +\begin{verbatim} + FTGTCS(unit,xcol,ycol, > + xrval,yrval,xrpix,yrpix,xinc,yinc,rot,coordtype,status) +\end{verbatim} + +\begin{description} +\item[3 ] Calculate the celestial coordinate corresponding to the input + X and Y pixel location in the image. +\end{description} + +\begin{verbatim} + FTWLDP(xpix,ypix,xrval,yrval,xrpix,yrpix,xinc,yinc,rot, + coordtype, > xpos,ypos,status) +\end{verbatim} + +\begin{description} +\item[4 ] Calculate the X and Y pixel location corresponding to the input + celestial coordinate in the image. +\end{description} + +\begin{verbatim} + FTXYPX(xpos,ypos,xrval,yrval,xrpix,yrpix,xinc,yinc,rot, + coordtype, > xpix,ypix,status) +\end{verbatim} + + +\section{File Checksum Subroutines \label{FTPCKS}} + +The following routines either compute or validate the checksums for the +CHDU. The DATASUM keyword is used to store the numerical value of the +32-bit, 1's complement checksum for the data unit alone. If there is +no data unit then the value is set to zero. The numerical value is +stored as an ASCII string of digits, enclosed in quotes, because the +value may be too large to represent as a 32-bit signed integer. The +CHECKSUM keyword is used to store the ASCII encoded COMPLEMENT of the +checksum for the entire HDU. Storing the complement, rather than the +actual checksum, forces the checksum for the whole HDU to equal zero. +If the file has been modified since the checksums were computed, then +the HDU checksum will usually not equal zero. These checksum keyword +conventions are based on a paper by Rob Seaman published in the +proceedings of the ADASS IV conference in Baltimore in November 1994 +and a later revision in June 1995. + + +\begin{description} +\item[1 ] Compute and write the DATASUM and CHECKSUM keyword values for the CHDU + into the current header. The DATASUM value is the 32-bit checksum + for the data unit, expressed as a decimal integer enclosed in single + quotes. The CHECKSUM keyword value is a 16-character string which + is the ASCII-encoded value for the complement of the checksum for + the whole HDU. If these keywords already exist, their values + will be updated only if necessary (i.e., if the file has been modified + since the original keyword values were computed). +\end{description} + +\begin{verbatim} + FTPCKS(unit, > status) +\end{verbatim} + +\begin{description} +\item[2 ] Update the CHECKSUM keyword value in the CHDU, assuming that the + DATASUM keyword exists and already has the correct value. This routine + calculates the new checksum for the current header unit, adds it to the + data unit checksum, encodes the value into an ASCII string, and writes + the string to the CHECKSUM keyword. +\end{description} + +\begin{verbatim} + FTUCKS(unit, > status) +\end{verbatim} + +\begin{description} +\item[3 ] Verify the CHDU by computing the checksums and comparing + them with the keywords. The data unit is verified correctly + if the computed checksum equals the value of the DATASUM + keyword. The checksum for the entire HDU (header plus data unit) is + correct if it equals zero. The output DATAOK and HDUOK parameters + in this subroutine are integers which will have a value = 1 + if the data or HDU is verified correctly, a value = 0 + if the DATASUM or CHECKSUM keyword is not present, or value = -1 + if the computed checksum is not correct. +\end{description} + +\begin{verbatim} + FTVCKS(unit, > dataok,hduok,status) +\end{verbatim} + +\begin{description} +\item[4 ] Compute and return the checksum values for the CHDU (as + double precision variables) without creating or modifying the + CHECKSUM and DATASUM keywords. This routine is used internally by + FTVCKS, but may be useful in other situations as well. +\end{description} + +\begin{verbatim} + FTGCKS(unit, > datasum,hdusum,status) +\end{verbatim} + +\begin{description} +\item[5 ] Encode a checksum value (stored in a double precision variable) + into a 16-character string. If COMPLEMENT = .true. then the 32-bit + sum value will be complemented before encoding. +\end{description} + +\begin{verbatim} + FTESUM(sum,complement, > checksum) +\end{verbatim} + +\begin{description} +\item[6 ] Decode a 16 character checksum string into a double precision value. + If COMPLEMENT = .true. then the 32-bit sum value will be complemented + after decoding. +\end{description} + +\begin{verbatim} + FTDSUM(checksum,complement, > sum) +\end{verbatim} + + +\section{ Date and Time Utility Routines \label{FTGSDT}} + +The following routines help to construct or parse the FITS date/time +strings. Starting in the year 2000, the FITS DATE keyword values (and +the values of other `DATE-' keywords) must have the form 'YYYY-MM-DD' +(date only) or 'YYYY-MM-DDThh:mm:ss.ddd...' (date and time) where the +number of decimal places in the seconds value is optional. These times +are in UTC. The older 'dd/mm/yy' date format may not be used for dates +after 01 January 2000. + + +\begin{description} +\item[1 ] Get the current system date. The returned year has 4 digits + (1999, 2000, etc.) +\end{description} + +\begin{verbatim} + FTGSDT( > day, month, year, status ) +\end{verbatim} + + +\begin{description} +\item[2 ] Get the current system date and time string ('YYYY-MM-DDThh:mm:ss'). +The time will be in UTC/GMT if available, as indicated by a returned timeref +value = 0. If the returned value of timeref = 1 then this indicates that +it was not possible to convert the local time to UTC, and thus the local +time was returned. +\end{description} + +\begin{verbatim} + FTGSTM(> datestr, timeref, status) +\end{verbatim} + + +\begin{description} +\item[3 ] Construct a date string from the input date values. If the year +is between 1900 and 1998, inclusive, then the returned date string will +have the old FITS format ('dd/mm/yy'), otherwise the date string will +have the new FITS format ('YYYY-MM-DD'). Use FTTM2S instead + to always return a date string using the new FITS format. +\end{description} + +\begin{verbatim} + FTDT2S( year, month, day, > datestr, status) +\end{verbatim} + + +\begin{description} +\item[4 ] Construct a new-format date + time string ('YYYY-MM-DDThh:mm:ss.ddd...'). + If the year, month, and day values all = 0 then only the time is encoded + with format 'hh:mm:ss.ddd...'. The decimals parameter specifies how many + decimal places of fractional seconds to include in the string. If `decimals' + is negative, then only the date will be return ('YYYY-MM-DD'). +\end{description} + +\begin{verbatim} + FTTM2S( year, month, day, hour, minute, second, decimals, + > datestr, status) +\end{verbatim} + + +\begin{description} +\item[5 ] Return the date as read from the input string, where the string may be +in either the old ('dd/mm/yy') or new ('YYYY-MM-DDThh:mm:ss' or +'YYYY-MM-DD') FITS format. +\end{description} + +\begin{verbatim} + FTS2DT(datestr, > year, month, day, status) +\end{verbatim} + + +\begin{description} +\item[6 ] Return the date and time as read from the input string, where the +string may be in either the old or new FITS format. The returned hours, +minutes, and seconds values will be set to zero if the input string +does not include the time ('dd/mm/yy' or 'YYYY-MM-DD') . Similarly, +the returned year, month, and date values will be set to zero if the +date is not included in the input string ('hh:mm:ss.ddd...'). +\end{description} + +\begin{verbatim} + FTS2TM(datestr, > year, month, day, hour, minute, second, status) +\end{verbatim} + + +\section{General Utility Subroutines \label{FTGHAD}} + +The following utility subroutines may be useful for certain applications: + + +\begin{description} +\item[1 ] Return the starting byte address of the CHDU and the next HDU. +\end{description} + +\begin{verbatim} + FTGHAD(iunit, > curaddr, nextaddr) +\end{verbatim} + +\begin{description} +\item[2 ] Convert a character string to uppercase (operates in place). +\end{description} + +\begin{verbatim} + FTUPCH(string) +\end{verbatim} + +\begin{description} +\item[3 ] Compare the input template string against the reference string + to see if they match. The template string may contain wildcard + characters: '*' will match any sequence of characters (including + zero characters) and '?' will match any single character in the + reference string. The '\#' character will match any consecutive string + of decimal digits (0 - 9). If CASESN = .true. then the match will be + case sensitive. The returned MATCH parameter will be .true. if + the 2 strings match, and EXACT will be .true. if the match is + exact (i.e., if no wildcard characters were used in the match). + Both strings must be 68 characters or less in length. +\end{description} + +\begin{verbatim} + FTCMPS(str_template, string, casesen, > match, exact) +\end{verbatim} + + +\begin{description} +\item[4 ] Test that the keyword name contains only legal characters: A-Z,0-9, + hyphen, and underscore. +\end{description} + +\begin{verbatim} + FTTKEY(keyword, > status) +\end{verbatim} + +\begin{description} +\item[5 ] Test that the keyword record contains only legal printable ASCII + characters +\end{description} + +\begin{verbatim} + FTTREC(card, > status) +\end{verbatim} + +\begin{description} +\item[6 ] Test whether the current header contains any NULL (ASCII 0) characters. + These characters are illegal in the header, but they will go undetected + by most of the CFITSIO keyword header routines, because the null is + interpreted as the normal end-of-string terminator. This routine returns + the position of the first null character in the header, or zero if there + are no nulls. For example a returned value of 110 would indicate that + the first NULL is located in the 30th character of the second keyword + in the header (recall that each header record is 80 characters long). + Note that this is one of the few FITSIO routines in which the returned + value is not necessarily equal to the status value). +\end{description} + +\begin{verbatim} + FTNCHK(unit, > status) +\end{verbatim} + +\begin{description} +\item[7 ] Parse a header keyword record and return the name of the keyword + and the length of the name. + The keyword name normally occupies the first 8 characters of the + record, except under the HIERARCH convention where the name can + be up to 70 characters in length. +\end{description} + +\begin{verbatim} + FTGKNM(card, > keyname, keylength, staThe '\#' character will match any consecutive string + of decimal digits (0 - 9). tus) +\end{verbatim} + +\begin{description} +\item[8 ] Parse a header keyword record. + This subroutine parses the input header record to return the value (as + a character string) and comment strings. If the keyword has no + value (columns 9-10 not equal to '= '), then the value string is returned + blank and the comment string is set equal to column 9 - 80 of the + input string. +\end{description} + +\begin{verbatim} + FTPSVC(card, > value,comment,status) +\end{verbatim} + +\begin{description} +\item[9 ] Construct a sequence keyword name (ROOT + nnn). + This subroutine appends the sequence number to the root string to create + a keyword name (e.g., 'NAXIS' + 2 = 'NAXIS2') +\end{description} + +\begin{verbatim} + FTKEYN(keyroot,seq_no, > keyword,status) +\end{verbatim} + +\begin{description} +\item[10] Construct a sequence keyword name (n + ROOT). + This subroutine concatenates the sequence number to the front of the + root string to create a keyword name (e.g., 1 + 'CTYP' = '1CTYP') +\end{description} + +\begin{verbatim} + FTNKEY(seq_no,keyroot, > keyword,status) +\end{verbatim} + +\begin{description} +\item[11] Determine the datatype of a keyword value string. + This subroutine parses the keyword value string (usually columns 11-30 + of the header record) to determine its datatype. +\end{description} + +\begin{verbatim} + FTDTYP(value, > dtype,status) +\end{verbatim} + +\begin{description} +\item[11] Return the class of input header record. The record is classified + into one of the following categories (the class values are + defined in fitsio.h). Note that this is one of the few FITSIO + routines that does not return a status value. +\end{description} + +\begin{verbatim} + Class Value Keywords + TYP_STRUC_KEY 10 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 20 The experimental keywords used in the compressed + image format ZIMAGE, ZCMPTYPE, ZNAMEn, ZVALn, + ZTILEn, ZBITPIX, ZNAXISn, ZSCALE, ZZERO, ZBLANK + TYP_SCAL_KEY 30 BSCALE, BZERO, TSCALn, TZEROn + TYP_NULL_KEY 40 BLANK, TNULLn + TYP_DIM_KEY 50 TDIMn + TYP_RANG_KEY 60 TLMINn, TLMAXn, TDMINn, TDMAXn, DATAMIN, DATAMAX + TYP_UNIT_KEY 70 BUNIT, TUNITn + TYP_DISP_KEY 80 TDISPn + TYP_HDUID_KEY 90 EXTNAME, EXTVER, EXTLEVEL, HDUNAME, HDUVER, HDULEVEL + TYP_CKSUM_KEY 100 CHECKSUM, DATASUM + TYP_WCS_KEY 110 CTYPEn, CUNITn, CRVALn, CRPIXn, CROTAn, CDELTn + CDj_is, PVj_ms, LONPOLEs, LATPOLEs + TCTYPn, TCTYns, TCUNIn, TCUNns, TCRVLn, TCRVns, TCRPXn, + TCRPks, TCDn_k, TCn_ks, TPVn_m, TPn_ms, TCDLTn, TCROTn + jCTYPn, jCTYns, jCUNIn, jCUNns, jCRVLn, jCRVns, iCRPXn, + iCRPns, jiCDn, jiCDns, jPVn_m, jPn_ms, jCDLTn, jCROTn + (i,j,m,n are integers, s is any letter) + TYP_REFSYS_KEY 120 EQUINOXs, EPOCH, MJD-OBSs, RADECSYS, RADESYSs + TYP_COMM_KEY 130 COMMENT, HISTORY, (blank keyword) + TYP_CONT_KEY 140 CONTINUE + TYP_USER_KEY 150 all other keywords + + class = FTGKCL (char *card) +\end{verbatim} + +\begin{description} +\item[12] Parse the 'TFORM' binary table column format string. + This subroutine parses the input TFORM character string and returns the + integer datatype code, the repeat count of the field, and, in the case + of character string fields, the length of the unit string. The following + datatype codes are returned (the negative of the value is returned + if the column contains variable-length arrays): +\end{description} + +\begin{verbatim} + Datatype DATACODE value + bit, X 1 + byte, B 11 + logical, L 14 + ASCII character, A 16 + short integer, I 21 + integer, J 41 + real, E 42 + double precision, D 82 + complex 83 + double complex 163 + + FTBNFM(tform, > datacode,repeat,width,status) +\end{verbatim} + +\begin{description} +\item[13] Parse the 'TFORM' keyword value that defines the column format in + an ASCII table. This routine parses the input TFORM character + string and returns the datatype code, the width of the column, + and (if it is a floating point column) the number of decimal places + to the right of the decimal point. The returned datatype codes are + the same as for the binary table, listed above, with the following + additional rules: integer columns that are between 1 and 4 characters + wide are defined to be short integers (code = 21). Wider integer + columns are defined to be regular integers (code = 41). Similarly, + Fixed decimal point columns (with TFORM = 'Fw.d') are defined to + be single precision reals (code = 42) if w is between 1 and 7 characters + wide, inclusive. Wider 'F' columns will return a double precision + data code (= 82). 'Ew.d' format columns will have datacode = 42, + and 'Dw.d' format columns will have datacode = 82. +\end{description} + +\begin{verbatim} + FTASFM(tform, > datacode,width,decimals,status) +\end{verbatim} + +\begin{description} +\item[14] Calculate the starting column positions and total ASCII table width + based on the input array of ASCII table TFORM values. The SPACE input + parameter defines how many blank spaces to leave between each column + (it is recommended to have one space between columns for better human + readability). +\end{description} + +\begin{verbatim} + FTGABC(tfields,tform,space, > rowlen,tbcol,status) +\end{verbatim} + +\begin{description} +\item[15] Parse a template string and return a formatted 80-character string + suitable for appending to (or deleting from) a FITS header file. + This subroutine is useful for parsing lines from an ASCII template file + and reformatting them into legal FITS header records. The formatted + string may then be passed to the FTPREC, FTMCRD, or FTDKEY subroutines + to append or modify a FITS header record. +\end{description} + +\begin{verbatim} + FTGTHD(template, > card,hdtype,status) +\end{verbatim} + The input TEMPLATE character string generally should contain 3 tokens: + (1) the KEYNAME, (2) the VALUE, and (3) the COMMENT string. The + TEMPLATE string must adhere to the following format: + + +\begin{description} +\item[- ] The KEYNAME token must begin in columns 1-8 and be a maximum of 8 + characters long. If the first 8 characters of the template line are + blank then the remainder of the line is considered to be a FITS comment + (with a blank keyword name). A legal FITS keyword name may only + contain the characters A-Z, 0-9, and '-' (minus sign) and + underscore. This subroutine will automatically convert any lowercase + characters to uppercase in the output string. If KEYNAME = 'COMMENT' + or 'HISTORY' then the remainder of the line is considered to be a FITS + COMMENT or HISTORY record, respectively. +\end{description} + + +\begin{description} +\item[- ] The VALUE token must be separated from the KEYNAME token by one or more + spaces and/or an '=' character. The datatype of the VALUE token + (numeric, logical, or character string) is automatically determined + and the output CARD string is formatted accordingly. The value + token may be forced to be interpreted as a string (e.g. if it is a + string of numeric digits) by enclosing it in single quotes. + If the value token is a character string that contains 1 or more + embedded blank space characters or slash ('/') characters then the + entire character string must be enclosed in single quotes. +\end{description} + + +\begin{description} +\item[- ] The COMMENT token is optional, but if present must be separated from + the VALUE token by a blank space or a '/' character. +\end{description} + + +\begin{description} +\item[- ] One exception to the above rules is that if the first non-blank + character in the template string is a minus sign ('-') followed + by a single token, or a single token followed by an equal sign, + then it is interpreted as the name of a keyword which is to be + deleted from the FITS header. +\end{description} + + +\begin{description} +\item[- ] The second exception is that if the template string starts with + a minus sign and is followed by 2 tokens then the second token + is interpreted as the new name for the keyword specified by + first token. In this case the old keyword name (first token) + is returned in characters 1-8 of the returned CARD string, and + the new keyword name (the second token) is returned in characters + 41-48 of the returned CARD string. These old and new names + may then be passed to the FTMNAM subroutine which will change + the keyword name. +\end{description} + + The HDTYPE output parameter indicates how the returned CARD string + should be interpreted: + +\begin{verbatim} + hdtype interpretation + ------ ------------------------------------------------- + -2 Modify the name of the keyword given in CARD(1:8) + to the new name given in CARD(41:48) + + -1 CARD(1:8) contains the name of a keyword to be deleted + from the FITS header. + + 0 append the CARD string to the FITS header if the + keyword does not already exist, otherwise update + the value/comment if the keyword is already present + in the header. + + 1 simply append this keyword to the FITS header (CARD + is either a HISTORY or COMMENT keyword). + + 2 This is a FITS END record; it should not be written + to the FITS header because FITSIO automatically + appends the END record when the header is closed. +\end{verbatim} + EXAMPLES: The following lines illustrate valid input template strings: + +\begin{verbatim} + INTVAL 7 This is an integer keyword + RVAL 34.6 / This is a floating point keyword + EVAL=-12.45E-03 This is a floating point keyword in exponential notation + lval F This is a boolean keyword + This is a comment keyword with a blank keyword name + SVAL1 = 'Hello world' / this is a string keyword + SVAL2 '123.5' this is also a string keyword + sval3 123+ / this is also a string keyword with the value '123+ ' + # the following template line deletes the DATE keyword + - DATE + # the following template line modifies the NAME keyword to OBJECT + - NAME OBJECT +\end{verbatim} + +\begin{description} +\item[16] Parse the input string containing a list of rows or row ranges, and + return integer arrays containing the first and last row in each + range. For example, if rowlist = "3-5, 6, 8-9" then it will + return numranges = 3, rangemin = 3, 6, 8 and rangemax = 5, 6, 9. + At most, 'maxranges' number of ranges will be returned. 'maxrows' + is the maximum number of rows in the table; any rows or ranges + larger than this will be ignored. The rows must be specified in + increasing order, and the ranges must not overlap. A minus sign + may be use to specify all the rows to the upper or lower bound, so + "50-" means all the rows from 50 to the end of the table, and "-" + means all the rows in the table, from 1 - maxrows. +\end{description} + +\begin{verbatim} + FTRWRG(rowlist, maxrows, maxranges, > + numranges, rangemin, rangemax, status) +\end{verbatim} + + + +\chapter{ The CFITSIO Iterator Function } + +The fits\_iterate\_data function in CFITSIO provides a unique method of +executing an arbitrary user-supplied `work' function that operates on +rows of data in FITS tables or on pixels in FITS images. Rather than +explicitly reading and writing the FITS images or columns of data, one +instead calls the CFITSIO iterator routine, passing to it the name of +the user's work function that is to be executed along with a list of +all the table columns or image arrays that are to be passed to the work +function. The CFITSIO iterator function then does all the work of +allocating memory for the arrays, reading the input data from the FITS +file, passing them to the work function, and then writing any output +data back to the FITS file after the work function exits. Because +it is often more efficient to process only a subset of the total table +rows at one time, the iterator function can determine the optimum +amount of data to pass in each iteration and repeatedly call the work +function until the entire table been processed. + +For many applications this single CFITSIO iterator function can +effectively replace all the other CFITSIO routines for reading or +writing data in FITS images or tables. Using the iterator has several +important advantages over the traditional method of reading and writing +FITS data files: + +\begin{itemize} +\item +It cleanly separates the data I/O from the routine that operates on +the data. This leads to a more modular and `object oriented' +programming style. + +\item +It simplifies the application program by eliminating the need to allocate +memory for the data arrays and eliminates most of the calls to the CFITSIO +routines that explicitly read and write the data. + +\item +It ensures that the data are processed as efficiently as possible. +This is especially important when processing tabular data since +the iterator function will calculate the most efficient number +of rows in the table to be passed at one time to the user's work +function on each iteration. + +\item +Makes it possible for larger projects to develop a library of work +functions that all have a uniform calling sequence and are all +independent of the details of the FITS file format. + +\end{itemize} + +There are basically 2 steps in using the CFITSIO iterator function. +The first step is to design the work function itself which must have a +prescribed set of input parameters. One of these parameters is a +structure containing pointers to the arrays of data; the work function +can perform any desired operations on these arrays and does not need to +worry about how the input data were read from the file or how the +output data get written back to the file. + +The second step is to design the driver routine that opens all the +necessary FITS files and initializes the input parameters to the +iterator function. The driver program calls the CFITSIO iterator +function which then reads the data and passes it to the user's work +function. + +Further details on using the iterator function can be found in the +companion CFITSIO User's Guide, and in the iter\_a.f, iter\_b.f and +iter\_c.f example programs. + + + +\chapter{ Extended File Name Syntax } + + +\section{Overview} + +CFITSIO supports an extended syntax when specifying the name of the +data file to be opened or created that includes the following +features: + +\begin{itemize} +\item +CFITSIO can read IRAF format images which have header file names that +end with the '.imh' extension, as well as reading and writing FITS +files, This feature is implemented in CFITSIO by first converting the +IRAF image into a temporary FITS format file in memory, then opening +the FITS file. Any of the usual CFITSIO routines then may be used to +read the image header or data. Similarly, raw binary data arrays can +be read by converting them on the fly into virtual FITS images. + +\item +FITS files on the Internet can be read (and sometimes written) using the FTP, +HTTP, or ROOT protocols. + +\item +FITS files can be piped between tasks on the stdin and stdout streams. + +\item +FITS files can be read and written in shared memory. This can potentially +achieve much better data I/O performance compared to reading and +writing the same FITS files on magnetic disk. + +\item +Compressed FITS files in gzip or Unix COMPRESS format can be directly read. + +\item +Output FITS files can be written directly in compressed gzip format, +thus saving disk space. + +\item +FITS table columns can be created, modified, or deleted 'on-the-fly' as +the table is opened by CFITSIO. This creates a virtual FITS file containing +the modifications that is then opened by the application program. + +\item +Table rows may be selected, or filtered out, on the fly when the table +is opened by CFITSIO, based on an arbitrary user-specified expression. +Only rows for which the expression evaluates to 'TRUE' are retained +in the copy of the table that is opened by the application program. + +\item +Histogram images may be created on the fly by binning the values in +table columns, resulting in a virtual N-dimensional FITS image. The +application program then only sees the FITS image (in the primary +array) instead of the original FITS table. +\end{itemize} + +The latter 3 features in particular add very powerful data processing +capabilities directly into CFITSIO, and hence into every task that uses +CFITSIO to read or write FITS files. For example, these features +transform a very simple program that just copies an input FITS file to +a new output file (like the `fitscopy' program that is distributed with +CFITSIO) into a multipurpose FITS file processing tool. By appending +fairly simple qualifiers onto the name of the input FITS file, the user +can perform quite complex table editing operations (e.g., create new +columns, or filter out rows in a table) or create FITS images by +binning or histogramming the values in table columns. In addition, +these functions have been coded using new state-of-the art algorithms +that are, in some cases, 10 - 100 times faster than previous widely +used implementations. + +Before describing the complete syntax for the extended FITS file names +in the next section, here are a few examples of FITS file names that +give a quick overview of the allowed syntax: + +\begin{itemize} +\item +{\tt 'myfile.fits'}: the simplest case of a FITS file on disk in the current +directory. + +\item +{\tt 'myfile.imh'}: opens an IRAF format image file and converts it on the +fly into a temporary FITS format image in memory which can then be read with +any other CFITSIO routine. + +\item +{\tt rawfile.dat[i512,512]}: opens a raw binary data array (a 512 x 512 +short integer array in this case) and converts it on the fly into a +temporary FITS format image in memory which can then be read with any +other CFITSIO routine. + +\item +{\tt myfile.fits.gz}: if this is the name of a new output file, the '.gz' +suffix will cause it to be compressed in gzip format when it is written to +disk. + +\item +{\tt 'myfile.fits.gz[events, 2]'}: opens and uncompresses the gzipped file +myfile.fits then moves to the extension which has the keywords EXTNAME += 'EVENTS' and EXTVER = 2. + +\item +{\tt '-'}: a dash (minus sign) signifies that the input file is to be read +from the stdin file stream, or that the output file is to be written to +the stdout stream. + +\item +{\tt 'ftp://legacy.gsfc.nasa.gov/test/vela.fits'}: FITS files in any ftp +archive site on the Internet may be directly opened with read-only +access. + +\item +{\tt 'http://legacy.gsfc.nasa.gov/software/test.fits'}: any valid URL to a +FITS file on the Web may be opened with read-only access. + +\item +{\tt 'root://legacy.gsfc.nasa.gov/test/vela.fits'}: similar to ftp access +except that it provides write as well as read access to the files +across the network. This uses the root protocol developed at CERN. + +\item +{\tt 'shmem://h2[events]'}: opens the FITS file in a shared memory segment and +moves to the EVENTS extension. + +\item +{\tt 'mem://'}: creates a scratch output file in core computer memory. The +resulting 'file' will disappear when the program exits, so this +is mainly useful for testing purposes when one does not want a +permanent copy of the output file. + +\item +{\tt 'myfile.fits[3; Images(10)]'}: opens a copy of the image contained in the +10th row of the 'Images' column in the binary table in the 3th extension +of the FITS file. The application just sees this single image as the +primary array. + +\item +{\tt 'myfile.fits[1:512:2, 1:512:2]'}: opens a section of the input image +ranging from the 1st to the 512th pixel in X and Y, and selects every +second pixel in both dimensions, resulting in a 256 x 256 pixel image +in this case. + +\item +{\tt 'myfile.fits[EVENTS][col Rad = sqrt(X**2 + Y**2)]'}: creates and opens +a temporary file on the fly (in memory or on disk) that is identical to +myfile.fits except that it will contain a new column in the EVENTS +extension called 'Rad' whose value is computed using the indicated +expression which is a function of the values in the X and Y columns. + +\item +{\tt 'myfile.fits[EVENTS][PHA > 5]'}: creates and opens a temporary FITS +files that is identical to 'myfile.fits' except that the EVENTS table +will only contain the rows that have values of the PHA column greater +than 5. In general, any arbitrary boolean expression using a C or +Fortran-like syntax, which may combine AND and OR operators, +may be used to select rows from a table. + +\item +{\tt 'myfile.fits[EVENTS][bin (X,Y)=1,2048,4]'}: creates a temporary FITS +primary array image which is computed on the fly by binning (i.e, +computing the 2-dimensional histogram) of the values in the X and Y +columns of the EVENTS extension. In this case the X and Y coordinates +range from 1 to 2048 and the image pixel size is 4 units in both +dimensions, so the resulting image is 512 x 512 pixels in size. + +\item +The final example combines many of these feature into one complex +expression (it is broken into several lines for clarity): + +\begin{verbatim} + 'ftp://legacy.gsfc.nasa.gov/data/sample.fits.gz[EVENTS] + [col phacorr = pha * 1.1 - 0.3][phacorr >= 5.0 && phacorr <= 14.0] + [bin (X,Y)=32]' +\end{verbatim} +In this case, CFITSIO (1) copies and uncompresses the FITS file from +the ftp site on the legacy machine, (2) moves to the 'EVENTS' +extension, (3) calculates a new column called 'phacorr', (4) selects +the rows in the table that have phacorr in the range 5 to 14, and +finally (5) bins the remaining rows on the X and Y column coordinates, +using a pixel size = 32 to create a 2D image. All this processing is +completely transparent to the application program, which simply sees +the final 2-D image in the primary array of the opened file. +\end{itemize} + +The full extended CFITSIO FITS file name can contain several different +components depending on the context. These components are described in +the following sections: + +\begin{verbatim} +When creating a new file: + filetype://BaseFilename(templateName) + +When opening an existing primary array or image HDU: + filetype://BaseFilename(outName)[HDUlocation][ImageSection] + +When opening an existing table HDU: + filetype://BaseFilename(outName)[HDUlocation][colFilter][rowFilter][binSpec] +\end{verbatim} +The filetype, BaseFilename, outName, HDUlocation, and ImageSection +components, if present, must be given in that order, but the colFilter, +rowFilter, and binSpec specifiers may follow in any order. Regardless +of the order, however, the colFilter specifier, if present, will be +processed first by CFITSIO, followed by the rowFilter specifier, and +finally by the binSpec specifier. + + +\section{Filetype} + +The type of file determines the medium on which the file is located +(e.g., disk or network) and, hence, which internal device driver is used by +CFITSIO to read and/or write the file. Currently supported types are + +\begin{verbatim} + file:// - file on local magnetic disk (default) + ftp:// - a readonly file accessed with the anonymous FTP protocol. + It also supports ftp://username:password@hostname/... + for accessing password-protected ftp sites. + http:// - a readonly file accessed with the HTTP protocol. It + supports username:password just like the ftp driver. + Proxy HTTP servers are supported using the http_proxy + environment variable (see following note). + stream:// - special driver to read an input FITS file from the stdin + stream, and/or write an output FITS file to the stdout + stream. This driver is fragile and has limited + functionality (see the following note). + gsiftp:// - access files on a computational grid using the gridftp + protocol in the Globus toolkit (see following note). + root:// - uses the CERN root protocol for writing as well as + reading files over the network. + shmem:// - opens or creates a file which persists in the computer's + shared memory. + mem:// - opens a temporary file in core memory. The file + disappears when the program exits so this is mainly + useful for test purposes when a permanent output file + is not desired. +\end{verbatim} +If the filetype is not specified, then type file:// is assumed. +The double slashes '//' are optional and may be omitted in most cases. + + +\subsection{Notes about HTTP proxy servers} + +A proxy HTTP server may be used by defining the address (URL) and port +number of the proxy server with the http\_proxy environment variable. +For example + +\begin{verbatim} + setenv http_proxy http://heasarc.gsfc.nasa.gov:3128 +\end{verbatim} +will cause CFITSIO to use port 3128 on the heasarc proxy server whenever +reading a FITS file with HTTP. + + +\subsection{Notes about the stream filetype driver} + +The stream driver can be used to efficiently read a FITS file from the stdin +file stream or write a FITS to the stdout file stream. However, because these +input and output streams must be accessed sequentially, the FITS file reading or +writing application must also read and write the file sequentially, at least +within the tolerances described below. + +CFITSIO supports 2 different methods for accessing FITS files on the stdin and +stdout streams. The original method, which is invoked by specifying a dash +character, "-", as the name of the file when opening or creating it, works by +storing a complete copy of the entire FITS file in memory. In this case, when +reading from stdin, CFITSIO will copy the entire stream into memory before doing +any processing of the file. Similarly, when writing to stdout, CFITSIO will +create a copy of the entire FITS file in memory, before finally flushing it out +to the stdout stream when the FITS file is closed. Buffering the entire FITS +file in this way allows the application to randomly access any part of the FITS +file, in any order, but it also requires that the user have sufficient available +memory (or virtual memory) to store the entire file, which may not be possible +in the case of very large files. + +The newer stream filetype provides a more memory-efficient method of accessing +FITS files on the stdin or stdout streams. Instead of storing a copy of the +entire FITS file in memory, CFITSIO only uses a set of internal buffer which by +default can store 40 FITS blocks, or about 100K bytes of the FITS file. The +application program must process the FITS file sequentially from beginning to +end, within this 100K buffer. Generally speaking the application program must +conform to the following restrictions: + +\begin{itemize} +\item +The program must finish reading or writing the header keywords +before reading or writing any data in the HDU. +\item +The HDU can contain at most about 1400 header keywords. This is the +maximum that can fit in the nominal 40 FITS block buffer. In principle, +this limit could be increased by recompiling CFITSIO with a larger +buffer limit, which is set by the NIOBUF parameter in fitsio2.h. +\item +The program must read or write the data in a sequential manner from the +beginning to the end of the HDU. Note that CFITSIO's internal +100K buffer allows a little latitude in meeting this requirement. +\item +The program cannot move back to a previous HDU in the FITS file. +\item +Reading or writing of variable length array columns in binary tables is not +supported on streams, because this requires moving back and forth between the +fixed-length portion of the binary table and the following heap area where the +arrays are actually stored. +\item +Reading or writing of tile-compressed images is not supported on streams, +because the images are internally stored using variable length arrays. +\end{itemize} + + +\subsection{Notes about the gsiftp filetype} + +DEPENDENCIES: Globus toolkit (2.4.3 or higher) (GT) should be installed. +There are two different ways to install GT: + +1) goto the globus toolkit web page www.globus.org and follow the + download and compilation instructions; + +2) goto the Virtual Data Toolkit web page http://vdt.cs.wisc.edu/ + and follow the instructions (STRONGLY SUGGESTED); + +Once a globus client has been installed in your system with a specific flavour +it is possible to compile and install the CFITSIO libraries. +Specific configuration flags must be used: + +1) --with-gsiftp[[=PATH]] Enable Globus Toolkit gsiftp protocol support + PATH=GLOBUS\_LOCATION i.e. the location of your globus installation + +2) --with-gsiftp-flavour[[=PATH] defines the specific Globus flavour + ex. gcc32 + +Both the flags must be used and it is mandatory to set both the PATH and the +flavour. + +USAGE: To access files on a gridftp server it is necessary to use a gsiftp prefix: + +example: gsiftp://remote\_server\_fqhn/directory/filename + +The gridftp driver uses a local buffer on a temporary file the file is located +in the /tmp directory. If you have special permissions on /tmp or you do not have a /tmp +directory, it is possible to force another location setting the GSIFTP\_TMPFILE environment +variable (ex. export GSIFTP\_TMPFILE=/your/location/yourtmpfile). + +Grid FTP supports multi channel transfer. By default a single channel transmission is +available. However, it is possible to modify this behavior setting the GSIFTP\_STREAMS +environment variable (ex. export GSIFTP\_STREAMS=8). + + +\subsection{Notes about the root filetype} + +The original rootd server can be obtained from: +\verb-ftp://root.cern.ch/root/rootd.tar.gz- +but, for it to work correctly with CFITSIO one has to use a modified +version which supports a command to return the length of the file. +This modified version is available in rootd subdirectory +in the CFITSIO ftp area at + +\begin{verbatim} + ftp://legacy.gsfc.nasa.gov/software/fitsio/c/root/rootd.tar.gz. +\end{verbatim} + +This small server is started either by inetd when a client requests a +connection to a rootd server or by hand (i.e. from the command line). +The rootd server works with the ROOT TNetFile class. It allows remote +access to ROOT database files in either read or write mode. By default +TNetFile assumes port 432 (which requires rootd to be started as root). +To run rootd via inetd add the following line to /etc/services: + +\begin{verbatim} + rootd 432/tcp +\end{verbatim} +and to /etc/inetd.conf, add the following line: + +\begin{verbatim} + rootd stream tcp nowait root /user/rdm/root/bin/rootd rootd -i +\end{verbatim} +Force inetd to reread its conf file with "kill -HUP ". +You can also start rootd by hand running directly under your private +account (no root system privileges needed). For example to start +rootd listening on port 5151 just type: \verb+rootd -p 5151+ +Notice: no \& is needed. Rootd will go into background by itself. + +\begin{verbatim} + Rootd arguments: + -i says we were started by inetd + -p port# specifies a different port to listen on + -d level level of debug info written to syslog + 0 = no debug (default) + 1 = minimum + 2 = medium + 3 = maximum +\end{verbatim} +Rootd can also be configured for anonymous usage (like anonymous ftp). +To setup rootd to accept anonymous logins do the following (while being +logged in as root): + +\begin{verbatim} + - Add the following line to /etc/passwd: + + rootd:*:71:72:Anonymous rootd:/var/spool/rootd:/bin/false + + where you may modify the uid, gid (71, 72) and the home directory + to suite your system. + + - Add the following line to /etc/group: + + rootd:*:72:rootd + + where the gid must match the gid in /etc/passwd. + + - Create the directories: + + mkdir /var/spool/rootd + mkdir /var/spool/rootd/tmp + chmod 777 /var/spool/rootd/tmp + + Where /var/spool/rootd must match the rootd home directory as + specified in the rootd /etc/passwd entry. + + - To make writeable directories for anonymous do, for example: + + mkdir /var/spool/rootd/pub + chown rootd:rootd /var/spool/rootd/pub +\end{verbatim} +That's all. Several additional remarks: you can login to an anonymous +server either with the names "anonymous" or "rootd". The password should +be of type user@host.do.main. Only the @ is enforced for the time +being. In anonymous mode the top of the file tree is set to the rootd +home directory, therefore only files below the home directory can be +accessed. Anonymous mode only works when the server is started via +inetd. + + +\subsection{Notes about the shmem filetype:} + +Shared memory files are currently supported on most Unix platforms, +where the shared memory segments are managed by the operating system +kernel and `live' independently of processes. They are not deleted (by +default) when the process which created them terminates, although they +will disappear if the system is rebooted. Applications can create +shared memory files in CFITSIO by calling: + +\begin{verbatim} + fit_create_file(&fitsfileptr, "shmem://h2", &status); +\end{verbatim} +where the root `file' names are currently restricted to be 'h0', 'h1', +'h2', 'h3', etc., up to a maximum number defined by the the value of +SHARED\_MAXSEG (equal to 16 by default). This is a prototype +implementation of the shared memory interface and a more robust +interface, which will have fewer restrictions on the number of files +and on their names, may be developed in the future. + +When opening an already existing FITS file in shared memory one calls +the usual CFITSIO routine: + +\begin{verbatim} + fits_open_file(&fitsfileptr, "shmem://h7", mode, &status) +\end{verbatim} +The file mode can be READWRITE or READONLY just as with disk files. +More than one process can operate on READONLY mode files at the same +time. CFITSIO supports proper file locking (both in READONLY and +READWRITE modes), so calls to fits\_open\_file may be locked out until +another other process closes the file. + +When an application is finished accessing a FITS file in a shared +memory segment, it may close it (and the file will remain in the +system) with fits\_close\_file, or delete it with fits\_delete\_file. +Physical deletion is postponed until the last process calls +ffclos/ffdelt. fits\_delete\_file tries to obtain a READWRITE lock on +the file to be deleted, thus it can be blocked if the object was not +opened in READWRITE mode. + +A shared memory management utility program called `smem', is included +with the CFITSIO distribution. It can be built by typing `make smem'; +then type `smem -h' to get a list of valid options. Executing smem +without any options causes it to list all the shared memory segments +currently residing in the system and managed by the shared memory +driver. To get a list of all the shared memory objects, run the system +utility program `ipcs [-a]'. + + +\section{Base Filename} + +The base filename is the name of the file optionally including the +director/subdirectory path, and in the case of `ftp', `http', and `root' +filetypes, the machine identifier. Examples: + +\begin{verbatim} + myfile.fits + !data.fits + /data/myfile.fits + fits.gsfc.nasa.gov/ftp/sampledata/myfile.fits.gz +\end{verbatim} + +When creating a new output file on magnetic disk (of type file://) if +the base filename begins with an exclamation point (!) then any +existing file with that same basename will be deleted prior to creating +the new FITS file. Otherwise if the file to be created already exists, +then CFITSIO will return an error and will not overwrite the existing +file. Note that the exclamation point, '!', is a special UNIX character, +so if it is used on the command line rather than entered at a task +prompt, it must be preceded by a backslash to force the UNIX +shell to pass it verbatim to the application program. + +If the output disk file name ends with the suffix '.gz', then CFITSIO +will compress the file using the gzip compression algorithm before +writing it to disk. This can reduce the amount of disk space used by +the file. Note that this feature requires that the uncompressed file +be constructed in memory before it is compressed and written to disk, +so it can fail if there is insufficient available memory. + +An input FITS file may be compressed with the gzip or Unix compress +algorithms, in which case CFITSIO will uncompress the file on the fly +into a temporary file (in memory or on disk). Compressed files may +only be opened with read-only permission. When specifying the name of +a compressed FITS file it is not necessary to append the file suffix +(e.g., `.gz' or `.Z'). If CFITSIO cannot find the input file name +without the suffix, then it will automatically search for a compressed +file with the same root name. In the case of reading ftp and http type +files, CFITSIO generally looks for a compressed version of the file +first, before trying to open the uncompressed file. By default, +CFITSIO copies (and uncompressed if necessary) the ftp or http FITS +file into memory on the local machine before opening it. This will +fail if the local machine does not have enough memory to hold the whole +FITS file, so in this case, the output filename specifier (see the next +section) can be used to further control how CFITSIO reads ftp and http +files. + +If the input file is an IRAF image file (*.imh file) then CFITSIO will +automatically convert it on the fly into a virtual FITS image before it +is opened by the application program. IRAF images can only be opened +with READONLY file access. + +Similarly, if the input file is a raw binary data array, then CFITSIO +will convert it on the fly into a virtual FITS image with the basic set +of required header keywords before it is opened by the application +program (with READONLY access). In this case the data type and +dimensions of the image must be specified in square brackets following +the filename (e.g. rawfile.dat[ib512,512]). The first character (case +insensitive) defines the datatype of the array: + +\begin{verbatim} + b 8-bit unsigned byte + i 16-bit signed integer + u 16-bit unsigned integer + j 32-bit signed integer + r or f 32-bit floating point + d 64-bit floating point +\end{verbatim} +An optional second character specifies the byte order of the array +values: b or B indicates big endian (as in FITS files and the native +format of SUN UNIX workstations and Mac PCs) and l or L indicates +little endian (native format of DEC OSF workstations and IBM PCs). If +this character is omitted then the array is assumed to have the native +byte order of the local machine. These datatype characters are then +followed by a series of one or more integer values separated by commas +which define the size of each dimension of the raw array. Arrays with +up to 5 dimensions are currently supported. Finally, a byte offset to +the position of the first pixel in the data file may be specified by +separating it with a ':' from the last dimension value. If omitted, it +is assumed that the offset = 0. This parameter may be used to skip +over any header information in the file that precedes the binary data. +Further examples: + +\begin{verbatim} + raw.dat[b10000] 1-dimensional 10000 pixel byte array + raw.dat[rb400,400,12] 3-dimensional floating point big-endian array + img.fits[ib512,512:2880] reads the 512 x 512 short integer array in + a FITS file, skipping over the 2880 byte header +\end{verbatim} + +One special case of input file is where the filename = `-' (a dash or +minus sign) or 'stdin' or 'stdout', which signifies that the input file +is to be read from the stdin stream, or written to the stdout stream if +a new output file is being created. In the case of reading from stdin, +CFITSIO first copies the whole stream into a temporary FITS file (in +memory or on disk), and subsequent reading of the FITS file occurs in +this copy. When writing to stdout, CFITSIO first constructs the whole +file in memory (since random access is required), then flushes it out +to the stdout stream when the file is closed. In addition, if the +output filename = '-.gz' or 'stdout.gz' then it will be gzip compressed +before being written to stdout. + +This ability to read and write on the stdin and stdout steams allows +FITS files to be piped between tasks in memory rather than having to +create temporary intermediate FITS files on disk. For example if task1 +creates an output FITS file, and task2 reads an input FITS file, the +FITS file may be piped between the 2 tasks by specifying + +\begin{verbatim} + task1 - | task2 - +\end{verbatim} +where the vertical bar is the Unix piping symbol. This assumes that the 2 +tasks read the name of the FITS file off of the command line. + + +\section{Output File Name when Opening an Existing File} + +An optional output filename may be specified in parentheses immediately +following the base file name to be opened. This is mainly useful in +those cases where CFITSIO creates a temporary copy of the input FITS +file before it is opened and passed to the application program. This +happens by default when opening a network FTP or HTTP-type file, when +reading a compressed FITS file on a local disk, when reading from the +stdin stream, or when a column filter, row filter, or binning specifier +is included as part of the input file specification. By default this +temporary file is created in memory. If there is not enough memory to +create the file copy, then CFITSIO will exit with an error. In these +cases one can force a permanent file to be created on disk, instead of +a temporary file in memory, by supplying the name in parentheses +immediately following the base file name. The output filename can +include the '!' clobber flag. + +Thus, if the input filename to CFITSIO is: +\verb+file1.fits.gz(file2.fits)+ +then CFITSIO will uncompress `file1.fits.gz' into the local disk file +`file2.fits' before opening it. CFITSIO does not automatically delete +the output file, so it will still exist after the application program +exits. + +In some cases, several different temporary FITS files will be created +in sequence, for instance, if one opens a remote file using FTP, then +filters rows in a binary table extension, then create an image by +binning a pair of columns. In this case, the remote file will be +copied to a temporary local file, then a second temporary file will be +created containing the filtered rows of the table, and finally a third +temporary file containing the binned image will be created. In cases +like this where multiple files are created, the outfile specifier will +be interpreted the name of the final file as described below, in descending +priority: + +\begin{itemize} +\item +as the name of the final image file if an image within a single binary +table cell is opened or if an image is created by binning a table column. +\item +as the name of the file containing the filtered table if a column filter +and/or a row filter are specified. +\item +as the name of the local copy of the remote FTP or HTTP file. +\item +as the name of the uncompressed version of the FITS file, if a +compressed FITS file on local disk has been opened. +\item +otherwise, the output filename is ignored. +\end{itemize} + + +The output file specifier is useful when reading FTP or HTTP-type +FITS files since it can be used to create a local disk copy of the file +that can be reused in the future. If the output file name = `*' then a +local file with the same name as the network file will be created. +Note that CFITSIO will behave differently depending on whether the +remote file is compressed or not as shown by the following examples: +\begin{itemize} +\item +`ftp://remote.machine/tmp/myfile.fits.gz(*)' - the remote compressed +file is copied to the local compressed file `myfile.fits.gz', which +is then uncompressed in local memory before being opened and passed +to the application program. + +\item +`ftp://remote.machine/tmp/myfile.fits.gz(myfile.fits)' - the remote +compressed file is copied and uncompressed into the local file +`myfile.fits'. This example requires less local memory than the +previous example since the file is uncompressed on disk instead of +in memory. + +\item +`ftp://remote.machine/tmp/myfile.fits(myfile.fits.gz)' - this will +usually produce an error since CFITSIO itself cannot compress files. +\end{itemize} + +The exact behavior of CFITSIO in the latter case depends on the type of +ftp server running on the remote machine and how it is configured. In +some cases, if the file `myfile.fits.gz' exists on the remote machine, +then the server will copy it to the local machine. In other cases the +ftp server will automatically create and transmit a compressed version +of the file if only the uncompressed version exists. This can get +rather confusing, so users should use a certain amount of caution when +using the output file specifier with FTP or HTTP file types, to make +sure they get the behavior that they expect. + + +\section{Template File Name when Creating a New File} + +When a new FITS file is created with a call to fits\_create\_file, the +name of a template file may be supplied in parentheses immediately +following the name of the new file to be created. This template is +used to define the structure of one or more HDUs in the new file. The +template file may be another FITS file, in which case the newly created +file will have exactly the same keywords in each HDU as in the template +FITS file, but all the data units will be filled with zeros. The +template file may also be an ASCII text file, where each line (in +general) describes one FITS keyword record. The format of the ASCII +template file is described below. + + +\section{Image Tile-Compression Specification} + +When specifying the name of the output FITS file to be created, the +user can indicate that images should be written in tile-compressed +format (see section 5.5, ``Primary Array or IMAGE Extension I/O +Routines'') by enclosing the compression parameters in square brackets +following the root disk file name. Here are some examples of the +syntax for specifying tile-compressed output images: + +\begin{verbatim} + myfile.fit[compress] - use Rice algorithm and default tile size + + myfile.fit[compress GZIP] - use the specified compression algorithm; + myfile.fit[compress Rice] only the first letter of the algorithm + myfile.fit[compress PLIO] name is required. + + myfile.fit[compress Rice 100,100] - use 100 x 100 pixel tile size + myfile.fit[compress Rice 100,100;2] - as above, and use noisebits = 2 +\end{verbatim} + + +\section{HDU Location Specification} + +The optional HDU location specifier defines which HDU (Header-Data +Unit, also known as an `extension') within the FITS file to initially +open. It must immediately follow the base file name (or the output +file name if present). If it is not specified then the first HDU (the +primary array) is opened. The HDU location specifier is required if +the colFilter, rowFilter, or binSpec specifiers are present, because +the primary array is not a valid HDU for these operations. The HDU may +be specified either by absolute position number, starting with 0 for +the primary array, or by reference to the HDU name, and optionally, the +version number and the HDU type of the desired extension. The location +of an image within a single cell of a binary table may also be +specified, as described below. + +The absolute position of the extension is specified either by enclosed +the number in square brackets (e.g., `[1]' = the first extension +following the primary array) or by preceded the number with a plus sign +(`+1'). To specify the HDU by name, give the name of the desired HDU +(the value of the EXTNAME or HDUNAME keyword) and optionally the +extension version number (value of the EXTVER keyword) and the +extension type (value of the XTENSION keyword: IMAGE, ASCII or TABLE, +or BINTABLE), separated by commas and all enclosed in square brackets. +If the value of EXTVER and XTENSION are not specified, then the first +extension with the correct value of EXTNAME is opened. The extension +name and type are not case sensitive, and the extension type may be +abbreviated to a single letter (e.g., I = IMAGE extension or primary +array, A or T = ASCII table extension, and B = binary table BINTABLE +extension). If the HDU location specifier is equal to `[PRIMARY]' or +`[P]', then the primary array (the first HDU) will be opened. + +FITS images are most commonly stored in the primary array or an image +extension, but images can also be stored as a vector in a single cell +of a binary table (i.e. each row of the vector column contains a +different image). Such an image can be opened with CFITSIO by +specifying the desired column name and the row number after the binary +table HDU specifier as shown in the following examples. The column name +is separated from the HDU specifier by a semicolon and the row number +is enclosed in parentheses. In this case CFITSIO copies the image from +the table cell into a temporary primary array before it is opened. The +application program then just sees the image in the primary array, +without any extensions. The particular row to be opened may be +specified either by giving an absolute integer row number (starting +with 1 for the first row), or by specifying a boolean expression that +evaluates to TRUE for the desired row. The first row that satisfies +the expression will be used. The row selection expression has the same +syntax as described in the Row Filter Specifier section, below. + + Examples: + +\begin{verbatim} + myfile.fits[3] - open the 3rd HDU following the primary array + myfile.fits+3 - same as above, but using the FTOOLS-style notation + myfile.fits[EVENTS] - open the extension that has EXTNAME = 'EVENTS' + myfile.fits[EVENTS, 2] - same as above, but also requires EXTVER = 2 + myfile.fits[events,2,b] - same, but also requires XTENSION = 'BINTABLE' + myfile.fits[3; images(17)] - opens the image in row 17 of the 'images' + column in the 3rd extension of the file. + myfile.fits[3; images(exposure > 100)] - as above, but opens the image + in the first row that has an 'exposure' column value + greater than 100. +\end{verbatim} + + +\section{Image Section} + +A virtual file containing a rectangular subsection of an image can be +extracted and opened by specifying the range of pixels (start:end) +along each axis to be extracted from the original image. One can also +specify an optional pixel increment (start:end:step) for each axis of +the input image. A pixel step = 1 will be assumed if it is not +specified. If the start pixel is larger then the end pixel, then the +image will be flipped (producing a mirror image) along that dimension. +An asterisk, '*', may be used to specify the entire range of an axis, +and '-*' will flip the entire axis. The input image can be in the +primary array, in an image extension, or contained in a vector cell of +a binary table. In the later 2 cases the extension name or number must +be specified before the image section specifier. + + Examples: + +\begin{verbatim} + myfile.fits[1:512:2, 2:512:2] - open a 256x256 pixel image + consisting of the odd numbered columns (1st axis) and + the even numbered rows (2nd axis) of the image in the + primary array of the file. + + myfile.fits[*, 512:256] - open an image consisting of all the columns + in the input image, but only rows 256 through 512. + The image will be flipped along the 2nd axis since + the starting pixel is greater than the ending pixel. + + myfile.fits[*:2, 512:256:2] - same as above but keeping only + every other row and column in the input image. + + myfile.fits[-*, *] - copy the entire image, flipping it along + the first axis. + + myfile.fits[3][1:256,1:256] - opens a subsection of the image that + is in the 3rd extension of the file. + + myfile.fits[4; images(12)][1:10,1:10] - open an image consisting + of the first 10 pixels in both dimensions. The original + image resides in the 12th row of the 'images' vector + column in the table in the 4th extension of the file. +\end{verbatim} + +When CFITSIO opens an image section it first creates a temporary file +containing the image section plus a copy of any other HDUs in the +file. This temporary file is then opened by the application program, +so it is not possible to write to or modify the input file when +specifying an image section. Note that CFITSIO automatically updates +the world coordinate system keywords in the header of the image +section, if they exist, so that the coordinate associated with each +pixel in the image section will be computed correctly. + + +\section{Image Transform Filters} + +CFITSIO can apply a user-specified mathematical function to the value +of every pixel in a FITS image, thus creating a new virtual image +in computer memory that is then opened and read by the application +program. The original FITS image is not modified by this process. + +The image transformation specifier is appended to the input +FITS file name and is enclosed in square brackets. It begins with the +letters 'PIX' to distinguish it from other types of FITS file filters +that are recognized by CFITSIO. The image transforming function may +use any of the mathematical operators listed in the following +'Row Filtering Specification' section of this document. +Some examples of image transform filters are: + +\begin{verbatim} + [pix X * 2.0] - multiply each pixel by 2.0 + [pix sqrt(X)] - take the square root of each pixel + [pix X + #ZEROPT - add the value of the ZEROPT keyword + [pix X>0 ? log10(X) : -99.] - if the pixel value is greater + than 0, compute the base 10 log, + else set the pixel = -99. +\end{verbatim} +Use the letter 'X' in the expression to represent the current pixel value +in the image. The expression is evaluated +independently for each pixel in the image and may be a function of 1) the +original pixel value, 2) the value of other pixels in the image at +a given relative offset from the position of the pixel that is being +evaluated, and 3) the value of +any header keywords. Header keyword values are represented +by the name of the keyword preceded by the '\#' sign. + + +To access the the value of adjacent pixels in the image, +specify the (1-D) offset from the current pixel in curly brackets. +For example + +\begin{verbatim} + [pix (x{-1} + x + x{+1}) / 3] +\end{verbatim} +will replace each pixel value with the running mean of the values of that +pixel and it's 2 neighboring pixels. Note that in this notation the image +is treated as a 1-D array, where each row of the image (or higher dimensional +cube) is appended one after another in one long array of pixels. +It is possible to refer to pixels +in the rows above or below the current pixel by using the value of the +NAXIS1 header keyword. For example + +\begin{verbatim} + [pix (x{-#NAXIS1} + x + x{#NAXIS1}) / 3] +\end{verbatim} +will compute the mean of each image pixel and the pixels immediately +above and below it in the adjacent rows of the image. +The following more complex example +creates a smoothed virtual image where each pixel +is a 3 x 3 boxcar average of the input image pixels: + +\begin{verbatim} + [pix (X + X{-1} + X{+1} + + X{-#NAXIS1} + X{-#NAXIS1 - 1} + X{-#NAXIS1 + 1} + + X{#NAXIS1} + X{#NAXIS1 - 1} + X{#NAXIS1 + 1}) / 9.] +\end{verbatim} +If the pixel offset +extends beyond the first or last pixel in the image, the function will +evaluate to undefined, or NULL. + +For complex or commonly used image filtering operations, +one can write the expression into an external text file and +then import it into the +filter using the syntax '[pix @filename.txt]'. The mathematical +expression can +extend over multiple lines of text in the file. +Any lines in the external text file +that begin with 2 slash characters ('//') will be ignored and may be +used to add comments into the file. + +By default, the datatype of the resulting image will be the same as +the original image, but one may force a different datatype by appended +a code letter to the 'pix' keyword: + +\begin{verbatim} + pixb - 8-bit byte image with BITPIX = 8 + pixi - 16-bit integer image with BITPIX = 16 + pixj - 32-bit integer image with BITPIX = 32 + pixr - 32-bit float image with BITPIX = -32 + pixd - 64-bit float image with BITPIX = -64 +\end{verbatim} +Also by default, any other HDUs in the input file will be copied without +change to the +output virtual FITS file, but one may discard the other HDUs by adding +the number '1' to the 'pix' keyword (and following any optional datatype code +letter). For example: + +\begin{verbatim} + myfile.fits[3][pixr1 sqrt(X)] +\end{verbatim} +will create a virtual FITS file containing only a primary array image +with 32-bit floating point pixels that have a value equal to the square +root of the pixels in the image that is in the 3rd extension +of the 'myfile.fits' file. + + + + +\section{Column and Keyword Filtering Specification} + +The optional column/keyword filtering specifier is used to modify the +column structure and/or the header keywords in the HDU that was +selected with the previous HDU location specifier. This filtering +specifier must be enclosed in square brackets and can be distinguished +from a general row filter specifier (described below) by the fact that +it begins with the string 'col ' and is not immediately followed by an +equals sign. The original file is not changed by this filtering +operation, and instead the modifications are made on a copy of the +input FITS file (usually in memory), which also contains a copy of all +the other HDUs in the file. This temporary file is passed to the +application program and will persist only until the file is closed or +until the program exits, unless the outfile specifier (see above) is +also supplied. + +The column/keyword filter can be used to perform the following +operations. More than one operation may be specified by separating +them with commas or semi-colons. + +\begin{itemize} + +\item +Copy only a specified list of columns columns to the filtered input file. +The list of column name should be separated by semi-colons. Wild card +characters may be used in the column names to match multiple columns. +If the expression contains both a list of columns to be included and +columns to be deleted, then all the columns in the original table +except the explicitly deleted columns will appear in the filtered +table (i.e., there is no need to explicitly list the columns to +be included if any columns are being deleted). + +\item +Delete a column or keyword by listing the name preceded by a minus +sign or an exclamation mark (!), e.g., '-TIME' will delete the TIME +column if it exists, otherwise the TIME keyword. An error is returned +if neither a column nor keyword with this name exists. Note that the +exclamation point, '!', is a special UNIX character, so if it is used +on the command line rather than entered at a task prompt, it must be +preceded by a backslash to force the UNIX shell to ignore it. + +\item +Rename an existing column or keyword with the syntax 'NewName == +OldName'. An error is returned if neither a column nor keyword with +this name exists. + +\item +Append a new column or keyword to the table. To create a column, +give the new name, optionally followed by the datatype in parentheses, +followed by a single equals sign and an expression to be used to +compute the value (e.g., 'newcol(1J) = 0' will create a new 32-bit +integer column called 'newcol' filled with zeros). The datatype is +specified using the same syntax that is allowed for the value of the +FITS TFORMn keyword (e.g., 'I', 'J', 'E', 'D', etc. for binary tables, +and 'I8', F12.3', 'E20.12', etc. for ASCII tables). If the datatype is +not specified then an appropriate datatype will be chosen depending on +the form of the expression (may be a character string, logical, bit, long +integer, or double column). An appropriate vector count (in the case +of binary tables) will also be added if not explicitly specified. + +When creating a new keyword, the keyword name must be preceded by a +pound sign '\#', and the expression must evaluate to a scalar +(i.e., cannot have a column name in the expression). The comment +string for the keyword may be specified in parentheses immediately +following the keyword name (instead of supplying a datatype as in +the case of creating a new column). If the keyword name ends with a +pound sign '\#', then cfitsio will substitute the number of the +most recently referenced column for the \# character . +This is especially useful when writing +a column-related keyword like TUNITn for a newly created column, +as shown in the following examples. + +\item +Recompute (overwrite) the values in an existing column or keyword by +giving the name followed by an equals sign and an arithmetic +expression. +\end{itemize} + +The expression that is used when appending or recomputing columns or +keywords can be arbitrarily complex and may be a function of other +header keyword values and other columns (in the same row). The full +syntax and available functions for the expression are described below +in the row filter specification section. + +If the expression contains both a list of columns to be included and +columns to be deleted, then all the columns in the original table +except the explicitly deleted columns will appear in the filtered +table. If no columns to be deleted are specified, then only the +columns that are explicitly listed will be included in the filtered +output table. To include all the columns, add the '*' wildcard +specifier at the end of the list, as shown in the examples. + +For complex or commonly used operations, one can also place the +operations into an external text file and import it into the column +filter using the syntax '[col @filename.txt]'. The operations can +extend over multiple lines of the file, but multiple operations must +still be separated by semicolons. Any lines in the external text file +that begin with 2 slash characters ('//') will be ignored and may be +used to add comments into the file. + +Examples: + +\begin{verbatim} + [col Time; rate] - only the Time and rate columns will + appear in the filtered input file. + + [col Time, *raw] - include the Time column and any other + columns whose name ends with 'raw'. + + [col -TIME; Good == STATUS] - deletes the TIME column and + renames the status column to 'Good' + + [col PI=PHA * 1.1 + 0.2; #TUNIT#(column units) = 'counts';*] + - creates new PI column from PHA values + and also writes the TUNITn keyword + for the new column. The final '*' + expression means preserve all the + columns in the input table in the + virtual output table; without the '*' + the output table would only contain + the single 'PI' column. + + [col rate = rate/exposure; TUNIT#(&) = 'counts/s';*] + - recomputes the rate column by dividing + it by the EXPOSURE keyword value. This + also modifies the value of the TUNITn + keyword for this column. The use of the + '&' character for the keyword comment + string means preserve the existing + comment string for that keyword. The + final '*' preserves all the columns + in the input table in the virtual + output table. +\end{verbatim} + + +\section{Row Filtering Specification} + + When entering the name of a FITS table that is to be opened by a + program, an optional row filter may be specified to select a subset + of the rows in the table. A temporary new FITS file is created on + the fly which contains only those rows for which the row filter + expression evaluates to true. (The primary array and any other + extensions in the input file are also copied to the temporary + file). The original FITS file is closed and the new virtual file + is opened by the application program. The row filter expression is + enclosed in square brackets following the file name and extension + name (e.g., 'file.fits[events][GRADE==50]' selects only those rows + where the GRADE column value equals 50). When dealing with tables + where each row has an associated time and/or 2D spatial position, + the row filter expression can also be used to select rows based on + the times in a Good Time Intervals (GTI) extension, or on spatial + position as given in a SAO-style region file. + + +\subsection{General Syntax} + + The row filtering expression can be an arbitrarily complex series + of operations performed on constants, keyword values, and column + data taken from the specified FITS TABLE extension. The expression + must evaluate to a boolean value for each row of the table, where + a value of FALSE means that the row will be excluded. + + For complex or commonly used filters, one can place the expression + into a text file and import it into the row filter using the syntax + '[@filename.txt]'. The expression can be arbitrarily complex and + extend over multiple lines of the file. Any lines in the external + text file that begin with 2 slash characters ('//') will be ignored + and may be used to add comments into the file. + + Keyword and column data are referenced by name. Any string of + characters not surrounded by quotes (ie, a constant string) or + followed by an open parentheses (ie, a function name) will be + initially interpreted as a column name and its contents for the + current row inserted into the expression. If no such column exists, + a keyword of that name will be searched for and its value used, if + found. To force the name to be interpreted as a keyword (in case + there is both a column and keyword with the same name), precede the + keyword name with a single pound sign, '\#', as in '\#NAXIS2'. Due to + the generalities of FITS column and keyword names, if the column or + keyword name contains a space or a character which might appear as + an arithmetic term then enclose the name in '\$' characters as in + \$MAX PHA\$ or \#\$MAX-PHA\$. Names are case insensitive. + + To access a table entry in a row other than the current one, follow + the column's name with a row offset within curly braces. For + example, 'PHA\{-3\}' will evaluate to the value of column PHA, 3 rows + above the row currently being processed. One cannot specify an + absolute row number, only a relative offset. Rows that fall outside + the table will be treated as undefined, or NULLs. + + Boolean operators can be used in the expression in either their + Fortran or C forms. The following boolean operators are available: + +\begin{verbatim} + "equal" .eq. .EQ. == "not equal" .ne. .NE. != + "less than" .lt. .LT. < "less than/equal" .le. .LE. <= =< + "greater than" .gt. .GT. > "greater than/equal" .ge. .GE. >= => + "or" .or. .OR. || "and" .and. .AND. && + "negation" .not. .NOT. ! "approx. equal(1e-7)" ~ +\end{verbatim} + +Note that the exclamation +point, '!', is a special UNIX character, so if it is used on the +command line rather than entered at a task prompt, it must be preceded +by a backslash to force the UNIX shell to ignore it. + + The expression may also include arithmetic operators and functions. + Trigonometric functions use radians, not degrees. The following + arithmetic operators and functions can be used in the expression + (function names are case insensitive). A null value will be returned + in case of illegal operations such as divide by zero, sqrt(negative) + log(negative), log10(negative), arccos(.gt. 1), arcsin(.gt. 1). + + +\begin{verbatim} + "addition" + "subtraction" - + "multiplication" * "division" / + "negation" - "exponentiation" ** ^ + "absolute value" abs(x) "cosine" cos(x) + "sine" sin(x) "tangent" tan(x) + "arc cosine" arccos(x) "arc sine" arcsin(x) + "arc tangent" arctan(x) "arc tangent" arctan2(y,x) + "hyperbolic cos" cosh(x) "hyperbolic sin" sinh(x) + "hyperbolic tan" tanh(x) "round to nearest int" round(x) + "round down to int" floor(x) "round up to int" ceil(x) + "exponential" exp(x) "square root" sqrt(x) + "natural log" log(x) "common log" log10(x) + "modulus" x % y "random # [0.0,1.0)" random() + "random Gaussian" randomn() "random Poisson" randomp(x) + "minimum" min(x,y) "maximum" max(x,y) + "cumulative sum" accum(x) "sequential difference" seqdiff(x) + "if-then-else" b?x:y + "angular separation" angsep(ra1,dec1,ra2,de2) (all in degrees) + "substring" strmid(s,p,n) "string search" strstr(s,r) +\end{verbatim} +Three different random number functions are provided: random(), with no +arguments, produces a uniform random deviate between 0 and 1; randomn(), +also with no arguments, produces a normal (Gaussian) random deviate with +zero mean and unit standard deviation; randomp(x) produces a Poisson random +deviate whose expected number of counts is X. X may be any positive real +number of expected counts, including fractional values, but the return value +is an integer. + +When the random functions are used in a vector expression, by default +the same random value will be used when evaluating each element of the vector. +If different random numbers are desired, then the name of a vector +column should be supplied as the single argument to the random +function (e.g., "flux + 0.1 * random(flux)", where "flux' is the +name of a vector column). This will create a vector of +random numbers that will be used in sequence when evaluating each +element of the vector expression. + + An alternate syntax for the min and max functions has only a single + argument which should be a vector value (see below). The result + will be the minimum/maximum element contained within the vector. + + The accum(x) function forms the cumulative sum of x, element by element. + Vector columns are supported simply by performing the summation process + through all the values. Null values are treated as 0. The seqdiff(x) + function forms the sequential difference of x, element by element. + The first value of seqdiff is the first value of x. A single null + value in x causes a pair of nulls in the output. The seqdiff and + accum functions are functional inverses, i.e., seqdiff(accum(x)) == x + as long as no null values are present. + +In the if-then-else expression, "b?x:y", b is an explicit boolean +value or expression. There is no automatic type conversion from +numeric to boolean values, so one needs to use "iVal!=0" instead of +merely "iVal" as the boolean argument. x and y can be any scalar data +type (including string). + + The angsep function computes the angular separation in degrees + between 2 celestial positions, where the first 2 parameters + give the RA-like and Dec-like coordinates (in decimal degrees) + of the first position, and the 3rd and 4th parameters give the + coordinates of the second position. + +The substring function strmid(S,P,N) extracts a substring from S, +starting at string position P, with a substring length N. The first +character position in S is labeled as 1. If P is 0, or refers to a +position beyond the end of S, then the extracted substring will be +NULL. S, P, and N may be functions of other columns. + +The string search function strstr(S,R) searches for the first occurrence +of the substring R in S. The result is an integer, indicating the +character position of the first match (where 1 is the first character +position of S). If no match is found, then strstr() returns a NULL +value. + + The following type casting operators are available, where the + enclosing parentheses are required and taken from the C language + usage. Also, the integer to real casts values to double precision: + +\begin{verbatim} + "real to integer" (int) x (INT) x + "integer to real" (float) i (FLOAT) i +\end{verbatim} + + In addition, several constants are built in for use in numerical + expressions: + + +\begin{verbatim} + #pi 3.1415... #e 2.7182... + #deg #pi/180 #row current row number + #null undefined value #snull undefined string +\end{verbatim} + + A string constant must be enclosed in quotes as in 'Crab'. The + "null" constants are useful for conditionally setting table values + to a NULL, or undefined, value (eg., "col1==-99 ? \#NULL : col1"). + + There is also a function for testing if two values are close to + each other, i.e., if they are "near" each other to within a user + specified tolerance. The arguments, value\_1 and value\_2 can be + integer or real and represent the two values who's proximity is + being tested to be within the specified tolerance, also an integer + or real: + +\begin{verbatim} + near(value_1, value_2, tolerance) +\end{verbatim} + When a NULL, or undefined, value is encountered in the FITS table, + the expression will evaluate to NULL unless the undefined value is + not actually required for evaluation, e.g. "TRUE .or. NULL" + evaluates to TRUE. The following two functions allow some NULL + detection and handling: + +\begin{verbatim} + "a null value?" ISNULL(x) + "define a value for null" DEFNULL(x,y) +\end{verbatim} + The former + returns a boolean value of TRUE if the argument x is NULL. The + later "defines" a value to be substituted for NULL values; it + returns the value of x if x is not NULL, otherwise it returns the + value of y. + + +\subsection{Bit Masks} + + Bit masks can be used to select out rows from bit columns (TFORMn = + \#X) in FITS files. To represent the mask, binary, octal, and hex + formats are allowed: + + +\begin{verbatim} + binary: b0110xx1010000101xxxx0001 + octal: o720x1 -> (b111010000xxx001) + hex: h0FxD -> (b00001111xxxx1101) +\end{verbatim} + + In all the representations, an x or X is allowed in the mask as a + wild card. Note that the x represents a different number of wild + card bits in each representation. All representations are case + insensitive. + + To construct the boolean expression using the mask as the boolean + equal operator described above on a bit table column. For example, + if you had a 7 bit column named flags in a FITS table and wanted + all rows having the bit pattern 0010011, the selection expression + would be: + + +\begin{verbatim} + flags == b0010011 + or + flags .eq. b10011 +\end{verbatim} + + It is also possible to test if a range of bits is less than, less + than equal, greater than and greater than equal to a particular + boolean value: + + +\begin{verbatim} + flags <= bxxx010xx + flags .gt. bxxx100xx + flags .le. b1xxxxxxx +\end{verbatim} + + Notice the use of the x bit value to limit the range of bits being + compared. + + It is not necessary to specify the leading (most significant) zero + (0) bits in the mask, as shown in the second expression above. + + Bit wise AND, OR and NOT operations are also possible on two or + more bit fields using the '\&'(AND), '$|$'(OR), and the '!'(NOT) + operators. All of these operators result in a bit field which can + then be used with the equal operator. For example: + + +\begin{verbatim} + (!flags) == b1101100 + (flags & b1000001) == bx000001 +\end{verbatim} + + Bit fields can be appended as well using the '+' operator. Strings + can be concatenated this way, too. + + +\subsection{Vector Columns} + + Vector columns can also be used in building the expression. No + special syntax is required if one wants to operate on all elements + of the vector. Simply use the column name as for a scalar column. + Vector columns can be freely intermixed with scalar columns or + constants in virtually all expressions. The result will be of the + same dimension as the vector. Two vectors in an expression, though, + need to have the same number of elements and have the same + dimensions. The only places a vector column cannot be used (for + now, anyway) are the SAO region functions and the NEAR boolean + function. + + Arithmetic and logical operations are all performed on an element by + element basis. Comparing two vector columns, eg "COL1 == COL2", + thus results in another vector of boolean values indicating which + elements of the two vectors are equal. + + Eight functions are available that operate on a vector and return a + scalar result: + +\begin{verbatim} + "minimum" MIN(V) "maximum" MAX(V) + "average" AVERAGE(V) "median" MEDIAN(V) + "summation" SUM(V) "standard deviation" STDDEV(V) + "# of values" NELEM(V) "# of non-null values" NVALID(V) +\end{verbatim} + where V represents the name of a vector column or a manually + constructed vector using curly brackets as described below. The + first 6 of these functions ignore any null values in the vector when + computing the result. The STDDEV() function computes the sample + standard deviation, i.e. it is proportional to 1/SQRT(N-1) instead + of 1/SQRT(N), where N is NVALID(V). + + The SUM function literally sums all the elements in x, returning a + scalar value. If x is a boolean vector, SUM returns the number + of TRUE elements. The NELEM function returns the number of elements + in vector x whereas NVALID return the number of non-null elements in + the vector. (NELEM also operates on bit and string columns, + returning their column widths.) As an example, to test whether all + elements of two vectors satisfy a given logical comparison, one can + use the expression + +\begin{verbatim} + SUM( COL1 > COL2 ) == NELEM( COL1 ) +\end{verbatim} + + which will return TRUE if all elements of COL1 are greater than + their corresponding elements in COL2. + + To specify a single element of a vector, give the column name + followed by a comma-separated list of coordinates enclosed in + square brackets. For example, if a vector column named PHAS exists + in the table as a one dimensional, 256 component list of numbers + from which you wanted to select the 57th component for use in the + expression, then PHAS[57] would do the trick. Higher dimensional + arrays of data may appear in a column. But in order to interpret + them, the TDIMn keyword must appear in the header. Assuming that a + (4,4,4,4) array is packed into each row of a column named ARRAY4D, + the (1,2,3,4) component element of each row is accessed by + ARRAY4D[1,2,3,4]. Arrays up to dimension 5 are currently + supported. Each vector index can itself be an expression, although + it must evaluate to an integer value within the bounds of the + vector. Vector columns which contain spaces or arithmetic operators + must have their names enclosed in "\$" characters as with + \$ARRAY-4D\$[1,2,3,4]. + + A more C-like syntax for specifying vector indices is also + available. The element used in the preceding example alternatively + could be specified with the syntax ARRAY4D[4][3][2][1]. Note the + reverse order of indices (as in C), as well as the fact that the + values are still ones-based (as in Fortran -- adopted to avoid + ambiguity for 1D vectors). With this syntax, one does not need to + specify all of the indices. To extract a 3D slice of this 4D + array, use ARRAY4D[4]. + + Variable-length vector columns are not supported. + + Vectors can be manually constructed within the expression using a + comma-separated list of elements surrounded by curly braces ('\{\}'). + For example, '\{1,3,6,1\}' is a 4-element vector containing the values + 1, 3, 6, and 1. The vector can contain only boolean, integer, and + real values (or expressions). The elements will be promoted to the + highest datatype present. Any elements which are themselves + vectors, will be expanded out with each of its elements becoming an + element in the constructed vector. + + +\subsection{Good Time Interval Filtering} + + A common filtering method involves selecting rows which have a time + value which lies within what is called a Good Time Interval or GTI. + The time intervals are defined in a separate FITS table extension + which contains 2 columns giving the start and stop time of each + good interval. The filtering operation accepts only those rows of + the input table which have an associated time which falls within + one of the time intervals defined in the GTI extension. A high + level function, gtifilter(a,b,c,d), is available which evaluates + each row of the input table and returns TRUE or FALSE depending + whether the row is inside or outside the good time interval. The + syntax is + +\begin{verbatim} + gtifilter( [ "gtifile" [, expr [, "STARTCOL", "STOPCOL" ] ] ] ) +\end{verbatim} + where each "[]" demarks optional parameters. Note that the quotes + around the gtifile and START/STOP column are required. Either single + or double quotes may be used. In cases where this expression is + entered on the Unix command line, enclose the entire expression in + double quotes, and then use single quotes within the expression to + enclose the 'gtifile' and other terms. It is also usually possible + to do the reverse, and enclose the whole expression in single quotes + and then use double quotes within the expression. The gtifile, + if specified, can be blank ("") which will mean to use the first + extension with the name "*GTI*" in the current file, a plain + extension specifier (eg, "+2", "[2]", or "[STDGTI]") which will be + used to select an extension in the current file, or a regular + filename with or without an extension specifier which in the latter + case will mean to use the first extension with an extension name + "*GTI*". Expr can be any arithmetic expression, including simply + the time column name. A vector time expression will produce a + vector boolean result. STARTCOL and STOPCOL are the names of the + START/STOP columns in the GTI extension. If one of them is + specified, they both must be. + + In its simplest form, no parameters need to be provided -- default + values will be used. The expression "gtifilter()" is equivalent to + +\begin{verbatim} + gtifilter( "", TIME, "*START*", "*STOP*" ) +\end{verbatim} + This will search the current file for a GTI extension, filter the + TIME column in the current table, using START/STOP times taken from + columns in the GTI extension with names containing the strings + "START" and "STOP". The wildcards ('*') allow slight variations in + naming conventions such as "TSTART" or "STARTTIME". The same + default values apply for unspecified parameters when the first one + or two parameters are specified. The function automatically + searches for TIMEZERO/I/F keywords in the current and GTI + extensions, applying a relative time offset, if necessary. + + +\subsection{Spatial Region Filtering} + + Another common filtering method selects rows based on whether the + spatial position associated with each row is located within a given + 2-dimensional region. The syntax for this high-level filter is + +\begin{verbatim} + regfilter( "regfilename" [ , Xexpr, Yexpr [ , "wcs cols" ] ] ) +\end{verbatim} + where each "[]" demarks optional parameters. The region file name + is required and must be enclosed in quotes. The remaining + parameters are optional. There are 2 supported formats for the + region file: ASCII file or FITS binary table. The region file + contains a list of one or more geometric shapes (circle, + ellipse, box, etc.) which defines a region on the celestial sphere + or an area within a particular 2D image. The region file is + typically generated using an image display program such as fv/POW + (distribute by the HEASARC), or ds9 (distributed by the Smithsonian + Astrophysical Observatory). Users should refer to the documentation + provided with these programs for more details on the syntax used in + the region files. The FITS region file format is defined in a document + available from the FITS Support Office at + http://fits.gsfc.nasa.gov/ registry/ region.html + + In its simplest form, (e.g., regfilter("region.reg") ) the + coordinates in the default 'X' and 'Y' columns will be used to + determine if each row is inside or outside the area specified in + the region file. Alternate position column names, or expressions, + may be entered if needed, as in + +\begin{verbatim} + regfilter("region.reg", XPOS, YPOS) +\end{verbatim} + Region filtering can be applied most unambiguously if the positions + in the region file and in the table to be filtered are both give in + terms of absolute celestial coordinate units. In this case the + locations and sizes of the geometric shapes in the region file are + specified in angular units on the sky (e.g., positions given in + R.A. and Dec. and sizes in arcseconds or arcminutes). Similarly, + each row of the filtered table will have a celestial coordinate + associated with it. This association is usually implemented using + a set of so-called 'World Coordinate System' (or WCS) FITS keywords + that define the coordinate transformation that must be applied to + the values in the 'X' and 'Y' columns to calculate the coordinate. + + Alternatively, one can perform spatial filtering using unitless + 'pixel' coordinates for the regions and row positions. In this + case the user must be careful to ensure that the positions in the 2 + files are self-consistent. A typical problem is that the region + file may be generated using a binned image, but the unbinned + coordinates are given in the event table. The ROSAT events files, + for example, have X and Y pixel coordinates that range from 1 - + 15360. These coordinates are typically binned by a factor of 32 to + produce a 480x480 pixel image. If one then uses a region file + generated from this image (in image pixel units) to filter the + ROSAT events file, then the X and Y column values must be converted + to corresponding pixel units as in: + +\begin{verbatim} + regfilter("rosat.reg", X/32.+.5, Y/32.+.5) +\end{verbatim} + Note that this binning conversion is not necessary if the region + file is specified using celestial coordinate units instead of pixel + units because CFITSIO is then able to directly compare the + celestial coordinate of each row in the table with the celestial + coordinates in the region file without having to know anything + about how the image may have been binned. + + The last "wcs cols" parameter should rarely be needed. If supplied, + this string contains the names of the 2 columns (space or comma + separated) which have the associated WCS keywords. If not supplied, + the filter will scan the X and Y expressions for column names. + If only one is found in each expression, those columns will be + used, otherwise an error will be returned. + + These region shapes are supported (names are case insensitive): + +\begin{verbatim} + Point ( X1, Y1 ) <- One pixel square region + Line ( X1, Y1, X2, Y2 ) <- One pixel wide region + Polygon ( X1, Y1, X2, Y2, ... ) <- Rest are interiors with + Rectangle ( X1, Y1, X2, Y2, A ) | boundaries considered + Box ( Xc, Yc, Wdth, Hght, A ) V within the region + Diamond ( Xc, Yc, Wdth, Hght, A ) + Circle ( Xc, Yc, R ) + Annulus ( Xc, Yc, Rin, Rout ) + Ellipse ( Xc, Yc, Rx, Ry, A ) + Elliptannulus ( Xc, Yc, Rinx, Riny, Routx, Routy, Ain, Aout ) + Sector ( Xc, Yc, Amin, Amax ) +\end{verbatim} + where (Xc,Yc) is the coordinate of the shape's center; (X\#,Y\#) are + the coordinates of the shape's edges; Rxxx are the shapes' various + Radii or semi-major/minor axes; and Axxx are the angles of rotation + (or bounding angles for Sector) in degrees. For rotated shapes, the + rotation angle can be left off, indicating no rotation. Common + alternate names for the regions can also be used: rotbox = box; + rotrectangle = rectangle; (rot)rhombus = (rot)diamond; and pie + = sector. When a shape's name is preceded by a minus sign, '-', + the defined region is instead the area *outside* its boundary (ie, + the region is inverted). All the shapes within a single region + file are OR'd together to create the region, and the order is + significant. The overall way of looking at region files is that if + the first region is an excluded region then a dummy included region + of the whole detector is inserted in the front. Then each region + specification as it is processed overrides any selections inside of + that region specified by previous regions. Another way of thinking + about this is that if a previous excluded region is completely + inside of a subsequent included region the excluded region is + ignored. + + The positional coordinates may be given either in pixel units, + decimal degrees or hh:mm:ss.s, dd:mm:ss.s units. The shape sizes + may be given in pixels, degrees, arcminutes, or arcseconds. Look + at examples of region file produced by fv/POW or ds9 for further + details of the region file format. + + There are three functions that are primarily for use with SAO region + files and the FSAOI task, but they can be used directly. They + return a boolean true or false depending on whether a two + dimensional point is in the region or not: + +\begin{verbatim} + "point in a circular region" + circle(xcntr,ycntr,radius,Xcolumn,Ycolumn) + + "point in an elliptical region" + ellipse(xcntr,ycntr,xhlf_wdth,yhlf_wdth,rotation,Xcolumn,Ycolumn) + + "point in a rectangular region" + box(xcntr,ycntr,xfll_wdth,yfll_wdth,rotation,Xcolumn,Ycolumn) + + where + (xcntr,ycntr) are the (x,y) position of the center of the region + (xhlf_wdth,yhlf_wdth) are the (x,y) half widths of the region + (xfll_wdth,yfll_wdth) are the (x,y) full widths of the region + (radius) is half the diameter of the circle + (rotation) is the angle(degrees) that the region is rotated with + respect to (xcntr,ycntr) + (Xcoord,Ycoord) are the (x,y) coordinates to test, usually column + names + NOTE: each parameter can itself be an expression, not merely a + column name or constant. +\end{verbatim} + + +\subsection{Example Row Filters} + +\begin{verbatim} + [ binary && mag <= 5.0] - Extract all binary stars brighter + than fifth magnitude (note that + the initial space is necessary to + prevent it from being treated as a + binning specification) + + [#row >= 125 && #row <= 175] - Extract row numbers 125 through 175 + + [IMAGE[4,5] .gt. 100] - Extract all rows that have the + (4,5) component of the IMAGE column + greater than 100 + + [abs(sin(theta * #deg)) < 0.5] - Extract all rows having the + absolute value of the sine of theta + less than a half where the angles + are tabulated in degrees + + [SUM( SPEC > 3*BACKGRND )>=1] - Extract all rows containing a + spectrum, held in vector column + SPEC, with at least one value 3 + times greater than the background + level held in a keyword, BACKGRND + + [VCOL=={1,4,2}] - Extract all rows whose vector column + VCOL contains the 3-elements 1, 4, and + 2. + + [@rowFilter.txt] - Extract rows using the expression + contained within the text file + rowFilter.txt + + [gtifilter()] - Search the current file for a GTI + extension, filter the TIME + column in the current table, using + START/STOP times taken from + columns in the GTI extension + + [regfilter("pow.reg")] - Extract rows which have a coordinate + (as given in the X and Y columns) + within the spatial region specified + in the pow.reg region file. + + [regfilter("pow.reg", Xs, Ys)] - Same as above, except that the + Xs and Ys columns will be used to + determine the coordinate of each + row in the table. +\end{verbatim} + + +\section{ Binning or Histogramming Specification} + +The optional binning specifier is enclosed in square brackets and can +be distinguished from a general row filter specification by the fact +that it begins with the keyword 'bin' not immediately followed by an +equals sign. When binning is specified, a temporary N-dimensional FITS +primary array is created by computing the histogram of the values in +the specified columns of a FITS table extension. After the histogram +is computed the input FITS file containing the table is then closed and +the temporary FITS primary array is opened and passed to the +application program. Thus, the application program never sees the +original FITS table and only sees the image in the new temporary file +(which has no additional extensions). Obviously, the application +program must be expecting to open a FITS image and not a FITS table in +this case. + +The data type of the FITS histogram image may be specified by appending +'b' (for 8-bit byte), 'i' (for 16-bit integers), 'j' (for 32-bit +integer), 'r' (for 32-bit floating points), or 'd' (for 64-bit double +precision floating point) to the 'bin' keyword (e.g. '[binr X]' +creates a real floating point image). If the datatype is not +explicitly specified then a 32-bit integer image will be created by +default, unless the weighting option is also specified in which case +the image will have a 32-bit floating point data type by default. + +The histogram image may have from 1 to 4 dimensions (axes), depending +on the number of columns that are specified. The general form of the +binning specification is: + +\begin{verbatim} + [bin{bijrd} Xcol=min:max:binsize, Ycol= ..., Zcol=..., Tcol=...; weight] +\end{verbatim} +in which up to 4 columns, each corresponding to an axis of the image, +are listed. The column names are case insensitive, and the column +number may be given instead of the name, preceded by a pound sign +(e.g., [bin \#4=1:512]). If the column name is not specified, then +CFITSIO will first try to use the 'preferred column' as specified by +the CPREF keyword if it exists (e.g., 'CPREF = 'DETX,DETY'), otherwise +column names 'X', 'Y', 'Z', and 'T' will be assumed for each of the 4 +axes, respectively. In cases where the column name could be confused +with an arithmetic expression, enclose the column name in parentheses to +force the name to be interpreted literally. + +Each column name may be followed by an equals sign and then the lower +and upper range of the histogram, and the size of the histogram bins, +separated by colons. Spaces are allowed before and after the equals +sign but not within the 'min:max:binsize' string. The min, max and +binsize values may be integer or floating point numbers, or they may be +the names of keywords in the header of the table. If the latter, then +the value of that keyword is substituted into the expression. + +Default values for the min, max and binsize quantities will be +used if not explicitly given in the binning expression as shown +in these examples: + +\begin{verbatim} + [bin x = :512:2] - use default minimum value + [bin x = 1::2] - use default maximum value + [bin x = 1:512] - use default bin size + [bin x = 1:] - use default maximum value and bin size + [bin x = :512] - use default minimum value and bin size + [bin x = 2] - use default minimum and maximum values + [bin x] - use default minimum, maximum and bin size + [bin 4] - default 2-D image, bin size = 4 in both axes + [bin] - default 2-D image +\end{verbatim} +CFITSIO will use the value of the TLMINn, TLMAXn, and TDBINn keywords, +if they exist, for the default min, max, and binsize, respectively. If +they do not exist then CFITSIO will use the actual minimum and maximum +values in the column for the histogram min and max values. The default +binsize will be set to 1, or (max - min) / 10., whichever is smaller, +so that the histogram will have at least 10 bins along each axis. + +A shortcut notation is allowed if all the columns/axes have the same +binning specification. In this case all the column names may be listed +within parentheses, followed by the (single) binning specification, as +in: + +\begin{verbatim} + [bin (X,Y)=1:512:2] + [bin (X,Y) = 5] +\end{verbatim} + +The optional weighting factor is the last item in the binning specifier +and, if present, is separated from the list of columns by a +semi-colon. As the histogram is accumulated, this weight is used to +incremented the value of the appropriated bin in the histogram. If the +weighting factor is not specified, then the default weight = 1 is +assumed. The weighting factor may be a constant integer or floating +point number, or the name of a keyword containing the weighting value. +Or the weighting factor may be the name of a table column in which case +the value in that column, on a row by row basis, will be used. + +In some cases, the column or keyword may give the reciprocal of the +actual weight value that is needed. In this case, precede the weight +keyword or column name by a slash '/' to tell CFITSIO to use the +reciprocal of the value when constructing the histogram. + +For complex or commonly used histograms, one can also place its +description into a text file and import it into the binning +specification using the syntax '[bin @filename.txt]'. The file's +contents can extend over multiple lines, although it must still +conform to the no-spaces rule for the min:max:binsize syntax and each +axis specification must still be comma-separated. Any lines in the +external text file that begin with 2 slash characters ('//') will be +ignored and may be used to add comments into the file. + + Examples: + + +\begin{verbatim} + [bini detx, dety] - 2-D, 16-bit integer histogram + of DETX and DETY columns, using + default values for the histogram + range and binsize + + [bin (detx, dety)=16; /exposure] - 2-D, 32-bit real histogram of DETX + and DETY columns with a bin size = 16 + in both axes. The histogram values + are divided by the EXPOSURE keyword + value. + + [bin time=TSTART:TSTOP:0.1] - 1-D lightcurve, range determined by + the TSTART and TSTOP keywords, + with 0.1 unit size bins. + + [bin pha, time=8000.:8100.:0.1] - 2-D image using default binning + of the PHA column for the X axis, + and 1000 bins in the range + 8000. to 8100. for the Y axis. + + [bin @binFilter.txt] - Use the contents of the text file + binFilter.txt for the binning + specifications. + +\end{verbatim} + + +\chapter{Template Files } + +When a new FITS file is created with a call to fits\_create\_file, the +name of a template file may be supplied in parentheses immediately +following the name of the new file to be created. This template is +used to define the structure of one or more HDUs in the new file. The +template file may be another FITS file, in which case the newly created +file will have exactly the same keywords in each HDU as in the template +FITS file, but all the data units will be filled with zeros. The +template file may also be an ASCII text file, where each line (in +general) describes one FITS keyword record. The format of the ASCII +template file is described in the following sections. + + +\section{Detailed Template Line Format} + +The format of each ASCII template line closely follows the format of a +FITS keyword record: + +\begin{verbatim} + KEYWORD = KEYVALUE / COMMENT +\end{verbatim} +except that free format may be used (e.g., the equals sign may appear +at any position in the line) and TAB characters are allowed and are +treated the same as space characters. The KEYVALUE and COMMENT fields +are optional. The equals sign character is also optional, but it is +recommended that it be included for clarity. Any template line that +begins with the pound '\#' character is ignored by the template parser +and may be use to insert comments into the template file itself. + +The KEYWORD name field is limited to 8 characters in length and only +the letters A-Z, digits 0-9, and the hyphen and underscore characters +may be used, without any embedded spaces. Lowercase letters in the +template keyword name will be converted to uppercase. Leading spaces +in the template line preceding the keyword name are generally ignored, +except if the first 8 characters of a template line are all blank, then +the entire line is treated as a FITS comment keyword (with a blank +keyword name) and is copied verbatim into the FITS header. + +The KEYVALUE field may have any allowed FITS data type: character +string, logical, integer, real, complex integer, or complex real. The +character string values need not be enclosed in single quote characters +unless they are necessary to distinguish the string from a different +data type (e.g. 2.0 is a real but '2.0' is a string). The keyword has +an undefined (null) value if the template record only contains blanks +following the "=" or between the "=" and the "/" comment field +delimiter. + +String keyword values longer than 68 characters (the maximum length +that will fit in a single FITS keyword record) are permitted using the +CFITSIO long string convention. They can either be specified as a +single long line in the template, or by using multiple lines where the +continuing lines contain the 'CONTINUE' keyword, as in this example: + +\begin{verbatim} + LONGKEY = 'This is a long string value that is contin&' + CONTINUE 'ued over 2 records' / comment field goes here +\end{verbatim} +The format of template lines with CONTINUE keyword is very strict: 3 +spaces must follow CONTINUE and the rest of the line is copied verbatim +to the FITS file. + +The start of the optional COMMENT field must be preceded by "/", which +is used to separate it from the keyword value field. Exceptions are if +the KEYWORD name field contains COMMENT, HISTORY, CONTINUE, or if the +first 8 characters of the template line are blanks. + +More than one Header-Data Unit (HDU) may be defined in the template +file. The start of an HDU definition is denoted with a SIMPLE or +XTENSION template line: + +1) SIMPLE begins a Primary HDU definition. SIMPLE may only appear as +the first keyword in the template file. If the template file begins +with XTENSION instead of SIMPLE, then a default empty Primary HDU is +created, and the template is then assumed to define the keywords +starting with the first extension following the Primary HDU. + +2) XTENSION marks the beginning of a new extension HDU definition. The +previous HDU will be closed at this point and processing of the next +extension begins. + + +\section{Auto-indexing of Keywords} + +If a template keyword name ends with a "\#" character, it is said to be +'auto-indexed'. Each "\#" character will be replaced by the current +integer index value, which gets reset = 1 at the start of each new HDU +in the file (or 7 in the special case of a GROUP definition). The +FIRST indexed keyword in each template HDU definition is used as the +'incrementor'; each subsequent occurrence of this SAME keyword will +cause the index value to be incremented. This behavior can be rather +subtle, as illustrated in the following examples in which the TTYPE +keyword is the incrementor in both cases: + +\begin{verbatim} + TTYPE# = TIME + TFORM# = 1D + TTYPE# = RATE + TFORM# = 1E +\end{verbatim} +will create TTYPE1, TFORM1, TTYPE2, and TFORM2 keywords. But if the +template looks like, + +\begin{verbatim} + TTYPE# = TIME + TTYPE# = RATE + TFORM# = 1D + TFORM# = 1E +\end{verbatim} +this results in a FITS files with TTYPE1, TTYPE2, TFORM2, and TFORM2, +which is probably not what was intended! + + +\section{Template Parser Directives} + +In addition to the template lines which define individual keywords, the +template parser recognizes 3 special directives which are each preceded +by the backslash character: \verb+ \include, \group+, and \verb+ \end+. + +The 'include' directive must be followed by a filename. It forces the +parser to temporarily stop reading the current template file and begin +reading the include file. Once the parser reaches the end of the +include file it continues parsing the current template file. Include +files can be nested, and HDU definitions can span multiple template +files. + +The start of a GROUP definition is denoted with the 'group' directive, +and the end of a GROUP definition is denoted with the 'end' directive. +Each GROUP contains 0 or more member blocks (HDUs or GROUPs). Member +blocks of type GROUP can contain their own member blocks. The GROUP +definition itself occupies one FITS file HDU of special type (GROUP +HDU), so if a template specifies 1 group with 1 member HDU like: + +\begin{verbatim} +\group +grpdescr = 'demo' +xtension bintable +# this bintable has 0 cols, 0 rows +\end +\end{verbatim} +then the parser creates a FITS file with 3 HDUs : + +\begin{verbatim} +1) dummy PHDU +2) GROUP HDU (has 1 member, which is bintable in HDU number 3) +3) bintable (member of GROUP in HDU number 2) +\end{verbatim} +Technically speaking, the GROUP HDU is a BINTABLE with 6 columns. Applications +can define additional columns in a GROUP HDU using TFORMn and TTYPEn +(where n is 7, 8, ....) keywords or their auto-indexing equivalents. + +For a more complicated example of a template file using the group directives, +look at the sample.tpl file that is included in the CFITSIO distribution. + + +\section{Formal Template Syntax} + +The template syntax can formally be defined as follows: + +\begin{verbatim} + TEMPLATE = BLOCK [ BLOCK ... ] + + BLOCK = { HDU | GROUP } + + GROUP = \GROUP [ BLOCK ... ] \END + + HDU = XTENSION [ LINE ... ] { XTENSION | \GROUP | \END | EOF } + + LINE = [ KEYWORD [ = ] ] [ VALUE ] [ / COMMENT ] + + X ... - X can be present 1 or more times + { X | Y } - X or Y + [ X ] - X is optional +\end{verbatim} + +At the topmost level, the template defines 1 or more template blocks. Blocks +can be either HDU (Header Data Unit) or a GROUP. For each block the parser +creates 1 (or more for GROUPs) FITS file HDUs. + + + +\section{Errors} + +In general the fits\_execute\_template() function tries to be as atomic +as possible, so either everything is done or nothing is done. If an +error occurs during parsing of the template, fits\_execute\_template() +will (try to) delete the top level BLOCK (with all its children if any) +in which the error occurred, then it will stop reading the template file +and it will return with an error. + + +\section{Examples} + +1. This template file will create a 200 x 300 pixel image, with 4-byte +integer pixel values, in the primary HDU: + +\begin{verbatim} + SIMPLE = T + BITPIX = 32 + NAXIS = 2 / number of dimensions + NAXIS1 = 100 / length of first axis + NAXIS2 = 200 / length of second axis + OBJECT = NGC 253 / name of observed object +\end{verbatim} +The allowed values of BITPIX are 8, 16, 32, -32, or -64, +representing, respectively, 8-bit integer, 16-bit integer, 32-bit +integer, 32-bit floating point, or 64 bit floating point pixels. + +2. To create a FITS table, the template first needs to include +XTENSION = TABLE or BINTABLE to define whether it is an ASCII or binary +table, and NAXIS2 to define the number of rows in the table. Two +template lines are then needed to define the name (TTYPEn) and FITS data +format (TFORMn) of the columns, as in this example: + +\begin{verbatim} + xtension = bintable + naxis2 = 40 + ttype# = Name + tform# = 10a + ttype# = Npoints + tform# = j + ttype# = Rate + tunit# = counts/s + tform# = e +\end{verbatim} +The above example defines a null primary array followed by a 40-row +binary table extension with 3 columns called 'Name', 'Npoints', and +'Rate', with data formats of '10A' (ASCII character string), '1J' +(integer) and '1E' (floating point), respectively. Note that the other +required FITS keywords (BITPIX, NAXIS, NAXIS1, PCOUNT, GCOUNT, TFIELDS, +and END) do not need to be explicitly defined in the template because +their values can be inferred from the other keywords in the template. +This example also illustrates that the templates are generally +case-insensitive (the keyword names and TFORMn values are converted to +upper-case in the FITS file) and that string keyword values generally +do not need to be enclosed in quotes. + + +\chapter{ Summary of all FITSIO User-Interface Subroutines } + + Error Status Routines page~\pageref{FTVERS} + +\begin{verbatim} + FTVERS( > version) + FTGERR(status, > errtext) + FTGMSG( > errmsg) + FTRPRT (stream, > status) + FTPMSG(errmsg) + FTPMRK + FTCMSG + FTCMRK +\end{verbatim} + FITS File Open and Close Subroutines: page~\pageref{FTOPEN} + +\begin{verbatim} + FTOPEN(unit,filename,rwmode, > blocksize,status) + FTDKOPEN(unit,filename,rwmode, > blocksize,status) + FTNOPN(unit,filename,rwmode, > status) + FTDOPN(unit,filename,rwmode, > status) + FTTOPN(unit,filename,rwmode, > status) + FTIOPN(unit,filename,rwmode, > status) + FTREOPEN(unit, > newunit, status) + FTINIT(unit,filename,blocksize, > status) + FTDKINIT(unit,filename,blocksize, > status) + FTTPLT(unit, filename, tplfilename, > status) + FTFLUS(unit, > status) + FTCLOS(unit, > status) + FTDELT(unit, > status) + FTGIOU( > iounit, status) + FTFIOU(iounit, > status) + CFITS2Unit(fitsfile *ptr) (C routine) + CUnit2FITS(int unit) (C routine) + FTEXTN(filename, > nhdu, status) + FTFLNM(unit, > filename, status) + FTFLMD(unit, > iomode, status) + FFURLT(unit, > urltype, status) + FTIURL(filename, > filetype, infile, outfile, extspec, filter, + binspec, colspec, status) + FTRTNM(filename, > rootname, status) + FTEXIST(filename, > exist, status) +\end{verbatim} + HDU-Level Operations: page~\pageref{FTMAHD} + +\begin{verbatim} + FTMAHD(unit,nhdu, > hdutype,status) + FTMRHD(unit,nmove, > hdutype,status) + FTGHDN(unit, > nhdu) + FTMNHD(unit, hdutype, extname, extver, > status) + FTGHDT(unit, > hdutype, status) + FTTHDU(unit, > hdunum, status) + FTCRHD(unit, > status) + FTIIMG(unit,bitpix,naxis,naxes, > status) + FTITAB(unit,rowlen,nrows,tfields,ttype,tbcol,tform,tunit,extname, > + status) + FTIBIN(unit,nrows,tfields,ttype,tform,tunit,extname,varidat > status) + FTRSIM(unit,bitpix,naxis,naxes,status) + FTDHDU(unit, > hdutype,status) + FTCPFL(iunit,ounit,previous, current, following, > status) + FTCOPY(iunit,ounit,morekeys, > status) + FTCPHD(inunit, outunit, > status) + FTCPDT(iunit,ounit, > status) +\end{verbatim} + Subroutines to specify or modify the structure of the CHDU: page~\pageref{FTRDEF} + +\begin{verbatim} + FTRDEF(unit, > status) (DEPRECATED) + FTPDEF(unit,bitpix,naxis,naxes,pcount,gcount, > status) (DEPRECATED) + FTADEF(unit,rowlen,tfields,tbcol,tform,nrows > status) (DEPRECATED) + FTBDEF(unit,tfields,tform,varidat,nrows > status) (DEPRECATED) + FTDDEF(unit,bytlen, > status) (DEPRECATED) + FTPTHP(unit,theap, > status) +\end{verbatim} + Header Space and Position Subroutines: page~\pageref{FTHDEF} + +\begin{verbatim} + FTHDEF(unit,morekeys, > status) + FTGHSP(iunit, > keysexist,keysadd,status) + FTGHPS(iunit, > keysexist,key_no,status) +\end{verbatim} + Read or Write Standard Header Subroutines: page~\pageref{FTPHPR} + +\begin{verbatim} + FTPHPS(unit,bitpix,naxis,naxes, > status) + FTPHPR(unit,simple,bitpix,naxis,naxes,pcount,gcount,extend, > status) + FTGHPR(unit,maxdim, > simple,bitpix,naxis,naxes,pcount,gcount,extend, + status) + FTPHTB(unit,rowlen,nrows,tfields,ttype,tbcol,tform,tunit,extname, > + status) + FTGHTB(unit,maxdim, > rowlen,nrows,tfields,ttype,tbcol,tform,tunit, + extname,status) + FTPHBN(unit,nrows,tfields,ttype,tform,tunit,extname,varidat > status) + FTGHBN(unit,maxdim, > nrows,tfields,ttype,tform,tunit,extname,varidat, + status) +\end{verbatim} + Write Keyword Subroutines: page~\pageref{FTPREC} + +\begin{verbatim} + FTPREC(unit,card, > status) + FTPCOM(unit,comment, > status) + FTPHIS(unit,history, > status) + FTPDAT(unit, > status) + FTPKY[JKLS](unit,keyword,keyval,comment, > status) + FTPKY[EDFG](unit,keyword,keyval,decimals,comment, > status) + FTPKLS(unit,keyword,keyval,comment, > status) + FTPLSW(unit, > status) + FTPKYU(unit,keyword,comment, > status) + FTPKN[JKLS](unit,keyroot,startno,no_keys,keyvals,comments, > status) + FTPKN[EDFG](unit,keyroot,startno,no_keys,keyvals,decimals,comments, > + status) + FTCPKYinunit, outunit, innum, outnum, keyroot, > status) + FTPKYT(unit,keyword,intval,dblval,comment, > status) + FTPKTP(unit, filename, > status) + FTPUNT(unit,keyword,units, > status) +\end{verbatim} + Insert Keyword Subroutines: page~\pageref{FTIREC} + +\begin{verbatim} + FTIREC(unit,key_no,card, > status) + FTIKY[JKLS](unit,keyword,keyval,comment, > status) + FTIKLS(unit,keyword,keyval,comment, > status) + FTIKY[EDFG](unit,keyword,keyval,decimals,comment, > status) + FTIKYU(unit,keyword,comment, > status) +\end{verbatim} + Read Keyword Subroutines: page~\pageref{FTGREC} + +\begin{verbatim} + FTGREC(unit,key_no, > card,status) + FTGKYN(unit,key_no, > keyword,value,comment,status) + FTGCRD(unit,keyword, > card,status) + FTGNXK(unit,inclist,ninc,exclist,nexc, > card,status) + FTGKEY(unit,keyword, > value,comment,status) + FTGKY[EDJKLS](unit,keyword, > keyval,comment,status) + FTGKN[EDJKLS](unit,keyroot,startno,max_keys, > keyvals,nfound,status) + FTGKYT(unit,keyword, > intval,dblval,comment,status) + FTGUNT(unit,keyword, > units,status) +\end{verbatim} + Modify Keyword Subroutines: page~\pageref{FTMREC} + +\begin{verbatim} + FTMREC(unit,key_no,card, > status) + FTMCRD(unit,keyword,card, > status) + FTMNAM(unit,oldkey,keyword, > status) + FTMCOM(unit,keyword,comment, > status) + FTMKY[JKLS](unit,keyword,keyval,comment, > status) + FTMKLS(unit,keyword,keyval,comment, > status) + FTMKY[EDFG](unit,keyword,keyval,decimals,comment, > status) + FTMKYU(unit,keyword,comment, > status) +\end{verbatim} + Update Keyword Subroutines: page~\pageref{FTUCRD} + +\begin{verbatim} + FTUCRD(unit,keyword,card, > status) + FTUKY[JKLS](unit,keyword,keyval,comment, > status) + FTUKLS(unit,keyword,keyval,comment, > status) + FTUKY[EDFG](unit,keyword,keyval,decimals,comment, > status) + FTUKYU(unit,keyword,comment, > status) +\end{verbatim} + Delete Keyword Subroutines: page~\pageref{FTDREC} + +\begin{verbatim} + FTDREC(unit,key_no, > status) + FTDKEY(unit,keyword, > status) +\end{verbatim} + Define Data Scaling Parameters and Undefined Pixel Flags: page~\pageref{FTPSCL} + +\begin{verbatim} + FTPSCL(unit,bscale,bzero, > status) + FTTSCL(unit,colnum,tscal,tzero, > status) + FTPNUL(unit,blank, > status) + FTSNUL(unit,colnum,snull > status) + FTTNUL(unit,colnum,tnull > status) +\end{verbatim} + FITS Primary Array or IMAGE Extension I/O Subroutines: page~\pageref{FTPPR} + +\begin{verbatim} + FTGIDT(unit, > bitpix,status) + FTGIET(unit, > bitpix,status) + FTGIDM(unit, > naxis,status) + FTGISZ(unit, maxdim, > naxes,status) + FTGIPR(unit, maxdim, > bitpix,naxis,naxes,status) + FTPPR[BIJKED](unit,group,fpixel,nelements,values, > status) + FTPPN[BIJKED](unit,group,fpixel,nelements,values,nullval > status) + FTPPRU(unit,group,fpixel,nelements, > status) + FTGPV[BIJKED](unit,group,fpixel,nelements,nullval, > values,anyf,status) + FTGPF[BIJKED](unit,group,fpixel,nelements, > values,flagvals,anyf,status) + FTPGP[BIJKED](unit,group,fparm,nparm,values, > status) + FTGGP[BIJKED](unit,group,fparm,nparm, > values,status) + FTP2D[BIJKED](unit,group,dim1,naxis1,naxis2,image, > status) + FTP3D[BIJKED](unit,group,dim1,dim2,naxis1,naxis2,naxis3,cube, > status) + FTG2D[BIJKED](unit,group,nullval,dim1,naxis1,naxis2, > image,anyf,status) + FTG3D[BIJKED](unit,group,nullval,dim1,dim2,naxis1,naxis2,naxis3, > + cube,anyf,status) + FTPSS[BIJKED](unit,group,naxis,naxes,fpixels,lpixels,array, > status) + FTGSV[BIJKED](unit,group,naxis,naxes,fpixels,lpixels,incs,nullval, > + array,anyf,status) + FTGSF[BIJKED](unit,group,naxis,naxes,fpixels,lpixels,incs, > + array,flagvals,anyf,status) +\end{verbatim} + Table Column Information Subroutines: page~\pageref{FTGCNO} + +\begin{verbatim} + FTGNRW(unit, > nrows, status) + FTGNCL(unit, > ncols, status) + FTGCNO(unit,casesen,coltemplate, > colnum,status) + FTGCNN(unit,casesen,coltemplate, > colnam,colnum,status) + FTGTCL(unit,colnum, > datacode,repeat,width,status) + FTEQTY(unit,colnum, > datacode,repeat,width,status) + FTGCDW(unit,colnum, > dispwidth,status) + FTGACL(unit,colnum, > + ttype,tbcol,tunit,tform,tscal,tzero,snull,tdisp,status) + FTGBCL(unit,colnum, > + ttype,tunit,datatype,repeat,tscal,tzero,tnull,tdisp,status) + FTPTDM(unit,colnum,naxis,naxes, > status) + FTGTDM(unit,colnum,maxdim, > naxis,naxes,status) + FTDTDM(unit,tdimstr,colnum,maxdim, > naxis,naxes, status) + FFGRSZ(unit, > nrows,status) +\end{verbatim} + Low-Level Table Access Subroutines: page~\pageref{FTGTBS} + +\begin{verbatim} + FTGTBS(unit,frow,startchar,nchars, > string,status) + FTPTBS(unit,frow,startchar,nchars,string, > status) + FTGTBB(unit,frow,startchar,nchars, > array,status) + FTPTBB(unit,frow,startchar,nchars,array, > status) +\end{verbatim} + Edit Rows or Columns page~\pageref{FTIROW} + +\begin{verbatim} + FTIROW(unit,frow,nrows, > status) + FTDROW(unit,frow,nrows, > status) + FTDRRG(unit,rowrange, > status) + FTDRWS(unit,rowlist,nrows, > status) + FTICOL(unit,colnum,ttype,tform, > status) + FTICLS(unit,colnum,ncols,ttype,tform, > status) + FTMVEC(unit,colnum,newveclen, > status) + FTDCOL(unit,colnum, > status) + FTCPCL(inunit,outunit,incolnum,outcolnum,createcol, > status); +\end{verbatim} + Read and Write Column Data Routines page~\pageref{FTPCLS} + +\begin{verbatim} + FTPCL[SLBIJKEDCM](unit,colnum,frow,felem,nelements,values, > status) + FTPCN[BIJKED](unit,colnum,frow,felem,nelements,values,nullval > status) + FTPCLX(unit,colnum,frow,fbit,nbit,lray, > status) + FTPCLU(unit,colnum,frow,felem,nelements, > status) + FTGCL(unit,colnum,frow,felem,nelements, > values,status) + FTGCV[SBIJKEDCM](unit,colnum,frow,felem,nelements,nullval, > + values,anyf,status) + FTGCF[SLBIJKEDCM](unit,colnum,frow,felem,nelements, > + values,flagvals,anyf,status) + FTGSV[BIJKED](unit,colnum,naxis,naxes,fpixels,lpixels,incs,nullval, > + array,anyf,status) + FTGSF[BIJKED](unit,colnum,naxis,naxes,fpixels,lpixels,incs, > + array,flagvals,anyf,status) + FTGCX(unit,colnum,frow,fbit,nbit, > lray,status) + FTGCX[IJD](unit,colnum,frow,nrows,fbit,nbit, > array,status) + FTGDES(unit,colnum,rownum, > nelements,offset,status) + FTPDES(unit,colnum,rownum,nelements,offset, > status) +\end{verbatim} + Row Selection and Calculator Routines: page~\pageref{FTFROW} + +\begin{verbatim} + FTFROW(unit,expr,firstrow, nrows, > n_good_rows, row_status, status) + FTFFRW(unit, expr, > rownum, status) + FTSROW(inunit, outunit, expr, > status ) + FTCROW(unit,datatype,expr,firstrow,nelements,nulval, > + array,anynul,status) + FTCALC(inunit, expr, outunit, parName, parInfo, > status) + FTCALC_RNG(inunit, expr, outunit, parName, parInfo, + nranges, firstrow, lastrow, > status) + FTTEXP(unit, expr, > datatype, nelem, naxis, naxes, status) +\end{verbatim} + Celestial Coordinate System Subroutines: page~\pageref{FTGICS} + +\begin{verbatim} + FTGICS(unit, > xrval,yrval,xrpix,yrpix,xinc,yinc,rot,coordtype,status) + FTGTCS(unit,xcol,ycol, > + xrval,yrval,xrpix,yrpix,xinc,yinc,rot,coordtype,status) + FTWLDP(xpix,ypix,xrval,yrval,xrpix,yrpix,xinc,yinc,rot, + coordtype, > xpos,ypos,status) + FTXYPX(xpos,ypos,xrval,yrval,xrpix,yrpix,xinc,yinc,rot, + coordtype, > xpix,ypix,status) +\end{verbatim} + File Checksum Subroutines: page~\pageref{FTPCKS} + +\begin{verbatim} + FTPCKS(unit, > status) + FTUCKS(unit, > status) + FTVCKS(unit, > dataok,hduok,status) + FTGCKS(unit, > datasum,hdusum,status) + FTESUM(sum,complement, > checksum) + FTDSUM(checksum,complement, > sum) + +\end{verbatim} + Time and Date Utility Subroutines: page~\pageref{FTGSDT} + +\begin{verbatim} + FTGSDT( > day, month, year, status ) + FTGSTM(> datestr, timeref, status) + FTDT2S( year, month, day, > datestr, status) + FTTM2S( year, month, day, hour, minute, second, decimals, + > datestr, status) + FTS2DT(datestr, > year, month, day, status) + FTS2TM(datestr, > year, month, day, hour, minute, second, status) +\end{verbatim} + General Utility Subroutines: page~\pageref{FTGHAD} + +\begin{verbatim} + FTGHAD(unit, > curaddr,nextaddr) + FTUPCH(string) + FTCMPS(str_template,string,casesen, > match,exact) + FTTKEY(keyword, > status) + FTTREC(card, > status) + FTNCHK(unit, > status) + FTGKNM(unit, > keyword, keylength, status) + FTPSVC(card, > value,comment,status) + FTKEYN(keyroot,seq_no, > keyword,status) + FTNKEY(seq_no,keyroot, > keyword,status) + FTDTYP(value, > dtype,status) + class = FTGKCL(card) + FTASFM(tform, > datacode,width,decimals,status) + FTBNFM(tform, > datacode,repeat,width,status) + FTGABC(tfields,tform,space, > rowlen,tbcol,status) + FTGTHD(template, > card,hdtype,status) + FTRWRG(rowlist, maxrows, maxranges, > numranges, rangemin, + rangemax, status) +\end{verbatim} + +\chapter{ Parameter Definitions } + +\begin{verbatim} +anyf - (logical) set to TRUE if any of the returned data values are undefined +array - (any datatype except character) array of bytes to be read or written. +bitpix - (integer) bits per pixel: 8, 16, 32, -32, or -64 +blank - (integer) value used for undefined pixels in integer primary array +blank - (integer*8) value used for undefined pixels in integer primary array +blocksize - (integer) 2880-byte logical record blocking factor + (if 0 < blocksize < 11) or the actual block size in bytes + (if 10 < blocksize < 28800). As of version 3.3 of FITSIO, + blocksizes greater than 2880 are no longer supported. +bscale - (double precision) scaling factor for the primary array +bytlen - (integer) length of the data unit, in bytes +bzero - (double precision) zero point for primary array scaling +card - (character*80) header record to be read or written +casesen - (logical) will string matching be case sensitive? +checksum - (character*16) encoded checksum string +colname - (character) ASCII name of the column +colnum - (integer) number of the column (first column = 1) +coltemplate - (character) template string to be matched to column names +comment - (character) the keyword comment field +comments - (character array) keyword comment fields +compid - (integer) the type of computer that the program is running on +complement - (logical) should the checksum be complemented? +coordtype - (character) type of coordinate projection (-SIN, -TAN, -ARC, + -NCP, -GLS, -MER, or -AIT) +cube - 3D data cube of the appropriate datatype +curaddr - (integer) starting address (in bytes) of the CHDU +current - (integer) if not equal to 0, copy the current HDU +datacode - (integer) symbolic code of the binary table column datatype +dataok - (integer) was the data unit verification successful (=1) or + not (= -1). Equals zero if the DATASUM keyword is not present. +datasum - (double precision) 32-bit 1's complement checksum for the data unit +datatype - (character) datatype (format) of the binary table column +datestr - (string) FITS date/time string: 'YYYY-MM-DDThh:mm:ss.ddd', + 'YYYY-MM-dd', or 'dd/mm/yy' +day - (integer) current day of the month +dblval - (double precision) fractional part of the keyword value +decimals - (integer) number of decimal places to be displayed +dim1 - (integer) actual size of the first dimension of the image or cube array +dim2 - (integer) actual size of the second dimension of the cube array +dispwidth - (integer) - the display width (length of string) for a column +dtype - (character) datatype of the keyword ('C', 'L', 'I', or 'F') + C = character string + L = logical + I = integer + F = floating point number +errmsg - (character*80) oldest error message on the internal stack +errtext - (character*30) descriptive error message corresponding to error number +casesen - (logical) true if column name matching is case sensitive +exact - (logical) do the strings match exactly, or were wildcards used? +exclist (character array) list of names to be excluded from search +exists - flag indicating whether the file or compressed file exists on disk +extend - (logical) true if there may be extensions following the primary data +extname - (character) value of the EXTNAME keyword (if not blank) +fbit - (integer) first bit in the field to be read or written +felem - (integer) first pixel of the element vector (ignored for ASCII tables) +filename - (character) name of the FITS file +flagvals - (logical array) True if corresponding data element is undefined +following - (integer) if not equal to 0, copy all following HDUs in the input file +fparm - (integer) sequence number of the first group parameter to read or write +fpixel - (integer) the first pixel position +fpixels - (integer array) the first included pixel in each dimension +frow - (integer) beginning row number (first row of table = 1) +frowll - (integer*8) beginning row number (first row of table = 1) +gcount - (integer) value of the GCOUNT keyword (usually = 1) +group - (integer) sequence number of the data group (=0 for non-grouped data) +hdtype - (integer) header record type: -1=delete; 0=append or replace; + 1=append; 2=this is the END keyword +hduok - (integer) was the HDU verification successful (=1) or + not (= -1). Equals zero if the CHECKSUM keyword is not present. +hdusum - (double precision) 32 bit 1's complement checksum for the entire CHDU +hdutype - (integer) type of HDU: 0 = primary array or IMAGE, 1 = ASCII table, + 2 = binary table, -1 = any HDU type or unknown type +history - (character) the HISTORY keyword comment string +hour - (integer) hour from 0 - 23 +image - 2D image of the appropriate datatype +inclist (character array) list of names to be included in search +incs - (integer array) sampling interval for pixels in each FITS dimension +intval - (integer) integer part of the keyword value +iounit - (integer) value of an unused I/O unit number +iunit - (integer) logical unit number associated with the input FITS file, 1-199 +key_no - (integer) sequence number (starting with 1) of the keyword record +keylength - (integer) length of the keyword name +keyroot - (character) root string for the keyword name +keysadd -(integer) number of new keyword records which can fit in the CHU +keysexist - (integer) number of existing keyword records in the CHU +keyval - value of the keyword in the appropriate datatype +keyvals - (array) value of the keywords in the appropriate datatype +keyword - (character*8) name of a keyword +lray - (logical array) array of logical values corresponding to the bit array +lpixels - (integer array) the last included pixel in each dimension +match - (logical) do the 2 strings match? +maxdim - (integer) dimensioned size of the NAXES, TTYPE, TFORM or TUNIT arrays +max_keys - (integer) maximum number of keywords to search for +minute - (integer) minute of an hour (0 - 59) +month - (integer) current month of the year (1 - 12) +morekeys - (integer) will leave space in the header for this many more keywords +naxes - (integer array) size of each dimension in the FITS array +naxesll - (integer*8 array) size of each dimension in the FITS array +naxis - (integer) number of dimensions in the FITS array +naxis1 - (integer) length of the X/first axis of the FITS array +naxis2 - (integer) length of the Y/second axis of the FITS array +naxis3 - (integer) length of the Z/third axis of the FITS array +nbit - (integer) number of bits in the field to read or write +nchars - (integer) number of characters to read and return +ncols - (integer) number of columns +nelements - (integer) number of data elements to read or write +nelementsll - (integer*8) number of data elements to read or write +nexc (integer) number of names in the exclusion list (may = 0) +nhdu - (integer) absolute number of the HDU (1st HDU = 1) +ninc (integer) number of names in the inclusion list +nmove - (integer) number of HDUs to move (+ or -), relative to current position +nfound - (integer) number of keywords found (highest keyword number) +no_keys - (integer) number of keywords to write in the sequence +nparm - (integer) number of group parameters to read or write +nrows - (integer) number of rows in the table +nrowsll - (integer*8) number of rows in the table +nullval - value to represent undefined pixels, of the appropriate datatype +nextaddr - (integer) starting address (in bytes) of the HDU following the CHDU +offset - (integer) byte offset in the heap to the first element of the array +offsetll - (integer*8) byte offset in the heap to the first element of the array +oldkey - (character) old name of keyword to be modified +ounit - (integer) logical unit number associated with the output FITS file 1-199 +pcount - (integer) value of the PCOUNT keyword (usually = 0) +previous - (integer) if not equal to 0, copy all previous HDUs in the input file +repeat - (integer) length of element vector (e.g. 12J); ignored for ASCII table +rot - (double precision) celestial coordinate rotation angle (degrees) +rowlen - (integer) length of a table row, in characters or bytes +rowlenll - (integer*8) length of a table row, in characters or bytes +rowlist - (integer array) list of row numbers to be deleted in increasing order +rownum - (integer) number of the row (first row = 1) +rowrange- (string) list of rows or row ranges to be deleted +rwmode - (integer) file access mode: 0 = readonly, 1 = readwrite +second (double)- second within minute (0 - 60.9999999999) (leap second!) +seq_no - (integer) the sequence number to append to the keyword root name +simple - (logical) does the FITS file conform to all the FITS standards +snull - (character) value used to represent undefined values in ASCII table +space - (integer) number of blank spaces to leave between ASCII table columns +startchar - (integer) first character in the row to be read +startno - (integer) value of the first keyword sequence number (usually 1) +status - (integer) returned error status code (0 = OK) +str_template (character) template string to be matched to reference string +stream - (character) output stream for the report: either 'STDOUT' or 'STDERR' +string - (character) character string +sum - (double precision) 32 bit unsigned checksum value +tbcol - (integer array) column number of the first character in the field(s) +tdisp - (character) Fortran type display format for the table column +template-(character) template string for a FITS header record +tfields - (integer) number of fields (columns) in the table +tform - (character array) format of the column(s); allowed values are: + For ASCII tables: Iw, Aw, Fww.dd, Eww.dd, or Dww.dd + For binary tables: rL, rX, rB, rI, rJ, rA, rAw, rE, rD, rC, rM + where 'w'=width of the field, 'd'=no. of decimals, 'r'=repeat count + Note that the 'rAw' form is non-standard extension to the + TFORM keyword syntax that is not specifically defined in the + Binary Tables definition document. +theap - (integer) zero indexed byte offset of starting address of the heap + relative to the beginning of the binary table data +tnull - (integer) value used to represent undefined values in binary table +tnullll - (integer*8) value used to represent undefined values in binary table +ttype - (character array) label for table column(s) +tscal - (double precision) scaling factor for table column +tunit - (character array) physical unit for table column(s) +tzero - (double precision) scaling zero point for table column +unit - (integer) logical unit number associated with the FITS file (1-199) +units - (character) the keyword units string (e.g., 'km/s') +value - (character) the keyword value string +values - array of data values of the appropriate datatype +varidat - (integer) size in bytes of the 'variable length data area' + following the binary table data (usually = 0) +version - (real) current revision number of the library +width - (integer) width of the character string field +xcol - (integer) number of the column containing the X coordinate values +xinc - (double precision) X axis coordinate increment at reference pixel (deg) +xpix - (double precision) X axis pixel location +xpos - (double precision) X axis celestial coordinate (usually RA) (deg) +xrpix - (double precision) X axis reference pixel array location +xrval - (double precision) X axis coordinate value at the reference pixel (deg) +ycol - (integer) number of the column containing the X coordinate values +year - (integer) last 2 digits of the year (00 - 99) +yinc - (double precision) Y axis coordinate increment at reference pixel (deg) +ypix - (double precision) y axis pixel location +ypos - (double precision) y axis celestial coordinate (usually DEC) (deg) +yrpix - (double precision) Y axis reference pixel array location +yrval - (double precision) Y axis coordinate value at the reference pixel (deg) +\end{verbatim} + +\chapter{ FITSIO Error Status Codes } + +\begin{verbatim} +Status codes in the range -99 to -999 and 1 to 999 are reserved for future +FITSIO use. + + 0 OK, no error +101 input and output files are the same +103 too many FITS files open at once; all internal buffers full +104 error opening existing file +105 error creating new FITS file; (does a file with this name already exist?) +106 error writing record to FITS file +107 end-of-file encountered while reading record from FITS file +108 error reading record from file +110 error closing FITS file +111 internal array dimensions exceeded +112 Cannot modify file with readonly access +113 Could not allocate memory +114 illegal logical unit number; must be between 1 - 199, inclusive +115 NULL input pointer to routine +116 error seeking position in file + +121 invalid URL prefix on file name +122 tried to register too many IO drivers +123 driver initialization failed +124 matching driver is not registered +125 failed to parse input file URL +126 parse error in range list + +151 bad argument in shared memory driver +152 null pointer passed as an argument +153 no more free shared memory handles +154 shared memory driver is not initialized +155 IPC error returned by a system call +156 no memory in shared memory driver +157 resource deadlock would occur +158 attempt to open/create lock file failed +159 shared memory block cannot be resized at the moment + + +201 header not empty; can't write required keywords +202 specified keyword name was not found in the header +203 specified header record number is out of bounds +204 keyword value field is blank +205 keyword value string is missing the closing quote character +206 illegal indexed keyword name (e.g. 'TFORM1000') +207 illegal character in keyword name or header record +208 keyword does not have expected name. Keyword out of sequence? +209 keyword does not have expected integer value +210 could not find the required END header keyword +211 illegal BITPIX keyword value +212 illegal NAXIS keyword value +213 illegal NAXISn keyword value: must be 0 or positive integer +214 illegal PCOUNT keyword value +215 illegal GCOUNT keyword value +216 illegal TFIELDS keyword value +217 negative ASCII or binary table width value (NAXIS1) +218 negative number of rows in ASCII or binary table (NAXIS2) +219 column name (TTYPE keyword) not found +220 illegal SIMPLE keyword value +221 could not find the required SIMPLE header keyword +222 could not find the required BITPIX header keyword +223 could not find the required NAXIS header keyword +224 could not find all the required NAXISn keywords in the header +225 could not find the required XTENSION header keyword +226 the CHDU is not an ASCII table extension +227 the CHDU is not a binary table extension +228 could not find the required PCOUNT header keyword +229 could not find the required GCOUNT header keyword +230 could not find the required TFIELDS header keyword +231 could not find all the required TBCOLn keywords in the header +232 could not find all the required TFORMn keywords in the header +233 the CHDU is not an IMAGE extension +234 illegal TBCOL keyword value; out of range +235 this operation only allowed for ASCII or BINARY table extension +236 column is too wide to fit within the specified width of the ASCII table +237 the specified column name template matched more than one column name +241 binary table row width is not equal to the sum of the field widths +251 unrecognizable type of FITS extension +252 unrecognizable FITS record +253 END keyword contains non-blank characters in columns 9-80 +254 Header fill area contains non-blank characters +255 Data fill area contains non-blank on non-zero values +261 unable to parse the TFORM keyword value string +262 unrecognizable TFORM datatype code +263 illegal TDIMn keyword value + +301 illegal HDU number; less than 1 or greater than internal buffer size +302 column number out of range (1 - 999) +304 attempt to move to negative file record number +306 attempted to read or write a negative number of bytes in the FITS file +307 illegal starting row number for table read or write operation +308 illegal starting element number for table read or write operation +309 attempted to read or write character string in non-character table column +310 attempted to read or write logical value in non-logical table column +311 illegal ASCII table TFORM format code for attempted operation +312 illegal binary table TFORM format code for attempted operation +314 value for undefined pixels has not been defined +317 attempted to read or write descriptor in a non-descriptor field +320 number of array dimensions out of range +321 first pixel number is greater than the last pixel number +322 attempt to set BSCALE or TSCALn scaling parameter = 0 +323 illegal axis length less than 1 + +340 NOT_GROUP_TABLE 340 Grouping function error +341 HDU_ALREADY_MEMBER +342 MEMBER_NOT_FOUND +343 GROUP_NOT_FOUND +344 BAD_GROUP_ID +345 TOO_MANY_HDUS_TRACKED +346 HDU_ALREADY_TRACKED +347 BAD_OPTION +348 IDENTICAL_POINTERS +349 BAD_GROUP_ATTACH +350 BAD_GROUP_DETACH + +360 NGP_NO_MEMORY malloc failed +361 NGP_READ_ERR read error from file +362 NGP_NUL_PTR null pointer passed as an argument. + Passing null pointer as a name of + template file raises this error +363 NGP_EMPTY_CURLINE line read seems to be empty (used + internally) +364 NGP_UNREAD_QUEUE_FULL cannot unread more then 1 line (or single + line twice) +365 NGP_INC_NESTING too deep include file nesting (infinite + loop, template includes itself ?) +366 NGP_ERR_FOPEN fopen() failed, cannot open template file +367 NGP_EOF end of file encountered and not expected +368 NGP_BAD_ARG bad arguments passed. Usually means + internal parser error. Should not happen +369 NGP_TOKEN_NOT_EXPECT token not expected here + +401 error attempting to convert an integer to a formatted character string +402 error attempting to convert a real value to a formatted character string +403 cannot convert a quoted string keyword to an integer +404 attempted to read a non-logical keyword value as a logical value +405 cannot convert a quoted string keyword to a real value +406 cannot convert a quoted string keyword to a double precision value +407 error attempting to read character string as an integer +408 error attempting to read character string as a real value +409 error attempting to read character string as a double precision value +410 bad keyword datatype code +411 illegal number of decimal places while formatting floating point value +412 numerical overflow during implicit datatype conversion +413 error compressing image +414 error uncompressing image +420 error in date or time conversion + +431 syntax error in parser expression +432 expression did not evaluate to desired type +433 vector result too large to return in array +434 data parser failed not sent an out column +435 bad data encounter while parsing column +436 parse error: output file not of proper type + +501 celestial angle too large for projection +502 bad celestial coordinate or pixel value +503 error in celestial coordinate calculation +504 unsupported type of celestial projection +505 required celestial coordinate keywords not found +506 approximate wcs keyword values were returned +\end{verbatim} +\end{document} diff --git a/external/cfitsio/fitsio.toc b/external/cfitsio/fitsio.toc new file mode 100644 index 0000000..c9b4f7c --- /dev/null +++ b/external/cfitsio/fitsio.toc @@ -0,0 +1,95 @@ +\contentsline {chapter}{\numberline {1}Introduction }{1} +\contentsline {chapter}{\numberline {2} Creating FITSIO/CFITSIO }{3} +\contentsline {section}{\numberline {2.1}Building the Library}{3} +\contentsline {section}{\numberline {2.2}Testing the Library}{6} +\contentsline {section}{\numberline {2.3}Linking Programs with FITSIO}{8} +\contentsline {section}{\numberline {2.4}Getting Started with FITSIO}{8} +\contentsline {section}{\numberline {2.5}Example Program}{8} +\contentsline {section}{\numberline {2.6}Legal Stuff}{10} +\contentsline {section}{\numberline {2.7}Acknowledgments}{10} +\contentsline {chapter}{\numberline {3} A FITS Primer }{13} +\contentsline {chapter}{\numberline {4}FITSIO Conventions and Guidelines }{15} +\contentsline {section}{\numberline {4.1}CFITSIO Size Limitations}{15} +\contentsline {section}{\numberline {4.2}Multiple Access to the Same FITS File}{16} +\contentsline {section}{\numberline {4.3}Current Header Data Unit (CHDU)}{16} +\contentsline {section}{\numberline {4.4}Subroutine Names}{16} +\contentsline {section}{\numberline {4.5}Subroutine Families and Datatypes}{17} +\contentsline {section}{\numberline {4.6}Implicit Data Type Conversion}{17} +\contentsline {section}{\numberline {4.7}Data Scaling}{18} +\contentsline {section}{\numberline {4.8}Error Status Values and the Error Message Stack}{18} +\contentsline {section}{\numberline {4.9}Variable-Length Array Facility in Binary Tables}{19} +\contentsline {section}{\numberline {4.10}Support for IEEE Special Values}{20} +\contentsline {section}{\numberline {4.11}When the Final Size of the FITS HDU is Unknown}{21} +\contentsline {section}{\numberline {4.12}Local FITS Conventions supported by FITSIO}{21} +\contentsline {subsection}{\numberline {4.12.1}Support for Long String Keyword Values.}{21} +\contentsline {subsection}{\numberline {4.12.2}Arrays of Fixed-Length Strings in Binary Tables}{22} +\contentsline {subsection}{\numberline {4.12.3}Keyword Units Strings}{23} +\contentsline {subsection}{\numberline {4.12.4}HIERARCH Convention for Extended Keyword Names}{23} +\contentsline {section}{\numberline {4.13}Optimizing Code for Maximum Processing Speed}{24} +\contentsline {subsection}{\numberline {4.13.1}Background Information: How CFITSIO Manages Data I/O}{25} +\contentsline {subsection}{\numberline {4.13.2}Optimization Strategies}{25} +\contentsline {chapter}{\numberline {5} Basic Interface Routines }{29} +\contentsline {section}{\numberline {5.1}FITSIO Error Status Routines }{29} +\contentsline {section}{\numberline {5.2}File I/O Routines}{30} +\contentsline {section}{\numberline {5.3}Keyword I/O Routines}{32} +\contentsline {section}{\numberline {5.4}Data I/O Routines}{33} +\contentsline {chapter}{\numberline {6} Advanced Interface Subroutines }{35} +\contentsline {section}{\numberline {6.1}FITS File Open and Close Subroutines: }{35} +\contentsline {section}{\numberline {6.2}HDU-Level Operations }{38} +\contentsline {section}{\numberline {6.3}Define or Redefine the structure of the CHDU }{41} +\contentsline {section}{\numberline {6.4}FITS Header I/O Subroutines}{43} +\contentsline {subsection}{\numberline {6.4.1}Header Space and Position Routines }{43} +\contentsline {subsection}{\numberline {6.4.2}Read or Write Standard Header Routines }{43} +\contentsline {subsection}{\numberline {6.4.3}Write Keyword Subroutines }{45} +\contentsline {subsection}{\numberline {6.4.4}Insert Keyword Subroutines }{47} +\contentsline {subsection}{\numberline {6.4.5}Read Keyword Subroutines }{47} +\contentsline {subsection}{\numberline {6.4.6}Modify Keyword Subroutines }{49} +\contentsline {subsection}{\numberline {6.4.7}Update Keyword Subroutines }{50} +\contentsline {subsection}{\numberline {6.4.8}Delete Keyword Subroutines }{50} +\contentsline {section}{\numberline {6.5}Data Scaling and Undefined Pixel Parameters }{51} +\contentsline {section}{\numberline {6.6}FITS Primary Array or IMAGE Extension I/O Subroutines }{52} +\contentsline {section}{\numberline {6.7}FITS ASCII and Binary Table Data I/O Subroutines}{55} +\contentsline {subsection}{\numberline {6.7.1}Column Information Subroutines }{55} +\contentsline {subsection}{\numberline {6.7.2}Low-Level Table Access Subroutines }{58} +\contentsline {subsection}{\numberline {6.7.3}Edit Rows or Columns }{58} +\contentsline {subsection}{\numberline {6.7.4}Read and Write Column Data Routines }{60} +\contentsline {section}{\numberline {6.8}Row Selection and Calculator Routines }{64} +\contentsline {section}{\numberline {6.9}Celestial Coordinate System Subroutines }{65} +\contentsline {section}{\numberline {6.10}File Checksum Subroutines }{67} +\contentsline {section}{\numberline {6.11} Date and Time Utility Routines }{68} +\contentsline {section}{\numberline {6.12}General Utility Subroutines }{69} +\contentsline {chapter}{\numberline {7} The CFITSIO Iterator Function }{75} +\contentsline {chapter}{\numberline {8} Extended File Name Syntax }{77} +\contentsline {section}{\numberline {8.1}Overview}{77} +\contentsline {section}{\numberline {8.2}Filetype}{80} +\contentsline {subsection}{\numberline {8.2.1}Notes about HTTP proxy servers}{80} +\contentsline {subsection}{\numberline {8.2.2}Notes about the stream filetype driver}{81} +\contentsline {subsection}{\numberline {8.2.3}Notes about the gsiftp filetype}{82} +\contentsline {subsection}{\numberline {8.2.4}Notes about the root filetype}{82} +\contentsline {subsection}{\numberline {8.2.5}Notes about the shmem filetype:}{84} +\contentsline {section}{\numberline {8.3}Base Filename}{84} +\contentsline {section}{\numberline {8.4}Output File Name when Opening an Existing File}{86} +\contentsline {section}{\numberline {8.5}Template File Name when Creating a New File}{88} +\contentsline {section}{\numberline {8.6}Image Tile-Compression Specification}{88} +\contentsline {section}{\numberline {8.7}HDU Location Specification}{88} +\contentsline {section}{\numberline {8.8}Image Section}{89} +\contentsline {section}{\numberline {8.9}Image Transform Filters}{90} +\contentsline {section}{\numberline {8.10}Column and Keyword Filtering Specification}{92} +\contentsline {section}{\numberline {8.11}Row Filtering Specification}{94} +\contentsline {subsection}{\numberline {8.11.1}General Syntax}{94} +\contentsline {subsection}{\numberline {8.11.2}Bit Masks}{97} +\contentsline {subsection}{\numberline {8.11.3}Vector Columns}{98} +\contentsline {subsection}{\numberline {8.11.4}Good Time Interval Filtering}{99} +\contentsline {subsection}{\numberline {8.11.5}Spatial Region Filtering}{100} +\contentsline {subsection}{\numberline {8.11.6}Example Row Filters}{103} +\contentsline {section}{\numberline {8.12} Binning or Histogramming Specification}{104} +\contentsline {chapter}{\numberline {9}Template Files }{107} +\contentsline {section}{\numberline {9.1}Detailed Template Line Format}{107} +\contentsline {section}{\numberline {9.2}Auto-indexing of Keywords}{108} +\contentsline {section}{\numberline {9.3}Template Parser Directives}{109} +\contentsline {section}{\numberline {9.4}Formal Template Syntax}{109} +\contentsline {section}{\numberline {9.5}Errors}{110} +\contentsline {section}{\numberline {9.6}Examples}{110} +\contentsline {chapter}{\numberline {10} Summary of all FITSIO User-Interface Subroutines }{113} +\contentsline {chapter}{\numberline {11} Parameter Definitions }{121} +\contentsline {chapter}{\numberline {12} FITSIO Error Status Codes }{127} diff --git a/external/cfitsio/fitsio2.h b/external/cfitsio/fitsio2.h new file mode 100644 index 0000000..b6fd66c --- /dev/null +++ b/external/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 +#include +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/external/cfitsio/fpack.c b/external/cfitsio/fpack.c new file mode 100644 index 0000000..8e17bd1 --- /dev/null +++ b/external/cfitsio/fpack.c @@ -0,0 +1,387 @@ +/* FPACK + * R. Seaman, NOAO, with a few enhancements by W. Pence, HEASARC + * + * Calls fits_img_compress in the CFITSIO library by W. Pence, HEASARC + */ + +#include +/* #include */ +#include "fitsio.h" +#include "fpack.h" + +/* ================================================================== */ +int main(int argc, char *argv[]) +{ + fpstate fpvar; + + if (argc <= 1) { fp_usage (); fp_hint (); exit (-1); } + + fp_init (&fpvar); + fp_get_param (argc, argv, &fpvar); + + if (fpvar.listonly) { + fp_list (argc, argv, fpvar); + + } else { + fp_preflight (argc, argv, FPACK, &fpvar); + fp_loop (argc, argv, FPACK, fpvar); + } + + exit (0); +} +/* ================================================================== */ +int fp_get_param (int argc, char *argv[], fpstate *fpptr) +{ + int gottype=0, gottile=0, wholetile=0, iarg, len, ndim, ii, doffset; + char tmp[SZ_STR], tile[SZ_STR]; + + if (fpptr->initialized != FP_INIT_MAGIC) { + fp_msg ("Error: internal initialization error\n"); exit (-1); + } + + tile[0] = 0; + + /* flags must come first and be separately specified + */ + for (iarg = 1; iarg < argc; iarg++) { + if ((argv[iarg][0] == '-' && strlen (argv[iarg]) == 2) || + !strncmp(argv[iarg], "-q", 2) || /* special case */ + !strncmp(argv[iarg], "-g1", 3) || /* special case */ + !strncmp(argv[iarg], "-g2", 3) || /* special case */ + !strncmp(argv[iarg], "-i2f", 4) || /* special case */ + !strncmp(argv[iarg], "-fast", 5) || /* special case */ + !strncmp(argv[iarg], "-n3ratio", 8) || /* special case */ + !strncmp(argv[iarg], "-n3min", 6) || /* special case */ + !strncmp(argv[iarg], "-BETAtable", 10) ) /* special case */ + { + + /* Rice is the default, so -r is superfluous + */ + if ( argv[iarg][1] == 'r') { + fpptr->comptype = RICE_1; + if (gottype) { + fp_msg ("Error: multiple compression flags\n"); + fp_usage (); exit (-1); + } else + gottype++; + + } else if (argv[iarg][1] == 'p') { + fpptr->comptype = PLIO_1; + if (gottype) { + fp_msg ("Error: multiple compression flags\n"); + fp_usage (); exit (-1); + } else + gottype++; + + } else if (argv[iarg][1] == 'g') { + /* test for modifiers following the 'g' */ + if (argv[iarg][2] == '2') + fpptr->comptype = GZIP_2; + else + fpptr->comptype = GZIP_1; + + if (gottype) { + fp_msg ("Error: multiple compression flags\n"); + fp_usage (); exit (-1); + } else + gottype++; +/* + } else if (argv[iarg][1] == 'b') { + fpptr->comptype = BZIP2_1; + if (gottype) { + fp_msg ("Error: multiple compression flags\n"); + fp_usage (); exit (-1); + } else + gottype++; +*/ + } else if (argv[iarg][1] == 'h') { + fpptr->comptype = HCOMPRESS_1; + if (gottype) { + fp_msg ("Error: multiple compression flags\n"); + fp_usage (); exit (-1); + } else + gottype++; + + } else if (argv[iarg][1] == 'd') { + fpptr->comptype = NOCOMPRESS; + if (gottype) { + fp_msg ("Error: multiple compression flags\n"); + fp_usage (); exit (-1); + } else + gottype++; + + } else if (!strcmp(argv[iarg], "-i2f")) { + /* this means convert integer images to float, and then */ + /* quantize and compress the float image. This lossy */ + /* compression method may give higher compression than the */ + /* lossless compression method that is usually applied to */ + /* integer images. */ + + fpptr->int_to_float = 1; + + } else if (!strcmp(argv[iarg], "-n3ratio")) { + /* this is the minimum ratio between the MAD noise sigma */ + /* and the q parameter value in the case where the integer */ + /* image is quantized and compressed like a float image. */ + if (++iarg >= argc) { + fp_usage (); exit (-1); + } else { + fpptr->n3ratio = (float) atof (argv[iarg]); + } + } else if (!strcmp(argv[iarg], "-n3min")) { + /* this is the minimum MAD noise sigma in the case where the */ + /* integer image is quantized and compressed like a float image. */ + if (++iarg >= argc) { + fp_usage (); exit (-1); + } else { + fpptr->n3min = (float) atof (argv[iarg]); + } + } else if (argv[iarg][1] == 'q') { + /* test for modifiers following the 'q' */ + if (argv[iarg][2] == 't') { + fpptr->dither_offset = -1; /* dither based on tile checksum */ + + } else if (isdigit(argv[iarg][2])) { /* is a number appended to q? */ + doffset = atoi(argv[iarg]+2); + + if (doffset == 0) { + fpptr->no_dither = 1; /* don't dither the quantized values */ + } else if (doffset > 0 && doffset <= 10000) { + fpptr->dither_offset = doffset; + } else { + fp_msg ("Error: invalid q suffix\n"); + fp_usage (); exit (-1); + } + } + + if (++iarg >= argc) { + fp_usage (); exit (-1); + } else { + fpptr->quantize_level = (float) atof (argv[iarg]); + } + } else if (argv[iarg][1] == 'n') { + if (++iarg >= argc) { + fp_usage (); exit (-1); + } else { + fpptr->rescale_noise = (float) atof (argv[iarg]); + } + } else if (argv[iarg][1] == 's') { + if (++iarg >= argc) { + fp_usage (); exit (-1); + } else { + fpptr->scale = (float) atof (argv[iarg]); + } + } else if (argv[iarg][1] == 't') { + if (gottile) { + fp_msg ("Error: multiple tile specifications\n"); + fp_usage (); exit (-1); + } else + gottile++; + + if (++iarg >= argc) { + fp_usage (); exit (-1); + } else + strncpy (tile, argv[iarg], SZ_STR); /* checked below */ + + } else if (argv[iarg][1] == 'v') { + fpptr->verbose = 1; + + } else if (argv[iarg][1] == 'w') { + wholetile++; + if (gottile) { + fp_msg ("Error: multiple tile specifications\n"); + fp_usage (); exit (-1); + } else + gottile++; + + } else if (argv[iarg][1] == 'F') { + fpptr->clobber++; /* overwrite existing file */ + + } else if (argv[iarg][1] == 'D') { + fpptr->delete_input++; + + } else if (argv[iarg][1] == 'Y') { + fpptr->do_not_prompt++; + + } else if (argv[iarg][1] == 'S') { + fpptr->to_stdout++; + + } else if (argv[iarg][1] == 'L') { + fpptr->listonly++; + + } else if (argv[iarg][1] == 'C') { + fpptr->do_checksums = 0; + + } else if (argv[iarg][1] == 'T') { + fpptr->test_all = 1; + + } else if (argv[iarg][1] == 'R') { + if (++iarg >= argc) { + fp_usage (); fp_hint (); exit (-1); + } else + strncpy (fpptr->outfile, argv[iarg], SZ_STR); + + } else if (argv[iarg][1] == 'H') { + fp_help (); exit (0); + + } else if (argv[iarg][1] == 'V') { + fp_version (); exit (0); + + } else if (!strcmp(argv[iarg], "-BETAtable")) { + fpptr->do_tables = 1; + fp_msg ("Caution: -BETAtable is for feasibility studies, not general use.\n"); + + } else if (!strcmp(argv[iarg], "-fast")) { + fpptr->do_fast = 1; + + } else { + fp_msg ("Error: unknown command line flag `"); + fp_msg (argv[iarg]); fp_msg ("'\n"); + fp_usage (); fp_hint (); exit (-1); + } + + } else + break; + } + + if (fpptr->scale != 0. && + fpptr->comptype != HCOMPRESS_1 && fpptr->test_all != 1) { + + fp_msg ("Error: `-s' requires `-h or -T'\n"); exit (-1); + } + + if (fpptr->quantize_level == 0) { + + if ((fpptr->comptype != GZIP_1) && (fpptr->comptype != GZIP_2)) { + fp_msg ("Error: `-q 0' only allowed with GZIP\n"); exit (-1); + } + + if (fpptr->int_to_float == 1) { + fp_msg ("Error: `-q 0' not allowed with -i2f\n"); exit (-1); + } + } + + if (wholetile) { + for (ndim=0; ndim < MAX_COMPRESS_DIM; ndim++) + fpptr->ntile[ndim] = (long) 0; + + } else if (gottile) { + len = strlen (tile); + for (ii=0, ndim=0; ii < len; ) { + if (! (isdigit (tile[ii]) || tile[ii] == ',')) { + fp_msg ("Error: `-t' requires comma separated tile dims, "); + fp_msg ("e.g., `-t 100,100'\n"); exit (-1); + } + + if (tile[ii] == ',') { ii++; continue; } + + fpptr->ntile[ndim] = atol (&tile[ii]); + for ( ; isdigit(tile[ii]); ii++); + + if (++ndim > MAX_COMPRESS_DIM) { + fp_msg ("Error: too many dimensions for `-t', max="); + sprintf (tmp, "%d\n", MAX_COMPRESS_DIM); fp_msg (tmp); + exit (-1); + } + } + } + + if (iarg >= argc) { + fp_msg ("Error: no FITS files to compress\n"); + fp_usage (); exit (-1); + } else + fpptr->firstfile = iarg; + + return(0); +} + +/* ================================================================== */ +int fp_usage (void) +{ +fp_msg ("usage: fpack "); +fp_msg ( +"[-r|-h|-g|-p] [-w|-t ] [-q ] [-s ] [-n ] -v \n"); +fp_msg ("more: [-T] [-R] [-F] [-D] [-Y] [-S] [-L] [-C] [-H] [-V] [-i2f]\n"); +return(0); +} + +/* ================================================================== */ +int fp_hint (void) +{ fp_msg (" `fpack -H' for help\n"); +return(0); +} + +/* ================================================================== */ +int fp_help (void) +{ +fp_msg ("fpack, a FITS image compression program. Version "); +fp_version (); +fp_usage (); +fp_msg ("\n"); + +fp_msg ("Flags must be separate and appear before filenames:\n"); +fp_msg (" -r Rice compression [default], or\n"); +fp_msg (" -h Hcompress compression, or\n"); +fp_msg (" -g or -g1 GZIP_1 (per-tile) compression, or\n"); +fp_msg (" -g2 GZIP_2 (per-tile) compression (with byte shuffling), or\n"); +/* +fp_msg (" -b BZIP2 (per-tile) compression, or\n"); +*/ +fp_msg (" -p PLIO compression (only for positive 8 or 16-bit integer images).\n"); +fp_msg (" -d Tile the image without compression (debugging mode).\n"); + +fp_msg (" -w Compress the whole image as a single large tile.\n"); +fp_msg (" -t Comma separated list of tile dimensions [default is row by row].\n"); + +fp_msg (" -q Quantized level spacing when converting floating point images to\n"); +fp_msg (" scaled integers. (+value relative to sigma of background noise;\n"); +fp_msg (" -value is absolute). Default q value of 4 gives a compression ratio\n"); +fp_msg (" of about 6 with very high fidelity (only 0.26% increase in noise).\n"); +fp_msg (" Using q values of 2, or 1 will give compression ratios of\n"); +fp_msg (" about 8, or 10, respectively (with 1.0% or 4.1% noise increase).\n"); +fp_msg (" The scaled quantized values are randomly dithered using a seed \n"); +fp_msg (" value determined from the system clock at run time.\n"); +fp_msg (" Use -q0 instead of -q to suppress random dithering.\n"); +fp_msg (" Use -qt to compute random dithering seed from first tile checksum.\n"); +fp_msg (" Use -qN, (N in range 1 to 10000) to use a specific dithering seed.\n"); +fp_msg (" Floating-point images can be losslessly compressed by selecting\n"); +fp_msg (" the GZIP algorithm and specifying -q 0, but this is slower and often\n"); +fp_msg (" produces much less compression than the default quantization method.\n"); +fp_msg (" -i2f Convert integer images to floating point, then quantize and compress\n"); +fp_msg (" using the specified q level. When used appropriately, this lossy\n"); +fp_msg (" compression method can give much better compression than the normal\n"); +fp_msg (" lossless compression methods without significant loss of information.\n"); +fp_msg (" The -n3ratio and -n3min flags control the minimum noise thresholds;\n"); +fp_msg (" Images below these thresholds will be losslessly compressed.\n"); +fp_msg (" -n3ratio Minimum ratio of background noise sigma divided by q. Default = 1.2.\n"); +fp_msg (" -n3min Minimum background noise sigma. Default = 6. The -i2f flag will be ignored\n"); +fp_msg (" if the noise level in the image does not exceed both thresholds.\n"); +fp_msg (" -s Scale factor for lossy Hcompress [default = 0 = lossless]\n"); +fp_msg (" (+values relative to RMS noise; -value is absolute)\n"); +fp_msg (" -n Rescale scaled-integer images to reduce noise and improve compression.\n"); +fp_msg (" -v Verbose mode; list each file as it is processed.\n"); +fp_msg (" -T Show compression algorithm comparison test statistics; files unchanged.\n"); +fp_msg (" -R Write the comparison test report (above) to a text file.\n"); +fp_msg (" -BETAtable Compress FITS binary tables using prototype method.\n"); +fp_msg (" This option is ONLY for experimental use! Do NOT use\n"); +fp_msg (" on FITS data files that are to be publicly distributed.\n"); +fp_msg (" -fast Used with -BETAtable to favor speed over maximum compression.\n"); + +fp_msg ("\nkeywords shared with funpack:\n"); + +fp_msg (" -F Overwrite input file by output file with same name.\n"); +fp_msg (" -D Delete input file after writing output.\n"); +fp_msg (" -Y Suppress prompts to confirm -F or -D options.\n"); + +fp_msg (" -S Output compressed FITS files to STDOUT.\n"); +fp_msg (" -L List contents; files unchanged.\n"); + +fp_msg (" -C Don't update FITS checksum keywords.\n"); + +fp_msg (" -H Show this message.\n"); +fp_msg (" -V Show version number.\n"); + +fp_msg ("\n FITS files to pack; enter '-' (a hyphen) to read input from stdin stream.\n"); +fp_msg (" Refer to the fpack User's Guide for more extensive help.\n"); +return(0); +} diff --git a/external/cfitsio/fpack.h b/external/cfitsio/fpack.h new file mode 100644 index 0000000..398840f --- /dev/null +++ b/external/cfitsio/fpack.h @@ -0,0 +1,171 @@ +/* used by FPACK and FUNPACK + * R. Seaman, NOAO + * W. Pence, NASA/GSFC + */ + +#include +#include +#include + +/* not needed any more */ +/* #include */ +/* #include */ +/* #include */ + +#define FPACK_VERSION "1.6.0 (Feb 2011)" +/* +VERSION History + +1.6.0 (June 2012) + - Fixed behavior of the "rename" function on Windows platforms so that + it will clobber/delete an existing file before renaming a file to + that name (the rename command behaves differently on POSIX and non-POSIX + environments). + +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/external/cfitsio/fpackutil.c b/external/cfitsio/fpackutil.c new file mode 100644 index 0000000..4829613 --- /dev/null +++ b/external/cfitsio/fpackutil.c @@ -0,0 +1,2391 @@ +/* FPACK utility routines + R. Seaman, NOAO & W. Pence, NASA/GSFC +*/ + +#include +#include +#include + +/* #include "bzlib.h" only for experimental purposes */ + +#if defined(unix) || defined(__unix__) || defined(__unix) +#include +#endif + +#include +#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; + } + } + + 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 defined(unix) || defined(__unix__) || defined(__unix) + /* rename clobbers input on Unix platforms */ + if (rename (outfits, temp) != 0) { + fp_msg ("\nError renaming tmp file to "); + fp_msg (temp); fp_msg ("\n"); exit (-1); + } +#else + /* rename DOES NOT clobber existing files on Windows platforms */ + /* so explicitly remove any existing file before renaming the file */ + remove(temp); + if (rename (outfits, temp) != 0) { + fp_msg ("\nError renaming tmp file to "); + fp_msg (temp); fp_msg ("\n"); exit (-1); + } +#endif + + 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/external/cfitsio/funpack.c b/external/cfitsio/funpack.c new file mode 100644 index 0000000..49b679c --- /dev/null +++ b/external/cfitsio/funpack.c @@ -0,0 +1,168 @@ +/* FUNPACK + * R. Seaman, NOAO + * uses fits_img_compress by W. Pence, HEASARC + */ + +#include "fitsio.h" +#include "fpack.h" + +int main (int argc, char *argv[]) +{ + fpstate fpvar; + + if (argc <= 1) { fu_usage (); fu_hint (); exit (-1); } + + fp_init (&fpvar); + fu_get_param (argc, argv, &fpvar); + + if (fpvar.listonly) { + fp_list (argc, argv, fpvar); + + } else { + fp_preflight (argc, argv, FUNPACK, &fpvar); + fp_loop (argc, argv, FUNPACK, fpvar); + } + + exit (0); +} + +int fu_get_param (int argc, char *argv[], fpstate *fpptr) +{ + int gottype=0, gottile=0, wholetile=0, iarg, len, ndim, ii; + char tmp[SZ_STR], tile[SZ_STR]; + + if (fpptr->initialized != FP_INIT_MAGIC) { + fp_msg ("Error: internal initialization error\n"); exit (-1); + } + + tile[0] = 0; + + /* by default, .fz suffix characters to be deleted from compressed file */ + fpptr->delete_suffix = 1; + + /* flags must come first and be separately specified + */ + for (iarg = 1; iarg < argc; iarg++) { + if (argv[iarg][0] == '-' && strlen (argv[iarg]) == 2) { + + if (argv[iarg][1] == 'F') { + fpptr->clobber++; + fpptr->delete_suffix = 0; /* no suffix in this case */ + + } else if (argv[iarg][1] == 'D') { + fpptr->delete_input++; + + } else if (argv[iarg][1] == 'P') { + if (++iarg >= argc) { + fu_usage (); fu_hint (); exit (-1); + } else + strncpy (fpptr->prefix, argv[iarg], SZ_STR); + + } else if (argv[iarg][1] == 'E') { + if (++iarg >= argc) { + fu_usage (); fu_hint (); exit (-1); + } else + strncpy (fpptr->extname, argv[iarg], SZ_STR); + + } else if (argv[iarg][1] == 'S') { + fpptr->to_stdout++; + + } else if (argv[iarg][1] == 'L') { + fpptr->listonly++; + + } else if (argv[iarg][1] == 'C') { + fpptr->do_checksums = 0; + + } else if (argv[iarg][1] == 'H') { + fu_help (); exit (0); + + } else if (argv[iarg][1] == 'V') { + fp_version (); exit (0); + + } else if (argv[iarg][1] == 'Z') { + fpptr->do_gzip_file++; + + } else if (argv[iarg][1] == 'v') { + fpptr->verbose = 1; + + } else if (argv[iarg][1] == 'O') { + if (++iarg >= argc) { + fu_usage (); fu_hint (); exit (-1); + } else + strncpy (fpptr->outfile, argv[iarg], SZ_STR); + + } else { + fp_msg ("Error: unknown command line flag `"); + fp_msg (argv[iarg]); fp_msg ("'\n"); + fu_usage (); fu_hint (); exit (-1); + } + + } else + break; + } + + if (fpptr->extname[0] && (fpptr->clobber || fpptr->delete_input)) { + fp_msg ("Error: -E option may not be used with -F or -D\n"); + fu_usage (); exit (-1); + } + + if (fpptr->to_stdout && (fpptr->outfile[0] || fpptr->prefix[0]) ) { + + fp_msg ("Error: -S option may not be used with -P or -O\n"); + fu_usage (); exit (-1); + } + + if (fpptr->outfile[0] && fpptr->prefix[0] ) { + fp_msg ("Error: -P and -O options may not be used together\n"); + fu_usage (); exit (-1); + } + + if (iarg >= argc) { + fp_msg ("Error: no FITS files to uncompress\n"); + fu_usage (); exit (-1); + } else + fpptr->firstfile = iarg; + + return(0); +} + +int fu_usage (void) +{ + fp_msg ("usage: funpack [-E ] [-P

] [-O ] [-Z] -v \n");
+        fp_msg ("more:   [-F] [-D] [-S] [-L] [-C] [-H] [-V] \n");
+	return(0);
+}
+
+int fu_hint (void)
+{
+	fp_msg ("      `funpack -H' for help\n");
+	return(0);
+}
+
+int fu_help (void)
+{
+fp_msg ("funpack, decompress fpacked files.  Version ");
+fp_version ();
+fu_usage ();
+fp_msg ("\n");
+
+fp_msg ("Flags must be separate and appear before filenames:\n");
+fp_msg (" -E  Unpack only the list of HDU names or numbers in the file.\n");
+fp_msg (" -P 
    Prepend 
 to create new output filenames.\n");
+fp_msg (" -O    Specify full output file name.\n");
+fp_msg (" -Z          Recompress the output file with host GZIP program.\n");
+fp_msg (" -F          Overwrite input file by output file with same name.\n");
+fp_msg (" -D          Delete input file after writing output.\n");
+fp_msg (" -S          Output uncompressed file to STDOUT file stream.\n");
+fp_msg (" -L          List contents, files unchanged.\n");
+
+fp_msg (" -C          Don't update FITS checksum keywords.\n");
+
+fp_msg (" -v          Verbose mode; list each file as it is processed.\n");
+fp_msg (" -H          Show this message.\n");
+fp_msg (" -V          Show version number.\n");
+
+fp_msg (" \n       FITS files to unpack; enter '-' (a hyphen) to read from stdin.\n");
+fp_msg (" Refer to the fpack User's Guide for more extensive help.\n");
+	return(0);
+}
diff --git a/external/cfitsio/getcol.c b/external/cfitsio/getcol.c
new file mode 100644
index 0000000..f40b91d
--- /dev/null
+++ b/external/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 
+#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/external/cfitsio/getcolb.c b/external/cfitsio/getcolb.c
new file mode 100644
index 0000000..9c2746f
--- /dev/null
+++ b/external/cfitsio/getcolb.c
@@ -0,0 +1,2002 @@
+/*  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 
+#include 
+#include 
+#include 
+#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, maxelem2, 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, maxelem;
+    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, &maxelem2, &startpos, &elemnum, &incre,
+         &repeat, &rowlen, &hdutype, &tnull, snull, status);
+    maxelem = maxelem2;
+
+    /* 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 = 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/external/cfitsio/getcold.c b/external/cfitsio/getcold.c
new file mode 100644
index 0000000..cbee5b0
--- /dev/null
+++ b/external/cfitsio/getcold.c
@@ -0,0 +1,1677 @@
+/*  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 
+#include 
+#include 
+#include 
+#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, maxelem2;
+    long twidth, incre;
+    long ii, xwidth, ntodo;
+    int convert, nulcheck, readcheck = 0;
+    LONGLONG repeat, startpos, elemnum, readptr, tnull;
+    LONGLONG rowlen, rownum, remain, next, rowincre, maxelem;
+    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, &maxelem2, &startpos, &elemnum, &incre,
+         &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0 )
+         return(*status);
+    maxelem = maxelem2;
+
+    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 = 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/external/cfitsio/getcole.c b/external/cfitsio/getcole.c
new file mode 100644
index 0000000..268c16d
--- /dev/null
+++ b/external/cfitsio/getcole.c
@@ -0,0 +1,1680 @@
+/*  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 
+#include 
+#include 
+#include 
+#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, maxelem2, 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, maxelem;
+    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, &maxelem2, &startpos, &elemnum, &incre,
+         &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0 )
+         return(*status);
+    maxelem = maxelem2;
+
+    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 = 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/external/cfitsio/getcoli.c b/external/cfitsio/getcoli.c
new file mode 100644
index 0000000..6ad2956
--- /dev/null
+++ b/external/cfitsio/getcoli.c
@@ -0,0 +1,1902 @@
+/*  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 
+#include 
+#include 
+#include 
+#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, maxelem2, 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, maxelem;
+    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, &maxelem2, &startpos, &elemnum, &incre,
+         &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0 )
+         return(*status);
+    maxelem = maxelem2;
+
+    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 = 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/external/cfitsio/getcolj.c b/external/cfitsio/getcolj.c
new file mode 100644
index 0000000..67a6cde
--- /dev/null
+++ b/external/cfitsio/getcolj.c
@@ -0,0 +1,3728 @@
+/*  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 
+#include 
+#include 
+#include 
+#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, maxelem2, 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, maxelem;
+    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, &maxelem2, &startpos, &elemnum, &incre,
+         &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0 )
+         return(*status);
+    maxelem = maxelem2;
+
+    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 = 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, maxelem2, 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, maxelem;
+    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, &maxelem2, &startpos, &elemnum, &incre,
+         &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0 )
+         return(*status);
+    maxelem = maxelem2;
+
+    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 = 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/external/cfitsio/getcolk.c b/external/cfitsio/getcolk.c
new file mode 100644
index 0000000..3958a59
--- /dev/null
+++ b/external/cfitsio/getcolk.c
@@ -0,0 +1,1895 @@
+/*  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 
+#include 
+#include 
+#include 
+#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, maxelem2, 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, maxelem;
+    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, &maxelem2, &startpos, &elemnum, &incre,
+         &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0 )
+         return(*status);
+    maxelem = maxelem2;
+
+    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 = 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/external/cfitsio/getcoll.c b/external/cfitsio/getcoll.c
new file mode 100644
index 0000000..427247d
--- /dev/null
+++ b/external/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 
+#include 
+#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/external/cfitsio/getcols.c b/external/cfitsio/getcols.c
new file mode 100644
index 0000000..7033d6c
--- /dev/null
+++ b/external/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 
+#include 
+/* stddef.h is apparently needed to define size_t */
+#include 
+#include 
+#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/external/cfitsio/getcolsb.c b/external/cfitsio/getcolsb.c
new file mode 100644
index 0000000..f750681
--- /dev/null
+++ b/external/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 
+#include 
+#include 
+#include 
+#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/external/cfitsio/getcolui.c b/external/cfitsio/getcolui.c
new file mode 100644
index 0000000..b98790a
--- /dev/null
+++ b/external/cfitsio/getcolui.c
@@ -0,0 +1,1908 @@
+/*  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 
+#include 
+#include 
+#include 
+#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, maxelem2, hdutype, xcode, decimals;
+    long twidth, incre;
+    long ii, xwidth, ntodo;
+    int nulcheck;
+    LONGLONG repeat, startpos, elemnum, readptr, tnull;
+    LONGLONG rowlen, rownum, remain, next, rowincre, maxelem;
+    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, &maxelem2, &startpos, &elemnum, &incre,
+         &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0 )
+         return(*status);
+    maxelem = maxelem2;
+
+    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 = 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/external/cfitsio/getcoluj.c b/external/cfitsio/getcoluj.c
new file mode 100644
index 0000000..79fb4b6
--- /dev/null
+++ b/external/cfitsio/getcoluj.c
@@ -0,0 +1,1902 @@
+/*  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 
+#include 
+#include 
+#include 
+#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, maxelem2, hdutype, xcode, decimals;
+    long twidth, incre;
+    long ii, xwidth, ntodo;
+    int nulcheck;
+    LONGLONG repeat, startpos, elemnum, readptr, tnull;
+    LONGLONG rowlen, rownum, remain, next, rowincre, maxelem;
+    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, &maxelem2, &startpos, &elemnum, &incre,
+         &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0 )
+         return(*status);
+    maxelem = maxelem2;
+
+    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 = 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/external/cfitsio/getcoluk.c b/external/cfitsio/getcoluk.c
new file mode 100644
index 0000000..4467fca
--- /dev/null
+++ b/external/cfitsio/getcoluk.c
@@ -0,0 +1,1917 @@
+/*  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 
+#include 
+#include 
+#include 
+#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, maxelem2, hdutype, xcode, decimals;
+    long twidth, incre;
+    long ii, xwidth, ntodo;
+    int nulcheck;
+    LONGLONG repeat, startpos, elemnum, readptr, tnull;
+    LONGLONG rowlen, rownum, remain, next, rowincre, maxelem;
+    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, &maxelem2, &startpos, &elemnum, &incre,
+         &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0 )
+         return(*status);
+    maxelem = maxelem2;
+
+    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 = 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/external/cfitsio/getkey.c b/external/cfitsio/getkey.c
new file mode 100644
index 0000000..09bf13f
--- /dev/null
+++ b/external/cfitsio/getkey.c
@@ -0,0 +1,3242 @@
+/*  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 
+#include 
+#include 
+#include 
+/* stddef.h is apparently needed to define size_t */
+#include 
+#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( void *value,       /* I - pointer to keyword value  */
+            int  *status)      /* IO - error status             */
+/*
+  Free the memory that was previously allocated by CFITSIO, 
+  such as by ffgkls or fits_hdr2str
+*/
+{
+    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  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/external/cfitsio/group.c b/external/cfitsio/group.c
new file mode 100644
index 0000000..2883e72
--- /dev/null
+++ b/external/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 
+#include 
+#include 
+
+#if defined(WIN32) || defined(__WIN32__)
+#include    /* defines the getcwd function on Windows PCs */
+#endif
+
+#if defined(unix) || defined(__unix__)  || defined(__unix)
+#include   /* 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 
+     */
+{
+  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 
+     */
+
+{
+    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/external/cfitsio/group.h b/external/cfitsio/group.h
new file mode 100644
index 0000000..f7aae5b
--- /dev/null
+++ b/external/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/external/cfitsio/grparser.c b/external/cfitsio/grparser.c
new file mode 100644
index 0000000..a5091ea
--- /dev/null
+++ b/external/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  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 
+#include 
+
+#ifdef sparc
+#include 
+#include 
+#endif
+
+#include 
+#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 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[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= '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= '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; itokcnt; 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; itokcnt; 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; jtok[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=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/external/cfitsio/grparser.h b/external/cfitsio/grparser.h
new file mode 100644
index 0000000..56bdea0
--- /dev/null
+++ b/external/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/external/cfitsio/histo.c b/external/cfitsio/histo.c
new file mode 100644
index 0000000..6af856f
--- /dev/null
+++ b/external/cfitsio/histo.c
@@ -0,0 +1,2221 @@
+/*   Globally defined histogram parameters */
+#include 
+#include 
+#include 
+#include 
+#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/external/cfitsio/imcompress.c b/external/cfitsio/imcompress.c
new file mode 100644
index 0000000..6330bf8
--- /dev/null
+++ b/external/cfitsio/imcompress.c
@@ -0,0 +1,9247 @@
+# include 
+# include 
+# include 
+# include 
+# include 
+# include 
+# 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", <rue, 
+        "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", <rue, 
+        "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/external/cfitsio/imcopy.c b/external/cfitsio/imcopy.c
new file mode 100644
index 0000000..253205d
--- /dev/null
+++ b/external/cfitsio/imcopy.c
@@ -0,0 +1,233 @@
+#include 
+#include 
+#include 
+#include "fitsio.h"
+
+int main(int argc, char *argv[])
+{
+    fitsfile *infptr, *outfptr;   /* FITS file pointers defined in fitsio.h */
+    int status = 0, tstatus, ii = 1, iteration = 0, single = 0, hdupos;
+    int hdutype, bitpix, bytepix, naxis = 0, nkeys, datatype = 0, anynul;
+    long naxes[9] = {1, 1, 1, 1, 1, 1, 1, 1, 1};
+    long first, totpix = 0, npix;
+    double *array, bscale = 1.0, bzero = 0.0, nulval = 0.;
+    char card[81];
+
+    if (argc != 3)
+    {
+ printf("\n");
+ printf("Usage:  imcopy inputImage outputImage[compress]\n");
+ printf("\n");
+ printf("Copy an input image to an output image, optionally compressing\n");
+ printf("or uncompressing the image in the process.  If the [compress]\n");
+ printf("qualifier is appended to the output file name then the input image\n");
+ printf("will be compressed using the tile-compressed format.  In this format,\n");
+ printf("the image is divided into rectangular tiles and each tile of pixels\n");
+ printf("is compressed and stored in a variable-length row of a binary table.\n");
+ printf("If the [compress] qualifier is omitted, and the input image is\n");
+ printf("in tile-compressed format, then the output image will be uncompressed.\n");
+ printf("\n");
+ printf("If an extension name or number is appended to the input file name, \n");
+ printf("enclosed in square brackets, then only that single extension will be\n");
+ printf("copied to the output file.  Otherwise, every extension in the input file\n");
+ printf("will be processed in turn and copied to the output file.\n");
+ printf("\n");
+ printf("Examples:\n");
+ printf("\n");
+ printf("1)  imcopy image.fit 'cimage.fit[compress]'\n");
+ printf("\n");
+ printf("    This compresses the input image using the default parameters, i.e.,\n");
+ printf("    using the Rice compression algorithm and using row by row tiles.\n");
+ printf("\n");
+ printf("2)  imcopy cimage.fit image2.fit\n");
+ printf("\n");
+ printf("    This uncompresses the image created in the first example.\n");
+ printf("    image2.fit should be identical to image.fit if the image\n");
+ printf("    has an integer datatype.  There will be small differences\n");
+ printf("    in the pixel values if it is a floating point image.\n");
+ printf("\n");
+ printf("3)  imcopy image.fit 'cimage.fit[compress GZIP 100,100;q 16]'\n");
+ printf("\n");
+ printf("    This compresses the input image using the following parameters:\n");
+ printf("         GZIP compression algorithm;\n");
+ printf("         100 X 100 pixel compression tiles;\n");
+ printf("         quantization level = 16 (only used with floating point images)\n");
+ printf("\n");
+ printf("The full syntax of the compression qualifier is:\n");
+ printf("    [compress ALGORITHM TDIM1,TDIM2,...; q QLEVEL s SCALE]\n");
+ printf("where the allowed ALGORITHM values are:\n");
+ printf("      Rice, HCOMPRESS, HSCOMPRESS, GZIP, or PLIO. \n");
+ printf("       (HSCOMPRESS is a variant of HCOMPRESS in which a small\n");
+ printf("        amount of smoothing is applied to the uncompressed image\n");
+ printf("        to help suppress blocky compression artifacts in the image\n");
+ printf("        when using large values for the 'scale' parameter).\n");
+ printf("TDIMn is the size of the compression tile in each dimension,\n");
+ printf("\n");
+ printf("QLEVEL specifies the quantization level when converting a floating\n");
+ printf("point image into integers, prior to compressing the image.  The\n");
+ printf("default value = 16, which means the image will be quantized into\n");
+ printf("integer levels that are spaced at intervals of sigma/16., where \n");
+ printf("sigma is the estimated noise level in background areas of the image.\n");
+ printf("If QLEVEL is negative, this means use the absolute value for the\n");
+ printf("quantization spacing (e.g. 'q -0.005' means quantize the floating\n");
+ printf("point image such that the scaled integers represent steps of 0.005\n");
+ printf("in the original image).\n");
+ printf("\n");
+ printf("SCALE is the integer scale factor that only applies to the HCOMPRESS\n");
+ printf("algorithm.  The default value SCALE = 0 forces the image to be\n");
+ printf("losslessly compressed; Greater amounts of lossy compression (resulting\n");
+ printf("in smaller compressed files) can be specified with larger SCALE values.\n");
+ printf("\n");
+ printf("\n");
+ printf("Note that it may be necessary to enclose the file names\n");
+ printf("in single quote characters on the Unix command line.\n");
+      return(0);
+    }
+
+    /* Open the input file and create output file */
+    fits_open_file(&infptr, argv[1], READONLY, &status);
+    fits_create_file(&outfptr, argv[2], &status);
+
+    if (status != 0) {    
+        fits_report_error(stderr, status);
+        return(status);
+    }
+
+    fits_get_hdu_num(infptr, &hdupos);  /* Get the current HDU position */
+
+    /* Copy only a single HDU if a specific extension was given */ 
+    if (hdupos != 1 || strchr(argv[1], '[')) single = 1;
+
+    for (; !status; hdupos++)  /* Main loop through each extension */
+    {
+
+      fits_get_hdu_type(infptr, &hdutype, &status);
+
+      if (hdutype == IMAGE_HDU) {
+
+          /* get image dimensions and total number of pixels in image */
+          for (ii = 0; ii < 9; ii++)
+              naxes[ii] = 1;
+
+          fits_get_img_param(infptr, 9, &bitpix, &naxis, naxes, &status);
+
+          totpix = naxes[0] * naxes[1] * naxes[2] * naxes[3] * naxes[4]
+             * naxes[5] * naxes[6] * naxes[7] * naxes[8];
+      }
+
+      if (hdutype != IMAGE_HDU || naxis == 0 || totpix == 0) { 
+
+          /* just copy tables and null images */
+          fits_copy_hdu(infptr, outfptr, 0, &status);
+
+      } else {
+
+          /* Explicitly create new image, to support compression */
+          fits_create_img(outfptr, bitpix, naxis, naxes, &status);
+          if (status) {
+                 fits_report_error(stderr, status);
+                 return(status);
+          }
+
+          if (fits_is_compressed_image(outfptr, &status)) {
+              /* write default EXTNAME keyword if it doesn't already exist */
+	      tstatus = 0;
+              fits_read_card(infptr, "EXTNAME", card, &tstatus);
+	      if (tstatus) {
+	         strcpy(card, "EXTNAME = 'COMPRESSED_IMAGE'   / name of this binary table extension");
+	         fits_write_record(outfptr, card, &status);
+	      }
+          }
+	  	    
+          /* copy all the user keywords (not the structural keywords) */
+          fits_get_hdrspace(infptr, &nkeys, NULL, &status); 
+
+          for (ii = 1; ii <= nkeys; ii++) {
+              fits_read_record(infptr, ii, card, &status);
+              if (fits_get_keyclass(card) > TYP_CMPRS_KEY)
+                  fits_write_record(outfptr, card, &status);
+          }
+
+              /* delete default EXTNAME keyword if it exists */
+/*
+          if (!fits_is_compressed_image(outfptr, &status)) {
+	      tstatus = 0;
+              fits_read_key(outfptr, TSTRING, "EXTNAME", card, NULL, &tstatus);
+	      if (!tstatus) {
+	         if (strcmp(card, "COMPRESSED_IMAGE") == 0)
+	            fits_delete_key(outfptr, "EXTNAME", &status);
+	      }
+          }
+*/
+	  
+          switch(bitpix) {
+              case BYTE_IMG:
+                  datatype = TBYTE;
+                  break;
+              case SHORT_IMG:
+                  datatype = TSHORT;
+                  break;
+              case LONG_IMG:
+                  datatype = TINT;
+                  break;
+              case FLOAT_IMG:
+                  datatype = TFLOAT;
+                  break;
+              case DOUBLE_IMG:
+                  datatype = TDOUBLE;
+                  break;
+          }
+
+          bytepix = abs(bitpix) / 8;
+
+          npix = totpix;
+          iteration = 0;
+
+          /* try to allocate memory for the entire image */
+          /* use double type to force memory alignment */
+          array = (double *) calloc(npix, bytepix);
+
+          /* if allocation failed, divide size by 2 and try again */
+          while (!array && iteration < 10)  {
+              iteration++;
+              npix = npix / 2;
+              array = (double *) calloc(npix, bytepix);
+          }
+
+          if (!array)  {
+              printf("Memory allocation error\n");
+              return(0);
+          }
+
+          /* turn off any scaling so that we copy the raw pixel values */
+          fits_set_bscale(infptr,  bscale, bzero, &status);
+          fits_set_bscale(outfptr, bscale, bzero, &status);
+
+          first = 1;
+          while (totpix > 0 && !status)
+          {
+             /* read all or part of image then write it back to the output file */
+             fits_read_img(infptr, datatype, first, npix, 
+                     &nulval, array, &anynul, &status);
+
+             fits_write_img(outfptr, datatype, first, npix, array, &status);
+             totpix = totpix - npix;
+             first  = first  + npix;
+          }
+          free(array);
+      }
+
+      if (single) break;  /* quit if only copying a single HDU */
+      fits_movrel_hdu(infptr, 1, NULL, &status);  /* try to move to next HDU */
+    }
+
+    if (status == END_OF_FILE)  status = 0; /* Reset after normal error */
+
+    fits_close_file(outfptr,  &status);
+    fits_close_file(infptr, &status);
+
+    /* if error occurred, print out error message */
+    if (status)
+       fits_report_error(stderr, status);
+    return(status);
+}
diff --git a/external/cfitsio/infback.c b/external/cfitsio/infback.c
new file mode 100644
index 0000000..af3a8c9
--- /dev/null
+++ b/external/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/external/cfitsio/inffast.c b/external/cfitsio/inffast.c
new file mode 100644
index 0000000..2f1d60b
--- /dev/null
+++ b/external/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/external/cfitsio/inffast.h b/external/cfitsio/inffast.h
new file mode 100644
index 0000000..e5c1aa4
--- /dev/null
+++ b/external/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/external/cfitsio/inffixed.h b/external/cfitsio/inffixed.h
new file mode 100644
index 0000000..75ed4b5
--- /dev/null
+++ b/external/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/external/cfitsio/inflate.c b/external/cfitsio/inflate.c
new file mode 100644
index 0000000..a8431ab
--- /dev/null
+++ b/external/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 
+
+/*
+   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/external/cfitsio/inflate.h b/external/cfitsio/inflate.h
new file mode 100644
index 0000000..95f4986
--- /dev/null
+++ b/external/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/external/cfitsio/inftrees.c b/external/cfitsio/inftrees.c
new file mode 100644
index 0000000..11e9c52
--- /dev/null
+++ b/external/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/external/cfitsio/inftrees.h b/external/cfitsio/inftrees.h
new file mode 100644
index 0000000..baa53a0
--- /dev/null
+++ b/external/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/external/cfitsio/iraffits.c b/external/cfitsio/iraffits.c
new file mode 100644
index 0000000..d8fc06b
--- /dev/null
+++ b/external/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 		/* define stderr, FD, and NULL */
+#include 
+#include   /* stddef.h is apparently needed to define size_t */
+#include 
+
+#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 =  {/ } */
+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 =  {/ } */
+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 =  {/ } */
+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 =  {/ }
+		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 =  {/ }
+		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
+			   =  {/ }
+			   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/external/cfitsio/iter_a.c b/external/cfitsio/iter_a.c
new file mode 100644
index 0000000..19ea1d1
--- /dev/null
+++ b/external/cfitsio/iter_a.c
@@ -0,0 +1,147 @@
+#include 
+#include 
+#include 
+#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/external/cfitsio/iter_a.f b/external/cfitsio/iter_a.f
new file mode 100644
index 0000000..e622189
--- /dev/null
+++ b/external/cfitsio/iter_a.f
@@ -0,0 +1,224 @@
+      program f77iterate_a
+
+      external flux_rate
+      integer ncols
+      parameter (ncols=3)
+      integer units(ncols), colnum(ncols), datatype(ncols)
+      integer iotype(ncols), offset, rows_per_loop, status
+      character*70 colname(ncols)
+      integer iunit, blocksize
+      character*80 fname
+
+C     include f77.inc -------------------------------------
+C     Codes for FITS extension types
+      integer IMAGE_HDU, ASCII_TBL, BINARY_TBL
+      parameter (
+     &     IMAGE_HDU  = 0,
+     &     ASCII_TBL  = 1,
+     &     BINARY_TBL = 2  )
+
+C     Codes for FITS table data types
+
+      integer TBIT,TBYTE,TLOGICAL,TSTRING,TSHORT,TINT
+      integer TFLOAT,TDOUBLE,TCOMPLEX,TDBLCOMPLEX
+      parameter (
+     &     TBIT        =   1,
+     &     TBYTE       =  11,
+     &     TLOGICAL    =  14,
+     &     TSTRING     =  16,
+     &     TSHORT      =  21,
+     &     TINT        =  31,
+     &     TFLOAT      =  42,
+     &     TDOUBLE     =  82,
+     &     TCOMPLEX    =  83,
+     &     TDBLCOMPLEX = 163  )
+
+C     Codes for iterator column types
+
+      integer InputCol, InputOutputCol, OutputCol
+      parameter (
+     &     InputCol       = 0,
+     &     InputOutputCol = 1,
+     &     OutputCol      = 2  )
+C     End of f77.inc -------------------------------------
+
+
+      iunit = 15
+
+      units(1) = iunit
+      units(2) = iunit
+      units(3) = iunit
+
+C open the file
+      fname = 'iter_a.fit'
+      call ftopen(iunit,fname,1,blocksize,status)
+
+C move to the HDU containing the rate table
+      call ftmnhd(iunit, BINARY_TBL, 'RATE', 0, status)
+
+C Select iotypes for column data
+      iotype(1) = InputCol
+      iotype(2) = InputCol
+      iotype(3) = OutputCol
+
+C Select desired datatypes for column data
+      datatype(1) = TINT
+      datatype(2) = TFLOAT
+      datatype(3) = TFLOAT
+
+C find the column number corresponding to each column
+      call ftgcno( iunit, 0, 'counts', colnum(1), status )
+      call ftgcno( iunit, 0, 'time', colnum(2), status )
+      call ftgcno( iunit, 0, 'rate', colnum(3), status )
+
+C use default optimum number of rows
+      rows_per_loop = 0
+      offset = 0
+
+C apply the rate function to each row of the table
+      print *, 'Calling iterator function...', status
+
+C although colname is not being used, still need to send a string
+C array in the function
+      call ftiter( ncols, units, colnum, colname, datatype, iotype,
+     &      offset, rows_per_loop, flux_rate, 3, status )
+
+      call ftclos(iunit, status)
+      stop
+      end
+
+C***************************************************************************
+C   Sample iterator function that calculates the output flux 'rate' column
+C   by dividing the input 'counts' by the 'time' column.
+C   It also applies a constant deadtime correction factor if the 'deadtime'
+C   keyword exists.  Finally, this creates or updates the 'LIVETIME'
+C   keyword with the sum of all the individual integration times.
+C***************************************************************************
+      subroutine flux_rate(totalrows, offset, firstrow, nrows, ncols,
+     &     units, colnum, datatype, iotype, repeat, status, userData,
+     &     counts, interval, rate )
+
+      integer totalrows, offset, firstrow, nrows, ncols
+      integer units(ncols), colnum(ncols), datatype(ncols)
+      integer iotype(ncols), repeat(ncols)
+      integer userData
+
+C     include f77.inc -------------------------------------
+C     Codes for FITS extension types
+      integer IMAGE_HDU, ASCII_TBL, BINARY_TBL
+      parameter (
+     &     IMAGE_HDU  = 0,
+     &     ASCII_TBL  = 1,
+     &     BINARY_TBL = 2  )
+
+C     Codes for FITS table data types
+
+      integer TBIT,TBYTE,TLOGICAL,TSTRING,TSHORT,TINT
+      integer TFLOAT,TDOUBLE,TCOMPLEX,TDBLCOMPLEX
+      parameter (
+     &     TBIT        =   1,
+     &     TBYTE       =  11,
+     &     TLOGICAL    =  14,
+     &     TSTRING     =  16,
+     &     TSHORT      =  21,
+     &     TINT        =  31,
+     &     TFLOAT      =  42,
+     &     TDOUBLE     =  82,
+     &     TCOMPLEX    =  83,
+     &     TDBLCOMPLEX = 163  )
+
+C     Codes for iterator column types
+
+      integer InputCol, InputOutputCol, OutputCol
+      parameter (
+     &     InputCol       = 0,
+     &     InputOutputCol = 1,
+     &     OutputCol      = 2  )
+C     End of f77.inc -------------------------------------
+
+      integer counts(*)
+      real interval(*),rate(*)
+
+      integer ii, status
+      character*80 comment
+
+C**********************************************************************
+C  must preserve these values between calls
+      real deadtime, livetime
+      common /fluxblock/ deadtime, livetime
+C**********************************************************************
+
+      if (status .ne. 0) return
+
+C    --------------------------------------------------------
+C      Initialization procedures: execute on the first call  
+C    --------------------------------------------------------
+      if (firstrow .eq. 1) then
+         if (ncols .ne. 3) then
+C     wrong number of columns
+            status = -1
+            return
+         endif
+
+         if (datatype(1).ne.TINT .or. datatype(2).ne.TFLOAT .or.
+     &        datatype(3).ne.TFLOAT ) then
+C     bad data type
+            status = -2
+            return
+         endif
+
+C     try to get the deadtime keyword value
+         call ftgkye( units(1), 'DEADTIME', deadtime, comment, status )
+
+         if (status.ne.0) then
+C     default deadtime if keyword doesn't exist
+            deadtime = 1.0
+            status = 0
+         elseif (deadtime .lt. 0.0 .or. deadtime .gt. 1.0) then
+C     bad deadtime value
+            status = -3
+            return
+         endif
+
+         print *, 'deadtime = ', deadtime
+
+         livetime = 0.0
+      endif
+
+C    --------------------------------------------
+C      Main loop: process all the rows of data
+C    --------------------------------------------
+      
+C     NOTE: 1st element of array is the null pixel value!
+C     Loop over elements 2 to nrows+1, not 1 to nrows.
+      
+C     this version ignores null values
+
+C     set the output null value to zero to ignore nulls */
+      rate(1) = 0.0
+      do 10 ii = 2,nrows+1
+         if ( interval(ii) .gt. 0.0) then
+           rate(ii) = counts(ii) / interval(ii) / deadtime
+           livetime = livetime + interval(ii)
+        else
+C     Nonsensical negative time interval
+           status = -3
+           return
+        endif
+ 10   continue
+
+C    -------------------------------------------------------
+C      Clean up procedures:  after processing all the rows  
+C    -------------------------------------------------------
+
+      if (firstrow + nrows - 1 .eq. totalrows) then
+C     update the LIVETIME keyword value
+
+         call ftukye( units(1),'LIVETIME', livetime, 3,
+     &        'total integration time', status )
+         print *,'livetime = ', livetime
+
+      endif
+ 
+      return
+      end
diff --git a/external/cfitsio/iter_a.fit b/external/cfitsio/iter_a.fit
new file mode 100644
index 0000000..0f951cf
--- /dev/null
+++ b/external/cfitsio/iter_a.fit
@@ -0,0 +1,1111 @@
+SIMPLE  =                    T / file does conform to FITS standard             BITPIX  =                   16 / number of bits per data pixel                  NAXIS   =                    0 / number of data axes                            EXTEND  =                    T / FITS dataset may contain extensions            COMMENT   FITS (Flexible Image Transport System) format defined in Astronomy andCOMMENT   Astrophysics Supplement Series v44/p363, v44/p371, v73/p359, v73/p365.COMMENT   Contact the NASA Science Office of Standards and Technology for the   COMMENT   FITS Definition document #100 and other FITS information.             HISTORY    TASK:FMERGE on file ratefile.fits                                    HISTORY   fmerge3.1c at 29/12/97 16:1:37.                                       HISTORY    TASK:FMERGE on file m1.fits                                          HISTORY   fmerge3.1c at 29/12/97 16:2:30.                                       HISTORY    TASK:FMERGE on file m3.fits                                          HISTORY   fmerge3.1c at 29/12/97 16:3:38.                                       HISTORY    TASK:FMERGE on file m5.fits                                          HISTORY   fmerge3.1c at 29/12/97 16:4:15.                                       HISTORY    TASK:FMERGE on file m7.fits                                          HISTORY   fmerge3.1c at 29/12/97 16:5:1.0                                       HISTORY    TASK:FMERGE on file m9.fits                                          HISTORY   fmerge3.1c at 29/12/97 16:6:48.                                       END                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             XTENSION= 'BINTABLE'           / binary table extension                         BITPIX  =                    8 / 8-bit bytes                                    NAXIS   =                    2 / 2-dimensional binary table                     NAXIS1  =                   12 / width of table in bytes                        NAXIS2  =                10000 / number of rows in table                        PCOUNT  =                    0 / size of special data area                      GCOUNT  =                    1 / one data group (required keyword)              TFIELDS =                    3 / number of fields in each row                   TTYPE1  = 'Counts  '           / label for field   1                            TFORM1  = 'J       '           / data format of field: 4-byte INTEGER           TTYPE2  = 'Time    '           / label for field   2                            TFORM2  = 'E       '           / data format of field: 4-byte REAL              TTYPE3  = 'Rate    '           / label for field   3                            TFORM3  = 'E       '           / data format of field: 4-byte REAL              EXTNAME = 'rate    '           / name of this binary table extension            DEADTIME=                  1.0                                                  HISTORY   This FITS file was created by the FCREATE task.                       HISTORY   fcreate3.1 at 29/12/97                                                HISTORY   File modified by user 'pence' with fv  on 97-12-29T15:45:06           HISTORY   File modified by user 'pence' with fv  on 97-12-29T15:54:30           LIVETIME=              30554.5 / total integration time                         HISTORY    TASK:FMERGE copied   26924 rows from file ratefile.fits              HISTORY    TASK:FMERGE appended   26924 rows from file r2.fits                  HISTORY    TASK:FMERGE copied   53848 rows from file m1.fits                    HISTORY    TASK:FMERGE appended   53848 rows from file m2.fits                  HISTORY    TASK:FMERGE copied  107696 rows from file m3.fits                    HISTORY    TASK:FMERGE appended  107696 rows from file m4.fits                  HISTORY    TASK:FMERGE copied  215392 rows from file m5.fits                    HISTORY    TASK:FMERGE appended  215392 rows from file m6.fits                  HISTORY    TASK:FMERGE copied  430784 rows from file m7.fits                    HISTORY    TASK:FMERGE appended  430784 rows from file m8.fits                  HISTORY    TASK:FMERGE copied  861568 rows from file m9.fits                    HISTORY    TASK:FMERGE appended  861568 rows from file m10.fits                 HISTORY   File modified by user 'pence' with fv  on 97-12-30T10:44:37           HISTORY   File modified by user 'pence' with fv  on 97-12-30T10:51:44           HISTORY   ftabcopy V4.0a copied columns from ratefile.fits                      HISTORY   ftabcopy V4.0a at 5/1/98 23:10:24                                     END                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À@@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À@@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ ?€@ÀdA A ?€@ ?€@@@AUU-@ð@À
+?€A @@°?€@ 
\ No newline at end of file
diff --git a/external/cfitsio/iter_b.c b/external/cfitsio/iter_b.c
new file mode 100644
index 0000000..296f4e1
--- /dev/null
+++ b/external/cfitsio/iter_b.c
@@ -0,0 +1,114 @@
+#include 
+#include 
+#include 
+#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/external/cfitsio/iter_b.f b/external/cfitsio/iter_b.f
new file mode 100644
index 0000000..7a2a6e7
--- /dev/null
+++ b/external/cfitsio/iter_b.f
@@ -0,0 +1,193 @@
+      program f77iterate_b
+
+C     external work function is passed to the iterator
+      external str_iter
+
+      integer ncols
+      parameter (ncols=2)
+      integer units(ncols), colnum(ncols), datatype(ncols)
+      integer iotype(ncols), offset, rows_per_loop, status
+      character*70 colname(ncols)
+
+      integer iunit, blocksize
+      character*80 fname
+
+C     include f77.inc -------------------------------------
+C     Codes for FITS extension types
+      integer IMAGE_HDU, ASCII_TBL, BINARY_TBL
+      parameter (
+     &     IMAGE_HDU  = 0,
+     &     ASCII_TBL  = 1,
+     &     BINARY_TBL = 2  )
+
+C     Codes for FITS table data types
+
+      integer TBIT,TBYTE,TLOGICAL,TSTRING,TSHORT,TINT
+      integer TFLOAT,TDOUBLE,TCOMPLEX,TDBLCOMPLEX
+      parameter (
+     &     TBIT        =   1,
+     &     TBYTE       =  11,
+     &     TLOGICAL    =  14,
+     &     TSTRING     =  16,
+     &     TSHORT      =  21,
+     &     TINT        =  31,
+     &     TFLOAT      =  42,
+     &     TDOUBLE     =  82,
+     &     TCOMPLEX    =  83,
+     &     TDBLCOMPLEX = 163  )
+
+C     Codes for iterator column types
+
+      integer InputCol, InputOutputCol, OutputCol
+      parameter (
+     &     InputCol       = 0,
+     &     InputOutputCol = 1,
+     &     OutputCol      = 2  )
+C     End of f77.inc -------------------------------------
+
+      status = 0
+
+      fname = 'iter_b.fit'
+      iunit = 15
+
+C     both columns are in the same FITS file
+      units(1) = iunit
+      units(2) = iunit
+
+C     open the file and move to the correct extension
+      call ftopen(iunit,fname,1,blocksize,status)
+      call ftmnhd(iunit, BINARY_TBL, 'iter_test', 0, status)
+
+C     define the desired columns by name
+      colname(1) = 'Avalue'
+      colname(2) = 'Lvalue'
+
+C     leave column numbers undefined
+      colnum(1) = 0
+      colnum(2) = 0  
+
+C     define the desired datatype for each column: TSTRING & TLOGICAL
+      datatype(1) = TSTRING
+      datatype(2) = TLOGICAL
+
+C     define whether columns are input, input/output, or output only
+C     Both in/out
+      iotype(1) = InputOutputCol
+      iotype(2) = InputOutputCol
+ 
+C     use default optimum number of rows and process all the rows
+      rows_per_loop = 0
+      offset = 0
+
+C     apply the  function to each row of the table
+      print *,'Calling iterator function...', status
+
+      call ftiter( ncols, units, colnum, colname, datatype, iotype,
+     &      offset, rows_per_loop, str_iter, 0, status )
+
+      call ftclos(iunit, status)
+
+C     print out error messages if problem
+      if (status.ne.0) call ftrprt('STDERR', status)
+      stop
+      end
+
+C--------------------------------------------------------------------------
+C
+C   Sample iterator function.
+C
+C--------------------------------------------------------------------------
+      subroutine str_iter(totalrows, offset, firstrow, nrows, ncols,
+     &     units, colnum, datatype, iotype, repeat, status, 
+     &     userData, stringCol, logicalCol )
+
+      integer totalrows,offset,firstrow,nrows,ncols,status
+      integer units(*),colnum(*),datatype(*),iotype(*),repeat(*)
+      integer userData
+      character*(*) stringCol(*)
+      logical logicalCol(*)
+
+      integer ii
+
+C     include f77.inc -------------------------------------
+C     Codes for FITS extension types
+      integer IMAGE_HDU, ASCII_TBL, BINARY_TBL
+      parameter (
+     &     IMAGE_HDU  = 0,
+     &     ASCII_TBL  = 1,
+     &     BINARY_TBL = 2  )
+
+C     Codes for FITS table data types
+
+      integer TBIT,TBYTE,TLOGICAL,TSTRING,TSHORT,TINT
+      integer TFLOAT,TDOUBLE,TCOMPLEX,TDBLCOMPLEX
+      parameter (
+     &     TBIT        =   1,
+     &     TBYTE       =  11,
+     &     TLOGICAL    =  14,
+     &     TSTRING     =  16,
+     &     TSHORT      =  21,
+     &     TINT        =  31,
+     &     TFLOAT      =  42,
+     &     TDOUBLE     =  82,
+     &     TCOMPLEX    =  83,
+     &     TDBLCOMPLEX = 163  )
+
+C     Codes for iterator column types
+
+      integer InputCol, InputOutputCol, OutputCol
+      parameter (
+     &     InputCol       = 0,
+     &     InputOutputCol = 1,
+     &     OutputCol      = 2  )
+C     End of f77.inc -------------------------------------
+
+      if (status .ne. 0) return
+
+C    --------------------------------------------------------
+C      Initialization procedures: execute on the first call  
+C    --------------------------------------------------------
+      if (firstrow .eq. 1) then
+         if (ncols .ne. 2) then
+            status = -1
+            return
+         endif
+         
+         if (datatype(1).ne.TSTRING .or. datatype(2).ne.TLOGICAL) then
+            status = -2
+            return
+         endif
+         
+         print *,'Total rows, No. rows = ',totalrows, nrows
+         
+      endif
+      
+C     -------------------------------------------
+C       Main loop: process all the rows of data 
+C     -------------------------------------------
+      
+C     NOTE: 1st element of array is the null pixel value!
+C     Loop over elements 2 to nrows+1, not 1 to nrows.
+      
+      do 10 ii=2,nrows+1
+         print *, stringCol(ii), logicalCol(ii)
+         if( logicalCol(ii) ) then
+            logicalCol(ii) = .false.
+            stringCol(ii) = 'changed to false'
+         else
+            logicalCol(ii) = .true.
+            stringCol(ii) = 'changed to true'
+         endif
+ 10   continue
+      
+C     -------------------------------------------------------
+C     Clean up procedures:  after processing all the rows  
+C     -------------------------------------------------------
+      
+      if (firstrow + nrows - 1 .eq. totalrows) then
+C     no action required in this case
+      endif
+      
+      return
+      end
+      
diff --git a/external/cfitsio/iter_b.fit b/external/cfitsio/iter_b.fit
new file mode 100644
index 0000000..74dcd9d
Binary files /dev/null and b/external/cfitsio/iter_b.fit differ
diff --git a/external/cfitsio/iter_c.c b/external/cfitsio/iter_c.c
new file mode 100644
index 0000000..bbf9774
--- /dev/null
+++ b/external/cfitsio/iter_c.c
@@ -0,0 +1,171 @@
+#include 
+#include 
+#include 
+#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/external/cfitsio/iter_c.f b/external/cfitsio/iter_c.f
new file mode 100644
index 0000000..f9abeaa
--- /dev/null
+++ b/external/cfitsio/iter_c.f
@@ -0,0 +1,347 @@
+      program f77iterate_c
+C
+C    This example program illustrates how to use the CFITSIO iterator function.
+C
+C    This program creates a 2D histogram of the X and Y columns of an event
+C    list.  The 'main' routine just creates the empty new image, then executes
+C    the 'writehisto' work function by calling the CFITSIO iterator function.
+C
+C    'writehisto' opens the FITS event list that contains the X and Y columns.
+C    It then calls a second work function, calchisto, (by recursively calling
+C    the CFITSIO iterator function) which actually computes the 2D histogram.
+
+C     external work function to be passed to the iterator
+      external writehisto
+
+      integer ncols
+      parameter (ncols=1)
+      integer units(ncols), colnum(ncols), datatype(ncols)
+      integer iotype(ncols), offset, n_per_loop, status
+      character*70 colname(ncols)
+
+      integer naxes(2), ounit, blocksize
+      character*80 fname
+      logical exists
+
+C     include f77.inc -------------------------------------
+C     Codes for FITS extension types
+      integer IMAGE_HDU, ASCII_TBL, BINARY_TBL
+      parameter (
+     &     IMAGE_HDU  = 0,
+     &     ASCII_TBL  = 1,
+     &     BINARY_TBL = 2  )
+
+C     Codes for FITS table data types
+
+      integer TBIT,TBYTE,TLOGICAL,TSTRING,TSHORT,TINT
+      integer TFLOAT,TDOUBLE,TCOMPLEX,TDBLCOMPLEX
+      parameter (
+     &     TBIT        =   1,
+     &     TBYTE       =  11,
+     &     TLOGICAL    =  14,
+     &     TSTRING     =  16,
+     &     TSHORT      =  21,
+     &     TINT        =  31,
+     &     TFLOAT      =  42,
+     &     TDOUBLE     =  82,
+     &     TCOMPLEX    =  83,
+     &     TDBLCOMPLEX = 163  )
+
+C     Codes for iterator column types
+
+      integer InputCol, InputOutputCol, OutputCol
+      parameter (
+     &     InputCol       = 0,
+     &     InputOutputCol = 1,
+     &     OutputCol      = 2  )
+C     End of f77.inc -------------------------------------
+
+C**********************************************************************
+C     Need to make these variables available to the 2 work functions
+      integer xsize,ysize,xbinsize,ybinsize
+      common /histcomm/ xsize,ysize,xbinsize,ybinsize
+C**********************************************************************
+
+      status = 0
+
+      xsize = 480
+      ysize = 480
+      xbinsize = 32
+      ybinsize = 32
+
+      fname = 'histoimg.fit'
+      ounit = 15
+
+C     delete previous version of the file if it exists
+      inquire(file=fname,exist=exists)
+      if( exists ) then
+         open(ounit,file=fname,status='old')
+         close(ounit,status='delete')
+      endif
+ 99   blocksize = 2880
+
+C     create new output image
+      call ftinit(ounit,fname,blocksize,status)
+
+      naxes(1) = xsize
+      naxes(2) = ysize
+
+C     create primary HDU
+      call ftiimg(ounit,32,2,naxes,status)
+
+      units(1) = ounit
+
+C     Define column as TINT and Output
+      datatype(1) = TINT
+      iotype(1) = OutputCol
+
+C     force whole array to be passed at one time
+      n_per_loop = -1
+      offset = 0
+
+C     execute the function to create and write the 2D histogram
+      print *,'Calling writehisto iterator work function... ',status
+
+      call ftiter( ncols, units, colnum, colname, datatype, iotype,
+     &      offset, n_per_loop, writehisto, 0, status )
+
+      call ftclos(ounit, status)
+
+C     print out error messages if problem
+      if (status.ne.0) then
+         call ftrprt('STDERR', status)
+      else
+        print *,'Program completed successfully.'
+      endif
+
+      stop
+      end
+
+C--------------------------------------------------------------------------
+C
+C   Sample iterator function.
+C
+C   Iterator work function that writes out the 2D histogram.
+C   The histogram values are calculated by another work function, calchisto.
+C
+C--------------------------------------------------------------------------
+      subroutine writehisto(totaln, offset, firstn, nvalues, narrays,
+     &     units_out, colnum_out, datatype_out, iotype_out, repeat,
+     &     status, userData, histogram )
+
+      integer totaln,offset,firstn,nvalues,narrays,status
+      integer units_out(narrays),colnum_out(narrays)
+      integer datatype_out(narrays),iotype_out(narrays)
+      integer repeat(narrays)
+      integer histogram(*), userData
+
+      external calchisto
+      integer ncols
+      parameter (ncols=2)
+      integer units(ncols), colnum(ncols), datatype(ncols)
+      integer iotype(ncols), rowoffset, rows_per_loop
+      character*70 colname(ncols)
+
+      integer iunit, blocksize
+      character*80 fname
+
+C     include f77.inc -------------------------------------
+C     Codes for FITS extension types
+      integer IMAGE_HDU, ASCII_TBL, BINARY_TBL
+      parameter (
+     &     IMAGE_HDU  = 0,
+     &     ASCII_TBL  = 1,
+     &     BINARY_TBL = 2  )
+
+C     Codes for FITS table data types
+
+      integer TBIT,TBYTE,TLOGICAL,TSTRING,TSHORT,TINT
+      integer TFLOAT,TDOUBLE,TCOMPLEX,TDBLCOMPLEX
+      parameter (
+     &     TBIT        =   1,
+     &     TBYTE       =  11,
+     &     TLOGICAL    =  14,
+     &     TSTRING     =  16,
+     &     TSHORT      =  21,
+     &     TINT        =  31,
+     &     TFLOAT      =  42,
+     &     TDOUBLE     =  82,
+     &     TCOMPLEX    =  83,
+     &     TDBLCOMPLEX = 163  )
+
+C     Codes for iterator column types
+
+      integer InputCol, InputOutputCol, OutputCol
+      parameter (
+     &     InputCol       = 0,
+     &     InputOutputCol = 1,
+     &     OutputCol      = 2  )
+C     End of f77.inc -------------------------------------
+
+C**********************************************************************
+C     Need to make these variables available to the 2 work functions
+      integer xsize,ysize,xbinsize,ybinsize
+      common /histcomm/ xsize,ysize,xbinsize,ybinsize
+C**********************************************************************
+
+      if (status .ne. 0) return
+
+C     name of FITS table
+      fname = 'iter_c.fit'
+      iunit = 16
+
+C     do sanity checking of input values
+      if (totaln .ne. nvalues) then
+C     whole image must be passed at one time
+         status = -1
+         return
+      endif
+
+      if (narrays .ne. 1) then
+C     number of images is incorrect
+         status = -2
+         return
+      endif
+
+      if (datatype_out(1) .ne. TINT) then
+C     input array has wrong data type
+         status = -3
+         return
+      endif
+
+C     open the file and move to the table containing the X and Y columns
+      call ftopen(iunit,fname,0,blocksize,status)
+      call ftmnhd(iunit, BINARY_TBL, 'EVENTS', 0, status)
+      if (status) return
+   
+C     both the columns are in the same FITS file
+      units(1) = iunit
+      units(2) = iunit
+
+C     desired datatype for each column: TINT
+      datatype(1) = TINT
+      datatype(2) = TINT
+
+C     names of the columns
+      colname(1) = 'X'
+      colname(2) = 'Y'
+
+C     leave column numbers undefined
+      colnum(1) = 0
+      colnum(2) = 0
+
+C     define whether columns are input, input/output, or output only
+C     Both input
+      iotype(1) = InputCol
+      iotype(1) = InputCol
+ 
+C     take default number of rows per iteration
+      rows_per_loop = 0
+      rowoffset = 0
+
+C     calculate the histogram
+      print *,'Calling calchisto iterator work function... ', status
+
+      call ftiter( ncols, units, colnum, colname, datatype, iotype,
+     &      rowoffset, rows_per_loop, calchisto, histogram, status )
+
+      call ftclos(iunit,status)
+      return
+      end
+
+C--------------------------------------------------------------------------
+C
+C   Iterator work function that calculates values for the 2D histogram.
+C
+C--------------------------------------------------------------------------
+      subroutine calchisto(totalrows, offset, firstrow, nrows, ncols,
+     &     units, colnum, datatype, iotype, repeat, status, 
+     &     histogram, xcol, ycol )
+
+      integer totalrows,offset,firstrow,nrows,ncols,status
+      integer units(ncols),colnum(ncols),datatype(ncols)
+      integer iotype(ncols),repeat(ncols)
+      integer histogram(*),xcol(*),ycol(*)
+C     include f77.inc -------------------------------------
+C     Codes for FITS extension types
+      integer IMAGE_HDU, ASCII_TBL, BINARY_TBL
+      parameter (
+     &     IMAGE_HDU  = 0,
+     &     ASCII_TBL  = 1,
+     &     BINARY_TBL = 2  )
+
+C     Codes for FITS table data types
+
+      integer TBIT,TBYTE,TLOGICAL,TSTRING,TSHORT,TINT
+      integer TFLOAT,TDOUBLE,TCOMPLEX,TDBLCOMPLEX
+      parameter (
+     &     TBIT        =   1,
+     &     TBYTE       =  11,
+     &     TLOGICAL    =  14,
+     &     TSTRING     =  16,
+     &     TSHORT      =  21,
+     &     TINT        =  31,
+     &     TFLOAT      =  42,
+     &     TDOUBLE     =  82,
+     &     TCOMPLEX    =  83,
+     &     TDBLCOMPLEX = 163  )
+
+C     Codes for iterator column types
+
+      integer InputCol, InputOutputCol, OutputCol
+      parameter (
+     &     InputCol       = 0,
+     &     InputOutputCol = 1,
+     &     OutputCol      = 2  )
+C     End of f77.inc -------------------------------------
+
+      integer ii, ihisto, xbin, ybin
+
+C**********************************************************************
+C     Need to make these variables available to the 2 work functions
+      integer xsize,ysize,xbinsize,ybinsize
+      common /histcomm/ xsize,ysize,xbinsize,ybinsize
+C**********************************************************************
+
+      if (status .ne. 0) return
+
+C    --------------------------------------------------------
+C      Initialization procedures: execute on the first call  
+C    --------------------------------------------------------
+      if (firstrow .eq. 1) then
+C     do sanity checking of input values
+
+         if (ncols .ne. 2) then
+C     number of arrays is incorrect
+            status = -4
+            return
+         endif
+
+         if (datatype(1).ne.TINT .or. datatype(2).ne.TINT) then
+C     wrong datatypes
+            status = -5
+            return
+         endif
+
+C     initialize the histogram image pixels = 0, including null value
+         do 10 ii = 1, xsize * ysize + 1
+            histogram(ii) = 0
+ 10     continue
+
+      endif
+
+C     ------------------------------------------------------------------
+C       Main loop: increment the 2D histogram at position of each event 
+C     ------------------------------------------------------------------
+
+      do 20 ii=2,nrows+1
+        xbin = xcol(ii) / xbinsize
+        ybin = ycol(ii) / ybinsize
+
+        ihisto = ( ybin * xsize ) + xbin + 2
+        histogram(ihisto) = histogram(ihisto) + 1
+ 20   continue
+
+      return
+      end
+
diff --git a/external/cfitsio/iter_c.fit b/external/cfitsio/iter_c.fit
new file mode 100644
index 0000000..f857674
--- /dev/null
+++ b/external/cfitsio/iter_c.fit
@@ -0,0 +1,701 @@
+SIMPLE  =                    T / file does conform to FITS standard             BITPIX  =                   32 / number of bits per data pixel                  NAXIS   =                    0 / number of data axes                            EXTEND  =                    T / FITS dataset may contain extensions            COMMENT   FITS (Flexible Image Transport System) format defined in Astronomy andCOMMENT   Astrophysics Supplement Series v44/p363, v44/p371, v73/p359, v73/p365.COMMENT   Contact the NASA Science Office of Standards and Technology for the   COMMENT   FITS Definition document #100 and other FITS information.             END                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             XTENSION= 'BINTABLE'  /  FITS 3D BINARY TABLE                                   BITPIX  =                    8  /  Binary data                                  NAXIS   =                    2  /  Table is a matrix                            NAXIS1  =                   16 /  Width of table in bytes                       NAXIS2  =                 5000 /  Number of entries in table                    PCOUNT  =                    0  /  Random parameter count                       GCOUNT  =                    1  /  Group count                                  TFIELDS =                    5 /  Number of fields in each row                  EXTNAME = 'EVENTS  '  /  Table name                                             EXTVER  =                    1  /  Version number of table                      TFORM1  = '1I      '  /  Data type for field                                    TTYPE1  = 'X       '  /  Label for field                                        TUNIT1  = '        '  /  Physical units for field                               TFORM2  = '1I      '  /  Data type for field                                    TTYPE2  = 'Y       '  /  Label for field                                        TUNIT2  = '        '  /  Physical units for field                               TFORM3  = '1I      '  /  Data type for field                                    TTYPE3  = 'PHA     '  /  Label for field                                        TUNIT3  = '        '  /  Physical units for field                               TFORM4  = '1D      '  /  Data type for field                                    TTYPE4  = 'TIME    '  /  Label for field                                        TUNIT4  = '        '  /  Physical units for field                               TFORM5  = '1I      '  /  Data type for field                                    TTYPE5  = 'DY      '  /  Label for field                                        TUNIT5  = '        '  /  Physical units for field                               TLMIN1  =                    1                                                  TLMAX1  =                15360                                                  TLMIN2  =                    1                                                  TLMAX2  =                15360                                                  NAXLEN  =                    2  /  Number of QPOE axes                          AXLEN1  =                15360  /  Dim. of qpoe axis 1                          AXLEN2  =                15360  /  Dim. of qpoe axis 2                          TELESCOP= 'ROSAT   '  /  telescope (mission) name                               INSTRUME= 'PSPC    '  /  instrument (detector) name                             RADECSYS= 'FK5     '  /  WCS for this file (e.g. Fk4)                           EQUINOX =           2.000000E3  /  equinox (epoch) for WCS                      CTYPE1  = 'RA---TAN'  /  axis type for dim. 1 (e.g. RA---TAN)                   CTYPE2  = 'DEC--TAN'  /  axis type for dim. 2 (e.g. DEC--TAN)                   CRVAL1  =           8.588000E1  /  sky coord of 1st axis (deg.)                 CRVAL2  =           6.926986E1  /  sky coord of 2nd axis (deg.)                 CDELT1  =         -1.388889E-4  /  x degrees per pixel                          CDELT2  =          1.388889E-4  /  y degrees per pixel                          CRPIX1  =           7.680000E3  /  x pixel of tangent plane direction           CRPIX2  =           7.680000E3  /  y pixel of tangent plane direction           CROTA2  =           0.000000E0  /  rotation angle (degrees)                     MJD-OBS =           4.905444E4  /  MJD of start of obs.                         DATE-OBS= '08/03/93'  /  date of observation start                              TIME-OBS= '10:30:32'  /  time of observation start                              DATE-END= '11/03/93'  /  date of observation end                                TIME-END= '05:02:18'  /  time of observation end                                XS-OBSID= 'US800282P.N1    '  /  observation ID                                 XS-SEQPI= 'ROTS, DR., ARNOLD,H.                                           '  /  XS-SUBIN=                    2  /  subinstrument id                             XS-OBSV =               800282  /  observer id                                  XS-CNTRY= 'USA     '  /  country where data was processed                       XS-FILTR=                    0  /  filter id: 0=none, 1=PSPC boron              XS-MODE =                    1  /  pointing mode: 1=point,2=slew,3=scan         XS-DANG =           0.000000E0  /  detector roll angle (degrees)                XS-MJDRD=                48043  /  integer portion of mjd for SC clock start    XS-MJDRF= 8.797453703700740E-1  /  fractional portion of mjd for SC clock start XS-EVREF=                    0  /  day offset from mjdrday to evenr start times XS-TBASE=  0.000000000000000E0  /  seconds from s/c clock start to obs start    XS-ONTI =  1.476600000000000E4  /  on time (seconds)                            XS-LIVTI=  1.476600000000000E4  /  live time (seconds)                          XS-DTCOR=           1.000000E0  /  dead time correction                         XS-BKDEN=           0.000000E0  /  bkgd density cts/arcmin**2                   XS-MINLT=           0.000000E0  /  min live time factor                         XS-MAXLT=           0.000000E0  /  max live time factor                         XS-XAOPT=           0.000000E0  /  avg. opt. axis x in degrees from tangent planXS-YAOPT=           0.000000E0  /  avg. opt. axis y in degrees from tangent planXS-XAOFF=           0.000000E0  /  avg x aspect offset (degrees)                XS-YAOFF=           0.000000E0  /  avg y aspect offset (degrees)                XS-RAROT=           0.000000E0  /  avg aspect rotation (degrees)                XS-XARMS=           0.000000E0  /  avg x aspect RMS (arcsec)                    XS-YARMS=           0.000000E0  /  avg y aspect RMS (arcsec)                    XS-RARMS=           0.000000E0  /  avg aspect rotation RMS (degrees)            XS-RAPT =           8.588000E1  /  nominal right ascension (degrees)            XS-DECPT=           6.926986E1  /  nominal declination (degrees)                XS-XPT  =                 4096  /  target pointing direction (pixels)           XS-YPT  =                 4096  /  target pointing direction (pixels)           XS-XDET =                 8192  /  x dimen. of detector                         XS-YDET =                 8192  /  y dimen. of detector                         XS-FOV  =                    0  /  field of view (degrees)                      XS-INPXX=          2.595021E-4  /  original degrees per pixel                   XS-INPXY=          2.595021E-4  /  original degrees per pixel                   XS-XDOPT=           4.119000E3  /  detector opt. axis x in detector pixels      XS-YDOPT=           3.929000E3  /  detector opt. axis y in detector pixels      XS-CHANS=                  256  /  pha channels                                 TDISP4  = 'I12     '                                                            HISTORY   modified by pence on Thu Apr 24 15:04:08 EDT 1997                     HISTORY   modified by pence on Thu Apr 24 15:07:24 EDT 1997                     TDISP5  = 'I4      '                                                            HISTORY   modified by pence on Thu Apr 24 16:06:08 EDT 1997                     HISTORY   File modified by user 'pence' with fv  on 97-11-25T14:34:58           HISTORY   File modified by user 'pence' with fv  on 98-01-12T14:03:09           HISTORY   File modified by user 'pence' with fv  on 98-02-06T15:18:24           END                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             óµA”ÑU=àq1±&ØA”ÑUAÀ	=#t6A”ÑVo€ö(²RA”ÑX/@¸)P#½FA”ÑX‰@Y[	ƒA”ÑXÕÀê
,~A”ÑY” @ SÁ?A”ÑZ¢ W^A”ÑZÄ€Ó},ŸaA”ÑZï@¨LzA”Ñ[":$÷A”Ñ^àNr€A”Ñ_MÀuëA”Ñ_†@˜'êìnA”Ñ`g@
÷ 	NA”Ñ`§àç$þA”Ñaƒ@ª°*Ÿ	A”Ña¡¢+91í2A”Ña¦À©%	¢A”Ñb tB( A”ÑbD@	Ñ*vhA”ÑbÐ I X,4A”ÑbÛ@O¤®A”Ñc7e$¬.+A”Ñc] 3	OA”ÑdW2+ý©
A”Ñd†€¹*2ÊIA”Ñd¦ C	é )A”Ñd×`ö[#A”Ñf$à™&•^A”Ñf® M
ƒA”ÑgoÀÙ“ÑxA”ÑgÀ å!%ÚA”ÑjS-`hA”ÑkàÄ\×
A”Ñk|À"0ç$SA”Ñl€@
+Œ?-aTA”Ñl×@à f)ü
+A”Ñm~às1\#âA”Ñm¨`
+Á äA”Ñmà`-V%ÍA”Ñnü œ
½¿A”Ñq×`¶5í	A”Ñr{€©Ê“-A”ÑrÆàæ#*)ë
+A”ÑrÛ Ps	€A”Ñsg€›'Ú 
A”ÑtìÅyA”Ñu@ PwXA”Ñv`à#cA”Ñyã@3+ËKA”Ñ{/ §!” \/A”Ñ{Q@
u,D
bA”Ñ{`÷
Ä:
+A”Ñ{ó@g"Ç'*%A”Ñ|À	ÄM*¨SA”Ñ} 	'2^Q
A”Ñ}s f ›“A”Ñ~0`×"0YA”Ñ~ØÀêŠ
+eA”ÑÀë%ÄDIA”Ñ`!v3A”Ñ}ÀT"»-A”Ñ­€½î*¼	A”Ñ€þ X,AN
A”Ñ„¿`ƒ($_A”Ñ„Éà
+î¬éOA”Ñ„Ü`ëi:A”Ñ…Ï v*¦-DA”ц´€
 ç0A”цï Ûy	A”чÀš§	A”чL ƒ$¨)oA”ч™À)¢-bA”чæ@
+ÝA”шâà(
Z(A”Ñ‹&@+ A”ÑŒý 	¬#±kA”Ñ/91Ö
A”ÑŽh ‚¥¹4A”ÑŽo 'ASA”Ñ޾,/B%vA”Ñ–à	íÙ3œA”Ñji!?åA”Ѥ€ë{+×A”ÑÀf."D:A”Ñê ¼l3A”Ñõ€üP'×	A”Ñ‘®`
+Ð1°
+A”Ñ’k DÝzA”Ñ”&…=A”Ñ•‹ vy# 
+A”Ñ–@
Q3&A”Ñ–ˆÀ	h'À3&0A”Ñ–À
+á(A”Ñ™àA/ÑÄA”Ñš€¡ï! ~A”Ñš: 
Öˆ‹A”Ñš¬@èSA”Ñ›& ˜	V2A”Ñžy *Ì&8A”ÑŸ‘@ØÍZA”Ñ v`#
YÁ¡A”Ñ£L€”S×A”Ñ£a@çA”Ѥ€Í1,PA”Ѥn ’/3xwA”ѤÉÍ7 PA”ѥ꠿*E	A”Ѧ« <3ŠëA”ѧ ¸ÊpA”ѧ=`?&ÒA”ѧÝ@zÊ–A”Ѩ\€h0¢¥A”ѨŒ 
õ“#ë?A”Ѩɠ
(U+DA”Ñ©b*:BA”Ñ©Ýà¢$$A”Ñ©à-3DÙA”ѪLÔ*[3'
A”Ѫ¡Ú³}zA”ÑÃl`
+$s
+ØA”Ñŵà—Ÿ.Å^A”ÑÅØà
+§$_'A”ÑÈç mF1A”ÑÊ’€žÿ+ŠA”ÑË`O&¿A”ÑËÖ s
ã7A”ÑËýà×"™A”ÑÍJ@/ P4ˆEA”ÑÍ»`ì
¸
+A”ÑÎÀàË+à
A”ÑÎá`×þ'È
A”ÑÏú€
+Û/Ù
A”ÑЛÀÐÍA”ÑÑh ð#
+A”ÑÒàß+Ì"SEA”ÑÒ ©©A”ÑÒ« ø/Ž 35A”ÑÒÂÀ•	¾)7ÛA”ÑÓïà	áÐ4¦A”ÑÔ4 ®ln	A”ÑÔ:@2%åõ
+A”ÑÔZ 
?aÄFA”ÑÔi€”.]´FA”ÑÕ*€
+
+ JA”ÑÕn`äÚ-§
A”ÑÖ`æ+ŠXA”ÑÖU`¥v A”ÑÖìW ŠA”Ñ×,à4`!A”Ñ©@"ÝdA”Ñ« C(ØjA”ѰÀ%,·&¨A”Ñæ€	>¤%L
Aӄo@
+¿¥AA”ÑÀ"!!®#A”Ѳ
3$À
A”Ñý`H/™&XA”Ñ 	h(-3ŠA”Ñ- ¢-øA”Ñ[À“,˜A”Ñc`+%$A”ÑúÀÇ4ïSA”Ñ-zâ3â
+A”ÑË 0(Õ%A”ÑÛÀ
+J	]{A”Ñ €y!7ÂA”Ñ‘ \'È
A”ÑÌ`	}&ø&|A”Ñù 	¥&,Ô	A”Ñ	ë`eç9A”Ñ
+—`d%x.A”Ñà"±*WA”Ñp€Õˆ}A”Ñ5`õâ(ÛA”ÑO€È+ÌŠ9A”ÑXÀ|"rAA”Ñy€Å\'«„A”Ñ7@	ÿÁ%
Aӄ
+€^
™
8A”Ñe ‚)0ñA”Ѽ ÷31«A”ѯWA”Ñ‚ 
ó"
+ØA”Ѱ€¤
+RÕA”Ñ€<°s-A”ÑÃ`A °cA”Ñ`L2‹ªA”Ñ@
,º'B@A”Ñ?à,	ðA”ÑÄ`uþ(-A”Ñ×@
+…;A”ÑûIãwA”ÑC !% A”Ñmq%§,d+A”Ñq –÷
A”Ñè ,1{—
A”ÑÕ`xv-tA”Ñk@k'£*ÒA”Ñ4`Pc-sA”Ñ ò m!IŸSA”Ñ!¼àh!ü`
+A”Ñ!í`÷CÖA”Ñ!òÂ$ÖÔA”Ñ"Š¢%,
++A”Ñ#PÀkó;1A”Ñ$.€æ"¼6A”Ñ$•àl!H
A”Ñ&- g%àA”Ñ'ý@
+‡4`PA”Ñ(Z@Ú_¨A”Ñ(È )-(”%A”Ñ)P€	¶	…A”Ñ)w,)œ¶A”Ñ)ì`
ª>ŒA”Ñ*A ©±¦A”Ñ,*@f€°
A”Ñ-I#ê{]A”Ñ-í@_t÷
A”Ñ.Ö yçn;A”Ñ/O Šb6œ	A”Ñ/‹ ….'A”Ñ/öü4²vA”Ñ4S`†·/A”Ñ5€ú%;A”Ñ5à
+Ôû¾4A”Ñ5./«ëA”Ñ5¹ ¤%{­A”Ñ6ˆ`¹!A”Ñ6ï`
R×3ÏA”Ñ7_ÿ0r
A”Ñ7µàûr!mA”Ñ8@
Ü>iA”Ñ9ƒ`Û/ŒÞWA”Ñ:f W·fA”Ñ;" y Ç"A”Ñ;:€ñé
‹
A”Ñ;S ³.b!Ð"A”Ñ;c`Æþ )eA”Ñ>@
ç)î"t{A”Ñ>à°íÑ-A”Ñ?=`ù	N&D5A”Ñ?g xb§0A”Ñ?å @Eï	A”Ñ?ùÀ¿Ü PA”Ñ@'à‰Ç$üTA”Ñ@V Ù.þVOA”Ñ@à Ë¢•
A”Ñ@ú`ˆ×ÓA”ÑAW`s!È·A”ÑBÖ e,£s*A”ÑCX wæ1Ù
+A”ÑCi€ñ$ùZ$A”ÑC— D
+_(UA”ÑDî€
+QJëA”ÑE& —wA”ÑFÙ(%Ü'A”ÑF÷ b
1)Õ2A”ÑG
+à	^
+^nA”ÑG;à¹-+§A”ÑHÀ¢ÎÎA”ÑJ€àóþ–GA”ÑJ› ®#$ÑA”ÑJ±@h1"%œ
A”ÑJú@	¡0"sA”ÑMo 
€3à›A”ÑNq ÏÜA”ÑOø’/ñjA”ÑPc D#;(e
A”ÑQ@ïâ-¡A”ÑQ¨à\4d
+A”ÑQþ |eA”ÑR)`G
+èA”ÑR- “8,dA”ÑR3$x­3A”ÑTÉàŽ&¸&A”ÑV‚ 
+*È
BAA”ÑWdÀù£× A”ÑW@óù3ëjA”ÑY8àÇ1m ¼A”ÑYóÀ?!ÃÎ
+A”Ñ\÷ (4Ì	A”Ñ]Π³FPTA”Ñ^+Àû4È	A”Ñ^é@)1¹* 	A”Ñ_“`ú)*¥>A”Ñ`¾àF.ö{OA”Ñ`àëæ#¬JA”ÑaŽ*˜7A”Ñb÷*4^ƒA”Ñb,`š,ã4A”Ñb¯@W´A”Ñeà¢Z'Ý&A”Ñeà
+–ªf§A”Ñe1À}!ø'jA”Ñe¸€	“
”
A”Ñeá ™1Ö+ˆA”ÑfAà'~A”ÑfI÷!w	A”ÑfrÌ“õA”Ñgˆ†(mA”Ñh@	u	-¯EA”ÑhD¡.‚©6A”ÑiÀ
	ÂØA”Ñjj€À~ÜA”Ñjí #lÁPA”Ñjô³(›"jHA”Ñk â*~JA”ÑkáÀ‚!ù1`A”Ñkè@Jð#\8A”Ñlà
\Ü1A”ÑlH@.ë%A”ÑnÜ€y/’/
A”Ño@Á%C#²A”Ño3`eA$»rA”ÑpSXöA”Ñq€0÷ænA”Ñq5À)I	’A”Ñq“@Ä2A”Ñr2Àü6:A”Ñr·à[Äâ™A”ÑtÀ’%û+VA”Ñt¡N3žA”Ñtì ©
+A”Ñu’@ýG Š
+A”Ñu•€í'–!ô{A”Ñvg 7A”Ñv€-!Ë7A”Ñv­ —í%tA”Ñx뀫'å4 A”Ñy“@ˆ¥1-A”Ñzl`&) ¦A”Ñz­
t#¿A”ÑzÇ Ç(Ÿ3A”ÑzàÀm"Ô*A”Ñ{>À&BA”Ñ{lÀê6‚	A”Ñ}ïÀ(„"iA”Ñ~àÿ%€_	A”Ñ~@Û)i%QA”Ñ~æ 
+Yª4pA”Ñ~þ :&ûA”Ñ€‘@!:÷	A”Ñ€˜à¹Á)‹A”Ñà	ù1ý!A”ÑL?Þ3A”Ñ €™
.	A”Ñ‚BàÖA”Ñ‚Àý$Ÿ1f7A”Ñ‚á`2'“ùA”уI€UØ>A”у €$ž!A”Ñ…{À
üÁLA”Ñ…” ÛA4JLA”Ñ…ä ¾sA”цNàdK?A”ц»€Ð
+ë,|LA”ш5ÀA2ú(†A”ÑŒá&.!ç,A”ÑŒîà1gA”Ñ*@¨!’6–A”ÑŽU ¢
+<A”ÑŽàÈ 3A”ÑQ ¡--f
+A”Ñ‹ ä&˜E
+A”Ñ£àI2V(ÈA”Ѩ2±%ÛA”Ñ‘µ€	™$C5Ê
A”Ñ’ÿ ìÎìA”Ñ“D M-!WA”Ñ”‰ 
Š6~ A”Ñ”ýy¤	A”Ñ–Iì!=9A”Ñ–‹ 1¬ÓA”Ñ–½ 
ò©
m
A”Ñ™a€
+ðA”Ñ™÷ à |mA”Ñš~`
÷'¦³A”Ñšé€<HjA”Ñœo Ì 4ÓA”щ€±ÍA”Ñšà(×!© A”ÑÝ 
Ú&2"vA”Ñž¿ !)˜$ýA”Ñžå 
+™-ˆA”ÑžúàTÔ
+A”ÑŸC ´°A”Ñ¢Y€ÏA”Ñ¢mÀÆËA”Ñ¢™€dY.òA”Ñ£
+ }]A”Ñ£
à5ÓtA”Ñ£¬@¨A”Ѥ–@ï,˜ìA”Ñ¥ A-¦&f	A”Ñ¥£à	£'œ0ž	A”Ñ¥Ë@ˆYA”ѦʀtÖ#¹A”Ѧí 
Tò!A”ѧ¢
†4!	A”ѧ½ «1ì!Y	A”Ѩ$àL#¬A”ѨC
s'y#UA”Ѩ{@Ÿ,
+NA”Ñ©|`±s
Ñ
+A”Ñ©àÛ´&×A”Ѫà
+÷ Ä6A”Ñ«ààSR":A”Ѭ 
+#Ó3A”Ѭ¬€my·A”Ñ¬Ü )åõA”Ѭú 6v"‘%A”Ñ­ 
4"Â#2A”Ñ®€÷çüA”Ñ®tÀÁ}("ŠA”Ñ®àÀ	°A”ѱdŽbB
A”Ѳ„ <.IÃSA”ѲÜlî|A”Ñ´°À£í)A”Ñ´ú jÌn
A”ѵ6@m.™A”ѵj §
÷
+A”ѵØ`BØA”ѵí`|¤hA”Ѷ#à“Úa&A”ѹ Œ‹ÁA”ѹ !÷5ÄA”ѹ£à&'–3QA”Ѻò %.xÍ
A”Ѽ‰`{(ú¢VA”ѽpà5š%˜A”ѽè`	®	“{ A”ѽö ’ª%y-A”ѾO¯)ŽbA”ѾS€0*f'
+PA”ÑÀ:€	‰,6VA”ÑÃDÀÉ@(A”ÑßÀ&œA”ÑÅÓ`!2æ!r	A”ÑÆ'`-^/5A”ÑÇkÀ
+''-A”ÑÈ	 
+Ôs	íA”ÑÉ â_$K[A”ÑÉž“	ŽÂA”ÑÉ·`ë%5 A”ÑÉÍ`
‰B5ßA”ÑÊ3€ò“!A”ÑÊe 
¦4û-A”ÑÊàŒŒA”ÑËÀ
+œ%LA”ÑϧB†MA”ÑÐ`Ø&˜õA”ÑÐ]€©k)A”ÑÑÖ€Y, A”ÑÓiÀ
9-åA”ÑÓûà1—9A”ÑÕ Î;)»A”ÑÕÜ@	‹x A”ÑÖ¸àí ÿ	A”ÑÖð`6´!A”Ñ×T :©A”Ñ×\ 7t(A”ÑØXÀȬ!´	A”ÑØ•`’é–A”ÑØï€$
+1)VA”ÑÙ`
+&bpA”ÑÙÿ@z0z+A”ÑÚ\à##èA”ÑÜS@«",6HŸA”ÑÜ~€ð#;.#A”ÑÜÚ 73¦m%A”ÑÝž`Tj$©A”ÑÝÊ@@) 
A”ÑÞX€
e˜ |
A”ÑÞƒà
¾ÇA”ÑÞ¨à€`4!-A”ÑÞÌ`! ûàoA”Ñ߀ëp.A”Ñß @mú#˜A”Ñái@
X"§*A”Ñ㈠¤!9!OA”Ñå9 
J-~1FA”Ñåg`ü»
+D.A”Ñåà®u1jA”ÑåìÀ?)’!–'A”Ñæ€ŽÒñ
A”ÑæKà‘†Ñ	A”Ñæ™à*(ó&¦A”Ñç¶`	å™6ã%A”ÑêJ F!Ê$¿
A”Ñì€P3¤ºA”ÑíJ€åA”Ñî( Q5*2A”ÑîŽA]A”Ñ𢠌ü6A”ÑñÀ@0
+‰,÷A”ÑñÏ€<4î‡A”ÑôÝ€
+ž¡A”Ñôà (­%oA”Ñ÷i€u!E5ò+A”Ñ÷p`3L	Õ	A”Ñø5@Ú!A”Ñø|@
ã™-­A”Ñù`T^A”ÑùßL.[)A	A”ÑûWÀ<  *
A”Ñü§ —[ÕA”ÑüÀàÞ'Ë
+Ê
A”Ñþ;àѧ!5A”ÑþýÀ
ò¯5e:A”ÑÿI`–Ä‡A”Ñ #@¬#V+uA”Ñ ­@­¼*A”Ñ Ï "ÄPA”Ñ ê Ó!	=A”Ñ è ’""VRA”Ñ 4@
S
+–#¶A”Ñ 	â€
.¬þ6A”Ñ 
+s€œ§îIA”Ñ Š %7|=A”Ñ Ÿ C/

A”Ñ ¼`ô.8€
A”Ñ Ú 
caA”Ñ âàÆ,.A”Ñ 
ÓàuM&¢A”Ñ 
ý &	û*8A”Ñ € 	ºç
A”Ñ  €àXA”Ñ ¬à§&¿DA”Ñ ®à | A”Ñ Là–½A”Ñ Mà[(®"5A”Ñ ÛàS õA”Ñ ® ´,¯CA”Ñ ¬ ¬
"Ï~A”Ñ ³
î3úB5A”Ñ ´`¹îÇDA”Ñ Å€¡U!¾
+A”Ñ   §0é.éA”Ñ 3@s!A”Ñ `@
¢#ô&A”Ñ ÂkSè
A”Ñ è ?QÆA”Ñ ñà
‡1òA”Ñ  >0ŸŸ	A”Ñ hà˜©$[A”Ñ Æ`ܸ'(A”Ñ C5ËA”Ñ U Ç)È
+[A”Ñ ¾ù,$ýA”Ñ ú!ÚA”Ñ O€¤(2¡A”Ñ !­“«A”Ñ #Àu("A”Ñ #`
”]A”Ñ %Ý`¡Ä5Ÿ	A”Ñ &ÞÀ æíA”Ñ 't€=!ýÏA”Ñ ' ¦‡A”Ñ (€	*©eAA”Ñ (:`¹(E  A”Ñ (‚`
7*«A”Ñ (§àÞ'»UA”Ñ )
++!EA”Ñ +€ 
¥ávjA”Ñ +±j#}$A”Ñ +À op6A”Ñ +Ð § ø6¾
+A”Ñ ,*`Ü',@A”Ñ ,Å@Q$1ÓA”Ñ -`?'a1KA”Ñ -Y h1ò±A”Ñ .b`”AÎ~A”Ñ .`Ñ!å
+=A”Ñ .Ÿ ï‰A”Ñ /# þÄ	A”Ñ /Ë*£ú'A”Ñ 0Ä ƒ%’A”Ñ 1;€MÊ‚KA”Ñ 2:@>']ÎA”Ñ 3SÀ	%&VA”Ñ 3Œ@
+Ã? NA”Ñ 3”à~ª:A”Ñ 4¡@ª%Ž(uFA”Ñ 4¹ 	:—+A”Ñ 4àÀœ ÔYA”Ñ 8Àz6C6A”Ñ 8}X@&
+A”Ñ :îÀ
+Þ]¼AA”Ñ <á`Ç0h,oA”Ñ >? ˆö ¾A”Ñ ? r.–A”Ñ A7 ó+uGA”Ñ AÆ Z$ª&w
+Aӄ BG 
+_9
©PA”Ñ C€Óv@A”Ñ Cµ@åa„A”Ñ D„€¢&¿úA”Ñ E1ú™#Š>A”Ñ H* 
¹
+#A”Ñ HeÀ³»øA”Ñ J{À¢ŸxA”Ñ JÔ@*%jZA”Ñ K­`§%¡-^*A”Ñ MÎ`«W4æÜA”Ñ Mã É][A”Ñ N`j­¢A”Ñ N? 5¿ãA”Ñ NX@|
Ã*£A”Ñ P`	rŒ&È`A”Ñ Q˜ .Z¨JA”Ñ R™=
R7A”Ñ S@¥\.÷A”Ñ S¯`e$?SA”Ñ TÛ Äì6W>A”Ñ TçÀÕKÎ
+A”Ñ TéÀLÑA”Ñ U2 ç»_A”Ñ V¾ 2 [.{SA”Ñ Wàbó qA”Ñ X Ì£EA”Ñ X  ÷ú/TA”Ñ Y€½ A”Ñ Z
+`gù,ŒA”Ñ Z—€SZ ÞA”Ñ \c€À6JA”Ñ ]`ÔH}A”Ñ ]€˜	ÕäA”Ñ _%`Ä%
+A”Ñ _–@`?EA”Ñ `FÀ0)f	A”Ñ `³²Ü(A”Ñ b,@¶2¢$m*A”Ñ cŸ
+¾0¢«A”Ñ da ü
Z1A”Ñ eSà?
+‰!A”Ñ e» 
K°A”Ñ e ?-ž±0A”Ñ eîð$CpA”Ñ g4ÀÞ$¤ùA”Ñ gˆà
æ+ÆA”Ñ h p:­A”Ñ h¢€'/ô!¯A”Ñ iÀ]ç5¶)A”Ñ iÀ4
›öA”Ñ jJ o"j4òA”Ñ kÓ Þ!zA”Ñ kØq]KÞA”Ñ l  Ë#ƒ0NA”Ñ mÌÀH)PY
A”Ñ n€·*£¤A”Ñ pÒ`I‹-ZJA”Ñ qØ@–&I/A”Ñ r+@s!o2õ(A”Ñ rÀ,5A”Ñ uÀà
ü	‚&„.A”Ñ uß õ' 	A”Ñ v8€ÇƒuÎA”Ñ v· ‚f¶
+A”Ñ vÙî“"ŠdA”Ñ w˜à
h+™e°A”Ñ w¸ù  $?A”Ñ yÍÀë¢(éA”Ñ {
+€
+¾3f?A”Ñ {à 
„$Õ
+A”Ñ |Ÿ€6Ä©A”Ñ €“`a&Ð'*A”Ñ >@
+(J a*A”Ñ ‚.`
‹-¦)wA”Ñ ƒ`i
Î*ÅA”Ñ ƒ>€	|%$³A”Ñ „H 
2I#òA”Ñ „Œà—ð
™A”Ñ †k€‘!T4­A”Ñ ‡l@"óA”Ñ ˆàA”Ñ ‰íÙ
+HA”Ñ Š- Y3KA”Ñ ŠI *(|.`"A”Ñ ‹äàΦA”Ñ ŒTO
+g(CA”Ñ ŒÖ ÿ.`A”Ñ ¯àè"É4-A”Ñ Ž†@U£ÚA”Ñ Žú@¤¾ñ®A”Ñ !À%ó‘A”Ñ ‘Z`ì0q	,A”Ñ ‘ëàà_8A”Ñ ’*`H2™+­
+A”Ñ ”ÍàþÏ(áSA”Ñ •÷ 	ê'f$¸A”Ñ –vàR"îA”Ñ –à
&V6iA”Ñ –÷àv-×A”Ñ —Ò j	¿A”Ñ ™`Ì+)5A”Ñ ™à
+H"´	+A”Ñ œ`I>#ŸA”Ñ œ0 ]"þsA”Ñ œæ |"$A”Ñ `öJA”Ñ ;`›+
“A”Ñ A 0/¥
A”Ñ Ÿl€F%£*ÅvA”Ñ ¡* 7ÓáA”Ñ ¡Ä@¦r/v
+A”Ñ £¥àþGA”Ñ £Î §!½OA”Ñ ¥±à½&—4â A”Ñ ¥Ó Ês"Y1A”Ñ ¥í@

#Ë
+A”Ñ ¦Àµ!Oe
A”Ñ ¦m
+@
+QA”Ñ ¦è ¿'@+gA”Ñ §àÍ+þ&—Ê*DA”Ñ ©º 	úàA”Ñ ©È@}2Iœ	A”Ñ ©ðàe€"A”Ñ ªÀ
ž-ÚÝA”Ñ ª—à’!àˆA”Ñ ¬Öàî9-	A”Ñ ¬í€ç5d#A”Ñ ®€­('.˜A”Ñ ¯Z ï%ÁA”Ñ ¯jÀt"¢ _A”Ñ ¯Ñ
ó5PA”Ñ ¯ú@
}ŽE
A”Ñ °§ÀTh„A”Ñ ±X@
Ð!'A”Ñ ²=À±)Ñ!#)A”Ñ ³và
&&‚(%A”Ñ ´ 	›ÿ0%7A”Ñ ´U€RÂ
•!A”Ñ ´{f!TA”Ñ ´Œ 
®&¶ê0A”Ñ ´•€<ò/A”Ñ ¶`àÎ`3A”Ñ ¶kà0Œ$D A”Ñ ¶s
,Š2x	A”Ñ ¸ûÀÈ‹¢FA”Ñ ¹%à®f	/A”Ñ ¹+€'ƒA”Ñ ¹êÀFr(ï	A”Ñ º÷à
+ú&yµA”Ñ ¼€ã6ššFA”Ñ ¼Làß*›
A”Ñ ½?àY¿
ÝRA”Ñ ½ºÀÃ0‰A”Ñ ½ý µõ#hA”Ñ ¾nà
 bA”Ñ ¾¶`N0ú";A”Ñ ¿øà2%5E$A”Ñ ÂéàÒ5A”Ñ ÂïÀ„1µ)šA”Ñ ÃQÀ= ¸
?CA”Ñ ÃÂ`G.‚!ŸvA”Ñ Å† « ×!Z4A”Ñ Åº@
’,A$A”Ñ ÆPÀ`†%%
+A”Ñ ÆW@Y¨"A”Ñ Æéà
IP=A”Ñ Èp ¢ oó9A”Ñ Êq U/MøA”Ñ Ë©`,'%A”Ñ ËÑ@‹lÃA”Ñ Ì@#-¸,.
+A”Ñ Ì„`"t	ç{A”Ñ ÌÌ€ù-v9A”Ñ ÎÒ D*p:A”Ñ ÏÈ/¦9A”Ñ Ð@` ˜!ÍA”Ñ ÐF€
Î1¦(ÙA”Ñ Ðþ@ªDA”Ñ Óq N!fXA”Ñ ÓŠ@`"r÷A”Ñ Óª N&u*lA”Ñ Ô³ u,)U	A”Ñ ÕÃ`	Ž#=hA”Ñ ×'àl)A”Ñ ×ë€ó4õA”Ñ Ù¹@£ô*¡A”Ñ Ú€Ó$hA”Ñ Û€ò/êë
A”Ñ ÜÉ C™]A”Ñ Üò`!;¨A”Ñ Ý(@#.*ÁtA”Ñ Ý¸€ß2"A”Ñ Þ@Iö
A”Ñ Þt`ð Ž-A”Ñ Þžj2…"
A”Ñ Þ¯ :	È5A”Ñ ß4ÖÍA”Ñ àV 1!Ô!
+A”Ñ à†à
ºO
+§@A”Ñ á±àæ'	'A”Ñ á² 3: âA”Ñ â`Ù4û+l
A”Ñ ä~%0J ËA”Ñ æ?À
©"‚`A”Ñ ç­À
°)È¢A”Ñ çÁ•0!A”Ñ èç _nôÕA”Ñ ê5ê!£	A”Ñ ê4P–!WA”Ñ êKÀ§ O8A”Ñ ë5 Ãá
A”Ñ ì³@Ú#'HA”Ñ ìµ€w
1‹
+A”Ñ ìí*ë	vA”Ñ ín Í$o4A”Ñ î­ t3?A”Ñ îâ`rŒgA”Ñ ïG‰ê(¡A”Ñ ïOÀ
+6³A”Ñ ñ;À4->?A”Ñ ñ@àyð02QA”Ñ ñ‹à¡æ(¬A”Ñ ñ`	êÐ(=
+A”Ñ óä@
+ï	'yA”Ñ óø ¸6Ã#A”Ñ õL ê#†ÄA”Ñ õ’À
ðÉ,¾1A”Ñ ÷Œ ï$A”Ñ øà
)â-A”Ñ ù5 
+91/ÜA”Ñ ú`ò1à$¥@A”Ñ ú}€
+ù)?/ñA”Ñ û p&—*ÌA”Ñ û] S(þXA”Ñ û¨às]ÙCA”Ñ üH`!(Ô%ÿA”Ñ üÚ€
+Â:1¢A”Ñ ý[ ß"è£)A”Ñ ýs€Ø*ú5¯A”Ñ þ@JH1TA”Ñ þy@%A6}KA”Ñ þÔàõ(…A”Ñ þÖ@͇ݧA”Ñ ÿ¯@v	3~
A”Ñ ÿü ‘T1/A”Ñ!c â*îA”Ñ!¨ Ã~
+A”Ñ!Ê 5Æ65A”Ñ!jàôT19
+A”Ñ!Í`þ•/i=A”Ñ!
@séŠ1A”Ñ!F 0t*A”Ñ!ä`	$\
+A”Ñ!	Tа'A”Ñ!	à/(ç)A”Ñ!mÀßðA”Ñ!ñ@·%€Ú=A”Ñ!þà	4ï ¨A”Ñ! ùɶA”Ñ!™ÀŸé(ê;A”Ñ!@
+)	ŠA”Ñ!Àó ŽÄ
+A”Ñ!ý?‹ÌA”Ñ! ¶%Ç,ê
+A”Ñ!Y@L,‹~A”Ñ!7ÀÕ(×_A”Ñ!-€:+ÁA”Ñ!> ™$A”Ñ!V ‹0×DA”Ñ!î€
ËöÕA”Ñ!
@Y
E
+Ö
A”Ñ!NÀÍF!	A”Ñ!Å€¶ô5(A”Ñ!-@&)A”Ñ!6@	^*4æA”Ñ!UàÒU!œ
A”Ñ!m¾-§ó	A”Ñ!mà
Ç4ŠåA”Ñ!кúIA”Ñ! Š`Û6žN%A”Ñ!!àó+è3–A”Ñ!!R`mG)\A”Ñ!"SàIr2vA”Ñ!"Ç€Æc ×A”Ñ!"È`¨5:	A”Ñ!#3 }¥lA”Ñ!'	€.½8A”Ñ!'- jˆ‰NA”Ñ!'½À·58A”Ñ!(c Y
#$A”Ñ!)= 
è%¢&ñA”Ñ!)F€
+…0‘
SA”Ñ!*, .Ód
+A”Ñ!-+~+¢A”Ñ!-¦|"
+A”Ñ!-á`¡-o)	A”Ñ!.Šàóß.A”Ñ!.®€€)¦/«	A”Ñ!/Àª0ñ#ýA”Ñ!0zÑ"A”Ñ!1.@!1–
+A”Ñ!1=À×s*A”Ñ!1øà½"†?RA”Ñ!2<V›A”Ñ!2Ë T#~A”Ñ!3 
€.Ó%A”Ñ!3¶ òÁ3ÐA”Ñ!4N@ô	)A”Ñ!4l1ñ%-
A”Ñ!4Ðà
+Ì$$!cA”Ñ!4ñ 
”ÐÉA”Ñ!5‘`E¹.%QA”Ñ!5×€¥s2&GA”Ñ!6\Àù (kA”Ñ!6Ž
+l],A”Ñ!6±@Á)(
+A”Ñ!7R–8F
+A”Ñ!8@ýÞ7&	A”Ñ!8m€Ú&ù6A”Ñ!8³`*Ê$àA”Ñ!8Ê 1zA”Ñ!9ÿ@à!ç,kA”Ñ!:u@Ø/F33A”Ñ!:—~2.4A”Ñ!:á 'Ú0Ú
A”Ñ!;$@# Z‰A”Ñ!< )ŠÁgA”Ñ!<þàÁ&d7]A”Ñ!=e Æ.jV	A”Ñ!=ˆ
+*5A”Ñ!=ûá#"L	A”Ñ!?…€
-¸"–
+A”Ñ!@M€
5¯àA”Ñ!@Áàö-Î
A”Ñ!@Ò å0ÚA”Ñ!AGà));îA”Ñ!D ]!J	¼A”Ñ!DŸ€\ÑüA”Ñ!DÙ`Ô/!@A”Ñ!F` V"2A”Ñ!FÂàF#+#0OA”Ñ!G(¶ Z\A”Ñ!GE`K((RA”Ñ!HEâA”Ñ!H’ ã.‚7	A”Ñ!I /H¨RA”Ñ!J
@4E1¸A”Ñ!J9À]	á,/A”Ñ!L d$,QA”Ñ!LSàQ!7òA”Ñ!Ld€ò%b!A”Ñ!L} 
¶qÚTA”Ñ!M@.*µ•…A”Ñ!M¿ J%(-wA”Ñ!Nfà%õ
A”Ñ!N›K1ÂãA”Ñ!Ov½R1VA”Ñ!Q Ì!‰2A”Ñ!Qh ì#I'£
+A”Ñ!Sz€
+[:	µA”Ñ!V# L3Ë úA”Ñ!V þ,
:A”Ñ!WzÀ1!(…A”Ñ!W©	 )¯5A”Ñ!WÏ@¢7ê ‘A”Ñ!W÷€ü­ MA”Ñ!X4€Ú3“µA”Ñ!YÀ¿)û#!'A”Ñ!Z€e4--ç
A”Ñ!Zu`!Ÿ
°A”Ñ![FœA”Ñ![À2£âA”Ñ!\)àïÍ-õ	A”Ñ!]a P0erA”Ñ!]r î(A”Ñ!^aÀ,W,dA”Ñ!^q@X"	A”Ñ!^¨`à'@A”Ñ!a.€
+ã&2»A”Ñ!a8@ÑOfaA”Ñ!a³@‹
˜zA”Ñ!aú r]Ó	A”Ñ!c8Àh5ñˆA”Ñ!cOÀÜ- zA”Ñ!cd€‰-ÁA”Ñ!d ÏŒ$ÁA”Ñ!dÆ
•'²5ÂA”Ñ!e¤¡4Á%ŸA”Ñ!f!À
+%Ñ+•sA”Ñ!fÔ@'
`#|	Aӄ!g|`
Ñè*˜A”Ñ!gªÀf) $;A”Ñ!gÉ Û%ÞM®A”Ñ!güÀ5Þ-/A”Ñ!h¡ zØPA”Ñ!háàv6à]A”Ñ!ià6ö6A”Ñ!iE œ!EŽA”Ñ!j
+àš-„‘A”Ñ!j7 Rˆ1ô"A”Ñ!j:€(‘)‡
+A”Ñ!ji@	!Z,?A”Ñ!jÀ
! !A”Ñ!krà„Çð	A”Ñ!k÷[V=A”Ñ!mµ-‚FA”Ñ!m@à“
+Ÿ
A”Ñ!mgàï!EŠA”Ñ!m €Z‡A”Ñ!o`Àº„øBA”Ñ!o†#3Ž DA”Ñ!o´`
mLõA”Ñ!rbÀØð,«A”Ñ!r@)ÍPA”Ñ!rî %6A”Ñ!s B±3A”Ñ!sT€S!ã!/3A”Ñ!s‘ 
í6ù£A”Ñ!tŠ 
•	h$ÂA”Ñ!uâ`
b='&(A”Ñ!vî@gS,%
+A”Ñ!w€h#c/ÞA”Ñ!y`	ÉA”Ñ!y=€'%a8
+A”Ñ!yØ`Š(çA”Ñ!z,€djú#A”Ñ!zr Ï
)43A”Ñ!{.à
+Ì·
+aAӄ!~
 õLKA”Ñ!~— þ0\ã
A”Ñ! ^µ	A”Ñ!1`«,çA”Ñ!n@yP
ÉA”Ñ!Èy5-'A”Ñ!€“À—2êNA”Ñ!
+ 
º*A%ÖBA”Ñ!#l1û
+A”Ñ!3ÀA7¿$ô6A”Ñ!A 
+»1SA”Ñ!Ð@•  FA”Ñ!‚‡“
+ãA”Ñ!„UÀÁ+,€A”Ñ!„Á´#XA”Ñ!†C`
§	(MA”Ñ!†œ€
+Ô«"¥A”Ñ!†ê±xíA”Ñ!ˆJÀ{ï3]A”Ñ!ˆ› »ØC0A”Ñ!ˆ«€70¶^A”Ñ!ˆÓ_$Ó¦A”Ñ!‰1@½.hÜ A”Ñ!ŠJ #7
+ŠA”Ñ!ŠkÀôsÓ`A”Ñ!Š… 3.R%}A”Ñ!Šþ
+þ*@(A”Ñ!‹y`@2Î"Ö	A”Ñ!‹¯@(#ï8]	A”Ñ!ŒzÀ…0Ô13A”Ñ!ŒÚàŸ(ª1íA”Ñ!
+ °XS
A”Ñ!x@„mìA”Ñ!“ v" A”Ñ!Ž:Àk'sÖA”Ñ!Ža€ü/¶bCA”Ñ!%À8^A”Ñ!@!"ÝA”Ñ!‘ àØ3-¦A”Ñ!“S@K
+A”Ñ!”–@ò,½á]A”Ñ!”˜`®3A”Ñ!”€ð†!öA”Ñ!–#
ÒJ7“A”Ñ!—›@G#µ/,A”Ñ!—è 6!wA”Ñ!—ò€µM7A”Ñ!˜8àuf*-A”Ñ!˜Ü@	á’4¹ÇA”Ñ!™ œ9"5A”Ñ!™Ëb-L#¼A”Ñ!™ïT
+»	A”Ñ!šV@¶"qA”Ñ!šz@Ã0‹F
+A”Ñ!šîà‰¬xA”Ñ!µe Œ&66ÏA”Ñ!¶ŠQ"íO2A”Ñ!¶‹ D*B!²A”Ñ!¶È 
UÐA”Ñ!¶Ï`lðBA”Ñ!·Àÿ@A”Ñ!·x€¶A”Ñ!·™à
Ÿ&×IA”Ñ!·þÀ/( $A”Ñ!¸XÐ
+˜
+A”Ñ!¹ W,Ô,9A”Ñ!º Ì÷*ÃFA”Ñ!º‡`
+#%0fA”Ñ!º¶@ÍÇ­A”Ñ!¼?ÀF2*yA”Ñ!¼ÿ€Z
$"ÉA”Ñ!½@@b£…A”Ñ!ÀjÍÃA”Ñ!À¶ ÃÍ£lA”Ñ!ÚÀ'¹­A”Ñ!Ä?`ÿjA”Ñ!Äd Î"ÚlA”Ñ!Ä÷ 
ü/sA”Ñ!Å`«6+ &(A”Ñ!ÆVÀ
ˆ1ï-H¼A”Ñ!Ç> Ç!sA”Ñ!È+àõ~»)]-A”Ñ!Ü_ 
+R-¦,žA”Ñ!Ü›€g IöA”Ñ!Üà G8A”Ñ!Ý ?#CJA”Ñ!Ý^@Ú+2A”Ñ!ÞÞ@X AA”Ñ!áµ ‚à¥A”Ñ!áÁà>1
A”Ñ!â>@4¼y ÛÞ	A”Ñ!ëP ¸%.THA”Ñ!ì#àø-qA”Ñ!ì© ‹+
qZA”Ñ!ì¬àb2çùA”Ñ!ìÚàÛ+/.¡1A”Ñ!î]@€µ"áA”Ñ!îÏÀ4«$íA”Ñ!îØ€$&¢ÝA”Ñ!ïY h½8ÑA”Ñ!ñs`Æ%³	ÓA”Ñ!ñéÀgÞ
+Ï9A”Ñ!òO@Ц,A”Ñ!ò’`¯?*iA”Ñ!òž	À,X+	A”Ñ!óA@Q+¨ã
+A”Ñ!óΠû*p¢
A”Ñ!óï »"	%o|A”Ñ!ô ês,çA”Ñ!ö“`	%,	A”Ñ!ùt@EÉ5A”Ñ!ú×€Q©,@A”Ñ!û
à	& ë8áÇA”Ñ!û†`£
+o7A”Ñ!û‹À½6aBA”Ñ!û˜€Î
*q
+A”Ñ!üé 	nÚª
+A”Ñ!ý<@ü2ØA”Ñ!ý¦@­0¹ ˜¤A”Ñ!ýæ@
²+`!˜A”Ñ!ÿÕ 
vÃ$ùA”Ñ"i 
”	±åA”Ñ"€7.è	A”Ñ"Ÿ'«!=-A”Ñ"W
Û
/‚A”Ñ"Šà¼êjA”Ñ"À€u4bA”Ñ"ZàÔ& ¥2A”Ñ"¨€F'ñA”Ñ"
+Ô®*¨NA”Ñ"	/`	[
VA”Ñ"	 (ê/‰	A”Ñ"
+C@1
+Ð'(A”Ñ"
8 _:"ŽA”Ñ"
w@ˆ-™(~A”Ñ"
È 	°ƒ,º
+A”Ñ" b1ì”+A”Ñ"M…&¹*A”Ñ"+ ã.Ÿ(cA”Ñ"Ô	±6¹$õkA”Ñ"|8.‡A”Ñ"¦`Ç.ç&
A”Ñ"Û€tô
GNA”Ñ"Ä q!µYA”Ñ"@&$F7¾
A”Ñ"ï »RA”Ñ"tÀ
*wA”Ñ"ð@Ó/f(vA”Ñ"h 	ŸLA”Ñ"¯Àç3›A”Ñ"À%f(þ&A”Ñ"& 	æ!‹çA”Ñ"j |˜1åA”Ñ"{`´¤‰A”Ñ"‡@ݵ(c
+Aӄ"@	%8
ÆA”Ñ"§f ©kA”Ñ" ?  8A”Ñ"ÝÀéö6ìA”Ñ" Wñ}A”Ñ"˜àv.50A”Ñ") )YÀ
A”Ñ"Ç`(\ A”Ñ"Ê Ì'áA”Ñ" é€{)i&hA”Ñ"#*€ÔwMA”Ñ"$= ;5ù^A”Ñ"$ O<k	A”Ñ"$¦àà& 
+A”Ñ"%4$K93A”Ñ"%æ`ç¼+ÆA”Ñ"%ë€	>
$~	Aӄ"&R
 ƒ
+A”Ñ"&r`Õ+ÓL	A”Ñ"&é q4¿)ìA”Ñ"'‚ ‘73EA”Ñ"'«€"ÑsA”Ñ"'þÀóf+›JA”Ñ"(	°3g"]A”Ñ"(-à>(ƒ4EYA”Ñ"(‹½j&A”Ñ"(Ò ú%¸7è
+A”Ñ"(Ó@õ	*$[*`â$ö)DA”Ñ">Á ({q
A”Ñ">Äà¢ûA”Ñ"?µÃ%>-A”Ñ"@oà€Ý
+OA”Ñ"Bàk*#3A”Ñ"BYà**H0A”Ñ"CÀà)Ü^"A”Ñ"C¶`ƒ"bA”Ñ"D@ $÷5'5A”Ñ"D†ƒÂ"˜DA”Ñ"DÐ@Jù'eA”Ñ"F@T
+A/(A”Ñ"GÈ`&ÒÏA”Ñ"HÀç#±:A”Ñ"Inàf!?!CA”Ñ"IÛàa`!A”Ñ"K€ 7)·A”Ñ"K 
+çÎÅ/A”Ñ"LW ±& ˜A”Ñ"M4 R ø¾KA”Ñ"M¶ r+,+qA”Ñ"NàC-t2GA”Ñ"NŒ ,v2'A”Ñ"P1Á4ðA”Ñ"QV€/Ÿ=]A”Ñ"Q¸ÀŽ"ï3^A”Ñ"Rã}!8fA”Ñ"T 1+çGA”Ñ"UÀ@	›3"³A”Ñ"V€–&*A”Ñ"Vl€ý™)AA”Ñ"V­à
+›¶LJA”Ñ"WqÀ_^0Í	A”Ñ"X<€)á7„A”Ñ"Xœ â
­0$EA”Ñ"Y[€_3+A”Ñ"Z"Àž,{5m…A”Ñ"ZÕ@Ùä6Ä
A”Ñ"[Ý@Ô*¤)3A”Ñ"]·€	sl»\A”Ñ"^Þ@•#gA”Ñ"_‘
zq'_A”Ñ"`€ %!„$r^A”Ñ"`€z'a_3A”Ñ"` )5F:
A”Ñ"`© k!>F$A”Ñ"a3à!šiA”Ñ"aø@Rÿ"†A”Ñ"b4 M)½ËA”Ñ"bs`Ñ/›šA”Ñ"c&@ÉU7A”Ñ"c4`÷Ux6A”Ñ"dãÀ[$R
+A”Ñ"e@å) ÕA”Ñ"e *}'lcA”Ñ"ežÀ·ú/d
A”Ñ"fX`c0Ð
A”Ñ"gQ@Qc‰A”Ñ"h
+iO0›A”Ñ"h€€%¦0Q#A”Ñ"h! ê»!!_A”Ñ"h³ ~jA”Ñ"hΠ¿'\y	A”Ñ"hÏQ(Ü	âA”Ñ"jCÀ0£A”Ñ"n\àn
¢ ÀA”Ñ"oà~%÷
A”Ñ"ob 2ìë-A”Ñ"oŸ`&2°IA”Ñ"o°`Ö

!A”Ñ"puû"Í7×A”Ñ"q£ 	…^A”Ñ"rÆ (eŠA”Ñ"r×àAß6
+A”Ñ"u{ N+H þA”Ñ"vd 
¶Î…MA”Ñ"vÐà@÷7áA”Ñ"w!@n:6A”Ñ"y€r35A”Ñ"z` õS½
A”Ñ"~Xà‡|3ŠjA”Ñ"p€“ÄA”Ñ"  >$FëA”Ñ"€-Ï=A”Ñ"‚4ÀþêA”Ñ"ƒˆ€ÖäÇA”Ñ"ƒ— 	âÛA”Ñ"ƒµÀž&ø*eA”Ñ"…æ`æ'š.3(Ëì{A”Ñ"¡•@¯ôìA”Ñ"¢ê .
A”Ñ"£ø õ2±­>A”Ñ"¤àÝ Î'A”Ñ"¤.€
+ëBqA”Ñ"§FÀ¦0`&H
A”Ñ"§K@
+{ ¶bA”Ñ"ªKà5è%/gA”Ñ"ªV€¿
BA”Ñ"¬& †ÖŠ6A”Ñ"®ÓÀT4ç¦7A”Ñ"¯l w0“$9
A”Ñ"¯¬€‹8SA”Ñ"°A@G%ÎA”Ñ"°Œ (''Ï,A”Ñ"°Ë¸
˜àA”Ñ"±*`t3)>A”Ñ"±µ
+â'àA”Ñ"²d`¢<.õ1A”Ñ"²ß È/"ù+A”Ñ"³§àI+ÒaA”Ñ"³Ìæ(O,á	A”Ñ"´» aÂ+oJA”Ñ"µ‹ 	I BCA”Ñ"·	Àa0NA”Ñ"·`9!ƒ$ÕA”Ñ"·à	6zÒ A”Ñ"·@ U'¨ìA”Ñ"¸ p²	A”Ñ"¸-€Â7"ýA”Ñ"¸ÿ
²CƒA”Ñ"¹Î€‚
+if
A”Ñ"º`¼"])B	A”Ñ"»L@	 1RA”Ñ"¼Ý@}0|(9
+A”Ñ"½ÍÀ	c+S	ZA”Ñ"¾K`‰9A”Ñ"¾„HjÖA”Ñ"¾™ P-7TA”Ñ"¿g`j!èTA”Ñ"Àx`…$
.QA”Ñ"À‘@Ó3<
A”Ñ"ÀÄ`h »¾A”Ñ"Á«€õ‹¼A”Ñ"Áó ô5|mA”Ñ"Ä2À
Æ ¼A”Ñ"ÄÊ`é4{!I
A”Ñ"ÄÜ€×û.,TA”Ñ"Å› 3í"A”Ñ"Æ+€o )œA”Ñ"Ær¡ ¢&ûA”Ñ"Æç`
+ç%7{LA”Ñ"Çá€(\ì
+A”Ñ"ÈEàÑ&±|	A”Ñ"ÉþÀ&/ø*†A”Ñ"Ê' )ö2(A”Ñ"Ê@`n(̺A”Ñ"Êt[%ß	A”Ñ"Ê~  ªŸA”Ñ"Ê„€Ð%t:A”Ñ"Ê¢ B&®1A”Ñ"ʹ`:
+åA”Ñ"Ëà#ÚZA”Ñ"ÌÑÀK4VWA”Ñ"Í@—­-
+A”Ñ"Ï?À) ¡A”Ñ"Ïø ô
+}ËRA”Ñ"Ѐì<#ÊA”Ñ"Ñ 
DëëA”Ñ"Ñ|Ž0i
A”Ñ"Ñ­€PëÂA”Ñ"Ò9´$»S*A”Ñ"Òú ¹4H)ŽA”Ñ"Óœ j™i@A”Ñ"Õ›¹ŽA”Ñ"Ö1 „
+ÙTA”Ñ"Ö@îÝ
JA”Ñ"×ÂÀí
GA”Ñ"ׯ@χ’$A”Ñ"Ø+àvçä8A”Ñ"ذ`.+A”Ñ"Øþ i(ÈmA”Ñ"Ù‚ÀÁ÷(ÐA”Ñ"Ú

+%)¯ëDA”Ñ"Úý¦!‘A”Ñ"Û·àÅ%€àA”Ñ"Ûäào"à<A”Ñ"Ü`àOP)A”Ñ"Üm 	;6A”Ñ"Ü–€tæRCA”Ñ"ß)A”Ñ"á@iaúA”Ñ"á ü(É427A”Ñ"åš@YVŽA”Ñ"æÛ@¤/V'ÀA”Ñ"æã 	›!Þ
+A”Ñ"çä2,+A”Ñ"ë5à	ÓΣA”Ñ"ëV e"j
PA”Ñ"ì÷àbŸqA”Ñ"í€ú&( ³A”Ñ"î¯à
Úæ.A”Ñ"îâ(Ã
W	A”Ñ"îïÀtð5¼A”Ñ"ï< *"ö!1A”Ñ"ï¥à
“p
+A”Ñ"ðÇ@_!
h
A”Ñ"ñä@_$EâA”Ñ"ò—¡# Z-A”Ñ"õ€¿/‘6A”Ñ"ø} /‹ÁA”Ñ"øƒÀv7±A”Ñ"øøà€(ï
A”Ñ"øû€i! CA”Ñ"ùŒà103A”Ñ"ù¨`É%ìA”Ñ"ùÅÀ<AA”Ñ"ùüà$0/Ê
+A”Ñ"úç%3&FA”Ñ"ú¸€=)»A”Ñ"û€O0H1ÈWA”Ñ"ûAà,(;A”Ñ"û×à
+Ü"ú
+;A”Ñ"üL`öïA”Ñ"üj 1/B>A”Ñ"ýk@9-¡&46A”Ñ"ýäà
+z)/#jA”Ñ"ÿ‘ 5+?A”Ñ#œ /A”Ñ#c@Æ"×
+A”Ñ#]à
2ª5×+A”Ñ#í@##ÝŒ5A”Ñ#|@Y=A”Ñ#â@Ù°
+ÕA”Ñ#á€éú+A”Ñ#Ä€	ÀéA”Ñ#	O@âs4bA”Ñ#	o@w_ÐA”Ñ#	ð€ù%A”Ñ#
+kÀGA”Ñ#`aª)àNA”Ñ#9`
+`"¢3èšA”Ñ#NàÃ¥ƒA”Ñ#5àú0ùA”Ñ#°@÷#«2ZA”Ñ#>`ˆ…‚pA”Ñ#P š%ÍA”Ñ#|*úA”Ñ#à9 @ Ç	A”Ñ#.DsA”Ñ# 
þ"(A”Ñ#j@
+Æ)£)A”Ñ#¯`ö(™
+A”Ñ#Å ÞL2/	A”Ñ#Àæy;
+A”Ñ#iÀýš)A”Ñ#@	¿,0ÃA”Ñ#1€ä#õ'IA”Ñ#t
+b^7ÐA”Ñ#À€V«ž"A”Ñ#/à½2±^A”Ñ#H€<(ÀÆA”Ñ#)`	#qê‰A”Ñ#“ Ð0²#A”Ñ#n Ò%×5A”Ñ#†@
ñ
+ÈA”Ñ#—€ÆB&ý'A”Ñ#o`
+öU4ýA”Ñ#ï@V\:A”Ñ#ñÀ6× A”Ñ#öÀ
ð ,A”Ñ# ä ž1P)ôA”Ñ# ñÀ6+S2L€A”Ñ#!
+.‡áA”Ñ#!~@×
05A”Ñ#"NÀý-/XVA”Ñ#"`’,q0A”Ñ#"À>ã
š.A”Ñ##
R$X#$AA”Ñ##€ $i‹
+A”Ñ#$ʽ67A”Ñ#$’€5+ÙWA”Ñ#$éà±Q%™A”Ñ#'“@1k%
A”Ñ#(ä@
+Ê$A”Ñ#*ìz Ý01
A”Ñ#,eàÇ(O×
+A”Ñ#-ßB-Ç(^‚A”Ñ#.À	8%=#A”Ñ#/F`À£A”Ñ#/p`ª1$ðA”Ñ#0Å`
+Ý#‘1É
+A”Ñ#1 Å0#:A”Ñ#1èÀÌï'4€‚'È-Ê
+A”Ñ#>½À¡0â
AA”Ñ#?€r#7	A”Ñ#@_ g¢2ûA”Ñ#@b Š#­5"A”Ñ#BÂàñïú
+A”Ñ#BØàŒÒ A”Ñ#C´&ÍA”Ñ#D  
+Òi. A”Ñ#FgÀ.3ç¨PA”Ñ#Fˆ€ˆ,Æ,þA”Ñ#F Ãe![0A”Ñ#FÇÀ¤1$A”Ñ#G?MÇ…A”Ñ#Gý`¸*A”Ñ#H±àM-@$=
A”Ñ#J`d!J wA”Ñ#Jm@0pA”Ñ#K@è3)%A”Ñ#Kcà	Ç“2ÏA”Ñ#L£ Ž+ä*A”Ñ#Mr26ûA”Ñ#Mx€¡»A”Ñ#M” ¯¿2A”Ñ#N×€Ü6„ì¶A”Ñ#O©à%`×A”Ñ#Oéœ#qþuA”Ñ#Oñ@·.µÁ2A”Ñ#P¥ š3ñùA”Ñ#Qh 
F H'µA”Ñ#Q`
+?á+Æ
+A”Ñ#R@E#A	A”Ñ#Tà
µÐöA”Ñ#T7`&#cA”Ñ#TE€Q/«*êA”Ñ#T¬`¯05þ"A”Ñ#T®`+'Å	A”Ñ#Uhà	œ*>)A”Ñ#Uš @Ì(Ø A”Ñ#U¶À
+› ­
§A”Ñ#V  ÒM3A”Ñ#VßàFà	A”Ñ#WÂ`S(Ÿ
 1A”Ñ#X3 §z/ÈA”Ñ#Xvë/y
+A”Ñ#XÍà,)é&ƒ†A”Ñ#YV 
+U
+”A”Ñ#]¶ ik—.A”Ñ#_€Ã2÷%ÒA”Ñ#_À
+1#o'…A”Ñ#_Æ 
+%ÏéA”Ñ#`qà0N++A”Ñ#`Ë |xµA”Ñ#aù€%o–A”Ñ#b¿ Ë$©+»	A”Ñ#c¸ ÔT#AA”Ñ#cÎà¬"í}A”Ñ#dà8	™(JA”Ñ#fù€(&…">
+A”Ñ#h•àÅuA”Ñ#hº€b!c!eA”Ñ#iTà
'"A”Ñ#kà	ý%‡vA”Ñ#l¦—‹ÈA”Ñ#l±m$BRA”Ñ#m…Ú%rºKA”Ñ#m™8 òA”Ñ#n® –ÊA”Ñ#oMàÄ,÷ ¾A”Ñ#oŽ`
2¦%Ç
A”Ñ#pR $2¶¶A”Ñ#p•YA”Ñ#q!`Ï)¸-A”Ñ#s`ÈnŒÌA”Ñ#sz@H¹7d
+A”Ñ#tp j¤mA”Ñ#u1€"âA”Ñ#u„€>}$xA”Ñ#u¥ò-*[7A”Ñ#uÛ€³÷!Á
Aӄ#v
+ Ü4‡+A”Ñ#x?@d"8 aA”Ñ#xvà7m#vGA”Ñ#x©à'"Ú6Ã
A”Ñ#y€!— ;A”Ñ#y-€(*ÐA”Ñ#y‡`	\,Î7A”Ñ#yËÀ#
¶#A”Ñ#z´
­-æ1¡^A”Ñ#|P`"ñ*ÏA”Ñ# àˆ)mWA”Ñ# Õ/!MA”Ñ#yÀÁò$30A”Ñ#‚”à%:.ªA”Ñ#‚¢à.…
A”Ñ#‚¶À£d*	A”Ñ#ƒ`	£5ìA”Ñ#ƒf@ 0E"^A”Ñ#ƒl€èœA”Ñ#„A@>.H(2A”Ñ#…`õ%1ÐOA”Ñ#‡ž`r0(~A”Ñ#‡¥€ç
+8/A”Ñ#ˆ@	7;`
A”Ñ#ˆ¨à	_
A”Ñ#‰[ ¯"(ÝA”Ñ#‰¥ 	n+å8A”Ñ#‹CÀG.·#
+A”Ñ#‹P@û/A”Ñ#‹Ú@C!Ÿ2A”Ñ#Œe Ô'2ºA”Ñ#ŒÉ€‡Ÿ(’A”Ñ#Œÿà	ò.8+úA”Ñ#6€ã
+A”Ñ#ñ ¢3!6A”Ñ#E€ž/Ÿ
+A”Ñ#m Ò(~ãA”Ñ#“H`¢®\A”Ñ#•À.äA”Ñ#•²@ð
•A”Ñ#•ßËF5ZA”Ñ#–†`OG"A”Ñ#—o€
n!@&R
A”Ñ#˜•@
+Î'Ë&fÖA”Ñ#™/`
+f	Ù9A”Ñ#™Æ@£"*ÄA”Ñ#šR€	x¼A”Ñ#›Ò@”¸ÅA”Ñ#›ð@îýrA”Ñ#œÀ¿^A”Ñ#œûÀV.àA”Ñ#a ;1¬A”Ñ#Ô ë/!Ã:A”Ñ#ž n!ÑÜA”Ñ#ž]`$º0Ö.A”Ñ#žº€&¿A”Ñ#Ÿ@W’|A”Ñ#Ÿ¦@Ð3*SA”Ñ#ŸÜÀ	(
+)¯qA”Ñ#¡2ÀŸ*
+A”Ñ#¢Høp-[A”Ñ#¢‡@ˆ
+[+ºA”Ñ#¢Ó@	.R/Ä
A”Ñ#£| A/*Î6A”Ñ#¤5 ¢!0A”Ñ#¤ËÀS/ƒß
A”Ñ#¥“ÀÐ	Œw
+A”Ñ#§Ý ¾¢@A”Ñ#ª!@D+%
A”Ñ#«Ù 
+â^À²A”Ñ#¬Nà7eáA”Ñ#¬d »0AA”Ñ#¬n ÿ-´›A”Ñ#­ô ·3Úô
+A”Ñ#¯@¤0–ØA”Ñ#°, ®øýA”Ñ#±1€oi’	A”Ñ#±j€¤)Ã+A”Ñ#²‡ÀÉ/A”Ñ#´š«%»7‚èA”Ñ#µ®À\Ý5:A”Ñ#¶, I¶ëA”Ñ#¶Ù `V4°A”Ñ#¶ý anA”Ñ#ºI€Žf	-A”Ñ#º”àm
+ŠA”Ñ#¼È 3(ÔA”Ñ#¼÷àô.ËŸ
+A”Ñ#½D Y"ô(A”Ñ#¾É€	²æ&;.A”Ñ#¿‚ 
+Ý!ù,å
A”Ñ#¿¡€3h"ïA”Ñ#Á¤ 
]"Ë7A”Ñ#Áä`Â6%heA”Ñ#Â2àdaãA”Ñ#Ã,À)feA”Ñ#ÄWæ1òA”Ñ#Å^€Uí0Ñ	A”Ñ#ÅÞ`þÁA”Ñ#ǽàäX6A”Ñ#Ê
+Àí)¬A.A”Ñ#Ê- *W%%
A”Ñ#ʘà
+Ø£2#}A”Ñ#ËJ 0¶pXA”Ñ#ËWÀpΆ
A”Ñ#Ë£b‰µ
A”Ñ#Ì~à”)ðŸA”Ñ#ÌÕx	A”Ñ#Ìê ,‘-ÚA”Ñ#ÍÀ%b	,
+A”Ñ#Í× #Y+mA”Ñ#Î2 â–#;(A”Ñ#Îå»ÙqâA”Ñ#Ï ¦F)‰PA”Ñ#Ïpà
++++Ç
+A”Ñ#ÏàE3•ÖA”Ñ#ÐQ ¾-daA”Ñ#ÑN@–Q·A”Ñ#ÑÄ`ÞÉ1Ü
+A”Ñ#Ò# ´1æA”Ñ#Ò¡½!xA”Ñ#Ò¤ 
ïþA”Ñ#ÓŽ€Þ*À3V	A”Ñ#Ô<€@y·+A”Ñ#Ô¶ %2¡A”Ñ#Ø.àï"µ}A”Ñ#Ømàs
ù±'A”Ñ#Øœ7·
A”Ñ#Øä Ò-9²A”Ñ#Úd i{º×A”Ñ#ÛDÀ‡†*
+A”Ñ#Û¦1Â-~'A”Ñ#Ý€,'(:A”Ñ#ÞI@	Q¥ ®A”Ñ#ßÀS.¸A”Ñ#à-`³¨$ÇA”Ñ#àrÀÜ5¥$|A”Ñ#â°`
+Å	•(xA”Ñ#âúà
+Ö(:$‰A”Ñ#âý;¿%|$A”Ñ#ã+ £€¦A”Ñ#ãjÀæ*'PA”Ñ#㜠	¦%̶A”Ñ#äkµxÔA”Ñ#åË@ë•%A”Ñ#ç¤À°5	¥+A”Ñ#ç­`	)ò®A”Ñ#èë 
´)Ì •-A”Ñ#é7€
;5A”Ñ#é[X°A”Ñ#éÕ@œÙŒ]A”Ñ#ë…@	
ÚÌA”Ñ#ëžÀOk0A”Ñ#ëÄ`&		!Ò
A”Ñ#ì+@a&ü
+žXA”Ñ#ìE &Ã[A”Ñ#ìq`c* A”Ñ#íD
8$Ë1?
+A”Ñ#íÙ  "£7
A”Ñ#îÆ §é$XA”Ñ#ïà
+6tÛA”Ñ#ðS@€)0£A”Ñ#ñYµ2eAA”Ñ#ñz€x!€A”Ñ#ñÿ@'Þ!ùA”Ñ#ò.À”[A”Ñ#ò® Ú8ßGA”Ñ#óÙ"«A”Ñ#óý*qA”Ñ#ôÝ 	©+…$ïA”Ñ#ôÞ€
+Ë$HÈA”Ñ#ôö`S,ž&{*A”Ñ#ö 	è(ˆ
Ÿ
+A”Ñ#öq ]×™A”Ñ$˜  y+Ä@A”Ñ$ÊÀ² 
+¦A”Ñ$ÀXVŽ2A”Ñ$ÀË6DA”Ñ$f€
!6PXA”Ñ$ª ~0Ç"ÞA”Ñ$œ@ˆGÙA”Ñ$`.-!
+A”Ñ$¢àn‚4A”Ñ$ðý¨&AAA”ÑS´` 
+ò
+OA”ÑSµ/ X³ ŸA”ÑSµh <%P7 -A”ÑSµÜ@É4¹%%A”ÑS·'
+È*! A”ÑS·8 Ö›YA”ÑS·§ Çö-Š
+A”ÑS¸@Ù&—3A”ÑS¸–À ú§PA”ÑS¸ü@¼,4	A”ÑSº_€T+?QA”ÑS»Sà¯Q!uA”ÑS»‘`7À2A”ÑS¼eÀ:'·?A”ÑS¼o 
+âíA”ÑS¼¯@†&{"A”ÑS½ü@ú"ÌvA”ÑS¿J`ë>AA”ÑS¿yàÔ±&k
+A”ÑS¿í`r-ŽA”ÑS¿òÀ	±.ÝA”ÑSÀ !Ë0ÙA”ÑS #Ö‡A”ÑSÊ 'ö4¥jA”ÑSá€@7Š
+
A”ÑSÄ@Ó$
+A”ÑSÅ@©Ï–7A”ÑSÆ€œ/æ1GA”ÑSÆÙÀ±	ÛþA”ÑSÇ> AtýOA”ÑSÇ•`'
+•%¼
+A”ÑSÇÄ Ö8ÒÍA”ÑSÈ1 
iÏA”ÑSÊB€7pA”ÑSËÀŽ3Î'ö)A”ÑSÌ®à	Zîq
+A”ÑSÍ´ -D#b
A”ÑSÎë (Ù²#A”ÑSÐà÷@A”ÑSÐo`
+H&WA”ÑSе`
+±Û„A”ÑSÐà@w19"A”ÑSÑ–ÀGA”ÑSÓN@V3"TA”ÑSÓd`f!lA”ÑSÓoÀõ'Ì:A”ÑSÓ ®D NA”ÑSÔ CÉ"‚A”ÑSÔ=€
Õm"-A”ÑSÕŠ 
ÝŒ HA”ÑT.! U{	"
A”ÑT.I€Õ#è"{A”ÑT/ýà
PTóA”ÑT1/Š$`÷A”ÑT1F ­ï|A”ÑT2”àržk
A”ÑT37@`"Ð,ï
+A”ÑT3`Ðwè|A”ÑT3¶@RvÑ/A”ÑT5°à.+Ä2A”ÑT5Ý —
+@$®A”ÑT6³@
•"´é%A”ÑT6³à)f —ƒA”ÑT7ªÀ1XlA”ÑT7åÀÄ ¸A”ÑT9 š(	Š?A”ÑT9c ]ö	A”ÑT9| äŸ#%TA”ÑT:
á/¯9
A”ÑT:{@A(|/bA”ÑT:½€3?+$A”ÑT:ÖàÝ•"ÍlA”ÑT=×`
õ“ØA”ÑT=ã#‡A”ÑT=õ€.@1è	A”ÑT>š@¨&à
A”ÑT>· F¹"ZA”ÑT?À
é~]A”ÑT?ÏÀi$*ÄA”ÑT?éàê!A”ÑT@Ê@‰ñ1˜A”ÑTC¿@‚ü
A”ÑTD] ÊH(A”ÑTEÌà½(Ô *eA”ÑTEáÀL2(ä
+A”ÑTFÌà×$– ¼A”ÑTG& ;4÷zA”ÑTGà¶6f&×RA”ÑTG»@	ü¿-kA”ÑTGãÀ¥åÕ*A”ÑTHÀ+ÊA”ÑTH€€_$XÎ
A”ÑTI€@½ö%¯A”ÑTIò`j-Û%×	A”ÑTJÀ
+ÿS7¾LA”ÑTJŒàVÜ21 A”ÑTJ§ ŠÏ—A”ÑTK: Ñc	A”ÑTKU § î"A”ÑTK^€
º
+Õ½A”ÑTKë ½	—Š>A”ÑTKë@“".4A”ÑTMj 7C0MA”ÑTN? ÜÝ60çA”ÑTQ\àÑ*!
A”ÑTRƒàq#øŽ	A”ÑTR¤`Ä2ˆ%¸A”ÑTSYÀ
+Ñ%5ZA”ÑTSå~(¼
AӄTTO@
+ë5a	A”ÑTTÈ`é1“A”ÑTUˆ@;..1A”ÑTVÀàG"‚íA”ÑTW.€ÍŸö
+A”ÑTW?€ûd]A”ÑTWo I4Ö]A”ÑTW—`V4¡<A”ÑTWÜ %5wÂA”ÑTX¹ ÷'ðA”ÑTXÆ@%59¼A”ÑTXÛ /Ì!]A”ÑTYJ`£4.6A”ÑTYP ø r—A”ÑTYu@Óø&·[A”ÑTYx`U*ÏŒA”ÑTZ`˜WúQA”ÑT[7àè.|%¬	A”ÑT[ù2ÕA”ÑT\n˜T%ÐA”ÑT\ò`œ+¡5A”ÑT]N`é§/A”ÑT]©àKŸ/A”ÑT^’ Ý,•ØA”ÑT^¶ ®1÷œ'A”ÑT^ÕG!ßA”ÑT_kM1A”ÑT_‡¼4±-AA”ÑTbÀ´$þ#œA”ÑTbk`·ú5‹GA”ÑTb´@3\¡
A”ÑTcàã#.-Â)A”ÑTc¬ jÝ*1A”ÑTcЀ	Ç'ö(;A”ÑTcß`
+‹žIA”ÑTdÈ@ÅA”ÑTe.`®ÙËA”ÑTe<@(•
+á-A”ÑTe@‚$iA”ÑTeà
¨&«&Œ
A”ÑTf0#A”ÑTfa2|C¬A”ÑTgŽ ¹‡VA”ÑThç ó!‰A”ÑTi³F&ø8g
+A”ÑTiä`‹(â#:
A”ÑTj­àµ+'‹mA”ÑTjÑ ƒ+º$1	A”ÑTjØ`	,t.˜A”ÑTkG u'hOA”ÑTk À3&FA”ÑTlÎÀV&Ø)|	A”ÑTm@	~$ÈA”ÑTm!àQ3$) 2A”ÑTm5@ÿs£A”ÑTn4àÄ $ÕA”ÑTnÐ »ä=A”ÑTo	àP
´A”ÑTpâÀu
|.fA”ÑTqR@JÒÒ1A”ÑTqUÀЏ4A”ÑTrË@.&	Ý	A”ÑTrÑÀê#A”ÑTs%À¢%ö	±A”ÑTsâ€v6W!5A”ÑTt‹à
.1 ¿A”ÑTt 
»	!&öOA”ÑTtø`~%šA”ÑTvvײ*ôA”ÑTv— 	ª/î
+A”ÑTy³ 7¬Â:A”ÑTyÓ b&A”ÑTz<àÓ'µxA”ÑTz¯àÄ-xA”ÑT~+E5‡&iA”ÑT~Ñ€
+La1ø
A”ÑT a ßA”ÑT€ö à+A”ÑT¯€	öÝ%A”ÑT‚ÇŸA”ÑT‚äàñU(aNA”ÑT„ _	‚
+A”ÑT„o 
/5ä5A”ÑT„ç)«#ÉA”ÑT…^ 
v14$×A”ÑT…b€]w zA”ÑT†­0öW
+A”ÑT‡Ñ@D#ˆA”ÑTˆÕ€
+sð[A”ÑTŠ
Àe4ç-]'A”ÑTŠyÀ[zA”ÑTŠ¡€áY	Ã/A”ÑT‹Š 6+ßA”ÑT‹Ã€	X!8'XA”ÑTŒ*`
+é5SédA”ÑTŒ2 »¾+zA”ÑTŒô€	Ú0•%,A”ÑT™€4*îlA”ÑT¶@”19
¼A”ÑTÊ£!Ç"’2A”ÑT‘«`
hg2lA”ÑT’Ààšd'öA”ÑT“e,ˆ!	A”ÑT“£à
•‚/qA”ÑT•9à=i
“A”ÑT•=àb.ÈFKA”ÑT•K@J¤®mA”ÑT•á@‰1gA”ÑT–K€˜;,p&A”ÑT–u Y«FA”ÑT–úÀû$…A”ÑT—ÀŸ5ÌA”ÑT—à'.-¬	A”ÑT™[€/!ŠA”ÑT™ÃÀ°
¡gA”ÑTšøàD!Ñ-ÑA”ÑT›( g‹ÑA”ÑT›/ .,“!A”ÑT›\@al)ÃA”ÑTœ4 
+#(¹/s
+A”ÑTœÄ`%(x7A”ÑT« !*‹$A”ÑTž
C+RA”ÑTŸš`ߨ!Ó@A”ÑT jÀ
ã%é
+cA”ÑT ½`Ä'Ð-A”ÑT Ì5!¼$A”ÑT¡w #ûA”ÑT¢d
tCiA”ÑT£Ò€£2âA”ÑT£Ó ÆvNA”ÑT£ýàØ2|6A”ÑT¤÷`N4ì%HA”ÑT¥Ì@
+Ü9%.A”ÑT¦À
("6A”ÑT§– Ÿ+øˆA”ÑT§ÛÀ&'A”ÑT¨8 Io
A”ÑT¨Uà~#÷$E
+A”ÑT¨€X]P	A”ÑT¨”Ào³!ü=A”ÑT¨Ÿ †E#3A”ÑT¨Ú€
;4ñÓA”ÑT©ž'?/ÿbA”ÑT©C€ë,XxEA”ÑTª
8i&™&A”ÑTª)àÍê	.EA”ÑTª’ ¶‚+HA”ÑTª¯ 	•	.ðA”ÑTªéàpÐ)A”ÑT­€žÈ¸oA”ÑT®|Àì"ò7A”ÑT®Ö 
í
+Þ"DGA”ÑT¯“ÀË"A”ÑT°« ¦2ïJPA”ÑT± /„æA”ÑT³` É%­ƒA”ÑT³‘ÀÁ%ÞËTA”ÑT³ç`¸!Â&A”ÑT´Uàh0*š
A”ÑT´`1-åBA”ÑT´°@²!ãµA”ÑT´ì€¶#r#lA”ÑTµ8ÀË
ÂA”ÑT¶€é)úA”ÑT¶X`	Žö"vHA”ÑT¶{À¦ LA”ÑT¸9`Å×}*A”ÑT¸|àK'ÿ8FA”ÑT¸¥HÁA”ÑT»å Bµ®A”ÑT¼~`s0ÌA”ÑT½À
ó$ûCA”ÑT¾È€³,"	²
A”ÑT¿~Àü0»`A”ÑT¿ù€‚5)Y?A”ÑTÂC E*Ê0
+A”ÑTÂä`™*°cA”ÑTÃ`,¤&ÊA”ÑTÅŽ %6~A”ÑTÆ6 ß#5qA”ÑTÈ æ½#žA”ÑTÈ'`
	ÀA”ÑTȨ€Œf+KNA”ÑTÈ÷@	1H,ÙA”ÑTÌ `z#*
+A”ÑTÍ!
ºk&ÛA”ÑTÍK@â1¸A”ÑTÎÄ@Fð¿A”ÑTÏ 10€äA”ÑTÐ{@£&¶
X!A”ÑTЀ€TÀ!A”ÑTÒªÀFÓ¾A”ÑTÒÉ€£.#"W
A”ÑTÓJà½(É0…MA”ÑTÓl€}&™	åA”ÑTÔ,`+-2#Ú:A”ÑTÔ  ü8!à7A”ÑTÔÂ`™.s²,A”ÑTÕyàs]6QA”ÑTÕ€`5½-€7A”ÑTÖe€¶‡#Á	A”ÑTÖ´€
£Ý3A”ÑTÖØigßA”ÑT×/ 2±3A”ÑT×/@r*·ÝPA”ÑT×çÀ*òA”ÑT×ñ@Ó46´A”ÑTØ\€.1-,hA”ÑTÙ6‚#¡A”ÑTÙw 
¦5` A”ÑTÛ™À
†}$ûA”ÑTÛë`
Š–õA”ÑTܱ ¦î)A”ÑTÜù€øƒ0ÏA”ÑTÝD  $OL5A”ÑTÝ–àâ Œ+A”ÑTÞ¦À¤4#•A”ÑTß`¼
ù(bA”ÑTßE€á
+´+ùA”ÑTßX€	”/3A”ÑTáÎÀö0ÚEA”ÑTáÝ15;‡A”ÑTâä9'ØA”ÑTãh 
+öw5¹A”ÑTãØ€9o;A”ÑTäšà¤³.A”ÑTä­€:!û-ÀA”ÑTäæÀS)î(ÊA”ÑTæ¸	…3ÝÀA”ÑT盀*“,Ú A”ÑTçû QÝIA”ÑTèÀH)(Þ
A”ÑTê		„(h!ƒ
+A”ÑTê
x5+PA”ÑTê@	6,å‚A”ÑTë/€áÔ"Ú
+A”ÑTíS ®˜-KA”ÑTí¡@w#9-
+A”ÑTíò <|"IA”ÑTîL`\a%OA”ÑTîÆ`84ó#H#A”ÑTðÀÐ*Á!¶
+A”ÑTð‚@
8	0"A”ÑTðê ß%'’	A”ÑTðñ 
+j.ZVA”ÑTñ| »)À	A”ÑTó2à	¤{78,A”ÑTó¢ ¬´-0
+A”ÑTôG@Æ.z
+¹A”ÑTôc@=¿Ñ9A”ÑTôêàùŸ °A”ÑTþ`

†OA”ÑTþoTjN	A”ÑTþp`ø%*A”ÑTþw 5Ô{A”ÑTÿ}À”+?!ì/A”ÑTÿÜà
&ý$A”ÑTÿñ Vð
+*A”ÑUA@Ã-á¯,A”ÑU€$CA”ÑU& Ð+’ê
+A”ÑUp #«,£\A”ÑUœ Â#q7.PA”ÑUt ( ’$Æ
+A”ÑU„:…A”ÑUb`XA”ÑUiàu
­-æA”ÑUÀOÁVA”ÑUß ©éÄsA”ÑU«Ã&¸µA”ÑU	à;Ç(øA”ÑU	|À
+\_»A”ÑU
+.๥
+AӄU
+:`E$N'AӄU
+Ë î	`'¦A”ÑU ÜÈ#¾
+A”ÑUSÀ
#)<%A”ÑU€yRŒA”ÑULG!1	A”ÑUÛÀ&*A”ÑU
à˰ž4A”ÑU
. %"ã#ò5A”ÑU
w g!9NAӄU
¦Àü@+A”ÑU
Î`ô5ïø
+A”ÑU@`®1/
A”ÑUŠ	Ý
+†A”ÑUÕ€6ló
A”ÑU¡ ‡	ScA”ÑUGàù,”–
+A”ÑUŒÀ0ñA”ÑU, Ñ/SÐBA”ÑUÊj»/FA”ÑU{Àí+ðóA”ÑUØ€A%d#efA”ÑUð ‹¬.ðA”ÑU„Æ'Ë(JA”ÑU 	ͺÁA”ÑU=Å ŠæjA”ÑUg`—+œ2$A”ÑUÅ W ,A”ÑUÑÛ˜ò(A”ÑU>€±²$éA”ÑU­`5.;A”ÑUà@ä7,4A”ÑU$¿G(A”ÑU‘`ª8"l¦A”ÑUÍÀ
ó-2ò
A”ÑU,`6~Ô	A”ÑU	@m%V.A”ÑUr`
7Ç6d
+A”ÑU`.'=ÎA”ÑU:€Þ(q©rA”ÑUjÀ3&¢" sA”ÑUã@
"#èÙA”ÑU8à )ê/üA”ÑU’+ZA”ÑUó 	€¸A”ÑU!³à<®)A”ÑU!ôà*)©!ºA”ÑU"UÀ
*§)WA”ÑU#	 
+V€(ÀA”ÑU#`
+a è18A”ÑU#Š€g4€B
+A”ÑU#±©v2ƒ!A”ÑU#ð v!0ŠA”ÑU%ËvR*A”ÑU%ÀHÅ8¼A”ÑU%ò`".R*OA”ÑU%õ`S!T/8A”ÑU&n lÜ)S
+A”ÑU(6 	Ã.øü	A”ÑU(ƒ’#O"‰A”ÑU*'
¶/A”ÑU*Ò€?.+5A”ÑU+£Àë­ÛA”ÑU,. J€!A”ÑU,6G6s7A”ÑU,O
ª&ñ÷A”ÑU,à}
@A”ÑU- ù,ÂA”ÑU-=àì @7À@A”ÑU-¤Àñ¶;A”ÑU.! óB-ñA”ÑU05àS)VA”ÑU0—À
+$	ÅfA”ÑU0Ú@H&dµA”ÑU2>€*)Wß
+A”ÑU2UÝ7'•
A”ÑU2Ö ¶Éo	A”ÑU3] x*16NA”ÑU4	 %žBA”ÑU5KàBÈ"A”ÑU5V€
Œ"§A”ÑU5Ï€a% A”ÑU6:@Ó£)dA”ÑU7¤`
+p*>ÏA”ÑU7ÄÁ,û)0A”ÑU8:À÷+‡2†A”ÑU8l )”A”ÑU9Ê`J$’)ÒA”ÑU:ÿÀ	8A”ÑU;%À1ÆL
+A”ÑU<š€x$&àA”ÑU=ˆ`
+§-˜A”ÑU>Tà˜‡*ÏA”ÑU?) ä
~A”ÑU?CŒ'?
A”ÑU?… DÄ
+QA”ÑU?¤ %Ñ)Ó A”ÑU?¯	-ý$àA”ÑU?Ç€/%„A”ÑU?ꀪ!32 8A”ÑU@à×,â(ÓaA”ÑUA4 	#,õ1TA”ÑUBI`›#Æ A”ÑUBËÀeÎúA”ÑUC- /ÒmGA”ÑUC©àÔ(¨'©
+A”ÑUCÔ€	ý:.
A”ÑUCí )ê	mA”ÑUGŒÀèZ:"A”ÑUGà>80…A”ÑUH@
+µ,ÈA”ÑUH÷ í{%.A”ÑUI$À[8-,A”ÑUI2À(uÃA”ÑUI>à˜[A”ÑUIZàÜ[
+A”ÑUI[ S™
A”ÑUJ, É0ª*P	A”ÑUJ¸ 3 !'A”ÑUJ¿ ÜÊ+tA”ÑUKy`‚-ÝŒxA”ÑUL®™ü#=A”ÑUM 
‰ pWRA”ÑUM Ç2ßUA”ÑUM-ÀZê¢lA”ÑUMÔ@¢4)ÌA”ÑUN¿+ !{A”ÑUO] £#7}&A”ÑUOÜ`n3|	A”ÑUPk`…Dô/A”ÑUQ
+ °EšAA”ÑUQ†à9
$ª'A”ÑUQž€Š3b!”A”ÑUQ¯`š%°˜*A”ÑUR`à5Ðy(A”ÑUR¦`$#8A”ÑUSC é, _qA”ÑUSï ÂJ™$A”ÑUTËÜ"w&§A”ÑUTÜà
+×P{0A”ÑUU ô¼&º
A”ÑUUˆ ­W5#A”ÑUU¸ý/DsA”ÑUV€¯ —
+A”ÑUVFÌ—l4A”ÑUWªà1!…"<	A”ÑUXV€
=˜+'A”ÑUXy	!2S*A”ÑUX¶ ¦(4$A”ÑUX ß"2A”ÑUYÖ€m e2‹
+A”ÑU[là.è­A”ÑU[Ë`é"ØA”ÑU\àH.ÃñA”ÑU\­`$|1Œ
+A”ÑU\å`ê	I%}fA”ÑU\ó@Ù&
©/¬#‘|A”ÑU^ò€º
Q,;A”ÑU^ó 		ÛA”ÑU_4À5¼2©
+A”ÑU_‡ @5
+SA”ÑU_ð€71Æ[A”ÑUat 
+&éQ8A”ÑUaìÀË"j
+½A”ÑUc8 ¬,È\A”ÑUc”àØ3($ 
A”ÑUdSà
+õ0¯¤A”ÑUdsÀ¼
+ïUA”ÑUdÖ€!×30!A”ÑUe ,²æA”ÑUeà !Ï!$)A”ÑUe- 
Ç©AA”ÑUfT Ñy4)A”ÑUfm s
Ç
§6A”ÑUgWD
ÐÝA”ÑUh(`KV*²A”ÑUho 	â7ñ ˜
A”ÑUix Ô)%	/A”ÑUjK`!Ê&J
+A”ÑUjß '2pbA”ÑUlZ %@A”ÑUloà¸é'NA”ÑUluà
+í3ã¦
+A”ÑUm4 °8¤NOA”ÑU©A 	&µÊ(A”ÑUª„àÏ
ÖHA”ÑUªÊà,2¹
A”ÑU«AŸ/@ßA”ÑU«JÀÍÜ(µA”ÑU«Ç@	ÜTMAA”ÑU¬úàÅ7&†A”ÑU­Ž2Ó+)A”ÑU®@SÃ
.A”ÑU®Ú@u©%8A”ÑU¯Ðàô4m@5A”ÑU°-À³ó*KA”ÑU°Ã€	¹IA”ÑU±¨€lYA”ÑU²€“.ÿA”ÑU³ÀkA”ÑU³`y&d ÎA”ÑU³a
…3ð,f	A”ÑU´F —ú$¬A”ÑU¶Ô`˜
A”ÑU·z@»Å
"A”ÑU¸?èF/`	A”ÑU¹L`C-C"­
A”ÑU¹y !{;A”ÑU¹„ [OåRA”ÑU¹ÆV‹"b8A”ÑUºS`
)ûÑA”ÑUº¬ ¿
+A”ÑUºÝ`i 0ûA”ÑU¼OÀÒ L5A”ÑU¼œ@o¹
A”ÑU¼· ì
+1?A”ÑU½ï`7
sª^A”ÑU¾Ü ( ( 	A”ÑU¿À
+Û
¹0É?A”ÑU¿ÀÀ]*o
A”ÑU¿ÏɃ5†A”ÑUÀ¡àZ€*=	A”ÑUÂJ@	±q2—A”ÑUà ”
±A”ÑUÃn`!”A”ÑUæ Ä#A5–A”ÑUÃÔÀš1¸aMA”ÑUÄ« 
ž³2%A”ÑUÆ /-D3A”ÑUÆH`ŽŽ$PA”ÑUÇIÀ;(Ÿ+mA”ÑUǼ@µ!	îA”ÑUÈàq!×,Ú	A”ÑUÈq€S6_&N	A”ÑUÈÒ`	ª*âç(A”ÑUÉp`}Ë#·A”ÑUÊ 
¿
‘A”ÑUÊ@9=!^YA”ÑUÊ6€j p*àA”ÑUË sœ'6FA”ÑUË< 
+ä.à(pA”ÑU˯Àñ«³	A”ÑUËÜ ¥Î$UA”ÑUÌyà!$ý˜3A”ÑUÌ× p‚%ÚA”ÑUÌà H5kjA”ÑUÍñÀ‘$ÊA”ÑU·@ßBÐ
A”ÑU΢€¾&Ü’A”ÑUÎÊÀàq0A”ÑUÎï Ø--"VA”ÑUÏY G«)ÄA”ÑUÏÈà
+w/ˆO7A”ÑUÏÔ`2r+JA”ÑUÐÀ7
|ÂA”ÑUÑs€Í*kÙA”ÑUÑ… j,/tA”ÑUÑŒ€Z+%_A”ÑUÑÖða')=A”ÑUÒ’€
+yÕ0=A”ÑUÓÌ	´¡ A”ÑUÓ~`{½%¾8A”ÑUÓ¡`
p 'A”ÑUÕ-à3:;6A”ÑUÕ2àCÏ0A”ÑUÕHà.2Ù A”ÑUÕo@¸
+ýçA”ÑU×±à7ÌA”ÑUØ€äWB?A”ÑUÙ·à5
+A”ÑUÚN`9ç6qfA”ÑUÚˆ Û†
+ïA”ÑUÛC€Â†Z&A”ÑUÜ8 Ô0üSA”ÑUßr@p!ä1³ÑA”ÑUß÷À´ ´3ˆA”ÑUàí Ì$P#ÏA”ÑUã‚`öíA”ÑUåРG<UA”ÑUåë€Á*Ù
`
A”ÑUç{ˆ!û"­A”ÑUç®`°Á2A”ÑUçÿ`Í$¯/A”ÑUè®à¶
ÊA”ÑUé©`»Y%l	A”ÑUêôÑ#—4`	A”ÑUê÷@,(ùAA”ÑUëº
+(èRGA”ÑUë¿à܉¸ÁA”ÑUí'	[ A”ÑUíÀÎ¹G	A”ÑUí> ÿ2­"
A”ÑUíEæYA”ÑUíÂ`Œ
‹)AA”ÑUî„ 
+Q#M0"A”ÑUñ q!A”ÑUòÐ '›1A”ÑUóV`f4fÃWA”ÑUõ@†!ªùRA”ÑUõx`…CÃA”ÑU÷Z MA”ÑUùxÀBø`1A”ÑUûØ€m+;
A”ÑUüO€sÅ.µ‚A”ÑUüÍ9 ®-	
+A”ÑUüæ€:"ïiA”ÑUüñÀk$A”ÑUýB
HðHA”ÑUÿ«€ñ0HA”ÑUÿÚ€V
A”ÑUÿÝ€ØRñA”ÑVg Çù6ˆA”ÑVå —*¾H	A”ÑV'@(þ,_A”ÑV° (ó.B	A”ÑV÷À

A”ÑVÁ@cƒ$dA”ÑVÇ@©,+
4A”ÑVn›¸ ‹1A”ÑV`ÝmÕA”ÑV¶ ›c2A”ÑVô Õ
µ)Ñ
A”ÑVøÀ	û Æ£ÔA”ÑV	
€t/7bA”ÑV	³—*l/äA”ÑV
+˜ &"I6A”ÑV
+Õ`
1*O"ó	A”ÑV„@-Q·A”ÑV³Àü¥'A”ÑVú ±è-I	A”ÑV
q`I‘
AӄV
ª`0I)!A”ÑV@0§(
A”ÑVh€
+m ½(WA”ÑVl 	Ò+»1A”ÑV®`? ·A”ÑVC %3,N1A”ÑVX€XœñA”ÑVö`iîAlA”ÑVÀŽå&ñA”ÑV¥ 
+Ä
+ü$A”ÑVª 
-#¥A”ÑVœà
½*—7A”ÑV@€»wö
A”ÑV)`'7	òA”ÑV¸ „-é ‹6A”ÑV‡ 
#B©A”ÑV]à)2´'cA”ÑVd@	4!M-A”ÑVЀ$߯MA”ÑVZ€~!7ò;A”ÑV¯€%e	*A”ÑV	½>4A”ÑVe€X/AA”ÑVÀ¦'€2,.A”ÑV[1A”ÑV3`!&Ã#b@A”ÑVƒàþ(1ùA”ÑV·€'0’{A”ÑV³§SA”ÑVé J5gA”ÑVÿà8(jA”ÑVF`X÷A”ÑVa`…/È óA”ÑV¯`É$ò%_A”ÑVð /ëAA”ÑV ã@w#2èA”ÑV ûÀíü|ÜA”ÑV!:`À|¿KA”ÑV"I^%[(œA”ÑV"‹À	H÷"'?A”ÑV$* 
¡»'¥A”ÑV$ 
+F$œ
+A”ÑV%Dào©$^	A”ÑV%tà;"JA”ÑV%‚À
Š'¥$
‡A”ÑV%ûÀ”1´	A”ÑV&À‹e'±âA”ÑV'O`
+`3P]A”ÑV(Àߤ5¦A”ÑV)P@Q¬,A”ÑV)e Çô4¯A”ÑV)³à¥+àeA”ÑV*yà»(1wA”ÑV*«àaP{A”ÑV+J€#$§”A”ÑV+ò€¥Ÿ%A”ÑV,-@܇3ÓÀA”ÑV,ˆà¼tA”ÑV,Õ@J&"ÀA”ÑV-C€
;º4ó
+A”ÑV.U ;òÊ-A”ÑV/«àü 
+éA”ÑV0€][-*A”ÑV0¹ ŒºA”ÑV1˜`
+‘A”ÑV2: ·"£â:A”ÑV2? 
+z$œ	A”ÑV2dáâ¸?A”ÑV3 P.þÌA”ÑV3¸ í¥1A”ÑV3ÿàÎ'½Y
A”ÑV4vÀO_A”ÑV5Ò`
Î#¢A”ÑV5ëÀü	g®	A”ÑV6Š,ÿNA”ÑV8àŸ11&ÖA”ÑV9«€	…–
+õA”ÑV9ßà3*ùA”ÑV:.	n$K2§	A”ÑV:2€ò"	A”ÑV:Ô Ð$&
+A”ÑV;· ;0Ù"Æ6A”ÑV;Éà²!L%ìA”ÑV;ó 
+â-+¸´A”ÑV<ð€$*›æA”ÑV>@¼)DA”ÑV>¶ 	¡ý B
+A”ÑV?é»#ÿ!A”ÑV?À`¬,A”ÑV?% x-N0€	A”ÑV?kÀ“YA”ÑV?
+$­
+A”ÑV?×`í
èA”ÑV@&@d)à3•A”ÑV@eQ	šPA”ÑV@Ì@!fwUA”ÑV@ßànú-Ü	A”ÑVA@@p#ÿ$A”ÑVB@:-wA”ÑVB-`x
+LA”ÑVB@€t?m+A”ÑVCEÀ(³"ëA”ÑVCË@(Q+KA”ÑVDÐ œ+1LA”ÑVE€B¾HA”ÑVEÀàrq3LA”ÑVEø "
+‚A”ÑVF´`f.ÍA”ÑVGÄ€“+ó
+’A”ÑVGù€Ï'2"lA”ÑVH `›*?A”ÑVJÄ »#>=A”ÑVK´@
#„
+ÇA”ÑVL!@%
¾A”ÑVLk á<c-A”ÑVMZÀ¬&,A”ÑVO=`I&L3A-A”ÑVO¸ n*Ê.U
A”ÑVOø€ÌÕ&òA”ÑVP~@,p
-A”ÑVQ	@["k0A”ÑVQ2`J>.ÃA”ÑVQQ@š*2A”ÑVQãÀ	5%!îA”ÑVST`Ïà*{A”ÑVUQ 	Ä"s)A”ÑVVÿ 	«"I	A”ÑVWÀÌ¿0A”ÑVWr@ê¢A”ÑVW¹ µ"(•A”ÑVWÉ º ·A”ÑVX2à
õEJ.A”ÑVXÛ@0%A”ÑVY ^œÊ,A”ÑVZaÀìNA”ÑVZË ©)Ã$ÈA”ÑV[=`
+ã6&²A”ÑV[ô€	.@îA”ÑV\@[ã*;NA”ÑV\ð€£Ò!WA”ÑV]; 
t*Â*'A”ÑV]>@öþ_A”ÑV^à&&*@A”ÑV^k€	bþbA”ÑV^{ E…†CA”ÑV^» Ô.¹A”ÑV^¿e0÷/—	A”ÑV_ÐÀµ Ý3A”ÑV`VÉh=A”ÑV`´€X\ü7A”ÑVaK M+Ã,eTA”ÑVa‡ °“$jA”ÑVa”à
Ï"iA”ÑVbRÀ
Žð.A”ÑVbs`-!K Ú4A”ÑVc
h?:A”ÑVc} ƒ)u2/
A”ÑVcй;l
+AӄVe
+ÀÎ(",gA”ÑVe0 Þ&˜¹A”ÑVfàq^A”ÑVfà”'ÞÔ‘A”ÑVf[À*'ÎöA”ÑVftôq&A”ÑVf¨ ™@6	A”ÑVh[à²	¢ù	A”ÑVh… 6##1A
+A”ÑVh™ ‹ø
rA”ÑViŸ`%A”ÑVj«€gÉ2ÙA”ÑVj½À˜1-,›A”ÑVjÄ@:2D u;A”ÑVk,à¢0ÎA”ÑVkc`âë’A”ÑVkº`¥3A”ÑVl¡àC')&A”ÑVl´ 	ÿ"&lA”ÑVnK@
+Q…A”ÑVn¨@²A”ÑVo€%ïz
+A”ÑVp 7"D ›A”ÑVpÊ
i;)?"A”ÑVpÎà	B¥ 1+A”ÑVqLàã.ÂpA”ÑVr1@
U/¹‘A”ÑVr× Î1m*ò	A”ÑVuU`
„(ZA”ÑVuà
+l‚A
A”ÑVuÄ Ò2vA”ÑVv~àd¥*"‰A”ÑVwm 	!ï).
A”ÑVxä 	¹2:%5A”ÑVz 
+"I,[A”ÑVz„`G1¼1A”ÑVz˜À{#ì5´	A”ÑV{Và

Á'A”ÑV{÷ 
6A”ÑV|4 j%W	•UA”ÑV|F€u.”kA”ÑV~	 ü.7yêA”ÑV~ã ˆo aA”ÑV~øÀ³ ¢!
+A”ÑV€ú Ä.Ä49A”ÑV€ü d#d+Ð[A”ÑVŒ`S(ø	A”ÑVÀ5!©
+?
A”ÑV‚ J4tIMA”ÑVƒIM$9$È0A”ÑVªØ€©"s’*A”ÑV¬T@ïV)¹A”ÑV¬tÀ	¥ /dA”ÑV® ‡m31A”ÑV®«€íÑ!“JA”ÑV®±
º
éA”ÑV¯àŸ!FA”ÑV°Z€
*ª®A”ÑV±&à€)v
A”ÑV²€	‰e¼DA”ÑV²`@€0A”ÑV³ŽÀ@/d ÁA”ÑV´£Àtô#'A”ÑV´Ñ 
P!˜RA”ÑVµdÀ*ûdA”ÑVµà`3GÕA”ÑVµú 
BzšA”ÑV¶q €*a1	A”ÑV¶æ`IÞA”ÑV·!€P£:A”ÑV·² ö¡ªA”ÑV·³ÀX¬2»A”ÑV¹³À'JA”ÑV»Ê2ü'
A”ÑV½,àj&o%ž
+A”ÑVÀS€
+W)[jA”ÑVÀ…Àwä1¿:A”ÑVÀÁ #á	A”ÑVÁNà("î0qA”ÑVÁl@ÆmóA”ÑVÁ¾@ª
•ÞA”ÑVÂë€tkA”ÑVÃ^€$4c":A”ÑVÃg^	×,A”ÑVì CÄòA”ÑVÄ´€¥Ä+šA”ÑVŘ`¢ mA”ÑVÅ¥à¡4Ä$PA”ÑVÅñ 
+à‹E€A”ÑVƆ@>)l1
+A”ÑVÆÛ`
+d‰A”ÑVÇ€'
Øð2A”ÑVÇX€s̺DA”ÑVÈ¡@.F$A”ÑVÉ`
+¶0Ê5AA”ÑVÌ4ÀF-0³A”ÑVÍ,`(,,½A”ÑVÝŽ€MêúA”ÑVÞL
·5‚$ÛeA”ÑVÞt	Ü%ÃâA”ÑVÞé`^Û0A”ÑVß¹à%0Õ%’2A”ÑVßÉà	½5)¤A”ÑVຠÔ A”ÑVàÔcVA”ÑVá`¶,,ý
+A”ÑVáÒ@{6yºA”ÑVâ
 Ð
+ýŒ)A”ÑVâ„à¤h4ÄA”ÑVã, +h0×A”ÑVã™à´N$#A”ÑVä§`"üA”ÑVäÛ tÏäA”ÑVäö v)+A”ÑVå µ¸-¶A”ÑVå|àõ–RA”ÑVæ3~J•¤A”ÑVæÕàrºî¾A”ÑVç(àÜ!a$²A”ÑVç„à
'†èA”ÑV甀C"
+A”ÑVçõ`ø/æ#A\A”ÑVèÀ!ßXA”ÑVé, Bè¸	A”ÑWm`š!Ø31*A”ÑWàB"91
+AӄWY@E
+V"m?A”ÑW¾
}%DA”ÑWÜÀÐ"Í
+A”ÑW™ "B	A”ÑW :)I+nA”ÑWfàø0÷)%A”ÑW˜@È4qéA”ÑWÌ`f13ÇA”ÑW€
C2£ÈA”ÑW@@Rû&ÃA”ÑWB€
+”!ØA”ÑW  ¡±2A”ÑWþ±¾)H8A”ÑW5 ¢B1A”ÑWn ]Z!?A”ÑWÌ 
'•Ö	A”ÑWï j 7
žA”ÑWj@U.À+ÛOA”ÑWÀm¥*;7A”ÑW 	…öj'A”ÑWg Ž$ÈH
+A”ÑW¯@Q"=,lA”ÑWöÑÒ	}A”ÑWz ¨µc
+A”ÑW @Ë(LƒÜA”ÑWË`$ô	ñA”ÑWãà)ÛA”ÑW €é#"³!A”ÑW!Lê+%ÔA”ÑW#­`R3
+µA”ÑW& $!%0?A”ÑW&- Ô°5¡A”ÑW&†€D
+F
+ìA”ÑW'¶@÷^4’
A”ÑW'ôÀ-µuA”ÑW)ûà.+Õ
+A”ÑW*HÀ°Ñ)¹3A”ÑW+ìàÀ/!NA”ÑW,i&©÷A”ÑW--÷DI6A”ÑW-Î;$9šA”ÑW/D !Ö%îA”ÑW0ê
+B,kK(A”ÑW1 f4i}A”ÑW2- ú A”ÑW2‘(Š19A”ÑW3`ßÏ”A”ÑW3 Ã,o÷A”ÑW3¢ Ã%j.«?A”ÑW4¯Àf Ç"9A”ÑW4´@b3˜(ÄA”ÑW5@Áˆ3	A”ÑW5ëàêä!ú"A”ÑW6@ ¯/°	A”ÑW6Ó ‚3öLA”ÑW7; /$·AA”ÑW7Œà´Æ	…;A”ÑW8‡`Š!T;=A”ÑW8à
ÖÓ:YA”ÑW;û`¢0Ó"ÕgA”ÑW<Ó 
54úwA”ÑW=þ€¦¿AA”ÑW>q0-Ã	A”ÑW>N@E ³A”ÑW>œ€×Y)q3A”ÑW@a ÆÚ)PA”ÑW@…@	í²0Ï8A”ÑW@µ@Uº~A”ÑWA8 Gþ2¬A”ÑWA<ào&ï$'	A”ÑWAe
+ã$BÖA”ÑWAo ¨$kA”ÑWAË<¤%,A”ÑWB½ 1 c B	A”ÑWBð`
R6A”ÑWCŒ o'£/Ø
+A”ÑWDyàž	ãLA”ÑWDÈ 
ì&|(A”ÑWDúà0u,gA”ÑWE€õ*WäA”ÑWEw€ŽªA”ÑWE¾ ï	KA”ÑWEê`"4QA”ÑWF`Š"!e&A”ÑWFê@œ&	*ƒA”ÑWG@Š/;ŸA”ÑWH4I=A”ÑWHœ`
Ï*#>A”ÑWHý@()ÐÊ?A”ÑWI§ ó%>A”ÑWJ3`º+A%JA”ÑWJ>àl+á(¡$A”ÑWJç 5Å4åA”ÑWJì ¡ÜsA”ÑWLt`˜+>+ÌA”ÑWMàŽ¼#¸5A”ÑWN`~3ž JIA”ÑWNË€6P!4A”ÑWNÛÀ
wŠXA”ÑWOj@¶ñ4Q}A”ÑWPdÀÎ$9~9A”ÑWQ0 F ÃA”ÑWQr€ç+0
b.A”ÑWQw@¼#ª´A”ÑWRƒµ&,ÕqA”ÑWR¯@
¹ò"aA”ÑWSA ð
+)ðA”ÑWTC	+‚ŠBA”ÑWU|`3¹A”ÑWWÙ 4"ç+A”ÑWX&€iZ£A”ÑWX=@ïzÃkA”ÑWX«À
+ÇÍA”ÑWX¬ —”)üYA”ÑWY!À‡B"vA”ÑWZ9
~)Â9A”ÑW[€˜ H'EvA”ÑW[!@	Š'(r
A”ÑW\€ˆ"9Õ	A”ÑW\ ×$$÷A”ÑW\¬à
+!þ–A”ÑW]¿ s&ZA”ÑW^è`D­,Ï*A”ÑW^þ`È oHA”ÑW`L`Y&##A”ÑWa g3%Ó.A”ÑWaÉ@ä2A”ÑWe¾€*‚ÂA”ÑWfÆ€S¯=A”ÑWg`µ"ô7A”ÑWgX øÓ,§-A”ÑWg`às%™!ê	A”ÑWh»À
+$
+A”ÑWhï Ü,rGA”ÑWi*ßvA”ÑWiË`¸öµ(A”ÑWjcà&,	$UA”ÑWjx`)&]£A”ÑWk(€Á"*"A”ÑWkŸ•Ü	TwA”ÑWlø v}!CA”ÑWp 
4z%à1A”ÑWpü๯
Ä#A”ÑWq
  Ã?8A”ÑWrK Ì
G$MA”ÑWs¨@)-¥A”ÑWt@1fA”ÑWt( $RTwA”ÑWtM›!#ÇA”ÑWtš@LB*A”ÑWu ‰C	Ã@A”ÑWu Z}÷A”ÑWv@¤ TãQA”ÑWw|Àˆ*ýÔA”ÑWw÷`Âa5A”ÑWy€ i'ÔÞ3A”ÑWy‹ÀÔ®A”ÑWy¦ ÖE
+A”ÑWy€~4-7A”ÑWyì $!Ø]A”ÑW{L@
Ϫ;A”ÑW{àÀ
¾òA”ÑW{óà1V%^$A”ÑW|Ï@	ˆ$ÂsA”ÑW}x€Å û
-
A”ÑW~ QŠ"GA”ÑWÿ 
~1ü'Õ	A”ÑW€ .ì
AGA”ÑW€o cÂÃA”ÑW€ÿ€[	­)EA”ÑW 	§&Ô{A”ÑWFà
Ç	k+»A”ÑW„@Y,¯0n%A”ÑW„, ã"2&n;A”ÑW„Y 	Ë-&A”ÑW†PÀ«V08A”ÑW†ãŸ!'=iA”ÑW†û€	mù,Å
+A”ÑW‡” `+$A”ÑW‡½@¹.5
+¨A”ÑW‡îé¼vA”ÑWˆÁà¨-XåA”ÑW‰› a'tE $Ò­A”ÑW¬£@+'A”ÑW­€è+Ê1/
+A”ÑW®
+Œ"×ýA”ÑW®G P$e2.	A”ÑW®§@nˆ×YA”ÑW®½ ‚~
A”ÑW¯ã ˜2râA”ÑW°Ÿ@/"fáA”ÑW²G@{ó*3A”ÑW³*ÀÛ-
+[´A”ÑW´Í€Ñß&l
A”ÑWµe 9åA”ÑWµÁ 0|…A”ÑWµÓ€°+ˆ(átA”ÑW¶@ÿ”+¢
+A”ÑW¶i ÜFA”ÑW·€ÂÚ4WA”ÑW¸ç`)
+FÐA”ÑW»1€hê£FA”ÑW¼añÝ5‡A”ÑW¼„44íS$A”ÑW¼´`ÖÝ1ˆA”ÑW½ÀqcMA”ÑW½VÀ„f«A”ÑW½` œòí±A”ÑW½eàŠüÐA”ÑW¿ÉÒ‹A”ÑWÀ@ÿ$¢.MA”ÑWÀàƒÎlA”ÑWÀb`T,é©
+A”ÑWÀ²`Õ#h! A”ÑWÀå˜™™ A”ÑWÁaàZ”- &A”ÑWÁÓ}-f'4A”ÑWÀ±w1lA”ÑWÃÏà1î‘A”ÑWÃß 9bÇA”ÑWÄ)@@^ÕA”ÑWÅ`²™ÿ
+A”ÑWÆ]€è¶A”ÑWÆØàk*s#A”ÑWÇ'@èI
++
A”ÑWÈI@G•6A”ÑWÊc@že#Ã\A”ÑWÊe@""³A”ÑWËÂà
q0Š*kA”ÑWÌY`ð Ï,Î6A”ÑWÍF€Šˆ"ÎHA”ÑWÍÏ
}û™fA”ÑWÎ!@x£)eA”ÑWÏE ¥¥+RA”ÑWÏË`	íA”ÑWÏå€M F%>FA”ÑWÐÑ 
+™
+].w	A”ÑWÒ/ è"‘2—	A”ÑWÒ5€^2Œ1,A”ÑWÒqàh!ÚX0A”ÑWÒïÀ!®0zrA”ÑWÓ÷ Œq(EA”ÑWÔ€	˜-D.—A”ÑWÔàèe 3
A”ÑWÔ9`
äª!’.A”ÑWÔ€ 
^
+˜A”ÑWÕ ‹2…ËA”ÑWÕAà‹IZA”ÑWÖ@çŸ(^
A”ÑWÖ:`	Ð'¸5A”ÑWÖ‡ C3QA”ÑWÖÃD2
$îA”ÑW×\à	Ê'H2"A”ÑW×zÀdDA”ÑW×Þ@å¹%	
A”ÑW×ø€
+Ï5±-A”ÑWØ·`
03
‡'A”ÑWÙ
“)Æ+kA”ÑWÙ1`Í/%
+A”ÑWÙU 	â"Ÿ1†A”ÑWÙ” ó}/B	A”ÑWÚ )
Þ›
A”ÑWÚÚƒ.A”ÑWÜr€
W
A”ÑWÜrÀ#
6DA”ÑWÝN€íÔA”ÑWÝn@° 64ÒA”ÑWÞæ`V/¡,«0A”ÑWß Ò/ 
A”ÑWà3à0Ú‹5A”ÑWàïÀÅ1J,>A”ÑWá@ö.\A”ÑWá”
DŠA”ÑWâMàŠ£A”ÑWâU ÷*.r
A”ÑWâð2©2âdA”ÑWãp@è*1<A”ÑWãs ¬*áWqA”ÑWä"@s%+"qA”ÑWäK`Ûõ×A”ÑWå9À£ƒ2ºA”ÑWæŽàû+>A”ÑWæ™à÷³™
A”ÑWæ¯`*;$¢2A”ÑWæ×à±+ºZ
+A”ÑWé9¿ã
ªA”ÑWê@äV<	A”ÑWê˜àÙ%­bA”ÑWë°ÀÝ$D'¾	A”ÑWí
+	kA”ÑWí$ ´»´A”ÑWí5`(â3U	A”ÑWíVà¯'•-E
A”ÑWíu ü(¢2'	A”ÑWí“ TÏ*ÒA”ÑWî Ç'¾¯:A”ÑWîš
¾sñA”ÑWï@€"-mA”ÑWïÉà7$°RA”ÑWñwÀ¿Ë
83A”ÑWñ–@~ã‰pA”ÑWòr¿-å
+A”ÑWôSàpüA”ÑWô”`i*?A”ÑWõ* Û"e4™0A”ÑWõI€d'Ÿ%A”ÑWö˜#JA”ÑW÷»`5	Ò"ëA”ÑWø 
+!3ÒA”ÑWùàŒƒJ.A”ÑWù Ð-Ò¼/A”ÑWù†`³%ÌFA”ÑWùâ€
+îž\A”ÑWý¢ V°%VA”ÑWýÄ`[*À*A”ÑWþÌà
æ/ñ A”ÑX¼àJ)êA”ÑX+`
›ù7A”ÑXl€–583A”ÑX£`È&{ÚA”ÑXô æ/ÈX9A”ÑXÿ Ý2w"*A”ÑX \†áA”ÑXl`½P A”ÑX¯ ²e÷A”ÑXè »1ÞA”ÑX	 E,B1Œ
+A”ÑX	¨ ˆ1(yA”ÑX
+­ ê
+A”ÑXp #–	”SA”ÑX
- (P+ØA”ÑX
õÀÎ&gA”ÑX ™ž&A”ÑX†@äÊ&ÚA”ÑXH 
+›YJA”ÑXz v{+ŽA”ÑX’ »*œ.¥A”ÑX³@1ï%;A”ÑXö@>ó"	A”ÑX5àwÌõ
A”ÑXbà²âA”ÑXl€ß<*Ÿ	A”ÑX–€•'Œ"#EA”ÑXÓ@&º&—A”ÑX"À	µq
C\A”ÑXK á"§$A”ÑXi`R0dA”ÑX±à	«)ÀA”ÑX²àm¥A”ÑXùÀŸ*“"A”ÑX3àE™ÍA”ÑX @³	
oA”ÑX®œx#A”ÑX¾ h
YWA”ÑX€l%S
+AӄXc
+Ü 7A”ÑXg ±!gh*A”ÑX®Àé2ù²sA”ÑX\ÀÙ4@
A”ÑXn@’#v('A”ÑXx€	5w¥A”ÑXª€ï ³2A”ÑXá€ÿ)Ç)^A”ÑXç +"æ'Q
A”ÑXÖ`	‘#˜
A”ÑX'`~z7UÃA”ÑX~JÀ(PA”ÑX°@°N–	A”ÑX“ Lç+?³A”ÑX×`5÷¶BA”ÑX ½`âu³$A”ÑX à ­¬'E_A”ÑX! 
+¼#îs	A”ÑX"ñ@Ré.ñA”ÑX# ¥
h‰
A”ÑX#¾`0¹%׃A”ÑX%W ¥1A”ÑX%ñ@¯1ª$¨#A”ÑX&x
+4%-ÌA”ÑX&£ ú!K}A”ÑX&®€(—GA”ÑX&Þ`	k+S	A”ÑX(và	$R! A”ÑX(‡ ‹-‡UA”ÑX(ŽàÓ–»A”ÑX)× Ö0O'–2A”ÑX+ð€½
+I.ÚnA”ÑX,} ü',1¡A”ÑX,ËÀèÃ!
A”ÑX-@© ’
+BA”ÑX- ŸèxA”ÑX.@è$( ÑA”ÑX0L€
Vû	A”ÑX0V€% yA”ÑX0²€u‰#ñ^A”ÑX2M¾
++âA”ÑX2S &
+-A”ÑX3s,˜!-
+A”ÑX3–`b#ƒ–A”ÑX3À
¼§£A”ÑX4I@´$åA”ÑX4›@µ#º4:A”ÑX4¶€¾.“-yA”ÑX5À¼ëA”ÑX5Aä.í¬A”ÑX5~fpÁ1A”ÑX5–`îê7éÖA”ÑX7ï@Ä3åA”ÑX8^	,jA”ÑX8W@¹"B$A”ÑX8Z 3 Þ5A”ÑX93@€,¢çA”ÑX9Æ€%·"A”ÑX:g Ò%åA”ÑX=z@´ ·
˜	A”ÑX=‚sfA”ÑX>c ›DÙA”ÑX?»ð›&ÇA”ÑXA (j
+°'A”ÑXAV€¢-¬A”ÑXAg`>Þ¶A”ÑXDGàä)
A”ÑXEw€U‹'yA”ÑXF˜'O4?·A”ÑXG6Œ
+A”ÑXG$`ý÷0LA”ÑXGm ¨
+©+‚
A”ÑXGùÖ*\Y
+AӄXI
+€*_5*A”ÑXI– ½#QA”ÑXJÑà
lß1^NA”ÑXKH V!A”ÑXKJ`
<EA”ÑXKz (hA”ÑXLœ€\
n(dA”ÑXLº@
+Z)3IA”ÑXLÖ /!A”ÑXM6Àl&î!A”ÑXMÕ w#>&ªA”ÑXN`
+!(î
A”ÑXN•€û¸
+7A”ÑXOjÀ™1:ÿA”ÑXQ`Ã
RéA”ÑXQ5¬
	A”ÑXQL¿ŽA
+A”ÑXR/À‰"°ÜA”ÑXRD€-#ÙA”ÑXRvÀÄ#Ò&‹,A”ÑXRê`
+î.A”ÑXTºy ;A”ÑXTâ@E3rA”ÑXU ð 7nA”ÑXU“`!.åA”ÑXVÏÀl/1'
+A”ÑXXÁdüÃA”ÑXZ™€P"4#A”ÑX[ w%. A”ÑX[¦À¿"õ0A”ÑX\(à1!9UA”ÑX\{@8*£1ÅA”ÑX]5`Ä2¾è?A”ÑX]ñÀž n+oA”ÑX^Š€µ4b#¦A”ÑX^ÂÀ
+¾3
2A”ÑX^Ø`°2Ã%ÏA”ÑX_h@	¯&	„LA”ÑX_Î@t5ÁA”ÑX`~@Å
Â¥A”ÑX`ˆÐ)Ö!¦A”ÑX`–@e{*â
+A”ÑXbAÀò2)SA”ÑXb`v)Ø1&>#u$A”ÑXo®à£íA”ÑXpÁýì)ˆA”ÑXtY	z!Ó!:A”ÑXu6@
$ÏvA”ÑXu¼àq_A”ÑXvàÿøÚ1A”ÑXw= ‹	Z±ÔA”ÑXwµ Ø'Y$v
+A”ÑXx=€.kA”ÑXylÀ5˜Æ-A”ÑXyø@|YèA”ÑXz`/q$,A”ÑXz À
+¿*Î
A”ÑXz¡à	u%¬dA”ÑXzÍ 5¸ oA”ÑXzúŽT^A”ÑX|c é EA”ÑX|‡€f¤2¿A”ÑX}ä`$3Ê	A”ÑX~
 k"’#<
A”ÑX~ž€õa,@ZA”ÑX~¼@«,˜³A”ÑX~È ¼fyA”ÑX~ìÀNd2pA”ÑX"@¯~ ŸA”ÑX€Àà=@ŠA”ÑXàâØA”ÑXd &™A”ÑX‚À‚
+¯*!A”ÑX‚†	œhAA”ÑXƒÇ =xbjA”ÑXƒÕÀtô .A”ÑXƒÛÀ*%{4A”ÑXƒë J#H(ŠA”ÑX„@	*P-ÏA”ÑX„f@ä)â
+A”ÑX„Û`ÈŸÖA”ÑX…€%$"FA”ÑX…`ÙA”ÑX… ±9+~A”ÑX†À®2"µA”ÑXˆ†@ø4‹ [A”ÑX‰o`z2÷
+A”ÑXŠl€íA”ÑX‹@S&vÙA”ÑX‹©)žfA”ÑXŒ 	B}üA”ÑXŒ+`ÛÌÑ A”ÑXŒlàb?70A”ÑXŒ¼ N4'LŒA”ÑXŽl`Ì3PTA”ÑXŽƒ`*+{A”ÑXŽñ€˜%þ^A”ÑXŽùà
+Ú"A”ÑX@Ã4^"A”ÑX ~ÙA”ÑX$ ‚Ž*A”ÑX-@÷!”Æ7A”ÑX¦ààºþ,A”ÑX‘ ÇÜ#¦/A”ÑX‘ 
?	`'bA”ÑX’8àÑ2ÎA”ÑX’Ú`<.dÜA”ÑX“{àBF)BA”ÑX“ `	a+ò"A”ÑX”½”'FA”ÑX”’à
+Š"HA”ÑX”ܶ)K>A”ÑX•)€û
+Í
+"	A”ÑX– š*	ÐFA”ÑX– `O
&ßA”ÑX–$ ÷x,ð#A”ÑX˜@ûûeOA”ÑXš}àr+þ!|A”ÑXšàS,tO	A”ÑX›ø€?3

+A”ÑXÏ€õ-VAA”ÑXžàæ’3IKA”ÑXž( ]"}.CA”ÑXŸX ;7{	A”ÑXŸr€Šc05,A”ÑX X`<·/Ý
+A”ÑX¡€Ê`
>BA”ÑX¡— Ë…6u-A”ÑX£AÀ-e—
+A”ÑX£[€’ý_KA”ÑX£‚ ‡!Hn1A”ÑX£Š`Üjƒ
A”ÑX¥-@/!ÃtXA”ÑX¥I€Ò,•é3A”ÑX¥V@úü~A”ÑX¥c€áO“A”ÑX¥† Å%ª%å5A”ÑX¥à€
+R4úA”ÑX§ö D*7¸ÎA”ÑX¨µ@\1Õ„A”ÑXª,¹A”ÑX­aÀ-¼YA”ÑX­iµ=-
A”ÑX®Ç Èç2zA”ÑX¯#À­"âA”ÑX¯ƒ`Ó+ÅñA”ÑX±"à;b	×	A”ÑX±H T~ÚA”ÑX´1À>"ä3*A”ÑX´Jàn)­$¸A”ÑX´œ
+¿Â‹A”ÑX´Ä Á¼
+‰
+A”ÑXµƒ ’ô,=4A”ÑXµ£@ÉŽ€
A”ÑXµÉ ©¹A”ÑX¶ Ä¥/$A”ÑX¶` *:Û	A”ÑXºÚàÖ¸1€A”ÑX»3 ..WsA”ÑX»¬b1è A”ÑX»ïà-u c	A”ÑX¼[ÀÝ¡ÊA”ÑX¼gŒ"&û
+A”ÑX¼ôà	ýÍ.ÂA”ÑX½@¤£
úA”ÑX½ÂÀŒ'Ž+”àA”ÑXŹ =í=CA”ÑXÇÀ`»èA”ÑXÇø ”&)íA”ÑXÉ&À: Ü73¬A”ÑXÊT€t«*æHA”ÑXʪà	uNËA”ÑXÊÊ Q*•"nA”ÑXË`C¢
+A”ÑXÌ8 +
+ñÿAA”ÑXÌD@`+"A”ÑX̨`\0$"+A”ÑXÌÜ Ð)þA”ÑXÍ &·WA”ÑXÎî`Ÿ"ç!A”ÑXÏG à$q4'A”ÑXÐ/`å0§.n	A”ÑXЪD,±A”ÑXÑâ`A(¾A”ÑXÒ
+€	b¡
A”ÑXÔ‡€
'/‰A”ÑXÖ€:*qA”ÑXÖh _5z%?>A”ÑXÖw 	èh"A”ÑX×)`
"#³'ÃNA”ÑXØ	#Ó..A”ÑXØ7 7‹j	A”ÑXØ_@'Ð-KA”ÑXØÖ`q'<™A”ÑXØðÀ”)àþA”ÑXÙ-À!@A”ÑXÙA@O%å-«A”ÑXÚ Lf‡ÏA”ÑXÚaŒ&˜lA”ÑXÚÙq(|<A”ÑXÛ(‘¸&+A”ÑXÛ‹`j6«ÁA”ÑXÛ±€	…ÒA”ÑXܨ ->,A”ÑXÜï@'L$NA”ÑXÜøÀ6,“.§?A”ÑXÝP`i.ŸÂ
A”ÑXݵ ä„1äA”ÑXÝñ€1%lA”ÑXÞn`˜ðYA”ÑXÞ¦À)!•#ëA”ÑXߞྨ%>A”ÑXß° Á§A”ÑXßÓ€’2üA”ÑXßéÀì!_A”ÑXàC@Ür€A”ÑXàf Ö ¥61A”ÑXá¤À àw"A”ÑXã*`:–‹&A”ÑXãV ¥
'ÃjA”ÑXã©€ž#_r
A”ÑXäà;2&#€A”ÑXä #2m	A”ÑXä6 ô
+ÍA”ÑXä: s
+®ìA”ÑXäZ@zê#A”ÑXä@¤%ÐA”ÑXå#€##9A”ÑXåÁ€a6ýA”ÑXåú`³3¦A”ÑXç  7=1|A”ÑXè@:Ž$A”ÑXèvà3Ý"A”ÑXèÉ€(-Û/ÓA”ÑXêoàÀ"CcA”ÑXêÄà#&,3¡A”ÑXêÝà'³2e
A”ÑXëÀRy;A”ÑXìnÀa';A”ÑXí• —ÅA”ÑXíïà_«3ÙA”ÑXî* è «=A”ÑXïf „,JA”ÑXïÑ€é+]A”ÑXðÀµ†AA”ÑXðµ€´Û$¡A”ÑXñ4@QA”ÑXó> ûž&A”ÑXó¬`ì
+*	A”ÑXôÀ	—‘
A”ÑXöÀó"ƒ7A”ÑXöNÀ… HA”ÑXöi`/6€JZA”ÑXö’`
 8Þ?A”ÑX÷a (î!A”ÑX÷Öà2#!‰7A”ÑXù‘Àü)î%A”ÑXùö
+¸*"ª	A”ÑXûÔ N%A”ÑXþ Ö
+&A”ÑXþ6 ü0;'×A”ÑXþP€ép
+A”ÑXÿ@õÚ30	A”ÑYB	#^)A”ÑYë!DA”ÑY ‘¸OA”ÑY—@}¨
¹A”ÑY× ¿´,{A”ÑY<àµ
+c0A”ÑYL"XA”ÑY	D`µQ H
+A”ÑY	zÀ2}RA”ÑY
+= k1%6A”ÑY
+Ó€
+M\FA”ÑYGÞ]èeA”ÑYè`T(H
+AӄY
n`9ÓA”ÑY
Ý ×
,/ A”ÑY`Ã+()1A”ÑYà
+.&£A”ÑYpð%$'A”ÑY‡ ¾$Û7¼A”ÑY÷@z
 'pTA”ÑY–À!#1QA”ÑY9@§+šA”ÑY‰@•“iA”ÑYÅ`ƒ$¿=A”ÑYóਠÊA”ÑY
ô)'3eA”ÑYö@7$Á*AA”ÑY{€2+i&bA”ÑYâ@
+þ%™%A”ÑY=w)
+ÞA”ÑY¶ Í$ê!ì8A”ÑYíÃ3A”ÑY[@ù:
+?A”ÑYÊàº+)ÂA”ÑYG@BEPA”ÑY)`í8A”ÑYFàÕ¢+’A”ÑYª`|
0 0QA”ÑY,€ )1A”ÑYR 
+4
é,ó
+A”ÑYº@0'¯âA”ÑYÅ ]4	)ûA”ÑYÒ@¨®‘A”ÑY" ª3W
+A”ÑY"™`S%¢ A”ÑY#`m‡A”ÑY$:Àu*EA”ÑY$Èà	€$»2A”ÑY$Í 9!•A”ÑY%¬ %„	›A”ÑY%Ïà´È75A”ÑY%ç`)zMA”ÑY&=€ÿ!/'èA”ÑY&è 	ј2—&A”ÑY&õ c)45qA”ÑY' +sø
+A”ÑY' Á$† »
+AӄY'f
t›-°A”ÑY(ÀÇ2˜'A”ÑY)`	H%*!w¿A”ÑY)! 
	W&#A”ÑY)nà´„]ÏA”ÑY*­ …=aA”ÑY,€€ò11-oA”ÑY-Q@!VÁ+A”ÑY-ÿ@ !(
A”ÑY/¤à
…+,¿1A”ÑY1f mÂA”ÑY1 ¹	þ[A”ÑY1Ü`èA”ÑY2“@24¸
AӄY3
+ 2 A”ÑY3' ”~*A”ÑY3¹ 	
" 	A”ÑY4.@Ô©÷A”ÑY4¡À
7¸^
+A”ÑY6œ@
¯I!½A”ÑY7~ 
x¨6ÙcA”ÑY7‰wôA”ÑY7­`ó
+É,Î
A”ÑY8·€ŸA”ÑY9¢ .+ôJA”ÑY:»@O![,A”ÑY;`<ÿ'#A”ÑY;xàgÛRA”ÑY;Š÷Î
A”ÑY;¬€¹s#‹A”ÑY<¯@¼!S·A”ÑY<ÐÀr$™6PA”ÑY=y )#rA”ÑY=€ Ð8,DA”ÑY=ê Û0v+QA”ÑY>ôà7-)@A”ÑY?B 	ˆ#ž#A”ÑY?t€¼î)A”ÑY?ì€lá!¢A”ÑY@þ@
hÁ&8A”ÑYAZ@À-ðA”ÑYC0Àh+rA”ÑYC@þÊ8A”ÑYC…çŽÛ`A”ÑYD C!f‘-A”ÑYDÁ`,%¥FA”ÑYEJàâ(Ý1
+A”ÑYF ž(tŽA”ÑYFYÀt`A”ÑYF¹ ¢±½QA”ÑYF퀛ä!ÅIA”ÑYG€I’)A”ÑYHh#
+,­A”ÑYHÛ€B1²(ÕA”ÑYI~¾4ÍA”ÑYI`s•#A”ÑYI¯ #,ˆA”ÑYJó`Wï	A”ÑYK]€	Ù2A”ÑYLs`N	yA”ÑYM„ ðþ,A”ÑYP`Z%·É)A”ÑYQPà:"1,A”ÑYQ“ 7Þ'A”ÑYR·@(/?"-A”ÑYRé /ëé)A”ÑYS`ðó!¨LA”ÑYSÀ Ý&n=
+A”ÑYT°@e;5lËA”ÑY™ O'7A”ÑY™ éµA”ÑY™ì ÃÍA”ÑYšˆQi+OA”ÑY›%‰yA”ÑY›@Y'-–vA”ÑY›Ú`µ`/dA”ÑYP`XB/A”ÑY^€Í4-A”ÑYž rÜÃ.A”ÑYžøà//-šA”ÑYŸI€±½¸A”ÑYŸ•€ô6ç#Á
+A”ÑY úàDêA”ÑY¡Aà‰)×3	A”ÑY¡†àz$Î6ƒ6A”ÑY¡Ý tÞ
+A”ÑY¡îaGA”ÑY¢K ÂË&A”ÑY£Ê$ãQA”ÑY£ÿ ¾#ƒA”ÑY¤€ÓA”ÑY¥)ÀžºA”ÑY¥;/‚!ÈA”ÑY¥‰`
z¯iwA”ÑY¦y µ.².3
+A”ÑY¦Ÿ@ù.A.øA”ÑY¦¡€–T$v-A”ÑY¦ª@
s²%
A”ÑY§ 
!c 
A”ÑY§Àº
+j(}
+A”ÑY§9æ&/
A”ÑY§d€+4é+ª A”ÑY¨®€ú ¼ÂA”ÑY©1`²("«,A”ÑY©‹ î+ŠA”ÑYª;@R&É5OA”ÑYªi€¡%HÝA”ÑY«d€2	óg
A”ÑY¬QÀä+øéA”ÑY¬³`—
ô%¶HA”ÑY¬ò P\ E
+A”ÑY®`”°+ÆA”ÑY¯´ â©!ÌEA”ÑY¯Þà¢ÔªA”ÑY°¢à}ÃÂ]A”ÑY±þ`Â
+-(A”ÑY²~àB(A”ÑY³u ßË/A”ÑY´
+ ps(SA”ÑY´, 
+“@-Æ>A”ÑY´Ü@3ØMA”ÑYµå€^íŠA”ÑY¶XÀ3)9¿A”ÑY· ‡%ï!A”ÑY·±:Å$Í	A”ÑY¸Î`ã3â?A”ÑY¸ûàS/	-RA”ÑY¹  ÷%.5QA”ÑY¹’‰6K(ËA”ÑY¹ý v$öA”ÑY½`“%A¦5A”ÑY¾"àH A”ÑY¿= ™%!µ	A”ÑYÀ
3#D(A”ÑYà  	ì#øA”ÑYÃåÀ
k*ÚA”ÑYÃó Û
Ñ,(A”ÑYÄ5€ñIŽA”ÑYÄâ@rÎÅ A”ÑYÄëÀè »°
A”ÑYÆ) !0¼RA”ÑYÆV Z	'A”ÑYÆÆ@©-2)A”ÑYÇ`t1ârA”ÑYÇ Á.|àA”ÑYǹÀ
¬Ì7:A”ÑYȳ +!
+-A”ÑYÈ÷Ú
ú/4A”ÑYÈþ`QîaA”ÑYÉ.@؈A”ÑYÉV¨a6cA”ÑYÊJ 2*ö¡	A”ÑYÊç§'Ò´A”ÑY˵ ËÓA”ÑYÌ ¿y*:A”ÑY̱ 
+!
+Å/¯
AӄY폈?
/.A”ÑYÌê þ5*Ñ:A”ÑYÍm€~)’!kA”ÑY̓â'ràA”ÑYÍ„ «dªA”ÑYÍ»€Û'HiA”ÑYÎK@aø)üLA”ÑYÏ#`	q d^A”ÑYÏe Ú")lA”ÑYω 5î A”ÑYÏ“ êe.'A”ÑY϶àÁ_$œA”ÑYÑs «,ÓA”ÑYÒd ô
,KA”ÑYÓðÙ%mA”ÑYÓaà

Q!¶A”ÑYÔPàB&íÌA”ÑYÔ`f$Ú¢A”ÑYÕC@ÒLcA”ÑYÕ²¦4÷A”ÑYÖ[`ÆA”ÑYÖeÀŠ<Ð
+A”ÑYÖÞ€Ø+$
ØA”ÑYÖá }Z8/A”ÑYØù@Ó3;‰0A”ÑYÙ%€).\"à‘A”ÑYÙ{€	A”ÑYÚ Â
ò
+„A”ÑYÚMÀãü2A”ÑYÜ5€û)jA”ÑYÝ<`	â;"×A”ÑYÞQà
µu,mlA”ÑYÞÕ&‘5»A”ÑYßB€UBý6A”ÑY߸@Ñ"دA”ÑYà@Ç%$ÚA”ÑYá+€›ÄA”ÑYá\ ·"½ß!A”ÑYã: Y%¡A”ÑYä¯t)ƒòA”ÑYäÀ³Ã#F/A”ÑYäÅÀ
>*AA”ÑYç#`o.œ=qA”ÑYç- þ )ÀA”ÑYçz 	H—³A”ÑYèM&•@-A”ÑYè} „-ð	A”ÑYê#à&&]A”ÑYê… ¤%¡ÐA”ÑYê¯àº÷ª
+A”ÑYêÅ!Ü)–	A”ÑYë 	0,¢A”ÑYìàßý,ŠæA”ÑYì
+Þ/0´A”ÑYì)$ `*g%A”ÑYîÀï8"oA”ÑYîÇ 
w/&Ù:A”ÑYïs ¬3!x)A”ÑY3*«
YA”ÑYï¾`Õ"±,Õ2A”ÑYðÄ@…
+øgA”ÑYðüà¶	€	A”ÑYñbà
“4A”ÑYñl $&ÆH
A”ÑYñ«Õ!¼A”ÑYñÊ hßA”ÑYñØh ­&¨
+A”ÑYñïà
+ì†x"A”ÑYò@A"¨
+â=A”ÑYó瀚£-A”ÑYõÍ@† –A”ÑYö16
á,]UA”ÑYöSÀï$r(îŽA”ÑYøH 	ƒ)DA”ÑYø`1î HuA”ÑYù^ %§*NA”ÑYùæ	âÍ8KA”ÑYù÷À	A”ÑYúË@—ÄËA”ÑYûz€_u$A”ÑYû”À	sñ	A”ÑYü‚àg$ÆA”ÑYýKÏX"¸A”ÑYýTÀ
V‡	A”ÑYþˆàX±/)A”ÑYþâÀ×g/…A”ÑYÿƒÀ0)Æ#¦
A”ÑYÿÉ€—'ôA”ÑYÿÿÀ
+ü6*%A”ÑZS€­
^A”ÑZJ‡*A”ÑZgÀ
+S„ødA”ÑZ`Àˆ*JW
A”ÑZÁ@û`xA”ÑZo€óÝA”ÑZUÀU5w!),A”ÑZœ »3A”ÑZ ?e2ë
A”ÑZ›À€á-A”ÑZ	W`£æ BA”ÑZ	Š€Ú!$UA”ÑZ	¬€	C)õIA”ÑZ
+™€
+V°A”ÑZ
+Ä@9#Å4¶A”ÑZ`@O4Ø
AӄZiێ
4“A”ÑZé@›$c2
A”ÑZôl&v+‚A”ÑZ
ê@‰#VRA”ÑZþ
"6™÷A”ÑZ ´,@áA”ÑZ"tè
u4A”ÑZb`n„*dµA”ÑZ˜@	7+ëƒA”ÑZ¸`¯ù!hA”ÑZc€5 J åA”ÑZ˜À¸!ñA”ÑZ0
í##¶A”ÑZv`Á L%3DA”ÑZ
+@Æ4ï9
+A”ÑZÀš )NA”ÑZ,À¥!v×A”ÑZ• "9§¬A”ÑZ®ÀY3%SA”ÑZ"`E •èA”ÑZ¸[*«
+A”ÑZš·#†#ìA”ÑZøHG&A”ÑZ; i!–)0	A”ÑZ\à	–½´äA”ÑZg Kì7îA”ÑZx€(öÍA”ÑZ}€Æ$:.c!A”ÑZÆ­(¤ß}A”ÑZí ó1J.‘	A”ÑZÝÀÜ/Ž2Õ-A”ÑZ€¯7	™A”ÑZ: /NÇ
A”ÑZœàè.®	A”ÑZÎ »"ëíA”ÑZO3¶øA”ÑZ®Â+
4V A”ÑZé`$ÊO!A”ÑZú€Én0A”ÑZ€ÉIaA”ÑZ 
+àSs1¨A”ÑZ SÀD$9'I9A”ÑZ"à
+y,h+A”ÑZ"ÀÀ)Á.JiA”ÑZ"§@q%ûB2A”ÑZ"ÍàÄ'L	’A”ÑZ#Cà HÄA”ÑZ#Ô`÷!#½FA”ÑZ$
m5¶A”ÑZ&Ô ‚CŸ	A”ÑZ(äóq39A”ÑZ)yæ*	-A”ÑZ)Þ@3•lA”ÑZ*R€C
+f!A”ÑZ*º€C
î#p7A”ÑZ+` 
É
+[A”ÑZ+{À¼»"l\A”ÑZ+© ÖÖ1ZtA”ÑZ,ý òøA”ÑZ-@Ï*¨—A”ÑZ-g éõ#e
A”ÑZ.˜
Š	1)¢A”ÑZ/Z 
+ÃHÅA”ÑZ1QÀs  7A”ÑZ5| Z*Ÿ1¿	A”ÑZ5øà›&¥A”ÑZ6àLg
|A”ÑZ6‹àIÑ)­A”ÑZ6›À
+d0DA”ÑZ7” Ñ
+1A”ÑZ7í`&×!¶A”ÑZ8!Û 89eŠA”ÑZ8OÀþ9A”ÑZ8– ®*¦à1A”ÑZ8è@1	9%²A”ÑZ9`âc+l8A”ÑZ9V@â0Ó¶A”ÑZ9ö(~A”ÑZ:Àc$¢+©A”ÑZ;V 2#¼>FA”ÑZ;’ !“…A”ÑZÀ.Ü+VA”ÑZA	 Ñ2K!LA”ÑZAk`ù*ä´%A”ÑZA«€#Ì
ÉA”ÑZB¼à+(. A”ÑZCï@1#A”ÑZDþà!H)«A”ÑZG/`ªÎ\A”ÑZGª@)^|A”ÑZH.`ØÚ)/aA”ÑZH’à	ý'éèA”ÑZH•À<2+ÂA”ÑZH÷àG)66{A”ÑZI,à:"ä1õA”ÑZI“ üÅ–A”ÑZK“ E-,ÅA”ÑZKÎ`+8#Ò
A”ÑZLF ê'A”ÑZLNàí¿A”ÑZN! ö&O#¤A”ÑZN[@nÈåA”ÑZN ¦ }7—A”ÑZQF$”+ÖA”ÑZR¥`	4ÁA”ÑZRø€-$3óA”ÑZS_`Å"Ü5UA”ÑZS£ 8/.2ÖƒA”ÑZTl€Þ*/~‚A”ÑZUà
+3~¿ A”ÑZV:@u7/sfA”ÑZVFà¬à1OA”ÑZVXà‰&A–	A”ÑZWU€7™A”ÑZW»@Ö	E#A”ÑZWÃÀ‰Ÿ$ú
A”ÑZWä $¥fA”ÑZX `¬
ÕØA”ÑZX3 +9ÅBA”ÑZXÕ€î&ŠA”ÑZY³€r#1/ðA”ÑZYÁ€_	XA”ÑZYÅ€¹L2[A”ÑZZ* j.2&A”ÑZ[b Þƒ-—8A”ÑZ[k/ÍÏ4A”ÑZ[  ¦-Û
+UAӄZ\ʊ#d
A”ÑZ\”À
=#^
A”ÑZ\²Àj
Ý%j
+A”ÑZ]M ã2$LA”ÑZ]xÀz
+œ>A”ÑZ]Ê€ê(&„A”ÑZ]Êà
+Ò&Ç;A”ÑZ]Ö Ý!ê+ÿ A”ÑZ^ F'¼(xfA”ÑZ_Þ€	ÖÃ/§
+A”ÑZ`iàY3z*[A”ÑZaf`1/ŒA”ÑZa¡ R¹Ò­A”ÑZb#@üzärA”ÑZbÍ@DKWA”ÑZcoà¥4L'ŽA”ÑZcƒ	¤M8™:A”ÑZd'`Ø™SA”ÑZde@Ê]A”ÑZdh )A”ÑZdž@þ
»(ÊRA”ÑZdÓ {$'
+A”ÑZdû
¸#¥œ
+A”ÑZe« Q*F*‰
+A”ÑZf)àžÐ,v}A”ÑZfßàI*«
A”ÑZh€ó%S¸"A”ÑZh– R S'A”ÑZi ¡4&­	A”ÑZiÇ b!iÄdA”ÑZjtB*P#A”ÑZjC€	y#ùˆA”ÑZjZàø$·8A”ÑZjÅ€À$C6;+A”ÑZk ¾ÎA”ÑZk* Ê(»
A”ÑZkHà+ô#œ	A”ÑZk¢à;¹»æA”ÑZl< „"A”ÑZmö eÓ%>A”ÑZo á53˜A”ÑZoÀä01
A”ÑZoâ Øu,áA”ÑZpH€ž&	ÈA”ÑZp`€5'fGA”ÑZq9@íPHA”ÑZquà*m4S
+A”ÑZr׎3í&A”ÑZsd`
+„/áJ	A”ÑZt ñËDA”ÑZuxÀV$Q$6A”ÑZv?€›)Ó÷VA”ÑZvKàTÉ¿A”ÑZv_†“#¶A”ÑZvË 
½Œ
+A”ÑZw7@úž/`A”ÑZxD@¥+Ô;A”ÑZy% Ñ T,cA”ÑZyÊ`B//CA”ÑZyæ â0«+‘A”ÑZz-@Ê!Q*ËA”ÑZ{F 	5A4A”ÑZ|I4
+1˜A”ÑZ|\cº+oA”ÑZ|î`	•8²!·A”ÑZ}R ˜)N!èA”ÑZ}lÀ
YICA”ÑZ}v`.sÑA”ÑZ~™ ›	O, A”ÑZ7`	°$`,znA”ÑZ­à$ü¡¡A”ÑZ·ÀÛQÃA”ÑZçÀ¤!ædA”ÑZ€SÀöï3A”ÑZ€§€sй	A”ÑZ‚ÚàÈhA”ÑZ„k€á5"W?A”ÑZ„Ï AÍ%,A”ÑZ„çÀŒ*ú/mA”ÑZ…fŸ' A”ÑZ…`0þ…
+A”ÑZ…Â@×$"ÓA”ÑZ†E€
-
+Œ%'$A”ÑZ†nÀ
[AwA”ÑZ‡ #ú#Ž5A”ÑZ‡qà
…+Œ'þA”ÑZˆÀ
+ÑwA”ÑZŠî€¨šA”ÑZ‹ð@ó'Ä04A”ÑZŒ ÞÞéA”ÑZŒí@Êì'~A”ÑZ¯@Ž^#L
A”ÑZa€
pà8¼A”ÑZ¢à*$âA”ÑZÆ`J#(&òA”ÑZNàÒA”ÑZ”; 3$±+÷A”ÑZ”\ÀSß1ŠA”ÑZ–—`Z9?gA”ÑZ—ZÀ¡/‚%A”ÑZ˜ {›ãA”ÑZ˜]À(Ï(ÔA”ÑZ™·@	È®)
+A”ÑZšàD¸¡A”ÑZš™à>"ý0A”ÑZ›ª@_•¼A”ÑZœ_À	+O¹A”ÑZœ‰/˜+ñ	A”ÑZœ¶ Â
+Ø)sA”ÑZœí€ "(œ	A”ÑZ†€%
ZA”ÑZÃàš/ç>A”ÑZž*2¬¦A”ÑZŸ® îLVA”ÑZ¡Ë€É½n-A”ÑZ¢ ¹)ø#ÓA”ÑZ¢p€h&&A”ÑZ¢½ i	é)¦9A”ÑZ¢ãÚ'A”ÑZ£U A-¸A”ÑZ£…À³ß%y	A”ÑZ£ù`ß¼bOA”ÑZ¤UÀõ3´PA”ÑZ¤©à‰&Á$œA”ÑZ¥-
5UA”ÑZ¥´ ™$×
+A”ÑZ§‰@,Ë@
A”ÑZ¨F@'*ë
+A”ÑZ¨H`E4N,.A”ÑZ¨ƒ@fs&×A”ÑZ¨‘€ô&ÿA”ÑZ©] ´5Ø#
+A”ÑZ©j ,%0<
+A”ÑZª# 
+p.b1A”ÑZ«EÀŽ•)ÄA”ÑZ¬ªà	ç"â/A”ÑZ¬Ö@Ó%¼0QA”ÑZ­ !Â!9}A”ÑZ®u@G‹#|
+A”ÑZ®Š 
y)b%A”ÑZ®£@ФlA”ÑZ®ñ€Û!7iA”ÑZ°ZS/&)A”ÑZ± è0=2ó&A”ÑZ²+@"Mg#A”ÑZ²àV'‘ÿA”ÑZ²å€
àÝA”ÑZ³_à¤
+A”ÑZ³z€I7A”ÑZ³¤È'î*L	A”ÑZµ9@	(‚ _A”ÑZ¶?À^1EA”ÑZ¸Œ 6Šh!A”ÑZ¹Ù`-,ÒÊ=A”ÑZ¹ù Å#ÿ'yA”ÑZº_ 
+Ø7eëA”ÑZº€"­JA”ÑZº’ 
'±7?A”ÑZ»cÀ>
¢$¥A”ÑZ»Ë€
—0(ÆA”ÑZ¼ƒ`‡t8ïA”ÑZ½)À¯
èA”ÑZ½²@Î- 	A”ÑZ½åÀs™›A”ÑZ¾V€h„A”ÑZ¿tiA”ÑZ¿Ëà•`+vA”ÑZÁ7 	³% þèA”ÑZÁ…à=9;~	A”ÑZÁ“ ”+$ÎA”ÑZÁ¢ ß³!ü“A”ÑZÁð « odA”ÑZ‚ÀÜ!\Â
A”ÑZÂ…@Â'y,¨A”ÑZ¬àäGaA”ÑZÃïàÑæ*r
+A”ÑZÄnÀ	î'!
	A”ÑZÄÍÀï1ºuA”ÑZÄ× ÿ0 Y
A”ÑZÆ*` ë3¢EA”ÑZƪ`	F
A”ÑZÈEà
\ No newline at end of file
diff --git a/external/cfitsio/iter_image.c b/external/cfitsio/iter_image.c
new file mode 100644
index 0000000..43f263c
--- /dev/null
+++ b/external/cfitsio/iter_image.c
@@ -0,0 +1,93 @@
+#include 
+#include 
+#include 
+#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/external/cfitsio/iter_var.c b/external/cfitsio/iter_var.c
new file mode 100644
index 0000000..50d0132
--- /dev/null
+++ b/external/cfitsio/iter_var.c
@@ -0,0 +1,100 @@
+#include 
+#include 
+#include 
+#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/external/cfitsio/longnam.h b/external/cfitsio/longnam.h
new file mode 100644
index 0000000..cac8da4
--- /dev/null
+++ b/external/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/external/cfitsio/make_dfloat.com b/external/cfitsio/make_dfloat.com
new file mode 100644
index 0000000..1a7841f
--- /dev/null
+++ b/external/cfitsio/make_dfloat.com
@@ -0,0 +1,90 @@
+$ ! Command file to build the CFITSIO library on a VMS systems (VAX or Alpha)
+$ set verify
+$ cc/float=d_float buffers.c
+$ cc/float=d_float cfileio.c
+$ cc/float=d_float checksum.c
+$ cc/float=d_float compress.c
+$ cc/float=d_float drvrfile.c
+$ cc/float=d_float drvrmem.c
+$ ! cc/float=d_float drvrnet.c   not currently supported on VMS
+$ ! cc/float=d_float drvsmem.c   not currently supported on VMS
+$ cc/float=d_float editcol.c
+$ cc/float=d_float edithdu.c
+$ cc/float=d_float eval_f.c
+$ cc/float=d_float eval_l.c
+$ cc/float=d_float eval_y.c
+$ cc/float=d_float fitscore.c
+$ cc/float=d_float f77_wrap1.c
+$ cc/float=d_float f77_wrap2.c
+$ cc/float=d_float f77_wrap3.c
+$ cc/float=d_float f77_wrap4.c
+$ cc/float=d_float getcol.c
+$ cc/float=d_float getcolb.c
+$ cc/float=d_float getcolsb.c
+$ cc/float=d_float getcoli.c
+$ cc/float=d_float getcolj.c
+$ cc/float=d_float getcolui.c
+$ cc/float=d_float getcoluj.c
+$ cc/float=d_float getcoluk.c
+$ cc/float=d_float getcolk.c
+$ cc/float=d_float getcole.c
+$ cc/float=d_float getcold.c
+$ cc/float=d_float getcoll.c
+$ cc/float=d_float getcols.c
+$ cc/float=d_float getkey.c
+$ cc/float=d_float group.c
+$ cc/float=d_float grparser.c
+$ cc/float=d_float histo.c
+$ cc/float=d_float iraffits.c
+$ cc/float=d_float modkey.c
+$ cc/float=d_float putcol.c
+$ cc/float=d_float putcolb.c
+$ cc/float=d_float putcolsb.c
+$ cc/float=d_float putcoli.c
+$ cc/float=d_float putcolj.c
+$ cc/float=d_float putcolk.c
+$ cc/float=d_float putcolui.c
+$ cc/float=d_float putcoluj.c
+$ cc/float=d_float putcoluk.c
+$ cc/float=d_float putcole.c
+$ cc/float=d_float putcold.c
+$ cc/float=d_float putcols.c
+$ cc/float=d_float putcoll.c
+$ cc/float=d_float putcolu.c
+$ cc/float=d_float putkey.c
+$ cc/float=d_float region.c
+$ cc/float=d_float scalnull.c
+$ cc/float=d_float swapproc.c
+$ cc/float=d_float wcsutil.c
+$ cc/float=d_float wcssub.c
+$ cc/float=d_float imcompress.c
+$ cc/float=d_float quantize.c
+$ cc/float=d_float ricecomp.c
+$ cc/float=d_float pliocomp.c
+$ cc/float=d_float fits_hcompress.c
+$ cc/float=d_float fits_hdecompress.c
+$ lib/create cfitsio buffers,cfileio,checksum,compress,drvrfile,drvrmem
+$ lib/insert cfitsio editcol,edithdu,eval_f,eval_l,eval_y
+$ lib/insert cfitsio f77_wrap1,f77_wrap2,f77_wrap3,f77_wrap4
+$ lib/insert cfitsio fitscore,getcol,getcolb,getcoli,getcolj,getcolk,getcole
+$ lib/insert cfitsio getcold,getcoll,getcols,getcolui,getcoluj,getcoluk,getcolsb
+$ lib/insert cfitsio getkey,group,grparser,histo,iraffits,modkey,putcol,putcolb
+$ lib/insert cfitsio putcoli,putcolj,putcolk,putcole,putcold,putcolui,putcolsb
+$ lib/insert cfitsio putcoluj,putcoluk,putcols,putcoll,putcolu,putkey,region
+$ lib/insert cfitsio scalnull,swapproc,wcsutil,wcssub,imcompress
+$ lib/insert cfitsio quantize,ricecomp,pliocomp,fits_hcompress,fits_hdecompress
+$ ! 
+$ if (F$GETSYI("ARCH_NAME") .eqs. "VAX") then goto VAX
+$ ! add C routine needed on Alpha to do D_FLOAT conversions
+$   cc/float=d_float vmsieee.c
+$   lib/insert cfitsio vmsieee
+$   set noverify
+$   exit
+$ !
+$ VAX:
+$ ! add macro routines not needed on Alpha and only used on VAX
+$   macro vmsieeer.mar
+$   macro vmsieeed.mar
+$   lib/insert cfitsio vmsieeer,vmsieeed
+$   set noverify
+$   exit
diff --git a/external/cfitsio/make_gfloat.com b/external/cfitsio/make_gfloat.com
new file mode 100644
index 0000000..bfacd8b
--- /dev/null
+++ b/external/cfitsio/make_gfloat.com
@@ -0,0 +1,88 @@
+$ ! Command file to build the CFITSIO library on a VMS systems (VAX or Alpha)
+$ !  This uses the default /float=G_FLOAT option on the Alpha
+$ set verify
+$ cc buffers.c
+$ cc cfileio.c
+$ cc checksum.c
+$ cc compress.c
+$ cc drvrfile.c
+$ cc drvrmem.c
+$ ! cc drvrnet.c   not currently supported on VMS
+$ ! cc drvsmem.c   not currently supported on VMS
+$ cc editcol.c
+$ cc edithdu.c
+$ cc eval_f.c
+$ cc eval_l.c
+$ cc eval_y.c
+$ cc fitscore.c
+$ cc f77_wrap1.c
+$ cc f77_wrap2.c
+$ cc f77_wrap3.c
+$ cc f77_wrap4.c
+$ cc getcol.c
+$ cc getcolb.c
+$ cc getcolsb.c
+$ cc getcoli.c
+$ cc getcolj.c
+$ cc getcolui.c
+$ cc getcoluj.c
+$ cc getcoluk.c
+$ cc getcolk.c
+$ cc getcole.c
+$ cc getcold.c
+$ cc getcoll.c
+$ cc getcols.c
+$ cc getkey.c
+$ cc group.c
+$ cc grparser.c
+$ cc histo.c
+$ cc iraffits.c
+$ cc modkey.c
+$ cc putcol.c
+$ cc putcolb.c
+$ cc putcolsb.c
+$ cc putcoli.c
+$ cc putcolj.c
+$ cc putcolk.c
+$ cc putcolui.c
+$ cc putcoluj.c
+$ cc putcoluk.c
+$ cc putcole.c
+$ cc putcold.c
+$ cc putcols.c
+$ cc putcoll.c
+$ cc putcolu.c
+$ cc putkey.c
+$ cc region.c
+$ cc scalnull.c
+$ cc swapproc.c
+$ cc wcsutil.c
+$ cc wcssub.c
+$ cc imcompress.c
+$ cc quantize.c
+$ cc ricecomp.c
+$ cc pliocomp.c
+$ cc/float=d_float fits_hcompress.c
+$ cc/float=d_float fits_hdecompress.c
+$ lib/create cfitsio buffers,cfileio,checksum,compress,drvrfile,drvrmem
+$ lib/insert cfitsio editcol,edithdu,eval_f,eval_l,eval_y
+$ lib/insert cfitsio f77_wrap1,f77_wrap2,f77_wrap3,f77_wrap4
+$ lib/insert cfitsio fitscore,getcol,getcolb,getcoli,getcolj,getcolk,getcole
+$ lib/insert cfitsio getcold,getcoll,getcols,getcolui,getcoluj,getcoluk,getcolsb
+$ lib/insert cfitsio getkey,group,grparser,histo,iraffits,modkey,putcol,putcolb
+$ lib/insert cfitsio putcoli,putcolj,putcolk,putcole,putcold,putcolui,putcolsb
+$ lib/insert cfitsio putcoluj,putcoluk,putcols,putcoll,putcolu,putkey,region
+$ lib/insert cfitsio scalnull,swapproc,wcsutil,wcssub,imcompress
+$ lib/insert cfitsio quantize,ricecomp,pliocomp,fits_hcompress,fits_hdecompress
+$ ! 
+$ if (F$GETSYI("ARCH_NAME") .eqs. "VAX") then goto VAX
+$   set noverify
+$   exit
+$ !
+$ VAX:
+$ ! add macro routines not needed on Alpha and only used on VAX
+$   macro vmsieeer.mar
+$   macro vmsieeed.mar
+$   lib/insert cfitsio vmsieeer,vmsieeed
+$   set noverify
+$   exit
diff --git a/external/cfitsio/make_ieee.com b/external/cfitsio/make_ieee.com
new file mode 100644
index 0000000..89ab437
--- /dev/null
+++ b/external/cfitsio/make_ieee.com
@@ -0,0 +1,87 @@
+$ ! Command file to build the CFITSIO library on a VMS systems (VAX or Alpha)
+$ set verify
+$ cc/float=ieee_float buffers.c
+$ cc/float=ieee_float cfileio.c
+$ cc/float=ieee_float checksum.c
+$ cc/float=ieee_float compress.c
+$ cc/float=ieee_float drvrfile.c
+$ cc/float=ieee_float drvrmem.c
+$ ! cc/float=ieee_float drvrnet.c   not currently supported on VMS
+$ ! cc/float=ieee_float drvsmem.c   not currently supported on VMS
+$ cc/float=ieee_float editcol.c
+$ cc/float=ieee_float edithdu.c
+$ cc/float=ieee_float eval_f.c
+$ cc/float=ieee_float eval_l.c
+$ cc/float=ieee_float eval_y.c
+$ cc/float=ieee_float fitscore.c
+$ cc/float=ieee_float f77_wrap1.c
+$ cc/float=ieee_float f77_wrap2.c
+$ cc/float=ieee_float f77_wrap3.c
+$ cc/float=ieee_float f77_wrap4.c
+$ cc/float=ieee_float getcol.c
+$ cc/float=ieee_float getcolb.c
+$ cc/float=ieee_float getcolsb.c
+$ cc/float=ieee_float getcoli.c
+$ cc/float=ieee_float getcolj.c
+$ cc/float=ieee_float getcolui.c
+$ cc/float=ieee_float getcoluj.c
+$ cc/float=ieee_float getcoluk.c
+$ cc/float=ieee_float getcolk.c
+$ cc/float=ieee_float getcole.c
+$ cc/float=ieee_float getcold.c
+$ cc/float=ieee_float getcoll.c
+$ cc/float=ieee_float getcols.c
+$ cc/float=ieee_float getkey.c
+$ cc/float=ieee_float group.c
+$ cc/float=ieee_float grparser.c
+$ cc/float=ieee_float histo.c
+$ cc/float=ieee_float iraffits.c
+$ cc/float=ieee_float modkey.c
+$ cc/float=ieee_float putcol.c
+$ cc/float=ieee_float putcolb.c
+$ cc/float=ieee_float putcolsb.c
+$ cc/float=ieee_float putcoli.c
+$ cc/float=ieee_float putcolj.c
+$ cc/float=ieee_float putcolk.c
+$ cc/float=ieee_float putcolui.c
+$ cc/float=ieee_float putcoluj.c
+$ cc/float=ieee_float putcoluk.c
+$ cc/float=ieee_float putcole.c
+$ cc/float=ieee_float putcold.c
+$ cc/float=ieee_float putcols.c
+$ cc/float=ieee_float putcoll.c
+$ cc/float=ieee_float putcolu.c
+$ cc/float=ieee_float putkey.c
+$ cc/float=ieee_float region.c
+$ cc/float=ieee_float scalnull.c
+$ cc/float=ieee_float swapproc.c
+$ cc/float=ieee_float wcsutil.c
+$ cc/float=ieee_float wcssub.c
+$ cc/float=ieee_float imcompress.c
+$ cc/float=ieee_float quantize.c
+$ cc/float=ieee_float ricecomp.c
+$ cc/float=ieee_float pliocomp.c
+$ cc/float=d_float fits_hcompress.c
+$ cc/float=d_float fits_hdecompress.c
+$ lib/create cfitsio buffers,cfileio,checksum,compress,drvrfile,drvrmem
+$ lib/insert cfitsio editcol,edithdu,eval_f,eval_l,eval_y
+$ lib/insert cfitsio f77_wrap1,f77_wrap2,f77_wrap3,f77_wrap4
+$ lib/insert cfitsio fitscore,getcol,getcolb,getcoli,getcolj,getcolk,getcole
+$ lib/insert cfitsio getcold,getcoll,getcols,getcolui,getcoluj,getcoluk,getcolsb
+$ lib/insert cfitsio getkey,group,grparser,histo,iraffits,modkey,putcol,putcolb
+$ lib/insert cfitsio putcoli,putcolj,putcolk,putcole,putcold,putcolui,putcolsb
+$ lib/insert cfitsio putcoluj,putcoluk,putcols,putcoll,putcolu,putkey,region
+$ lib/insert cfitsio scalnull,swapproc,wcsutil,wcssub,imcompress
+$ lib/insert cfitsio quantize,ricecomp,pliocomp,fits_hcompress,fits_hdecompress
+$ ! 
+$ if (F$GETSYI("ARCH_NAME") .eqs. "VAX") then goto VAX
+$   set noverify
+$   exit
+$ !
+$ VAX:
+$ ! add macro routines not needed on Alpha and only used on VAX
+$   macro vmsieeer.mar
+$   macro vmsieeed.mar
+$   lib/insert cfitsio vmsieeer,vmsieeed
+$   set noverify
+$   exit
diff --git a/external/cfitsio/makefile.bc b/external/cfitsio/makefile.bc
new file mode 100644
index 0000000..8e8526e
--- /dev/null
+++ b/external/cfitsio/makefile.bc
@@ -0,0 +1,588 @@
+#
+# Borland C++ IDE generated makefile
+# Generated 10/12/99 at 1:24:11 PM 
+#
+.AUTODEPEND
+
+
+#
+# Borland C++ tools
+#
+IMPLIB  = Implib
+BCC32   = Bcc32 +BccW32.cfg 
+BCC32I  = Bcc32i +BccW32.cfg 
+TLINK32 = TLink32
+ILINK32 = Ilink32
+TLIB    = TLib
+BRC32   = Brc32
+TASM32  = Tasm32
+#
+# IDE macros
+#
+
+
+#
+# Options
+#
+IDE_LinkFLAGS32 =  -LD:\BC5\LIB
+LinkerLocalOptsAtC32_cfitsiodlib =  -Tpd -ap -c
+ResLocalOptsAtC32_cfitsiodlib = 
+BLocalOptsAtC32_cfitsiodlib = 
+CompInheritOptsAt_cfitsiodlib = -ID:\BC5\INCLUDE -D_RTLDLL -DWIN32;
+LinkerInheritOptsAt_cfitsiodlib = -x
+LinkerOptsAt_cfitsiodlib = $(LinkerLocalOptsAtC32_cfitsiodlib)
+ResOptsAt_cfitsiodlib = $(ResLocalOptsAtC32_cfitsiodlib)
+BOptsAt_cfitsiodlib = $(BLocalOptsAtC32_cfitsiodlib)
+
+#
+# Dependency List
+#
+Dep_cfitsio = \
+   cfitsio.lib
+
+cfitsio : BccW32.cfg $(Dep_cfitsio)
+  echo MakeNode
+
+cfitsio.lib : cfitsio.dll
+  $(IMPLIB) $@ cfitsio.dll
+
+
+Dep_cfitsioddll = \
+   listhead.obj\
+   imcompress.obj\
+   quantize.obj\
+   ricecomp.obj\
+   pliocomp.obj\
+   iraffits.obj\
+   wcsutil.obj\
+   histo.obj\
+   scalnull.obj\
+   region.obj\
+   putkey.obj\
+   putcoluk.obj\
+   putcoluj.obj\
+   putcolui.obj\
+   putcolu.obj\
+   putcols.obj\
+   putcoll.obj\
+   putcolk.obj\
+   putcolj.obj\
+   putcoli.obj\
+   putcole.obj\
+   putcold.obj\
+   putcolb.obj\
+   putcolsb.obj\
+   putcol.obj\
+   modkey.obj\
+   swapproc.obj\
+   getcol.obj\
+   group.obj\
+   getkey.obj\
+   getcoluk.obj\
+   getcoluj.obj\
+   getcolui.obj\
+   getcols.obj\
+   getcoll.obj\
+   getcolk.obj\
+   getcolj.obj\
+   getcoli.obj\
+   getcole.obj\
+   getcold.obj\
+   getcolb.obj\
+   getcolsb.obj\
+   grparser.obj\
+   fitscore.obj\
+   f77_wrap1.obj\
+   f77_wrap2.obj\
+   f77_wrap3.obj\
+   f77_wrap4.obj\
+   eval_y.obj\
+   eval_l.obj\
+   eval_f.obj\
+   edithdu.obj\
+   editcol.obj\
+   drvrmem.obj\
+   drvrfile.obj\
+   checksum.obj\
+   cfileio.obj\
+   buffers.obj\
+   fits_hcompress.obj\
+   fits_hdecompress.obj\
+   zuncompress.obj\
+   zcompress.obj\
+   adler32.obj\
+   crc32.obj\
+   inffast.obj\
+   inftrees.obj\
+   trees.obj\
+   zutil.obj\
+   deflate.obj\
+   infback.obj\
+   inflate.obj\
+   uncompr.obj
+
+cfitsio.dll : $(Dep_cfitsioddll) cfitsio.def
+  $(ILINK32) @&&|
+ /v $(IDE_LinkFLAGS32) $(LinkerOptsAt_cfitsiodlib) $(LinkerInheritOptsAt_cfitsiodlib) +
+D:\BC5\LIB\c0d32.obj+
+listhead.obj+
+imcompress.obj+
+quantize.obj+
+ricecomp.obj+
+pliocomp.obj+
+iraffits.obj+
+wcsutil.obj+
+histo.obj+
+iraffits.obj+
+scalnull.obj+
+region.obj+
+putkey.obj+
+putcoluk.obj+
+putcoluj.obj+
+putcolui.obj+
+putcolu.obj+
+putcols.obj+
+putcoll.obj+
+putcolk.obj+
+putcolj.obj+
+putcoli.obj+
+putcole.obj+
+putcold.obj+
+putcolb.obj+
+putcolsb.obj+
+putcol.obj+
+modkey.obj+
+swapproc.obj+
+getcol.obj+
+group.obj+
+getkey.obj+
+getcoluk.obj+
+getcoluj.obj+
+getcolui.obj+
+getcols.obj+
+getcoll.obj+
+getcolk.obj+
+getcolj.obj+
+getcoli.obj+
+getcole.obj+
+getcold.obj+
+getcolb.obj+
+getcolsb.obj+
+grparser.obj+
+fitscore.obj+
+f77_wrap1.obj+
+f77_wrap2.obj+
+f77_wrap3.obj+
+f77_wrap4.obj+
+eval_y.obj+
+eval_l.obj+
+eval_f.obj+
+edithdu.obj+
+editcol.obj+
+drvrmem.obj+
+drvrfile.obj+
+checksum.obj+
+cfileio.obj+
+buffers.obj+
+fits_hcompress.obj+
+fits_hdecompress.obj+
+zuncompress.obj+
+zcompress.obj+
+adler32.obj+
+crc32.obj+
+inffast.obj+
+inftrees.obj+
+trees.obj+
+zutil.obj+
+deflate.obj+
+infback.obj+
+inflate.obj+
+uncompr.obj
+$<,$*
+D:\BC5\LIB\import32.lib+
+D:\BC5\LIB\cw32i.lib
+cfitsio.def
+
+
+|
+wcsutil.obj :  wcsutil.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ wcsutil.c
+|
+iraffits.obj :  iraffits.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ iraffits.c
+|
+histo.obj :  histo.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ histo.c
+|
+
+scalnull.obj :  scalnull.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ scalnull.c
+|
+
+region.obj :  region.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ region.c
+|
+
+putkey.obj :  putkey.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ putkey.c
+|
+
+putcoluk.obj :  putcoluk.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ putcoluk.c
+|
+
+putcoluj.obj :  putcoluj.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ putcoluj.c
+|
+
+putcolui.obj :  putcolui.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ putcolui.c
+|
+
+putcolu.obj :  putcolu.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ putcolu.c
+|
+
+putcols.obj :  putcols.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ putcols.c
+|
+
+putcoll.obj :  putcoll.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ putcoll.c
+|
+
+putcolk.obj :  putcolk.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ putcolk.c
+|
+
+putcolj.obj :  putcolj.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ putcolj.c
+|
+
+putcoli.obj :  putcoli.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ putcoli.c
+|
+
+putcole.obj :  putcole.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ putcole.c
+|
+
+putcold.obj :  putcold.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ putcold.c
+|
+
+putcolb.obj :  putcolb.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ putcolb.c
+|
+
+putcolsb.obj :  putcolsb.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ putcolsb.c
+|
+
+putcol.obj :  putcol.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ putcol.c
+|
+
+modkey.obj :  modkey.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ modkey.c
+|
+
+swapproc.obj :  swapproc.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ swapproc.c
+|
+
+getcol.obj :  getcol.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ getcol.c
+|
+
+group.obj :  group.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ group.c
+|
+
+getkey.obj :  getkey.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ getkey.c
+|
+
+getcoluk.obj :  getcoluk.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ getcoluk.c
+|
+
+getcoluj.obj :  getcoluj.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ getcoluj.c
+|
+
+getcolui.obj :  getcolui.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ getcolui.c
+|
+
+getcols.obj :  getcols.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ getcols.c
+|
+
+getcoll.obj :  getcoll.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ getcoll.c
+|
+
+getcolk.obj :  getcolk.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ getcolk.c
+|
+
+getcolj.obj :  getcolj.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ getcolj.c
+|
+
+getcoli.obj :  getcoli.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ getcoli.c
+|
+
+getcole.obj :  getcole.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ getcole.c
+|
+
+getcold.obj :  getcold.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ getcold.c
+|
+
+getcolb.obj :  getcolb.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ getcolb.c
+|
+getcolsb.obj :  getcolsb.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ getcolsb.c
+|
+
+grparser.obj :  grparser.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ grparser.c
+|
+
+fitscore.obj :  fitscore.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ fitscore.c
+|
+
+f77_wrap1.obj :  f77_wrap1.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ f77_wrap1.c
+|
+
+f77_wrap2.obj :  f77_wrap2.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ f77_wrap2.c
+|
+
+f77_wrap3.obj :  f77_wrap3.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ f77_wrap3.c
+|
+
+f77_wrap4.obj :  f77_wrap4.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ f77_wrap4.c
+|
+
+eval_y.obj :  eval_y.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ eval_y.c
+|
+
+eval_l.obj :  eval_l.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ eval_l.c
+|
+
+eval_f.obj :  eval_f.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ eval_f.c
+|
+
+edithdu.obj :  edithdu.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ edithdu.c
+|
+
+editcol.obj :  editcol.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ editcol.c
+|
+
+drvrmem.obj :  drvrmem.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ drvrmem.c
+|
+
+drvrfile.obj :  drvrfile.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ drvrfile.c
+|
+
+checksum.obj :  checksum.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ checksum.c
+|
+
+cfileio.obj :  cfileio.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ cfileio.c
+|
+
+listhead.obj :  listhead.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ listhead.c
+|
+
+imcompress.obj :  imcompress.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ imcompress.c
+|
+
+quantize.obj :  quantize.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ quantize.c
+|
+
+ricecomp.obj :  ricecomp.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ ricecomp.c
+|
+
+pliocomp.obj :  pliocomp.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ pliocomp.c
+|
+
+buffers.obj :  buffers.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ buffers.c
+|
+
+fits_hcompress.obj :  fits_hcompress.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ fits_hcompress.c
+|
+
+fits_hdecompress.obj :  fits_hdecompress.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ fits_hdecompress.c
+|
+
+zuncompress.obj :  zuncompress.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ zuncompress.c
+|
+
+zcompress.obj :  zcompress.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ zcompress.c
+|
+
+adler32.obj :  adler32.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ adler32.c
+|
+
+crc32.obj :  crc32.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ crc32.c
+|
+
+inffast.obj :  inffast.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ inffast.c
+|
+
+inftrees.obj :  inftrees.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ inftrees.c
+|
+
+trees.obj :  trees.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ trees.c
+|
+
+zutil.obj :  zutil.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ zutil.c
+|
+
+deflate.obj :  deflate.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ deflate.c
+|
+
+infback.obj :  infback.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ infback.c
+|
+
+inflate.obj :  inflate.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ inflate.c
+|
+
+uncompr.obj :  uncompr.c
+  $(BCC32) -P- -c @&&|
+ $(CompOptsAt_cfitsiodlib) $(CompInheritOptsAt_cfitsiodlib) -o$@ uncompr.c
+|
+
+
+windumpexts.exe :  windumpexts.c
+  bcc32 windumpexts.c
+
+
+cfitsio.def: windumpexts.exe
+	windumpexts -o cfitsio.def cfitsio.dll @&&|
+		$(Dep_cfitsioddll)
+|
+
+# Compiler configuration file
+BccW32.cfg : 
+   Copy &&|
+-w
+-R
+-v
+-WM-
+-vi
+-H
+-H=cfitsio.csm
+-WCD
+| $@
+
+
diff --git a/external/cfitsio/makefile.vcc b/external/cfitsio/makefile.vcc
new file mode 100644
index 0000000..e0e28b8
--- /dev/null
+++ b/external/cfitsio/makefile.vcc
@@ -0,0 +1,793 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on cfitsio.dsp
+!IF "$(CFG)" == ""
+CFG=Win32 Release
+!MESSAGE No configuration specified. Defaulting to Win32 Release.
+!ENDIF 
+
+!IF "$(CFG)" != "Win32 Release" && "$(CFG)" != "Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE 
+!MESSAGE NMAKE /f "cfitsio.mak" CFG="Win32 Debug"
+!MESSAGE 
+!MESSAGE Possible choices for configuration are:
+!MESSAGE 
+!MESSAGE "Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE 
+!ERROR An invalid configuration is specified.
+!ENDIF 
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE 
+NULL=nul
+!ENDIF 
+
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF  "$(CFG)" == "Win32 Release"
+
+OUTDIR=.
+INTDIR=.\Release
+# Begin Custom Macros
+OutDir=.
+# End Custom Macros
+
+ALL : "$(OUTDIR)\cfitsio.dll"
+
+
+CLEAN :
+	-@erase "$(INTDIR)\buffers.obj"
+	-@erase "$(INTDIR)\cfileio.obj"
+	-@erase "$(INTDIR)\checksum.obj"
+	-@erase "$(INTDIR)\drvrfile.obj"
+	-@erase "$(INTDIR)\drvrmem.obj"
+	-@erase "$(INTDIR)\editcol.obj"
+	-@erase "$(INTDIR)\edithdu.obj"
+	-@erase "$(INTDIR)\eval_f.obj"
+	-@erase "$(INTDIR)\eval_l.obj"
+	-@erase "$(INTDIR)\eval_y.obj"
+	-@erase "$(INTDIR)\fitscore.obj"
+	-@erase "$(INTDIR)\f77_wrap1.obj"
+	-@erase "$(INTDIR)\f77_wrap2.obj"
+	-@erase "$(INTDIR)\f77_wrap3.obj"
+	-@erase "$(INTDIR)\f77_wrap4.obj"
+	-@erase "$(INTDIR)\getcol.obj"
+	-@erase "$(INTDIR)\getcolb.obj"
+	-@erase "$(INTDIR)\getcolsb.obj"
+	-@erase "$(INTDIR)\getcold.obj"
+	-@erase "$(INTDIR)\getcole.obj"
+	-@erase "$(INTDIR)\getcoli.obj"
+	-@erase "$(INTDIR)\getcolj.obj"
+	-@erase "$(INTDIR)\getcolk.obj"
+	-@erase "$(INTDIR)\getcoll.obj"
+	-@erase "$(INTDIR)\getcols.obj"
+	-@erase "$(INTDIR)\getcolui.obj"
+	-@erase "$(INTDIR)\getcoluj.obj"
+	-@erase "$(INTDIR)\getcoluk.obj"
+	-@erase "$(INTDIR)\getkey.obj"
+	-@erase "$(INTDIR)\group.obj"
+	-@erase "$(INTDIR)\grparser.obj"
+	-@erase "$(INTDIR)\histo.obj"
+	-@erase "$(INTDIR)\iraffits.obj"
+	-@erase "$(INTDIR)\modkey.obj"
+	-@erase "$(INTDIR)\putcol.obj"
+	-@erase "$(INTDIR)\putcolb.obj"
+	-@erase "$(INTDIR)\putcolsb.obj"
+	-@erase "$(INTDIR)\putcold.obj"
+	-@erase "$(INTDIR)\putcole.obj"
+	-@erase "$(INTDIR)\putcoli.obj"
+	-@erase "$(INTDIR)\putcolj.obj"
+	-@erase "$(INTDIR)\putcolk.obj"
+	-@erase "$(INTDIR)\putcoll.obj"
+	-@erase "$(INTDIR)\putcols.obj"
+	-@erase "$(INTDIR)\putcolu.obj"
+	-@erase "$(INTDIR)\putcolui.obj"
+	-@erase "$(INTDIR)\putcoluj.obj"
+	-@erase "$(INTDIR)\putcoluk.obj"
+	-@erase "$(INTDIR)\putkey.obj"
+	-@erase "$(INTDIR)\region.obj"
+	-@erase "$(INTDIR)\scalnull.obj"
+	-@erase "$(INTDIR)\swapproc.obj"
+	-@erase "$(INTDIR)\wcssub.obj"
+	-@erase "$(INTDIR)\wcsutil.obj"
+	-@erase "$(INTDIR)\imcompress.obj"
+	-@erase "$(INTDIR)\ricecomp.obj"
+	-@erase "$(INTDIR)\quantize.obj"
+	-@erase "$(INTDIR)\pliocomp.obj"
+	-@erase "$(INTDIR)\fits_hcompress.obj"
+	-@erase "$(INTDIR)\fits_hdecompress.obj"
+	-@erase "$(INTDIR)\zuncompress.obj"
+	-@erase "$(INTDIR)\zcompress.obj"
+	-@erase "$(INTDIR)\adler32.obj"
+	-@erase "$(INTDIR)\crc32.obj"
+	-@erase "$(INTDIR)\inffast.obj"
+	-@erase "$(INTDIR)\inftrees.obj"
+	-@erase "$(INTDIR)\trees.obj"
+	-@erase "$(INTDIR)\zutil.obj"
+	-@erase "$(INTDIR)\deflate.obj"
+	-@erase "$(INTDIR)\infback.obj"
+	-@erase "$(INTDIR)\inflate.obj"
+	-@erase "$(INTDIR)\uncompr.obj"
+	-@erase "$(INTDIR)\vc60.idb"
+	-@erase "$(OUTDIR)\cfitsio.dll"
+	-@erase "$(OUTDIR)\cfitsio.exp"
+	-@erase "$(OUTDIR)\cfitsio.lib"
+
+"$(OUTDIR)" :
+    if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+"$(INTDIR)" :
+    if not exist "$(INTDIR)/$(NULL)" mkdir "$(INTDIR)"
+
+CPP_PROJ=/nologo /MD /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "CFITSIO_EXPORTS" /D "_CRT_SECURE_NO_DEPRECATE" /Fp"$(INTDIR)\cfitsio.pch" /YX /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\\" /FD /c 
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32 
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\cfitsio.bsc" 
+BSC32_SBRS= \
+	
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /incremental:no /pdb:"$(OUTDIR)\cfitsio.pdb" /machine:I386 /def:".\cfitsio.def" /out:"$(OUTDIR)\cfitsio.dll" /implib:"$(OUTDIR)\cfitsio.lib" 
+DEF_FILE= ".\cfitsio.def"
+LINK32_OBJS= \
+	"$(INTDIR)\buffers.obj" \
+	"$(INTDIR)\cfileio.obj" \
+	"$(INTDIR)\checksum.obj" \
+	"$(INTDIR)\drvrfile.obj" \
+	"$(INTDIR)\drvrmem.obj" \
+	"$(INTDIR)\editcol.obj" \
+	"$(INTDIR)\edithdu.obj" \
+	"$(INTDIR)\eval_f.obj" \
+	"$(INTDIR)\eval_l.obj" \
+	"$(INTDIR)\eval_y.obj" \
+	"$(INTDIR)\fitscore.obj" \
+	"$(INTDIR)\f77_wrap1.obj" \
+	"$(INTDIR)\f77_wrap2.obj" \
+	"$(INTDIR)\f77_wrap3.obj" \
+	"$(INTDIR)\f77_wrap4.obj" \
+	"$(INTDIR)\getcol.obj" \
+	"$(INTDIR)\getcolb.obj" \
+	"$(INTDIR)\getcolsb.obj" \
+	"$(INTDIR)\getcold.obj" \
+	"$(INTDIR)\getcole.obj" \
+	"$(INTDIR)\getcoli.obj" \
+	"$(INTDIR)\getcolj.obj" \
+	"$(INTDIR)\getcolk.obj" \
+	"$(INTDIR)\getcoll.obj" \
+	"$(INTDIR)\getcols.obj" \
+	"$(INTDIR)\getcolui.obj" \
+	"$(INTDIR)\getcoluj.obj" \
+	"$(INTDIR)\getcoluk.obj" \
+	"$(INTDIR)\getkey.obj" \
+	"$(INTDIR)\group.obj" \
+	"$(INTDIR)\grparser.obj" \
+	"$(INTDIR)\histo.obj" \
+	"$(INTDIR)\iraffits.obj" \
+	"$(INTDIR)\modkey.obj" \
+	"$(INTDIR)\putcol.obj" \
+	"$(INTDIR)\putcolb.obj" \
+	"$(INTDIR)\putcolsb.obj" \
+	"$(INTDIR)\putcold.obj" \
+	"$(INTDIR)\putcole.obj" \
+	"$(INTDIR)\putcoli.obj" \
+	"$(INTDIR)\putcolj.obj" \
+	"$(INTDIR)\putcolk.obj" \
+	"$(INTDIR)\putcoll.obj" \
+	"$(INTDIR)\putcols.obj" \
+	"$(INTDIR)\putcolu.obj" \
+	"$(INTDIR)\putcolui.obj" \
+	"$(INTDIR)\putcoluj.obj" \
+	"$(INTDIR)\putcoluk.obj" \
+	"$(INTDIR)\putkey.obj" \
+	"$(INTDIR)\region.obj" \
+	"$(INTDIR)\scalnull.obj" \
+	"$(INTDIR)\swapproc.obj" \
+	"$(INTDIR)\wcssub.obj"  \
+	"$(INTDIR)\wcsutil.obj" \
+	"$(INTDIR)\imcompress.obj" \
+	"$(INTDIR)\ricecomp.obj" \
+	"$(INTDIR)\quantize.obj" \
+	"$(INTDIR)\pliocomp.obj" \
+	"$(INTDIR)\fits_hcompress.obj" \
+	"$(INTDIR)\fits_hdecompress.obj" \
+	"$(INTDIR)\zuncompress.obj" \
+	"$(INTDIR)\zcompress.obj" \
+	"$(INTDIR)\adler32.obj" \
+	"$(INTDIR)\crc32.obj" \
+	"$(INTDIR)\inffast.obj" \
+	"$(INTDIR)\inftrees.obj" \
+	"$(INTDIR)\trees.obj" \
+	"$(INTDIR)\zutil.obj" \
+	"$(INTDIR)\deflate.obj" \
+	"$(INTDIR)\infback.obj" \
+	"$(INTDIR)\inflate.obj" \
+	"$(INTDIR)\uncompr.obj" 
+
+"$(OUTDIR)\cfitsio.dll" : $(LINK32_OBJS) WINDUMP
+	windumpexts -o $(DEF_FILE) cfitsio.dll $(LINK32_OBJS)
+	$(LINK32) @<<
+  $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+!ELSEIF  "$(CFG)" == "Win32 Debug"
+
+OUTDIR=.
+INTDIR=.\Debug
+# Begin Custom Macros
+OutDir=.
+# End Custom Macros
+
+ALL : "$(OUTDIR)\cfitsio.dll"
+
+
+CLEAN :
+	-@erase "$(INTDIR)\buffers.obj"
+	-@erase "$(INTDIR)\cfileio.obj"
+	-@erase "$(INTDIR)\checksum.obj"
+	-@erase "$(INTDIR)\drvrfile.obj"
+	-@erase "$(INTDIR)\drvrmem.obj"
+	-@erase "$(INTDIR)\editcol.obj"
+	-@erase "$(INTDIR)\edithdu.obj"
+	-@erase "$(INTDIR)\eval_f.obj"
+	-@erase "$(INTDIR)\eval_l.obj"
+	-@erase "$(INTDIR)\eval_y.obj"
+	-@erase "$(INTDIR)\fitscore.obj"
+	-@erase "$(INTDIR)\f77_wrap1.obj"
+	-@erase "$(INTDIR)\f77_wrap2.obj"
+	-@erase "$(INTDIR)\f77_wrap3.obj"
+	-@erase "$(INTDIR)\f77_wrap4.obj"
+	-@erase "$(INTDIR)\getcol.obj"
+	-@erase "$(INTDIR)\getcolb.obj"
+	-@erase "$(INTDIR)\getcolsb.obj"
+	-@erase "$(INTDIR)\getcold.obj"
+	-@erase "$(INTDIR)\getcole.obj"
+	-@erase "$(INTDIR)\getcoli.obj"
+	-@erase "$(INTDIR)\getcolj.obj"
+	-@erase "$(INTDIR)\getcolk.obj"
+	-@erase "$(INTDIR)\getcoll.obj"
+	-@erase "$(INTDIR)\getcols.obj"
+	-@erase "$(INTDIR)\getcolui.obj"
+	-@erase "$(INTDIR)\getcoluj.obj"
+	-@erase "$(INTDIR)\getcoluk.obj"
+	-@erase "$(INTDIR)\getkey.obj"
+	-@erase "$(INTDIR)\group.obj"
+	-@erase "$(INTDIR)\grparser.obj"
+	-@erase "$(INTDIR)\histo.obj"
+	-@erase "$(INTDIR)\iraffits.obj"
+	-@erase "$(INTDIR)\modkey.obj"
+	-@erase "$(INTDIR)\putcol.obj"
+	-@erase "$(INTDIR)\putcolb.obj"
+	-@erase "$(INTDIR)\putcolsb.obj"
+	-@erase "$(INTDIR)\putcold.obj"
+	-@erase "$(INTDIR)\putcole.obj"
+	-@erase "$(INTDIR)\putcoli.obj"
+	-@erase "$(INTDIR)\putcolj.obj"
+	-@erase "$(INTDIR)\putcolk.obj"
+	-@erase "$(INTDIR)\putcoll.obj"
+	-@erase "$(INTDIR)\putcols.obj"
+	-@erase "$(INTDIR)\putcolu.obj"
+	-@erase "$(INTDIR)\putcolui.obj"
+	-@erase "$(INTDIR)\putcoluj.obj"
+	-@erase "$(INTDIR)\putcoluk.obj"
+	-@erase "$(INTDIR)\putkey.obj"
+	-@erase "$(INTDIR)\region.obj"
+	-@erase "$(INTDIR)\scalnull.obj"
+	-@erase "$(INTDIR)\swapproc.obj"
+	-@erase "$(INTDIR)\vc60.idb"
+	-@erase "$(INTDIR)\vc60.pdb"
+	-@erase "$(INTDIR)\wcssub.obj"
+	-@erase "$(INTDIR)\wcsutil.obj"
+	-@erase "$(INTDIR)\imcompress.obj"
+	-@erase "$(INTDIR)\ricecomp.obj"
+	-@erase "$(INTDIR)\quantize.obj"
+	-@erase "$(INTDIR)\pliocomp.obj"
+	-@erase "$(INTDIR)\fits_hcompress.obj"
+	-@erase "$(INTDIR)\fits_hdecompress.obj"
+	-@erase "$(INTDIR)\zuncompress.obj"
+	-@erase "$(INTDIR)\zcompress.obj"
+	-@erase "$(INTDIR)\adler32.obj"
+	-@erase "$(INTDIR)\crc32.obj"
+	-@erase "$(INTDIR)\inffast.obj"
+	-@erase "$(INTDIR)\inftrees.obj"
+	-@erase "$(INTDIR)\trees.obj"
+	-@erase "$(INTDIR)\zutil.obj"
+	-@erase "$(INTDIR)\deflate.obj"
+	-@erase "$(INTDIR)\infback.obj"
+	-@erase "$(INTDIR)\inflate.obj"
+	-@erase "$(INTDIR)\uncompr.obj"
+	-@erase "$(OUTDIR)\cfitsio.dll"
+	-@erase "$(OUTDIR)\cfitsio.exp"
+	-@erase "$(OUTDIR)\cfitsio.ilk"
+	-@erase "$(OUTDIR)\cfitsio.lib"
+	-@erase "$(OUTDIR)\cfitsio.pdb"
+
+"$(OUTDIR)" :
+    if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+"$(INTDIR)" :
+    if not exist "$(INTDIR)/$(NULL)" mkdir "$(INTDIR)"
+
+CPP_PROJ=/nologo /MDd /W3 /Gm /GX /ZI /Od /D "__WIN32__" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "CFITSIO_EXPORTS" /D "_CRT_SECURE_NO_DEPRECATE" /Fp"$(INTDIR)\cfitsio.pch" /YX /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\\" /FD /GZ /c 
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32 
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\cfitsio.bsc" 
+BSC32_SBRS= \
+	
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /incremental:yes /pdb:"$(OUTDIR)\cfitsio.pdb" /debug /machine:I386 /def:".\cfitsio.def" /out:"$(OUTDIR)\cfitsio.dll" /implib:"$(OUTDIR)\cfitsio.lib" /pdbtype:sept 
+DEF_FILE= ".\cfitsio.def"
+LINK32_OBJS= \
+	"$(INTDIR)\buffers.obj" \
+	"$(INTDIR)\cfileio.obj" \
+	"$(INTDIR)\checksum.obj" \
+	"$(INTDIR)\drvrfile.obj" \
+	"$(INTDIR)\drvrmem.obj" \
+	"$(INTDIR)\editcol.obj" \
+	"$(INTDIR)\edithdu.obj" \
+	"$(INTDIR)\eval_f.obj" \
+	"$(INTDIR)\eval_l.obj" \
+	"$(INTDIR)\eval_y.obj" \
+	"$(INTDIR)\fitscore.obj" \
+	"$(INTDIR)\f77_wrap1.obj" \
+	"$(INTDIR)\f77_wrap2.obj" \
+	"$(INTDIR)\f77_wrap3.obj" \
+	"$(INTDIR)\f77_wrap4.obj" \
+	"$(INTDIR)\getcol.obj" \
+	"$(INTDIR)\getcolb.obj" \
+	"$(INTDIR)\getcolsb.obj" \
+	"$(INTDIR)\getcold.obj" \
+	"$(INTDIR)\getcole.obj" \
+	"$(INTDIR)\getcoli.obj" \
+	"$(INTDIR)\getcolj.obj" \
+	"$(INTDIR)\getcolk.obj" \
+	"$(INTDIR)\getcoll.obj" \
+	"$(INTDIR)\getcols.obj" \
+	"$(INTDIR)\getcolui.obj" \
+	"$(INTDIR)\getcoluj.obj" \
+	"$(INTDIR)\getcoluk.obj" \
+	"$(INTDIR)\getkey.obj" \
+	"$(INTDIR)\group.obj" \
+	"$(INTDIR)\grparser.obj" \
+	"$(INTDIR)\histo.obj" \
+	"$(INTDIR)\iraffits.obj" \
+	"$(INTDIR)\modkey.obj" \
+	"$(INTDIR)\putcol.obj" \
+	"$(INTDIR)\putcolb.obj" \
+	"$(INTDIR)\putcolsb.obj" \
+	"$(INTDIR)\putcold.obj" \
+	"$(INTDIR)\putcole.obj" \
+	"$(INTDIR)\putcoli.obj" \
+	"$(INTDIR)\putcolj.obj" \
+	"$(INTDIR)\putcolk.obj" \
+	"$(INTDIR)\putcoll.obj" \
+	"$(INTDIR)\putcols.obj" \
+	"$(INTDIR)\putcolu.obj" \
+	"$(INTDIR)\putcolui.obj" \
+	"$(INTDIR)\putcoluj.obj" \
+	"$(INTDIR)\putcoluk.obj" \
+	"$(INTDIR)\putkey.obj" \
+	"$(INTDIR)\region.obj" \
+	"$(INTDIR)\scalnull.obj" \
+	"$(INTDIR)\swapproc.obj" \
+	"$(INTDIR)\wcssub.obj" \
+	"$(INTDIR)\wcsutil.obj" \
+	"$(INTDIR)\imcompress.obj" \
+	"$(INTDIR)\ricecomp.obj" \
+	"$(INTDIR)\quantize.obj" \
+	"$(INTDIR)\pliocomp.obj" \
+	"$(INTDIR)\fits_hcompress.obj" \
+	"$(INTDIR)\fits_hdecompress.obj" \
+	"$(INTDIR)\zuncompress.obj" \
+	"$(INTDIR)\zcompress.obj" \
+	"$(INTDIR)\adler32.obj" \
+	"$(INTDIR)\crc32.obj" \
+	"$(INTDIR)\inffast.obj" \
+	"$(INTDIR)\inftrees.obj" \
+	"$(INTDIR)\trees.obj" \
+	"$(INTDIR)\zutil.obj" \
+	"$(INTDIR)\deflate.obj" \
+	"$(INTDIR)\infback.obj" \
+	"$(INTDIR)\inflate.obj" \
+	"$(INTDIR)\uncompr.obj" 
+
+"$(OUTDIR)\cfitsio.dll" : $(LINK32_OBJS) WINDUMP
+	windumpexts -o $(DEF_FILE) cfitsio.dll $(LINK32_OBJS)    
+	$(LINK32) @<<
+	$(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+!ENDIF 
+
+.c{$(INTDIR)}.obj::
+   $(CPP) @<<
+   $(CPP_PROJ) $< 
+<<
+
+.cpp{$(INTDIR)}.obj::
+   $(CPP) @<<
+   $(CPP_PROJ) $< 
+<<
+
+.cxx{$(INTDIR)}.obj::
+   $(CPP) @<<
+   $(CPP_PROJ) $< 
+<<
+
+.c{$(INTDIR)}.sbr::
+   $(CPP) @<<
+   $(CPP_PROJ) $< 
+<<
+
+.cpp{$(INTDIR)}.sbr::
+   $(CPP) @<<
+   $(CPP_PROJ) $< 
+<<
+
+.cxx{$(INTDIR)}.sbr::
+   $(CPP) @<<
+   $(CPP_PROJ) $< 
+<<
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("cfitsio.dep")
+!INCLUDE "cfitsio.dep"
+!ELSE 
+!MESSAGE Warning: cannot find "cfitsio.dep"
+!ENDIF 
+!ENDIF 
+
+
+!IF "$(CFG)" == "Win32 Release" || "$(CFG)" == "Win32 Debug"
+SOURCE=.\buffers.c
+
+"$(INTDIR)\buffers.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\cfileio.c
+
+"$(INTDIR)\cfileio.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\checksum.c
+
+"$(INTDIR)\checksum.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\drvrfile.c
+
+"$(INTDIR)\drvrfile.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\drvrmem.c
+
+"$(INTDIR)\drvrmem.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\editcol.c
+
+"$(INTDIR)\editcol.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\edithdu.c
+
+"$(INTDIR)\edithdu.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\eval_f.c
+
+"$(INTDIR)\eval_f.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\eval_l.c
+
+"$(INTDIR)\eval_l.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\eval_y.c
+
+"$(INTDIR)\eval_y.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\fitscore.c
+
+"$(INTDIR)\fitscore.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\f77_wrap1.c
+
+"$(INTDIR)\f77_wrap1.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\f77_wrap2.c
+
+"$(INTDIR)\f77_wrap2.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\f77_wrap3.c
+
+"$(INTDIR)\f77_wrap3.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\f77_wrap4.c
+
+"$(INTDIR)\f77_wrap4.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\getcol.c
+
+"$(INTDIR)\getcol.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\getcolb.c
+
+"$(INTDIR)\getcolb.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\getcolsb.c
+
+"$(INTDIR)\getcolsb.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\getcold.c
+
+"$(INTDIR)\getcold.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\getcole.c
+
+"$(INTDIR)\getcole.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\getcoli.c
+
+"$(INTDIR)\getcoli.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\getcolj.c
+
+"$(INTDIR)\getcolj.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\getcolk.c
+
+"$(INTDIR)\getcolk.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\getcoll.c
+
+"$(INTDIR)\getcoll.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\getcols.c
+
+"$(INTDIR)\getcols.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\getcolui.c
+
+"$(INTDIR)\getcolui.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\getcoluj.c
+
+"$(INTDIR)\getcoluj.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\getcoluk.c
+
+"$(INTDIR)\getcoluk.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\getkey.c
+
+"$(INTDIR)\getkey.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\group.c
+
+"$(INTDIR)\group.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\grparser.c
+
+"$(INTDIR)\grparser.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\histo.c
+
+"$(INTDIR)\histo.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\iraffits.c
+
+"$(INTDIR)\iraffits.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\modkey.c
+
+"$(INTDIR)\modkey.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\putcol.c
+
+"$(INTDIR)\putcol.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\putcolb.c
+
+"$(INTDIR)\putcolb.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\putcolsb.c
+
+"$(INTDIR)\putcolsb.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\putcold.c
+
+"$(INTDIR)\putcold.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\putcole.c
+
+"$(INTDIR)\putcole.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\putcoli.c
+
+"$(INTDIR)\putcoli.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\putcolj.c
+
+"$(INTDIR)\putcolj.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\putcolk.c
+
+"$(INTDIR)\putcolk.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\putcoll.c
+
+"$(INTDIR)\putcoll.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\putcols.c
+
+"$(INTDIR)\putcols.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\putcolu.c
+
+"$(INTDIR)\putcolu.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\putcolui.c
+
+"$(INTDIR)\putcolui.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\putcoluj.c
+
+"$(INTDIR)\putcoluj.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\putcoluk.c
+
+"$(INTDIR)\putcoluk.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\putkey.c
+
+"$(INTDIR)\putkey.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\region.c
+
+"$(INTDIR)\region.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\scalnull.c
+
+"$(INTDIR)\scalnull.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\swapproc.c
+
+"$(INTDIR)\swapproc.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\wcssub.c
+
+"$(INTDIR)\wcssub.obj" : $(SOURCE) "$(INTDIR)"
+
+SOURCE=.\wcsutil.c
+
+"$(INTDIR)\wcsutil.obj" : $(SOURCE) "$(INTDIR)"
+
+SOURCE=imcompress.c
+
+"$(INTDIR)\imcompress.obj" : $(SOURCE) "$(INTDIR)"
+
+SOURCE=ricecomp.c
+
+"$(INTDIR)\ricecomp.obj" : $(SOURCE) "$(INTDIR)"
+
+SOURCE=quantize.c
+
+"$(INTDIR)\quantize.obj" : $(SOURCE) "$(INTDIR)"
+
+SOURCE=pliocomp.c
+
+"$(INTDIR)\pliocomp.obj" : $(SOURCE) "$(INTDIR)"
+
+SOURCE=fits_hcompress.c
+
+"$(INTDIR)\fits_hcompress.obj" : $(SOURCE) "$(INTDIR)"
+
+SOURCE=fits_hdecompress.c
+
+"$(INTDIR)\fits_hdecompress.obj" : $(SOURCE) "$(INTDIR)"
+
+SOURCE=zuncompress.c
+
+"$(INTDIR)\zuncompress.obj" : $(SOURCE) "$(INTDIR)"
+
+SOURCE=zcompress.c
+
+"$(INTDIR)\zcompress.obj" : $(SOURCE) "$(INTDIR)"
+
+SOURCE=adler32.c
+
+"$(INTDIR)\adler32.obj" : $(SOURCE) "$(INTDIR)"
+
+SOURCE=crc32.c
+
+"$(INTDIR)\crc32.obj" : $(SOURCE) "$(INTDIR)"
+
+SOURCE=inffast.c
+
+"$(INTDIR)\inffast.obj" : $(SOURCE) "$(INTDIR)"
+
+SOURCE=inftrees.c
+
+"$(INTDIR)\inftrees.obj" : $(SOURCE) "$(INTDIR)"
+
+SOURCE=trees.c
+
+"$(INTDIR)\trees.obj" : $(SOURCE) "$(INTDIR)"
+
+SOURCE=zutil.c
+
+"$(INTDIR)\zutil.obj" : $(SOURCE) "$(INTDIR)"
+
+SOURCE=deflate.c
+
+"$(INTDIR)\deflate.obj" : $(SOURCE) "$(INTDIR)"
+
+SOURCE=infback.c
+
+"$(INTDIR)\infback.obj" : $(SOURCE) "$(INTDIR)"
+
+SOURCE=inflate.c
+
+"$(INTDIR)\inflate.obj" : $(SOURCE) "$(INTDIR)"
+
+SOURCE=uncompr.c
+
+"$(INTDIR)\uncompr.obj" : $(SOURCE) "$(INTDIR)"
+
+!ENDIF 
+
+$(DEF_FILE):  
+
+
+
+WINDUMP:
+	nmake -f winDumpExts.mak
diff --git a/external/cfitsio/makepc.bat b/external/cfitsio/makepc.bat
new file mode 100644
index 0000000..3079a29
--- /dev/null
+++ b/external/cfitsio/makepc.bat
@@ -0,0 +1,87 @@
+rem:  this batch file builds the cfitsio library 
+rem:  using the Borland C++ v4.5 or new free v5.5 compiler
+rem:
+bcc32 -c buffers.c
+bcc32 -c cfileio.c
+bcc32 -c checksum.c
+bcc32 -c drvrfile.c
+bcc32 -c drvrmem.c
+bcc32 -c editcol.c
+bcc32 -c edithdu.c
+bcc32 -c eval_l.c
+bcc32 -c eval_y.c
+bcc32 -c eval_f.c
+bcc32 -c fitscore.c
+bcc32 -c getcol.c
+bcc32 -c getcolb.c
+bcc32 -c getcolsb.c
+bcc32 -c getcoli.c
+bcc32 -c getcolj.c
+bcc32 -c getcolui.c
+bcc32 -c getcoluj.c
+bcc32 -c getcoluk.c
+bcc32 -c getcolk.c
+bcc32 -c getcole.c
+bcc32 -c getcold.c
+bcc32 -c getcoll.c
+bcc32 -c getcols.c
+bcc32 -c getkey.c
+bcc32 -c group.c
+bcc32 -c grparser.c
+bcc32 -c histo.c
+bcc32 -c iraffits.c
+bcc32 -c modkey.c
+bcc32 -c putcol.c
+bcc32 -c putcolb.c
+bcc32 -c putcolsb.c
+bcc32 -c putcoli.c
+bcc32 -c putcolj.c
+bcc32 -c putcolui.c
+bcc32 -c putcoluj.c
+bcc32 -c putcoluk.c
+bcc32 -c putcolk.c
+bcc32 -c putcole.c
+bcc32 -c putcold.c
+bcc32 -c putcols.c
+bcc32 -c putcoll.c
+bcc32 -c putcolu.c
+bcc32 -c putkey.c
+bcc32 -c region.c
+bcc32 -c scalnull.c
+bcc32 -c swapproc.c
+bcc32 -c wcsutil.c
+bcc32 -c wcssub.c
+bcc32 -c imcompress.c
+bcc32 -c quantize.c
+bcc32 -c ricecomp.c
+bcc32 -c pliocomp.c
+bcc32 -c fits_hcompress.c
+bcc32 -c fits_hdecompress.c
+bcc32 -c zuncompress.c
+bcc32 -c zcompress.c
+bcc32 -c adler32.c
+bcc32 -c crc32.c
+bcc32 -c inffast.c
+bcc32 -c inftrees.c
+bcc32 -c trees.c
+bcc32 -c zutil.c
+bcc32 -c deflate.c
+bcc32 -c infback.c
+bcc32 -c inflate.c
+bcc32 -c uncompr.c
+del cfitsio.lib
+tlib cfitsio +buffers +cfileio +checksum +drvrfile +drvrmem 
+tlib cfitsio +editcol +edithdu +eval_l +eval_y +eval_f +fitscore
+tlib cfitsio +getcol +getcolb +getcolsb +getcoli +getcolj +getcolk +getcoluk 
+tlib cfitsio +getcolui +getcoluj +getcole +getcold +getcoll +getcols
+tlib cfitsio +getkey +group +grparser +histo +iraffits +modkey +putkey 
+tlib cfitsio +putcol  +putcolb +putcoli +putcolj +putcolk +putcole +putcold
+tlib cfitsio +putcoll +putcols +putcolu +putcolui +putcoluj +putcoluk
+tlib cfitsio +region +scalnull +swapproc +wcsutil +wcssub +putcolsb
+tlib cfitsio +imcompress +quantize +ricecomp +pliocomp
+tlib cfitsio +fits_hcompress +fits_hdecompress
+tlib cfitsio +zuncompress +zcompress +adler32 +crc32 +inffast
+tlib cfitsio +inftrees +trees +zutil +deflate +infback +inflate +uncompr
+bcc32 -f testprog.c cfitsio.lib
+bcc32 -f cookbook.c cfitsio.lib
+
diff --git a/external/cfitsio/mkpkg b/external/cfitsio/mkpkg
new file mode 100644
index 0000000..e428f68
--- /dev/null
+++ b/external/cfitsio/mkpkg
@@ -0,0 +1,74 @@
+# CFITSIO -- Update the CFITSIO library.
+# copied from the fitsio mkpkg by jdd 12 sept 1996
+#
+#  this is a sample IRAF mkfile which builds a local version of the CFITSIO lib.
+
+$call update
+$exit
+
+update:
+	$checkout libcfitsio.a ftoolsbin$
+	$update   libcfitsio.a
+	$checkin  libcfitsio.a ftoolsbin$
+	;
+
+updateftools:
+	#Extra target for building fitsio inside the FTOOLS distribution
+        $checkout libcfitsio.a  ftoolsbin$
+        $update   libcfitsio.a
+        $checkin  libcfitsio.a  ftoolsbin$
+	;
+
+cfitsio:
+	# Update fitsio subdirectory if new version of cfitsio.c installed.
+	$ifolder (splitc/cfileio.c, cfileio.c)
+	    $echo "update cfitsio splitc subdirectory..."
+            $iffile(splitc/cfileio.c)
+                !\rm splitc/*
+                $delete splitc/mkpkg
+            $else
+                !mkdir splitc
+	        !./configure
+            $endif
+	    $copy buffers.c splitc/buffers.c
+	    $copy cfileio.c splitc/cfileio.c
+	    $copy checksum.c splitc/checksum.c
+	    $copy compress.c splitc/compress.c
+	    $copy f77_iter.c splitc/f77_iter.c
+	    $copy f77_wrap.c splitc/f77_wrap.c
+	    $copy drvrfile.c splitc/drvrfile.c
+	    $copy fitscore.c splitc/fitscore.c
+	    $copy editcol.c splitc/editcol.c
+	    $copy edithdu.c splitc/edithdu.c
+	    $copy getkey.c splitc/getkey.c
+	    $copy modkey.c splitc/modkey.c
+	    $copy putkey.c splitc/putkey.c
+	    $copy scalnull.c splitc/scalnull.c
+	    $copy swapproc.c splitc/swapproc.c
+	    $copy wcsutil.c splitc/wcsutil.c
+	    !cp getcol*.c splitc/
+	    !cp putcol*.c splitc/
+	    !cp *.h splitc/
+	    !cp *.h ../include
+            !cd splitc
+            !echo '$checkout libcfitsio.a ../libcfitsio.a' > splitc/mkpkg
+            !echo '$update   libcfitsio.a' >> splitc/mkpkg
+            !echo '$checkin libcfitsio.a ../libcfitsio.a' >> splitc/mkpkg
+            !echo ' ' >> splitc/mkpkg
+            !echo 'libcfitsio.a:' >> splitc/mkpkg
+            !cd splitc; ls -1 *.c | sed 's/^/ /' >> mkpkg
+            !echo '  ;' >> splitc/mkpkg
+	$endif
+	;
+
+libcfitsio.a:
+	 $ifeq (hostid, unix)
+	    # cheat and use Makefile....
+	    $call cfitsio
+	    @splitc
+	$else
+	    # simply compile the files on VMS systems.
+	    !@makevms.com
+	$endif
+
+	;
diff --git a/external/cfitsio/modkey.c b/external/cfitsio/modkey.c
new file mode 100644
index 0000000..e6fe03a
--- /dev/null
+++ b/external/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 
+/* stddef.h is apparently needed to define size_t */
+#include 
+#include 
+#include 
+#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/external/cfitsio/pliocomp.c b/external/cfitsio/pliocomp.c
new file mode 100644
index 0000000..682599f
--- /dev/null
+++ b/external/cfitsio/pliocomp.c
@@ -0,0 +1,331 @@
+/* stdlib is needed for the abs function */
+#include 
+/*
+   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/external/cfitsio/putcol.c b/external/cfitsio/putcol.c
new file mode 100644
index 0000000..0bfc927
--- /dev/null
+++ b/external/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 
+#include 
+#include 
+#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/external/cfitsio/putcolb.c b/external/cfitsio/putcolb.c
new file mode 100644
index 0000000..8b96202
--- /dev/null
+++ b/external/cfitsio/putcolb.c
@@ -0,0 +1,1013 @@
+/*  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 
+#include 
+#include 
+#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, maxelem2, hdutype, writeraw;
+    long twidth, incre;
+    long  ntodo;
+    LONGLONG repeat, startpos, elemnum, wrtptr, rowlen, rownum, remain, next, tnull, maxelem;
+    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, &maxelem2, &startpos,  &elemnum, &incre,
+        &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0)
+        return(*status);
+    maxelem = maxelem2;
+
+    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 = 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/external/cfitsio/putcold.c b/external/cfitsio/putcold.c
new file mode 100644
index 0000000..8291367
--- /dev/null
+++ b/external/cfitsio/putcold.c
@@ -0,0 +1,1060 @@
+/*  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 
+#include 
+#include 
+#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, maxelem2, hdutype, writeraw;
+    long twidth, incre;
+    long ntodo;
+    LONGLONG repeat, startpos, elemnum, wrtptr, rowlen, rownum, remain, next, tnull, maxelem;
+    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, &maxelem2, &startpos,  &elemnum, &incre,
+        &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0)
+        return(*status);
+    maxelem = maxelem2;
+
+    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 = 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/external/cfitsio/putcole.c b/external/cfitsio/putcole.c
new file mode 100644
index 0000000..af9aa86
--- /dev/null
+++ b/external/cfitsio/putcole.c
@@ -0,0 +1,1074 @@
+/*  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 
+#include 
+#include 
+#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, maxelem2, hdutype, writeraw;
+    long twidth, incre;
+    long ntodo;
+    LONGLONG repeat, startpos, elemnum, wrtptr, rowlen, rownum, remain, next, tnull, maxelem;
+    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, &maxelem2, &startpos,  &elemnum, &incre,
+        &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0)
+        return(*status);
+    maxelem = maxelem2;
+
+    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 = 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/external/cfitsio/putcoli.c b/external/cfitsio/putcoli.c
new file mode 100644
index 0000000..9a623f9
--- /dev/null
+++ b/external/cfitsio/putcoli.c
@@ -0,0 +1,986 @@
+/*  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 
+#include 
+#include 
+#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, maxelem2, hdutype, writeraw;
+    long twidth, incre;
+    long ntodo;
+    LONGLONG repeat, startpos, elemnum, wrtptr, rowlen, rownum, remain, next, tnull, maxelem;
+    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, &maxelem2, &startpos,  &elemnum, &incre,
+        &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0)
+        return(*status);
+    maxelem = maxelem2;
+
+    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 = 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/external/cfitsio/putcolj.c b/external/cfitsio/putcolj.c
new file mode 100644
index 0000000..57b1777
--- /dev/null
+++ b/external/cfitsio/putcolj.c
@@ -0,0 +1,1992 @@
+/*  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 
+#include 
+#include 
+#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, maxelem2, hdutype, writeraw;
+    long twidth, incre;
+    long ntodo;
+    LONGLONG repeat, startpos, elemnum, wrtptr, rowlen, rownum, remain, next, tnull, maxelem;
+    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, &maxelem2, &startpos,  &elemnum, &incre,
+        &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0)
+        return(*status);
+    maxelem = maxelem2;
+
+    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 = 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, maxelem2, hdutype, writeraw;
+    long twidth, incre;
+    long  ntodo;
+    LONGLONG repeat, startpos, elemnum, wrtptr, rowlen, rownum, remain, next, tnull, maxelem;
+    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, &maxelem2, &startpos,  &elemnum, &incre,
+        &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0)
+        return(*status);
+    maxelem = maxelem2;
+
+    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 = 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/external/cfitsio/putcolk.c b/external/cfitsio/putcolk.c
new file mode 100644
index 0000000..9036057
--- /dev/null
+++ b/external/cfitsio/putcolk.c
@@ -0,0 +1,1013 @@
+/*  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 
+#include 
+#include 
+#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, maxelem2, hdutype, writeraw;
+    long twidth, incre;
+    long ntodo;
+    LONGLONG repeat, startpos, elemnum, wrtptr, rowlen, rownum, remain, next, tnull, maxelem;
+    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, &maxelem2, &startpos,  &elemnum, &incre,
+        &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0)
+        return(*status);
+    maxelem = maxelem2;
+
+    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 = 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/external/cfitsio/putcoll.c b/external/cfitsio/putcoll.c
new file mode 100644
index 0000000..b26e911
--- /dev/null
+++ b/external/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 
+#include 
+#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/external/cfitsio/putcols.c b/external/cfitsio/putcols.c
new file mode 100644
index 0000000..86d624e
--- /dev/null
+++ b/external/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 
+#include 
+#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/external/cfitsio/putcolsb.c b/external/cfitsio/putcolsb.c
new file mode 100644
index 0000000..25ee5d1
--- /dev/null
+++ b/external/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 
+#include 
+#include 
+#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/external/cfitsio/putcolu.c b/external/cfitsio/putcolu.c
new file mode 100644
index 0000000..15840d8
--- /dev/null
+++ b/external/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 
+#include 
+#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
+         ffswap8((double *)(&i8null), 1);  /* 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/external/cfitsio/putcolui.c b/external/cfitsio/putcolui.c
new file mode 100644
index 0000000..e52b176
--- /dev/null
+++ b/external/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 
+#include 
+#include 
+#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/external/cfitsio/putcoluj.c b/external/cfitsio/putcoluj.c
new file mode 100644
index 0000000..9f89f10
--- /dev/null
+++ b/external/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 
+#include 
+#include 
+#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/external/cfitsio/putcoluk.c b/external/cfitsio/putcoluk.c
new file mode 100644
index 0000000..dd92fe3
--- /dev/null
+++ b/external/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 
+#include 
+#include 
+#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/external/cfitsio/putkey.c b/external/cfitsio/putkey.c
new file mode 100644
index 0000000..2a88096
--- /dev/null
+++ b/external/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 
+#include 
+#include 
+#include 
+/* stddef.h is apparently needed to define size_t */
+#include 
+#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/external/cfitsio/quantize.c b/external/cfitsio/quantize.c
new file mode 100644
index 0000000..8fe6acc
--- /dev/null
+++ b/external/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 
+# include 
+# include 
+# include 
+# include 
+
+#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/external/cfitsio/quick.pdf b/external/cfitsio/quick.pdf
new file mode 100644
index 0000000..487e1ec
Binary files /dev/null and b/external/cfitsio/quick.pdf differ
diff --git a/external/cfitsio/quick.ps b/external/cfitsio/quick.ps
new file mode 100644
index 0000000..7503d84
--- /dev/null
+++ b/external/cfitsio/quick.ps
@@ -0,0 +1,3850 @@
+%!PS-Adobe-2.0
+%%Creator: dvips(k) 5.86 Copyright 1999 Radical Eye Software
+%%Title: quick.dvi
+%%Pages: 41
+%%PageOrder: Ascend
+%%BoundingBox: 0 0 596 842
+%%EndComments
+%DVIPSWebPage: (www.radicaleye.com)
+%DVIPSCommandLine: dvips -N0 quick
+%DVIPSParameters: dpi=600, compressed
+%DVIPSSource:  TeX output 2003.06.23:1300
+%%BeginProcSet: texc.pro
+%!
+/TeXDict 300 dict def TeXDict begin/N{def}def/B{bind def}N/S{exch}N/X{S
+N}B/A{dup}B/TR{translate}N/isls false N/vsize 11 72 mul N/hsize 8.5 72
+mul N/landplus90{false}def/@rigin{isls{[0 landplus90{1 -1}{-1 1}ifelse 0
+0 0]concat}if 72 Resolution div 72 VResolution div neg scale isls{
+landplus90{VResolution 72 div vsize mul 0 exch}{Resolution -72 div hsize
+mul 0}ifelse TR}if Resolution VResolution vsize -72 div 1 add mul TR[
+matrix currentmatrix{A A round sub abs 0.00001 lt{round}if}forall round
+exch round exch]setmatrix}N/@landscape{/isls true N}B/@manualfeed{
+statusdict/manualfeed true put}B/@copies{/#copies X}B/FMat[1 0 0 -1 0 0]
+N/FBB[0 0 0 0]N/nn 0 N/IEn 0 N/ctr 0 N/df-tail{/nn 8 dict N nn begin
+/FontType 3 N/FontMatrix fntrx N/FontBBox FBB N string/base X array
+/BitMaps X/BuildChar{CharBuilder}N/Encoding IEn N end A{/foo setfont}2
+array copy cvx N load 0 nn put/ctr 0 N[}B/sf 0 N/df{/sf 1 N/fntrx FMat N
+df-tail}B/dfs{div/sf X/fntrx[sf 0 0 sf neg 0 0]N df-tail}B/E{pop nn A
+definefont setfont}B/Cw{Cd A length 5 sub get}B/Ch{Cd A length 4 sub get
+}B/Cx{128 Cd A length 3 sub get sub}B/Cy{Cd A length 2 sub get 127 sub}
+B/Cdx{Cd A length 1 sub get}B/Ci{Cd A type/stringtype ne{ctr get/ctr ctr
+1 add N}if}B/id 0 N/rw 0 N/rc 0 N/gp 0 N/cp 0 N/G 0 N/CharBuilder{save 3
+1 roll S A/base get 2 index get S/BitMaps get S get/Cd X pop/ctr 0 N Cdx
+0 Cx Cy Ch sub Cx Cw add Cy setcachedevice Cw Ch true[1 0 0 -1 -.1 Cx
+sub Cy .1 sub]/id Ci N/rw Cw 7 add 8 idiv string N/rc 0 N/gp 0 N/cp 0 N{
+rc 0 ne{rc 1 sub/rc X rw}{G}ifelse}imagemask restore}B/G{{id gp get/gp
+gp 1 add N A 18 mod S 18 idiv pl S get exec}loop}B/adv{cp add/cp X}B
+/chg{rw cp id gp 4 index getinterval putinterval A gp add/gp X adv}B/nd{
+/cp 0 N rw exit}B/lsh{rw cp 2 copy get A 0 eq{pop 1}{A 255 eq{pop 254}{
+A A add 255 and S 1 and or}ifelse}ifelse put 1 adv}B/rsh{rw cp 2 copy
+get A 0 eq{pop 128}{A 255 eq{pop 127}{A 2 idiv S 128 and or}ifelse}
+ifelse put 1 adv}B/clr{rw cp 2 index string putinterval adv}B/set{rw cp
+fillstr 0 4 index getinterval putinterval adv}B/fillstr 18 string 0 1 17
+{2 copy 255 put pop}for N/pl[{adv 1 chg}{adv 1 chg nd}{1 add chg}{1 add
+chg nd}{adv lsh}{adv lsh nd}{adv rsh}{adv rsh nd}{1 add adv}{/rc X nd}{
+1 add set}{1 add clr}{adv 2 chg}{adv 2 chg nd}{pop nd}]A{bind pop}
+forall N/D{/cc X A type/stringtype ne{]}if nn/base get cc ctr put nn
+/BitMaps get S ctr S sf 1 ne{A A length 1 sub A 2 index S get sf div put
+}if put/ctr ctr 1 add N}B/I{cc 1 add D}B/bop{userdict/bop-hook known{
+bop-hook}if/SI save N @rigin 0 0 moveto/V matrix currentmatrix A 1 get A
+mul exch 0 get A mul add .99 lt{/QV}{/RV}ifelse load def pop pop}N/eop{
+SI restore userdict/eop-hook known{eop-hook}if showpage}N/@start{
+userdict/start-hook known{start-hook}if pop/VResolution X/Resolution X
+1000 div/DVImag X/IEn 256 array N 2 string 0 1 255{IEn S A 360 add 36 4
+index cvrs cvn put}for pop 65781.76 div/vsize X 65781.76 div/hsize X}N
+/p{show}N/RMat[1 0 0 -1 0 0]N/BDot 260 string N/Rx 0 N/Ry 0 N/V{}B/RV/v{
+/Ry X/Rx X V}B statusdict begin/product where{pop false[(Display)(NeXT)
+(LaserWriter 16/600)]{A length product length le{A length product exch 0
+exch getinterval eq{pop true exit}if}{pop}ifelse}forall}{false}ifelse
+end{{gsave TR -.1 .1 TR 1 1 scale Rx Ry false RMat{BDot}imagemask
+grestore}}{{gsave TR -.1 .1 TR Rx Ry scale 1 1 false RMat{BDot}
+imagemask grestore}}ifelse B/QV{gsave newpath transform round exch round
+exch itransform moveto Rx 0 rlineto 0 Ry neg rlineto Rx neg 0 rlineto
+fill grestore}B/a{moveto}B/delta 0 N/tail{A/delta X 0 rmoveto}B/M{S p
+delta add tail}B/b{S p tail}B/c{-4 M}B/d{-3 M}B/e{-2 M}B/f{-1 M}B/g{0 M}
+B/h{1 M}B/i{2 M}B/j{3 M}B/k{4 M}B/w{0 rmoveto}B/l{p -4 w}B/m{p -3 w}B/n{
+p -2 w}B/o{p -1 w}B/q{p 1 w}B/r{p 2 w}B/s{p 3 w}B/t{p 4 w}B/x{0 S
+rmoveto}B/y{3 2 roll p a}B/bos{/SS save N}B/eos{SS restore}B end
+
+%%EndProcSet
+TeXDict begin 39158280 55380996 1000 600 600 (quick.dvi)
+@start
+%DVIPSBitmapFont: Fa cmsy10 10.95 4
+/Fa 4 107 df15
+D<153FEC03FFEC0FE0EC3F80EC7E00495A5C495AA2495AB3AA130F5C131F495A91C7FC13
+FEEA03F8EA7FE048C8FCEA7FE0EA03F8EA00FE133F806D7E130F801307B3AA6D7EA26D7E
+80EB007EEC3F80EC0FE0EC03FFEC003F205B7AC32D>102 D<12FCEAFFC0EA07F0EA01FC
+EA007E6D7E131F6D7EA26D7EB3AA801303806D7E1300147FEC1FC0EC07FEEC00FFEC07FE
+EC1FC0EC7F0014FC1301495A5C13075CB3AA495AA2495A133F017EC7FC485AEA07F0EAFF
+C000FCC8FC205B7AC32D>I<126012F0B3B3B3B3B11260045B76C319>106
+D E
+%EndDVIPSBitmapFont
+%DVIPSBitmapFont: Fb cmbx12 12 41
+/Fb 41 122 df45 DII49
+DII<163FA25E5E5D5DA25D5D5D5D
+A25D92B5FCEC01F7EC03E7140715C7EC0F87EC1F07143E147E147C14F8EB01F0EB03E013
+0714C0EB0F80EB1F00133E5BA25B485A485A485A120F5B48C7FC123E5A12FCB91280A5C8
+000F90C7FCAC027FB61280A531417DC038>I<0007150301E0143F01FFEB07FF91B6FC5E
+5E5E5E5E16804BC7FC5D15E092C8FC01C0C9FCAAEC3FF001C1B5FC01C714C001DF14F090
+39FFE03FFC9138000FFE01FC6D7E01F06D13804915C0497F6C4815E0C8FC6F13F0A317F8
+A4EA0F80EA3FE0487E12FF7FA317F05B5D6C4815E05B007EC74813C0123E003F4A1380D8
+1FC0491300D80FF0495AD807FEEBFFFC6CB612F0C65D013F1480010F01FCC7FC010113C0
+2D427BC038>I<4AB47E021F13F0027F13FC49B6FC01079038807F8090390FFC001FD93F
+F014C04948137F4948EBFFE048495A5A1400485A120FA248486D13C0EE7F80EE1E00003F
+92C7FCA25B127FA2EC07FC91381FFF8000FF017F13E091B512F89039F9F01FFC9039FBC0
+07FE9039FF8003FF17804A6C13C05B6F13E0A24915F0A317F85BA4127FA5123FA217F07F
+121FA2000F4A13E0A26C6C15C06D4913806C018014006C6D485A6C9038E01FFC6DB55A01
+1F5C010714C0010191C7FC9038003FF02D427BC038>I<121E121F13FC90B712FEA45A17
+FC17F817F017E017C0A2481680007EC8EA3F00007C157E5E00785D15014B5A00F84A5A48
+4A5A5E151FC848C7FC157E5DA24A5A14035D14074A5AA2141F5D143FA2147F5D14FFA25B
+A35B92C8FCA35BA55BAA6D5A6D5A6D5A2F447AC238>I67 DIII72 DI<
+B76C0103B512F8A526003FFEC93807E0004F5A4F5A077EC7FC614E5A4E5A4E5AF01F804E
+C8FC187E604D5AEF07F0EF0FC04D5A4DC9FC177E4C5AEE03F04C5A4C5A4C7EEE7FF04C7E
+5D4B7F4B7F4B7FED3F3FDB7E1F7F03FC806E486C7F4B7E4B6C7F0380804B6C7F4A7F717E
+84717F83717F85717F83717F85717F187F727E86727F84727F86727F84B76C90B612FCA5
+4E447CC358>75 D
+78 D<923807FFC092B512FE0207ECFFC0021F15F091267FFE0013FC902601FFF0EB1FFF
+01070180010313C04990C76C7FD91FFC6E6C7E49486F7E49486F7E01FF8348496F7E4849
+6F1380A248496F13C0A24890C96C13E0A24819F04982003F19F8A3007F19FC49177FA400
+FF19FEAD007F19FC6D17FFA3003F19F8A26D5E6C19F0A26E5D6C19E0A26C6D4B13C06C19
+806E5D6C6D4B13006C6D4B5A6D6C4B5A6D6C4B5A6D6C4A5B6D01C001075B6D01F0011F5B
+010101FE90B5C7FC6D90B65A023F15F8020715C002004AC8FC030713C047467AC454>I<
+B812F8EFFFC018F818FE727ED8001F90C7003F13E005037F05007F727E727E727EA28684
+A286A762A24E90C7FCA24E5A61187F943801FFF005075B053F138092B7C8FC18F818E018
+F892C77FEF3FFF050F7F717F717FA2717FA2717FA785A61B0F85A2187F73131F72141EB7
+00E06DEB803E72EBE0FC72EBFFF8060114F0726C13E0CC0007138050457DC354>82
+D<003FBA12E0A59026FE000FEB8003D87FE09338003FF049171F90C71607A2007E180300
+7C1801A300781800A400F819F8481978A5C81700B3B3A20107B8FCA545437CC24E>84
+DI<903801FFE0011F13
+FE017F6D7E48B612E03A03FE007FF84848EB1FFC6D6D7E486C6D7EA26F7FA36F7F6C5A6C
+5AEA00F090C7FCA40203B5FC91B6FC1307013F13F19038FFFC01000313E0000F1380381F
+FE00485A5B127F5B12FF5BA35DA26D5B6C6C5B4B13F0D83FFE013EEBFFC03A1FFF80FC7F
+0007EBFFF86CECE01FC66CEB8007D90FFCC9FC322F7DAD36>97 DI
+100 DI<
+DAFFE0137E010F9039FE03FF80013FEBFF8F90B812C048D9C07F133F489038001FF84848
+EB0FFC4848903907FE1F80001F9238FF0F00496D90C7FCA2003F82A8001F93C7FCA26D5B
+000F5D6C6C495A6C6C495A6C9038C07FF04890B55A1680D8078F49C8FC018013E0000F90
+CAFCA47F7F7F90B612C016FC6CEDFF8017E06C826C16FC7E000382000F82D81FF0C77ED8
+3FC014074848020113808248C9FC177FA46D15FF007F17006D5C6C6C4A5A6C6C4A5AD80F
+FEEC3FF83B07FFC001FFF0000190B612C06C6C92C7FC010F14F8D9007F90C8FC32427DAC
+38>103 D<137C48B4FC4813804813C0A24813E0A56C13C0A26C13806C1300EA007C90C7
+FCAAEB7FC0EA7FFFA512037EB3AFB6FCA518467CC520>105 D108 D<90277F8007FEEC0FFCB590263FFFC090387FFF80
+92B5D8F001B512E002816E4880913D87F01FFC0FE03FF8913D8FC00FFE1F801FFC0003D9
+9F009026FF3E007F6C019E6D013C130F02BC5D02F86D496D7EA24A5D4A5DA34A5DB3A7B6
+0081B60003B512FEA5572D7CAC5E>I<90397F8007FEB590383FFF8092B512E0028114F8
+913987F03FFC91388F801F000390399F000FFE6C139E14BC02F86D7E5CA25CA35CB3A7B6
+0083B512FEA5372D7CAC3E>II<90397FC00FF8B590B57E02C314E002CF14F89139DFC03FFC9139FF001FFE000301FC
+EB07FF6C496D13804A15C04A6D13E05C7013F0A2EF7FF8A4EF3FFCACEF7FF8A318F017FF
+A24C13E06E15C06E5B6E4913806E4913006E495A9139DFC07FFC02CFB512F002C314C002
+C091C7FCED1FF092C9FCADB67EA536407DAC3E>I<90387F807FB53881FFE0028313F002
+8F13F8ED8FFC91389F1FFE000313BE6C13BC14F8A214F0ED0FFC9138E007F8ED01E092C7
+FCA35CB3A5B612E0A5272D7DAC2E>114 D<90391FFC038090B51287000314FF120F381F
+F003383FC00049133F48C7121F127E00FE140FA215077EA27F01E090C7FC13FE387FFFF0
+14FF6C14C015F06C14FC6C800003806C15806C7E010F14C0EB003F020313E0140000F014
+3FA26C141F150FA27EA26C15C06C141FA26DEB3F8001E0EB7F009038F803FE90B55A00FC
+5CD8F03F13E026E007FEC7FC232F7CAD2C>IIIIIII
+E
+%EndDVIPSBitmapFont
+%DVIPSBitmapFont: Fc cmtt10 10.95 94
+/Fc 94 127 df<121C127FEAFF80B3EA7F00B2123EC7FCA8121C127FA2EAFF80A3EA7F00
+A2121C09396DB830>33 D<00101304007C131F00FEEB3F80A26C137FA248133FB2007E14
+00007C7F003C131E00101304191C75B830>I<903907C007C0A2496C487EA8011F131FA2
+02C05BA3007FB7FCA2B81280A36C16006C5D3A007F807F80A2020090C7FCA9495BA2003F
+90B512FE4881B81280A36C1600A22701FC01FCC7FCA300031303A201F85BA76C486C5AA2
+29387DB730>I<1438147C14FCA4EB03FF011F13E090B512FC4880000780481580261FFE
+FD13C09039F0FC3FE0D83FC0131FD87F80EB0FF001001307007E15F800FE14035A1507A3
+6CEC03F0A2007F91C7FC138013C0EA3FF0EA1FFE13FF6C13FF6C14E0000114F86C6C7F01
+1F7F01037F0100148002FD13C09138FC7FE0151FED0FF015070018EC03F8127E1501B4FC
+A35AA26CEC03F07E01801307ED0FE0D83FC0131F01F0EB7FC0D81FFEB512806CB612006C
+5C6C5CC614F0013F13C0D907FEC7FCEB00FCA5147C143825477BBE30>II
+II<141E147F14FF5BEB03
+FEEB07FCEB0FF0EB1FE0EB3FC0EB7F80EBFF00485A5B12035B485A120F5BA2485AA2123F
+5BA2127F90C7FCA412FEAD127FA47F123FA27F121FA26C7EA27F12076C7E7F12017F6C7E
+EB7F80EB3FC0EB1FE0EB0FF0EB07FCEB03FEEB01FF7F147F141E184771BE30>I<127812
+FE7E7F6C7E6C7EEA0FF06C7E6C7E6C7E6C7EEB7F80133F14C0131FEB0FE014F01307A2EB
+03F8A214FC1301A214FE1300A4147FAD14FEA4130114FCA2130314F8A2EB07F0A2130F14
+E0EB1FC0133F1480137FEBFF00485A485A485A485AEA3FE0485A485A90C7FC5A12781847
+78BE30>I<14E0497E497EA60038EC0380007EEC0FC0D8FF83EB3FE001C3137F9038F3F9
+FF267FFBFB13C06CB61280000FECFE00000314F86C5C6C6C13C0011F90C7FC017F13C048
+B512F04880000F14FE003FECFF80267FFBFB13C026FFF3F913E09038C3F87F0183133FD8
+7E03EB0FC00038EC0380000091C7FCA66D5A6D5A23277AAE30>I<143EA2147FAF007FB7
+FCA2B81280A36C1600A2C76CC8FCAF143EA229297DAF30>II<007FB612F0A2B712F8A36C15F0A225077B9E30>I<120F
+EA3FC0EA7FE0A2EAFFF0A4EA7FE0A2EA3FC0EA0F000C0C6E8B30>I<16F01501ED03F8A2
+1507A2ED0FF0A2ED1FE0A2ED3FC0A2ED7F80A2EDFF00A24A5AA25D1403A24A5AA24A5AA2
+4A5AA24A5AA24A5AA24AC7FCA2495AA25C1303A2495AA2495AA2495AA2495AA2495AA249
+C8FCA2485AA25B1203A2485AA2485AA2485AA2485AA2485AA248C9FCA25AA2127CA22547
+7BBE30>I<14FE903807FFC0497F013F13F8497F90B57E48EB83FF4848C6138049137F48
+48EB3FC04848EB1FE049130F001F15F0491307A24848EB03F8A290C712014815FCA400FE
+EC00FEAD6C14016C15FCA36D1303003F15F8A26D1307001F15F0A26D130F6C6CEB1FE0A2
+6C6CEB3FC06C6CEB7F806D13FF2601FF8313006CEBFFFE6D5B6D5B010F13E06D5BD900FE
+C7FC273A7CB830>IIIII<000FB6128048
+15C05AA316800180C8FCAEEB83FF019F13C090B512F015FC8181D9FE0313809039F0007F
+C049133F0180EB1FE06CC7120F000E15F0C81207A216F81503A31218127EA2B4FC150716
+F048140F6C15E06C141F6DEB3FC06D137F3A3FE001FF80261FFC0F13006CB55A6C5C6C5C
+6C14E06C6C1380D90FFCC7FC25397BB730>II<127CB712FC16FEA416FC48C7EA
+0FF816F0ED1FE0007CEC3FC0C8EA7F80EDFF00A24A5A4A5A5D14075D140F5D4A5AA24A5A
+A24AC7FCA25C5C13015CA213035CA213075CA4495AA6131F5CA96D5A6DC8FC273A7CB830
+>I<49B4FC011F13F0017F13FC90B57E0003ECFF804815C048010113E03A1FF8003FF049
+131FD83FC0EB07F8A24848EB03FC90C71201A56D1303003F15F86D13076C6CEB0FF06C6C
+EB1FE0D807FCEB7FC03A03FF83FF806C90B512006C6C13FC011F13F0497F90B512FE4880
+2607FE0013C0D80FF8EB3FE0D81FE0EB0FF04848EB07F8491303007F15FC90C712014815
+FE481400A66C14016C15FC6D1303003F15F86D1307D81FF0EB1FF06D133F3A0FFF01FFE0
+6C90B512C06C1580C6ECFE006D5B011F13F0010190C7FC273A7CB830>I<49B4FC010F13
+E0013F13F890B57E4880488048010113803A0FFC007FC0D81FF0EB3FE04848131F49EB0F
+F048481307A290C7EA03F85A4815FC1501A416FEA37E7E6D1303A26C6C13076C6C130F6D
+133FD80FFC13FF6CB6FC7E6C14FE6C14F9013FEBE1FC010F138190380060011400ED03F8
+A2150716F0150F000F15E0486C131F486CEB3FC0157FEDFF804A1300EC07FE391FF01FFC
+90B55A6C5C6C5C6C1480C649C7FCEB3FF0273A7CB830>I<120FEA3FC0EA7FE0A2EAFFF0
+A4EA7FE0A2EA3FC0EA0F00C7FCAF120FEA3FC0EA7FE0A2EAFFF0A4EA7FE0A2EA3FC0EA0F
+000C276EA630>II<16F01503ED07F8151F157FEDFFF0
+14034A13C0021F138091383FFE00ECFFF8495B010713C0495BD93FFEC7FC495A3801FFF0
+485B000F13804890C8FCEA7FFC5BEAFFE05B7FEA7FF87FEA1FFF6C7F000313E06C7F3800
+7FFC6D7E90380FFF806D7F010113F06D7FEC3FFE91381FFF80020713C06E13F01400ED7F
+F8151F1507ED03F01500252F7BB230>I<007FB7FCA2B81280A36C16006C5DCBFCA7003F
+B612FE4881B81280A36C1600A229157DA530>I<1278127EB4FC13C07FEA7FF813FEEA1F
+FF6C13C000037F6C13F86C6C7EEB1FFF6D7F010313E06D7F9038007FFC6E7E91380FFF80
+6E13C0020113F080ED3FF8151F153FEDFFF05C020713C04A138091383FFE004A5A903801
+FFF0495B010F13804990C7FCEB7FFC48485A4813E0000F5B4890C8FCEA7FFE13F8EAFFE0
+5B90C9FC127E1278252F7BB230>III<147F4A7EA2497FA4497F14F7A401077F14E3A301
+0F7FA314C1A2011F7FA490383F80FEA590387F007FA4498049133F90B6FCA34881A39038
+FC001F00038149130FA4000781491307A2D87FFFEB7FFFB56CB51280A46C496C13002939
+7DB830>I<007FB512F0B612FE6F7E82826C813A03F8001FF815076F7E1501A26F7EA615
+015EA24B5A1507ED1FF0ED7FE090B65A5E4BC7FC6F7E16E0829039F8000FF8ED03FC6F7E
+1500167FA3EE3F80A6167F1700A25E4B5A1503ED1FFC007FB6FCB75A5E16C05E6C02FCC7
+FC29387EB730>I<91387F803C903903FFF03E49EBFC7E011F13FE49EBFFFE5B9038FFE0
+7F48EB801F3903FE000F484813075B48481303A2484813015B123F491300A2127F90C8FC
+167C16005A5AAC7E7EA2167C6D14FE123FA27F121F6D13016C6C14FCA26C6CEB03F86D13
+076C6CEB0FF03901FF801F6C9038E07FE06DB512C06D14806D1400010713FC6D13F09038
+007FC0273A7CB830>I<003FB512E04814FCB67E6F7E6C816C813A03F8007FF0ED1FF815
+0F6F7E6F7E15016F7EA2EE7F80A2163F17C0161FA4EE0FE0AC161F17C0A3163F1780A216
+7F17005E4B5A15034B5A150F4B5AED7FF0003FB65A485DB75A93C7FC6C14FC6C14E02B38
+7FB730>I<007FB7FCB81280A47ED803F8C7123FA8EE1F0093C7FCA4157C15FEA490B5FC
+A6EBF800A4157C92C8FCA5EE07C0EE0FE0A9007FB7FCB8FCA46C16C02B387EB730>I<00
+3FB712804816C0B8FCA27E7ED801FCC7121FA8EE0F8093C7FCA5153E157FA490B6FCA690
+38FC007FA4153E92C8FCAE383FFFF8487FB5FCA27E6C5B2A387EB730>I<02FF13F00103
+EBC0F8010F13F1013F13FD4913FF90B6FC4813C1EC007F4848133F4848131F49130F485A
+491307121F5B123F491303A2127F90C7FC6F5A92C8FC5A5AA892B5FC4A14805CA26C7F6C
+6D1400ED03F8A27F003F1407A27F121F6D130F120F7F6C6C131FA2D803FE133F6C6C137F
+ECC1FF6C90B5FC7F6D13FB010F13F30103EBC1F0010090C8FC293A7DB830>I<3B3FFF80
+0FFFE0486D4813F0B56C4813F8A26C496C13F06C496C13E0D803F8C7EAFE00B290B6FCA6
+01F8C7FCB3A23B3FFF800FFFE0486D4813F0B56C4813F8A26C496C13F06C496C13E02D38
+7FB730>I<007FB6FCB71280A46C1500260007F0C7FCB3B3A8007FB6FCB71280A46C1500
+213879B730>I<49B512F04914F85BA27F6D14F090C7EAFE00B3B3123C127EB4FCA24A5A
+1403EB8007397FF01FF86CB55A5D6C5C00075C000149C7FC38003FF025397AB730>II<383FFFF8487FB57EA26C5B6C5BD801FCC9FCB3B0EE0F80EE1FC0A9003FB7FC5AB8
+FCA27E6C16802A387EB730>III<90383FFFE048B512FC000714FF4815804815C04815
+E0EBF80001E0133FD87F80EB0FF0A290C71207A44815F8481403B3A96C1407A26C15F0A3
+6D130FA26D131F6C6CEB3FE001F813FF90B6FC6C15C06C15806C1500000114FCD8003F13
+E0253A7BB830>I<007FB512F0B612FE6F7E16E0826C813903F8003FED0FFCED03FE1501
+6F7EA2821780163FA6167F17005EA24B5A1503ED0FFCED3FF890B6FC5E5E16804BC7FC15
+F001F8C9FCB0387FFFC0B57EA46C5B29387EB730>I<90383FFFE048B512FC000714FF48
+15804815C04815E0EBF80001E0133F4848EB1FF049130F90C71207A44815F8481403B3A8
+147E14FE6CEBFF076C15F0EC7F87A2EC3FC7018013CF9038C01FFFD83FE014E0EBF80F90
+B6FC6C15C06C15806C1500000114FCD8003F7FEB00016E7EA21680157F16C0153F16E015
+1F16F0150FED07E025467BB830>I<003FB57E4814F0B612FC15FF6C816C812603F8017F
+9138003FF0151F6F7E15071503821501A515035E1507150F4B5A153F4AB45A90B65A5E93
+C7FC5D8182D9F8007FED3FE0151F150F821507A817F8EEF1FCA53A3FFF8003FB4801C0EB
+FFF8B56C7E17F06C496C13E06C49EB7FC0C9EA1F002E397FB730>I<90390FF803C0D97F
+FF13E048B512C74814F74814FF5A381FF80F383FE001497E4848137F90C7123F5A48141F
+A2150FA37EED07C06C91C7FC7F7FEA3FF0EA1FFEEBFFF06C13FF6C14E0000114F86C8001
+1F13FF01031480D9003F13C014019138007FE0151FED0FF0A2ED07F8A2007C140312FEA5
+6C140716F07F6DEB0FE06D131F01F8EB3FC001FF13FF91B51280160000FD5CD8FC7F13F8
+D8F81F5BD878011380253A7BB830>I<003FB712C04816E0B8FCA43AFE003F800FA8007C
+ED07C0C791C7FCB3B1011FB5FC4980A46D91C7FC2B387EB730>I<3B7FFFC007FFFCB56C
+4813FEA46C496C13FCD803F8C7EA3F80B3B16D147F00011600A36C6C14FE6D13016D5CEC
+800390393FE00FF890391FF83FF06DB55A6D5C6D5C6D91C7FC9038007FFCEC1FF02F3980
+B730>III<3A3FFF01FF
+F84801837F02C77FA202835B6C01015B3A01FC007F806D91C7FC00005C6D5BEB7F01EC81
+FCEB3F8314C3011F5B14E7010F5B14FF6D5BA26D5BA26D5BA26D90C8FCA4497FA2497FA2
+815B81EB0FE781EB1FC381EB3F8181EB7F0081497F49800001143F49800003141F498000
+07140FD87FFEEB7FFFB590B5128080A25C6C486D130029387DB730>II<001FB612
+FC4815FE5AA490C7EA03FCED07F816F0150FED1FE016C0153FED7F80003E1500C85A4A5A
+5D14034A5A5D140F4A5A5D143F4A5A92C7FC5C495A5C1303495A5C130F495A5C133F495A
+91C8FC5B4848147C4914FE1203485A5B120F485A5B123F485A90B6FCB7FCA46C15FC2738
+7CB730>I<007FB5FCB61280A4150048C8FCB3B3B3A5B6FC1580A46C140019476DBE30>I<
+127CA212FEA27EA26C7EA26C7EA26C7EA26C7EA26C7EA26C7EA212017FA26C7EA26D7EA2
+6D7EA26D7EA26D7EA26D7EA26D7EA2130180A26D7EA26E7EA26E7EA26E7EA26E7EA26E7E
+A26E7EA2140181A26E7EA2ED7F80A2ED3FC0A2ED1FE0A2ED0FF0A2ED07F8A21503A2ED01
+F0150025477BBE30>I<007FB5FCB61280A47EC7123FB3B3B3A5007FB5FCB6FCA46C1400
+19477DBE30>I<1307EB1FC0EB7FF0497E000313FE000FEBFF80003F14E0D87FFD13F039
+FFF07FF8EBC01FEB800F38FE0003007CEB01F00010EB00401D0E77B730>I<007FB612F0
+A2B712F8A36C15F0A225077B7D30>I<1338137CEA01FE12031207EA0FFC13F0EA1FE013
+C0EA3F8013005A127EA212FE5AA5EAFFC013E013F0127FA2123FA2EA1FE0EA07C00F1D70
+BE30>IIII<913801FFE04A7F5CA28080EC0007AAEB03FE90381FFF874913E790B6FC5A
+5A481303380FFC00D81FF0133F49131F485A150F4848130790C7FCA25AA25AA87E6C140F
+A27F003F141F6D133F6C7E6D137F390FF801FF2607FE07EBFFC06CB712E06C16F06C14F7
+6D01C713E0011F010313C0D907FCC8FC2C397DB730>I<49B4FC010713E0011F13F8017F
+7F90B57E488048018113803A07FC007FC04848133FD81FE0EB1FE0150F484814F0491307
+127F90C7FCED03F85A5AB7FCA516F048C9FC7E7EA27F003FEC01F06DEB03F86C7E6C7E6D
+1307D807FEEB1FF03A03FFC07FE06C90B5FC6C15C0013F14806DEBFE00010713F8010013
+C0252A7CA830>IIII<14E0EB03F8A2497EA36D5AA2EB00E091C8FCA9381FFF
+F8487F5AA27E7EEA0001B3A9003FB612C04815E0B7FCA27E6C15C023397AB830>III<387FFFF8B57EA47EEA0001B3B3A8007F
+B612F0B712F8A46C15F025387BB730>I<02FC137E3B7FC3FF01FF80D8FFEF01877F90B5
+00CF7F15DF92B57E6C010F13872607FE07EB03F801FC13FE9039F803FC01A201F013F8A3
+01E013F0B3A23C7FFE0FFF07FF80B548018F13C0A46C486C01071380322881A730>II<49B4FC010F13E0013F13F8497F90B57E0003ECFF8014
+013A07FC007FC04848EB3FE0D81FE0EB0FF0A24848EB07F8491303007F15FC90C71201A3
+00FEEC00FEA86C14016C15FCA26D1303003F15F86D13076D130F6C6CEB1FF06C6CEB3FE0
+6D137F3A07FF01FFC06C90B512806C15006C6C13FC6D5B010F13E0010190C7FC272A7CA8
+30>II<49B413F8010FEBC1
+FC013F13F14913FD48B6FC5A481381390FFC007F49131F4848130F491307485A49130312
+7F90C7FC15015A5AA77E7E15037FA26C6C1307150F6C6C131F6C6C133F01FC137F3907FF
+01FF6C90B5FC6C14FD6C14F9013F13F1010F13C1903803FE0190C7FCAD92B512F84A14FC
+A46E14F82E3C7DA730>II<90381FFC1E48B5129F000714FF5A5A5A387FF0
+07EB800100FEC7FC4880A46C143E007F91C7FC13E06CB4FC6C13FC6CEBFF806C14E00001
+14F86C6C7F01037F9038000FFF02001380007C147F00FEEC1FC0A2150F7EA27F151F6DEB
+3F806D137F9039FC03FF0090B6FC5D5D00FC14F0D8F83F13C026780FFEC7FC222A79A830
+>III<3B3FFFC07FFF80486DB512C0B515E0A26C16C06C496C1380
+3B01F80003F000A26D130700005DA26D130F017E5CA2017F131F6D5CA2EC803F011F91C7
+FCA26E5A010F137EA2ECE0FE01075BA214F101035BA3903801FBF0A314FF6D5BA36E5A6E
+5A2B277EA630>I<3B3FFFC01FFFE0486D4813F0B515F8A26C16F06C496C13E0D807E0C7
+EA3F00A26D5C0003157EA56D14FE00015DEC0F80EC1FC0EC3FE0A33A00FC7FF1F8A2147D
+A2ECFDF9017C5C14F8A3017E13FBA290393FF07FE0A3ECE03FA2011F5C90390F800F802D
+277FA630>I<3A3FFF81FFFC4801C37FB580A26C5D6C01815BC648C66CC7FC137FEC80FE
+90383F81FC90381FC3F8EB0FE3ECE7F06DB45A6D5B7F6D5B92C8FC147E147F5C497F8190
+3803F7E0EB07E790380FE3F0ECC1F890381F81FC90383F80FE90387F007E017E137F01FE
+6D7E48486D7E267FFF80B5FCB500C1148014E3A214C16C0180140029277DA630>I<3B3F
+FFC07FFF80486DB512C0B515E0A26C16C06C496C13803B01FC0003F000A2000014076D5C
+137E150F017F5C7F151FD91F805BA214C0010F49C7FCA214E00107137EA2EB03F0157C15
+FCEB01F85DA2EB00F9ECFDF0147D147FA26E5AA36E5AA35DA2143F92C8FCA25C147EA200
+0F13FE486C5AEA3FC1EBC3F81387EB8FF0EBFFE06C5B5C6C90C9FC6C5AEA01F02B3C7EA6
+30>I<001FB612FC4815FE5AA316FC90C7EA0FF8ED1FF0ED3FE0ED7FC0EDFF80003E4913
+00C7485A4A5A4A5A4A5A4A5A4A5A4A5A4990C7FC495A495A495A495A495A495A4948133E
+4890C7127F485A485A485A485A485A48B7FCB8FCA46C15FE28277DA630>II<127CA212FEB3B3B3AD127CA207476CBE30>II<017C133848B4137C48EB80FE4813C14813C348EBEFFC397FEFFF
+F0D8FF8713E0010713C0486C1380D87C0113003838007C1F0C78B730>I
+E
+%EndDVIPSBitmapFont
+%DVIPSBitmapFont: Fd cmti9 9 16
+/Fd 16 119 df<121C127F12FFA412FE12380808778718>46 D64 D<0107B612C04915F017FC903A003F8001FEEE007FEF1F80
+92C7EA0FC0EF07E05CEF03F0147E170102FE15F8A25CA21301A25CA2130317035CA21307
+18F04A1407A2130F18E04A140F18C0011F151F18805CEF3F00133F177E91C85AA2494A5A
+4C5A017E4A5A4C5A01FE4A5A047EC7FC49495A0001EC0FF8007FB612E0B7C8FC15F83533
+7BB23A>68 D<0107B612C04915F883903A003F8001FEEE003FEF1F8092C713C0170F5C18
+E0147EA214FEEF1FC05CA201011680173F4A1500177E010315FE5F4AEB03F8EE07E00107
+EC3FC091B6C7FC16F802E0C9FC130FA25CA2131FA25CA2133FA291CAFCA25BA2137EA213
+FEA25B1201387FFFF0B5FCA233337CB234>80 D87
+D97 D<14FCEB07FF90381F078090383E03C0EBFC013801F8033803F0073807E0
+0F13C0120F391F80070091C7FC48C8FCA35A127EA312FE5AA4007C14C0EC01E0A2EC03C0
+6CEB0F80EC1F006C137C380F81F03803FFC0C648C7FC1B2278A023>99
+D101
+D<143FECFF80903803E1E6903807C0FF90380F807FEB1F00133E017E133F49133EA24848
+137EA24848137CA215FC12074913F8A21401A2D80FC013F0A21403120715E01407140F14
+1F3903E03FC00001137FEBF0FF38007FCF90381F0F801300141FA21500A25C143E123800
+7E137E5C00FE5B48485A387803E0387C0F80D81FFFC7FCEA07F820317CA023>103
+D105
+D<133FEA07FF5A13FEEA007EA3137CA213FCA213F8A21201A213F0A21203A213E0A21207
+A213C0A2120FA21380A2121FA21300A25AA2123EA2127EA2127C1318EAFC1C133CEAF838
+A21378137012F013F0EAF8E01279EA3FC0EA0F00103579B314>108
+D<2703C003F8137F3C0FF00FFE01FFC03C1E783C1F07C1E03C1C7CF00F8F01F03B3C3DE0
+079E0026383FC001FC7FD97F805B007001005B5E137ED8F0FC90380FC00100E05FD860F8
+148012000001021F130360491400A200034A13076049013E130FF081800007027EEC83C0
+051F138049017C1403A2000F02FC1407053E130049495CEF1E0E001F01015D183C010049
+EB0FF0000E6D48EB03E03A227AA03F>I<3903C007F0390FF01FFC391E787C1E391C7CF0
+1F393C3DE00F26383FC01380EB7F8000781300EA707EA2D8F0FC131F00E01500EA60F812
+0000015C153E5BA20003147E157C4913FCEDF8180007153C0201133801C013F0A2000F15
+78EDE070018014F016E0001FECE1C015E390C7EAFF00000E143E26227AA02B>I<14FCEB
+07FF90381F07C090383E03E09038FC01F0EA01F83903F000F8485A5B120F484813FCA248
+C7FCA214014814F8127EA2140300FE14F05AA2EC07E0A2007CEB0FC01580141FEC3F006C
+137E5C381F01F0380F83E03803FF80D800FCC7FC1E2278A027>I115 D<01F01338D803FC13FCEA0F1E120E121C123C
+0038147CEA783E0070143CA2137ED8F07C1338EA60FCC65A1578000114705BA215F00003
+14E05BA2EC01C0A2EBC003158014071500EBE00EA26C6C5A3800F878EB7FE0EB1F801E22
+7AA023>118 D E
+%EndDVIPSBitmapFont
+%DVIPSBitmapFont: Fe cmr9 9 23
+/Fe 23 117 df<123C127EB4FCA21380A2127F123D1201A412031300A25A1206120E120C
+121C5A5A126009177A8715>44 D<15E0A34A7EA24A7EA34A7EA3EC0DFE140CA2EC187FA3
+4A6C7EA202707FEC601FA202E07FECC00FA2D901807F1507A249486C7EA301066D7EA201
+0E80010FB5FCA249800118C77EA24981163FA2496E7EA3496E7EA20001821607487ED81F
+F04A7ED8FFFE49B512E0A333367DB53A>65 D67
+D69 DIII78 D82 D<90381FE00390387FFC0748
+B5FC3907F01FCF390F8003FF48C7FC003E80814880A200788000F880A46C80A27E92C7FC
+127F13C0EA3FF013FF6C13F06C13FF6C14C06C14F0C680013F7F01037F9038003FFF1403
+02001380157F153FED1FC0150F12C0A21507A37EA26CEC0F80A26C15006C5C6C143E6C14
+7E01C05B39F1FC03F800E0B512E0011F138026C003FEC7FC22377CB42B>I97 D99 D<153FEC0FFFA3EC007F81AEEB07F0EB3FFCEBFC0F3901F003
+BF3907E001FF48487E48487F8148C7FCA25A127E12FEAA127E127FA27E6C6C5BA26C6C5B
+6C6C4813803A03F007BFFC3900F81E3FEB3FFCD90FE0130026357DB32B>II<151F90391FC07F809039FFF8E3C039
+01F07FC73907E03F033A0FC01F83809039800F8000001F80EB00074880A66C5CEB800F00
+0F5CEBC01F6C6C48C7FCEBF07C380EFFF8380C1FC0001CC9FCA3121EA2121F380FFFFEEC
+FFC06C14F06C14FC4880381F0001003EEB007F4880ED1F8048140FA56C141F007C15006C
+143E6C5C390FC001F83903F007E0C6B51280D91FFCC7FC22337EA126>103
+DII108 D<3903F01FC000FFEB7FF09038
+F1E0FC9038F3807C3907F7007EEA03FE497FA25BA25BB3486CEB7F80B538C7FFFCA32621
+7EA02B>110 DI<3903F03F
+8000FFEBFFE09038F3C0F89038F7007ED807FE7F6C48EB1F804914C049130F16E0ED07F0
+A3ED03F8A9150716F0A216E0150F16C06D131F6DEB3F80160001FF13FC9038F381F89038
+F1FFE0D9F07FC7FC91C8FCAA487EB512C0A325307EA02B>I<3803E07C38FFE1FF9038E3
+8F809038E71FC0EA07EEEA03ECA29038FC0F8049C7FCA35BB2487EB512E0A31A217FA01E
+>114 D<1330A51370A313F0A21201A212031207381FFFFEB5FCA23803F000AF1403A814
+073801F806A23800FC0EEB7E1CEB1FF8EB07E0182F7FAD1E>116
+D E
+%EndDVIPSBitmapFont
+%DVIPSBitmapFont: Ff cmsy6 6 1
+/Ff 1 4 df<136013701360A20040132000E0137038F861F0387E67E0381FFF803807FE
+00EA00F0EA07FE381FFF80387E67E038F861F038E060700040132000001300A213701360
+14157B9620>3 D E
+%EndDVIPSBitmapFont
+%DVIPSBitmapFont: Fg cmr10 10.95 89
+/Fg 89 124 df<4AB4EB0FE0021F9038E03FFC913A7F00F8FC1ED901FC90383FF03FD907
+F090397FE07F80494801FF13FF4948485BD93F805C137F0200ED7F00EF003E01FE6D91C7
+FC82ADB97EA3C648C76CC8FCB3AE486C4A7E007FD9FC3FEBFF80A339407FBF35>11
+DIII<121EEA7F80EAFFC0A9EA7F80ACEA3F00AC
+121EAB120CC7FCA8121EEA7F80A2EAFFC0A4EA7F80A2EA1E000A4179C019>33
+D<001E130F397F803FC000FF137F01C013E0A201E013F0A3007F133F391E600F30000013
+00A401E01370491360A3000114E04913C00003130101001380481303000EEB070048130E
+0018130C0038131C003013181C1C7DBE2D>I<4B6C130C4B6C131EA20307143EA24C133C
+A2030F147CA293C71278A24B14F8A2031E5CA2033E1301A2033C5CA3037C1303A203785C
+A203F81307A24B5CA20201140F007FBAFCBB1280A26C1900C72707C0003EC8FC4B133CA3
+020F147CA292C71278A24A14F8A2021E5CA3023E1301007FBAFCBB1280A26C1900C727F8
+0007C0C8FC4A5CA20101140FA24A91C9FCA301035CA24A131EA20107143EA24A133CA201
+0F147CA291C71278A34914F8A2011E5CA2013E1301A2013C5CA201186D5A41517BBE4C>
+I<14E0A4EB07FC90383FFF8090B512E03901F8E3F03903E0E0FCD807C0133CD80F807FD8
+1F007F003E80003C1580007C140316C00078141F00F8143F157FA47EED3F806CEC0E0092
+C7FC127F138013C0EA3FF013FEEA1FFF6C13FC6C13FF6C14C06C806C6C13F8011F7F1303
+01007FECE7FF14E102E01380157F153FED1FC0A2003E140F127FD8FF801307A5130000FC
+158000F0140F1270007815005D6C141E153E6C5C6C5C3907C0E1F03903F8EFE0C6B51280
+D93FFEC7FCEB0FF8EB00E0A422497BC32D>I38
+D<121EEA7F8012FF13C0A213E0A3127FEA1E601200A413E013C0A312011380120313005A
+120E5A1218123812300B1C79BE19>I<1430147014E0EB01C0EB03801307EB0F00131E13
+3E133C5B13F85B12015B1203A2485AA2120F5BA2121F90C7FCA25AA3123E127EA6127C12
+FCB2127C127EA6123E123FA37EA27F120FA27F1207A26C7EA212017F12007F13787F133E
+131E7FEB07801303EB01C0EB00E014701430145A77C323>I<12C07E12707E7E121E7E6C
+7E7F12036C7E7F12007F1378137CA27FA2133F7FA21480130FA214C0A3130714E0A61303
+14F0B214E01307A614C0130FA31480A2131F1400A25B133EA25BA2137813F85B12015B48
+5A12075B48C7FC121E121C5A5A5A5A145A7BC323>II<1506150FB3A9007FB912E0BA12F0
+A26C18E0C8000FC9FCB3A915063C3C7BB447>I<121EEA7F8012FF13C0A213E0A3127FEA
+1E601200A413E013C0A312011380120313005A120E5A1218123812300B1C798919>II<121EEA7F80A2EAFFC0A4EA7F80A2EA1E000A0A798919>II<
+EB01FE90380FFFC090383F03F090387C00F849137C48487F48487F4848EB0F80A2000F15
+C04848EB07E0A3003F15F0A290C712034815F8A64815FCB3A26C15F8A56C6CEB07F0A300
+1F15E0A36C6CEB0FC0A26C6CEB1F80000315006C6C133E6C6C5B017C5B90383F03F09038
+0FFFC0D901FEC7FC263F7DBC2D>IIII<150E151E153EA2157EA215FE1401A21403EC077E1406140E14
+1CA214381470A214E0EB01C0A2EB0380EB0700A2130E5BA25B5BA25B5B1201485A90C7FC
+5A120E120C121C5AA25A5AB8FCA3C8EAFE00AC4A7E49B6FCA3283E7EBD2D>I<00061403
+D80780131F01F813FE90B5FC5D5D5D15C092C7FC14FCEB3FE090C9FCACEB01FE90380FFF
+8090383E03E090387001F8496C7E49137E497F90C713800006141FC813C0A216E0150FA3
+16F0A3120C127F7F12FFA416E090C7121F12FC007015C012780038EC3F80123C6CEC7F00
+001F14FE6C6C485A6C6C485A3903F80FE0C6B55A013F90C7FCEB07F8243F7CBC2D>II<1238123C123F90B612FCA316F85A16
+F016E00078C712010070EC03C0ED078016005D48141E151C153C5DC8127015F04A5A5D14
+034A5A92C7FC5C141EA25CA2147C147814F8A213015C1303A31307A3130F5CA2131FA613
+3FAA6D5A0107C8FC26407BBD2D>III<121EEA7F80A2EAFF
+C0A4EA7F80A2EA1E00C7FCB3121EEA7F80A2EAFFC0A4EA7F80A2EA1E000A2779A619>I<
+121EEA7F80A2EAFFC0A4EA7F80A2EA1E00C7FCB3121E127FEAFF80A213C0A4127F121E12
+00A412011380A3120313005A1206120E120C121C5A1230A20A3979A619>I<007FB912E0
+BA12F0A26C18E0CDFCAE007FB912E0BA12F0A26C18E03C167BA147>61
+D63 DI<15074B7EA34B7EA34B7EA34B7EA34B7E15E7A2913801C7FC15C3A291380381
+FEA34AC67EA3020E6D7EA34A6D7EA34A6D7EA34A6D7EA34A6D7EA349486D7E91B6FCA249
+819138800001A249C87EA24982010E157FA2011E82011C153FA2013C820138151FA20178
+82170F13FC00034C7ED80FFF4B7EB500F0010FB512F8A33D417DC044>IIIIIIIII<011FB512FCA3D9000713006E5A1401B3B3A6123FEA7F80EAFFC0A44A5A
+1380D87F005B007C130700385C003C495A6C495A6C495A2603E07EC7FC3800FFF8EB3FC0
+26407CBD2F>IIIIIII82 DI<003F
+B91280A3903AF0007FE001018090393FC0003F48C7ED1FC0007E1707127C00781703A300
+701701A548EF00E0A5C81600B3B14B7E4B7E0107B612FEA33B3D7DBC42>IIII<007FB5D8C003B512E0A3C649C7EBFC00D93FF8EC3FE06D48EC1F806D6C92C7
+FC171E6D6C141C6D6C143C5F6D6C14706D6D13F04C5ADA7FC05B023F13036F485ADA1FF0
+90C8FC020F5BEDF81E913807FC1C163C6E6C5A913801FF7016F06E5B6F5AA26F7E6F7EA2
+8282153FED3BFEED71FF15F103E07F913801C07F0203804B6C7EEC07004A6D7E020E6D7E
+5C023C6D7E02386D7E14784A6D7E4A6D7F130149486E7E4A6E7E130749C86C7E496F7E49
+7ED9FFC04A7E00076DEC7FFFB500FC0103B512FEA33F3E7EBD44>II<003FB712F8A391C7EA1FF013F801E0EC3FE00180EC7F
+C090C8FC003EEDFF80A2003C4A1300007C4A5A12784B5A4B5AA200704A5AA24B5A4B5AA2
+C8485A4A90C7FCA24A5A4A5AA24A5AA24A5A4A5AA24A5A4A5AA24990C8FCA2495A494814
+1CA2495A495AA2495A495A173C495AA24890C8FC485A1778485A484815F8A24848140116
+034848140F4848143FED01FFB8FCA32E3E7BBD38>II<486C13C00003130101001380481303000EEB070048130E0018130C
+0038131C003013180070133800601330A300E01370481360A400CFEB678039FFC07FE001
+E013F0A3007F133FA2003F131F01C013E0390F0007801C1C73BE2D>II96
+DII<
+49B4FC010F13E090383F00F8017C131E4848131F4848137F0007ECFF80485A5B121FA248
+48EB7F00151C007F91C7FCA290C9FC5AAB6C7EA3003FEC01C07F001F140316806C6C1307
+6C6C14000003140E6C6C131E6C6C137890383F01F090380FFFC0D901FEC7FC222A7DA828
+>IIII<167C903903F801FF903A1FFF078F8090397E0FDE1F9038
+F803F83803F001A23B07E000FC0600000F6EC7FC49137E001F147FA8000F147E6D13FE00
+075C6C6C485AA23901F803E03903FE0FC026071FFFC8FCEB03F80006CAFC120EA3120FA2
+7F7F6CB512E015FE6C6E7E6C15E06C810003813A0FC0001FFC48C7EA01FE003E14004815
+7E825A82A46C5D007C153E007E157E6C5D6C6C495A6C6C495AD803F0EB0FC0D800FE017F
+C7FC90383FFFFC010313C0293D7EA82D>III<1478EB01FEA2EB
+03FFA4EB01FEA2EB00781400AC147FEB7FFFA313017F147FB3B3A5123E127F38FF807E14
+FEA214FCEB81F8EA7F01387C03F0381E07C0380FFF803801FC00185185BD1C>III<2701F801FE14FF00FF902707FFC0
+0313E0913B1E07E00F03F0913B7803F03C01F80007903BE001F87000FC2603F9C06D487F
+000101805C01FBD900FF147F91C75B13FF4992C7FCA2495CB3A6486C496CECFF80B5D8F8
+7FD9FC3F13FEA347287DA74C>I<3901F801FE00FF903807FFC091381E07E091387803F0
+00079038E001F82603F9C07F0001138001FB6D7E91C7FC13FF5BA25BB3A6486C497EB5D8
+F87F13FCA32E287DA733>I<14FF010713E090381F81F890387E007E01F8131F4848EB0F
+804848EB07C04848EB03E0000F15F04848EB01F8A2003F15FCA248C812FEA44815FFA96C
+15FEA36C6CEB01FCA3001F15F86C6CEB03F0A26C6CEB07E06C6CEB0FC06C6CEB1F80D800
+7EEB7E0090383F81FC90380FFFF0010090C7FC282A7EA82D>I<3901FC03FC00FF90381F
+FF8091387C0FE09039FDE003F03A07FFC001FC6C496C7E6C90C7127F49EC3F805BEE1FC0
+17E0A2EE0FF0A3EE07F8AAEE0FF0A4EE1FE0A2EE3FC06D1580EE7F007F6E13FE9138C001
+F89039FDE007F09039FC780FC0DA3FFFC7FCEC07F891C9FCAD487EB512F8A32D3A7EA733
+>I<02FF131C0107EBC03C90381F80F090397F00387C01FC131CD803F8130E4848EB0FFC
+150748481303121F485A1501485AA448C7FCAA6C7EA36C7EA2001F14036C7E15076C6C13
+0F6C7E6C6C133DD8007E137990383F81F190380FFFC1903801FE0190C7FCAD4B7E92B512
+F8A32D3A7DA730>I<3901F807E000FFEB1FF8EC787CECE1FE3807F9C100031381EA01FB
+1401EC00FC01FF1330491300A35BB3A5487EB512FEA31F287EA724>I<90383FC0603901
+FFF8E03807C03F381F000F003E1307003C1303127C0078130112F81400A27E7E7E6D1300
+EA7FF8EBFFC06C13F86C13FE6C7F6C1480000114C0D8003F13E0010313F0EB001FEC0FF8
+00E01303A214017E1400A27E15F07E14016C14E06CEB03C0903880078039F3E01F0038E0
+FFFC38C01FE01D2A7DA824>I<131CA6133CA4137CA213FCA2120112031207001FB512C0
+B6FCA2D801FCC7FCB3A215E0A912009038FE01C0A2EB7F03013F138090381F8700EB07FE
+EB01F81B397EB723>IIIIII<001FB61280
+A2EBE0000180140049485A001E495A121C4A5A003C495A141F00385C4A5A147F5D4AC7FC
+C6485AA2495A495A130F5C495A90393FC00380A2EB7F80EBFF005A5B4848130712074914
+00485A48485BA248485B4848137F00FF495A90B6FCA221277EA628>II E
+%EndDVIPSBitmapFont
+%DVIPSBitmapFont: Fh cmbx10 10.95 43
+/Fh 43 122 df46 D<140F143F5C495A130F48B5FCB6FCA313F7EAFE071200B3B3A8007FB612
+F0A5243C78BB34>49 D<903803FF80013F13F890B512FE00036E7E4881260FF80F7F261F
+C0037F4848C67F486C6D7E6D6D7E487E6D6D7EA26F1380A46C5A6C5A6C5A0007C7FCC8FC
+4B1300A25E153F5E4B5AA24B5A5E4A5B4A5B4A48C7FC5D4A5AEC1FE04A5A4A5A9139FF00
+0F80EB01FC495A4948EB1F00495AEB1F8049C7FC017E5C5B48B7FC485D5A5A5A5A5AB7FC
+5EA4293C7BBB34>I<903801FFE0010F13FE013F6D7E90B612E04801817F3A03FC007FF8
+D807F06D7E82D80FFC131F6D80121F7FA56C5A5E6C48133FD801F05CC8FC4B5A5E4B5A4A
+5B020F5B902607FFFEC7FC15F815FEEDFFC0D9000113F06E6C7E6F7E6F7E6F7E1780A26F
+13C0A217E0EA0FC0487E487E487E487EA317C0A25D491580127F49491300D83FC0495A6C
+6C495A3A0FFE01FFF86CB65A6C5DC61580013F49C7FC010313E02B3D7CBB34>II<00071538D80FE0EB01F801FE13
+3F90B6FC5E5E5E5E93C7FC5D15F85D15C04AC8FC0180C9FCA9ECFFC0018713FC019F13FF
+90B67E020113E09039F8007FF0496D7E01C06D7E5B6CC77FC8120F82A31780A21207EA1F
+C0487E487E12FF7FA21700A25B4B5A6C5A01805C6CC7123F6D495AD81FE0495A260FFC07
+5B6CB65A6C92C7FCC614FC013F13F0010790C8FC293D7BBB34>II56
+D66
+D<922607FFC0130E92B500FC131E020702FF133E023FEDC07E91B7EAE1FE01039138803F
+FB499039F80003FF4901C01300013F90C8127F4948151FD9FFF8150F48491507485B4A15
+03481701485B18004890CAFC197E5A5B193E127FA349170012FFAC127F7F193EA2123FA2
+7F6C187E197C6C7F19FC6C6D16F86C6D150119F06C6D15036C6DED07E0D97FFEED0FC06D
+6CED3F80010F01C0ECFF006D01F8EB03FE6D9039FF801FFC010091B55A023F15E0020715
+80020002FCC7FC030713C03F407ABE4C>I69 DI<922607FFC0130E92B500FC
+131E020702FF133E023FEDC07E91B7EAE1FE01039138803FFB499039F80003FF4901C013
+00013F90C8127F4948151FD9FFF8150F48491507485B4A1503481701485B18004890CAFC
+197E5A5B193E127FA34994C7FC12FFAB0407B612FC127F7FA3003F92C7383FFE00A27F7E
+A26C7FA26C7F6C7FA26C7F6C7FD97FFE157F6D6C7E010F01E014FF6D01F813036D9038FF
+801F010091B512F3023F15C00207ED803E02009138FE000E030701E090C7FC46407ABE52
+>I73 D75 D78 DII82 D<903A03FFC001C0011FEBF803017FEBFE0748B6128F4815DF
+48010013FFD80FF8130F48481303497F4848EB007F127F49143F161F12FF160FA27F1607
+A27F7F01FC91C7FCEBFF806C13F8ECFFC06C14FCEDFF806C15E016F86C816C816C816C16
+806C6C15C07F010715E0EB007F020714F0EC003F1503030013F8167F163F127800F8151F
+A2160FA27EA217F07E161F6C16E06D143F01E015C001F8EC7F8001FEEB01FF9026FFE007
+13004890B55A486C14F8D8F81F5CD8F00314C027E0003FFEC7FC2D407ABE3A>I<003FB9
+12FCA5903BFE003FFE003FD87FF0EE0FFE01C0160349160190C71500197E127EA2007C18
+3EA400FC183F48181FA5C81600B3AF010FB712F8A5403D7CBC49>II<903807FFC0013F13F848B6FC48812607FE037F26
+0FF8007F6DEB3FF0486C806F7EA36F7EA26C5A6C5AEA01E0C8FC153F91B5FC130F137F39
+01FFFE0F4813E0000F1380381FFE00485A5B485A12FF5BA4151F7F007F143F6D90387BFF
+806C6C01FB13FE391FFF07F36CEBFFE100031480C6EC003FD91FF890C7FC2F2B7DA933>
+97 D<13FFB5FCA512077EAFEDFFE0020713FC021FEBFF80027F80DAFF8113F09139FC00
+3FF802F06D7E4A6D7E4A13074A80701380A218C082A318E0AA18C0A25E1880A218005E6E
+5C6E495A6E495A02FCEB7FF0903AFCFF01FFE0496CB55AD9F01F91C7FCD9E00713FCC700
+0113C033407DBE3A>IIIII<903A03FF80
+07F0013F9038F83FF8499038FCFFFC48B712FE48018313F93A07FC007FC34848EB3FE100
+1FEDF1FC4990381FF0F81700003F81A7001F5DA26D133F000F5D6C6C495A3A03FF83FF80
+91B5C7FC4814FC01BF5BD80F03138090CAFCA2487EA27F13F06CB6FC16F016FC6C15FF17
+806C16C06C16E01207001F16F0393FE000034848EB003F49EC1FF800FF150F90C81207A5
+6C6CEC0FF06D141F003F16E001F0147FD81FFC903801FFC02707FF800F13006C90B55AC6
+15F8013F14E0010101FCC7FC2F3D7DA834>I105
+D<13FFB5FCA512077EB3B3AFB512FCA5163F7CBE1D>108 D<01FFD91FF8ECFFC0B590B5
+010713F80203DAC01F13FE4A6E487FDA0FE09026F07F077F91261F003FEBF8010007013E
+DAF9F0806C0178ECFBC04A6DB4486C7FA24A92C7FC4A5CA34A5CB3A4B5D8FE07B5D8F03F
+EBFF80A551297CA858>I<01FFEB1FF8B5EBFFFE02036D7E4A80DA0FE07F91381F007F00
+07013C806C5B4A6D7E5CA25CA35CB3A4B5D8FE0FB512E0A533297CA83A>II<01FFEBFFE0B5000713FC021FEBFF80027F80DA
+FF8113F09139FC007FF8000701F06D7E6C496D7E4A130F4A6D7E1880A27013C0A38218E0
+AA4C13C0A318805E18005E6E5C6E495A6E495A02FCEBFFF0DAFF035B92B55A029F91C7FC
+028713FC028113C00280C9FCACB512FEA5333B7DA83A>I<3901FE01FE00FF903807FF80
+4A13E04A13F0EC3F1F91387C3FF8000713F8000313F0EBFFE0A29138C01FF0ED0FE09138
+8007C092C7FCA391C8FCB3A2B6FCA525297DA82B>114 D<90383FFC1E48B512BE000714
+FE5A381FF00F383F800148C7FC007E147EA200FE143EA27E7F6D90C7FC13F8EBFFE06C13
+FF15C06C14F06C806C806C806C80C61580131F1300020713C014000078147F00F8143F15
+1F7EA27E16806C143F6D140001E013FF9038F803FE90B55A15F0D8F87F13C026E00FFEC7
+FC222B7DA929>IIIIIII E
+%EndDVIPSBitmapFont
+%DVIPSBitmapFont: Fi cmbx12 14.4 33
+/Fi 33 121 df<157815FC14031407141F14FF130F0007B5FCB6FCA2147F13F0EAF800C7
+FCB3B3B3A6007FB712FEA52F4E76CD43>49 DI<9138
+0FFFC091B512FC0107ECFF80011F15E090263FF8077F9026FF800113FC4848C76C7ED803
+F86E7E491680D807FC8048B416C080486D15E0A4805CA36C17C06C5B6C90C75AD801FC16
+80C9FC4C13005FA24C5A4B5B4B5B4B13C04B5BDBFFFEC7FC91B512F816E016FCEEFF80DA
+000713E0030113F89238007FFE707E7013807013C018E07013F0A218F8A27013FCA218FE
+A2EA03E0EA0FF8487E487E487EB57EA318FCA25E18F891C7FC6C17F0495C6C4816E001F0
+4A13C06C484A1380D80FF84A13006CB44A5A6CD9F0075BC690B612F06D5D011F15800103
+02FCC7FCD9001F1380374F7ACD43>I<177C17FEA2160116031607160FA2161F163F167F
+A216FF5D5DA25D5DED1FBFED3F3F153E157C15FCEC01F815F0EC03E01407EC0FC01580EC
+1F005C147E147C5C1301495A495A5C495A131F49C7FC133E5B13FC485A5B485A1207485A
+485A90C8FC123E127E5ABA12C0A5C96C48C7FCAF020FB712C0A53A4F7CCE43>III<932601FFFCEC01C0047FD9FFC013030307B600F8130703
+3F03FE131F92B8EA803F0203DAE003EBC07F020F01FCC7383FF0FF023F01E0EC0FF94A01
+800203B5FC494848C9FC4901F8824949824949824949824949824990CA7E494883A24849
+83485B1B7F485B481A3FA24849181FA3485B1B0FA25AA298C7FC5CA2B5FCAE7EA280A2F3
+07C07EA36C7FA21B0F6C6D1980A26C1A1F6C7F1C006C6D606C6D187EA26D6C606D6D4C5A
+6D6D16036D6D4C5A6D6D4C5A6D01FC4C5A6D6DEE7F806D6C6C6C4BC7FC6E01E0EC07FE02
+0F01FEEC1FF80203903AFFE001FFF0020091B612C0033F93C8FC030715FCDB007F14E004
+0101FCC9FC525479D261>67 D69 DI73 D78 D<93380FFFC00303B6FC031F15E092B712FC0203D9FC0013
+FF020F01C0010F13C0023F90C7000313F0DA7FFC02007F494848ED7FFE4901E0ED1FFF49
+496F7F49496F7F4990C96C7F49854948707F4948707FA24849717E48864A83481B804A83
+481BC0A2481BE04A83A2481BF0A348497113F8A5B51AFCAF6C1BF86E5FA46C1BF0A26E5F
+6C1BE0A36C6D4D13C0A26C6D4D1380A26C1B006C6D4D5A6E5E6C626D6C4C5B6D6D4B5B6D
+6D4B5B6D6D4B5B6D6D4B5B6D6D4B90C7FC6D6D4B5A6D01FF02035B023F01E0011F13F002
+0F01FC90B512C0020390B7C8FC020016FC031F15E0030392C9FCDB001F13E0565479D265
+>II<
+B912F0F0FF8019F819FF1AC0D8000701F0C714F0060F7F060113FE727F737F737F85737F
+87A2737FA387A863A2616363A24F5B4F5B4F90C8FC4F5A06035B060F13F095B512C092B8
+C9FC19F819E019F89226F0000313FE9439007FFF80727F727F727F727F727F8684A28684
+A787A71D1C75133EA38575137E73157C7513FC731401B86C6D9038F803F807039038FE07
+F07390B512E0736C14C0080F1400CEEA7FFC5F537CD164>82 D<91260FFF80130791B500
+F85B010702FF5B011FEDC03F49EDF07F9026FFFC006D5A4801E0EB0FFD4801800101B5FC
+4848C87E48488149150F001F824981123F4981007F82A28412FF84A27FA26D82A27F7F6D
+93C7FC14C06C13F014FF15F86CECFF8016FC6CEDFFC017F06C16FC6C16FF6C17C06C836C
+836D826D82010F821303010082021F16801400030F15C0ED007F040714E01600173F050F
+13F08383A200788200F882A3187FA27EA219E07EA26CEFFFC0A27F6D4B13806D17006D5D
+01FC4B5A01FF4B5A02C04A5A02F8EC7FF0903B1FFFC003FFE0486C90B65AD8FC0393C7FC
+48C66C14FC48010F14F048D9007F90C8FC3C5479D24B>I<003FBC1280A59126C0003F90
+38C0007F49C71607D87FF8060113C001E08449197F49193F90C8171FA2007E1A0FA3007C
+1A07A500FC1BE0481A03A6C994C7FCB3B3AC91B912F0A553517BD05E>II97
+D<913801FFF8021FEBFF8091B612F0010315FC010F9038C00FFE903A1FFE0001FFD97FFC
+491380D9FFF05B4817C048495B5C5A485BA2486F138091C7FC486F1300705A4892C8FC5B
+A312FFAD127F7FA27EA2EF03E06C7F17076C6D15C07E6E140F6CEE1F806C6DEC3F006C6D
+147ED97FFE5C6D6CEB03F8010F9038E01FF0010390B55A01001580023F49C7FC020113E0
+33387CB63C>99 D<4DB47E0407B5FCA5EE001F1707B3A4913801FFE0021F13FC91B6FC01
+0315C7010F9038E03FE74990380007F7D97FFC0101B5FC49487F4849143F484980485B83
+485B5A91C8FC5AA3485AA412FFAC127FA36C7EA37EA26C7F5F6C6D5C7E6C6D5C6C6D49B5
+FC6D6C4914E0D93FFED90FEFEBFF80903A0FFFC07FCF6D90B5128F0101ECFE0FD9003F13
+F8020301C049C7FC41547CD24B>I<913803FFC0023F13FC49B6FC010715C04901817F90
+3A3FFC007FF849486D7E49486D7E4849130F48496D7E48178048497F18C0488191C7FC48
+17E0A248815B18F0A212FFA490B8FCA318E049CAFCA6127FA27F7EA218E06CEE01F06E14
+037E6C6DEC07E0A26C6DEC0FC06C6D141F6C6DEC3F806D6CECFF00D91FFEEB03FE903A0F
+FFC03FF8010390B55A010015C0021F49C7FC020113F034387CB63D>I103 D<137F497E000313E0487FA2487FA76C5BA26C5BC613806DC7FC90C8FCAD
+EB3FF0B5FCA512017EB3B3A6B612E0A51B547BD325>105 D108 DII<913801FFE0021F13FE91B612C0010315F0010F9038807FFC903A1FFC000FFED97F
+F86D6C7E49486D7F48496D7F48496D7F4A147F48834890C86C7EA24883A248486F7EA300
+7F1880A400FF18C0AC007F1880A3003F18006D5DA26C5FA26C5F6E147F6C5F6C6D4A5A6C
+6D495B6C6D495B6D6C495BD93FFE011F90C7FC903A0FFF807FFC6D90B55A010015C0023F
+91C8FC020113E03A387CB643>I<903A3FF001FFE0B5010F13FE033FEBFFC092B612F002
+F301017F913AF7F8007FFE0003D9FFE0EB1FFFC602806D7F92C76C7F4A824A6E7F4A6E7F
+A2717FA285187F85A4721380AC1A0060A36118FFA2615F616E4A5BA26E4A5B6E4A5B6F49
+5B6F4990C7FC03F0EBFFFC9126FBFE075B02F8B612E06F1480031F01FCC8FC030313C092
+CBFCB1B612F8A5414D7BB54B>I<90397FE003FEB590380FFF80033F13E04B13F09238FE
+1FF89139E1F83FFC0003D9E3E013FEC6ECC07FECE78014EF150014EE02FEEB3FFC5CEE1F
+F8EE0FF04A90C7FCA55CB3AAB612FCA52F367CB537>114 D<903903FFF00F013FEBFE1F
+90B7FC120348EB003FD80FF81307D81FE0130148487F4980127F90C87EA24881A27FA27F
+01F091C7FC13FCEBFFC06C13FF15F86C14FF16C06C15F06C816C816C81C681013F158001
+0F15C01300020714E0EC003F030713F015010078EC007F00F8153F161F7E160FA27E17E0
+7E6D141F17C07F6DEC3F8001F8EC7F0001FEEB01FE9039FFC00FFC6DB55AD8FC1F14E0D8
+F807148048C601F8C7FC2C387CB635>I<143EA6147EA414FEA21301A313031307A2130F
+131F133F13FF5A000F90B6FCB8FCA426003FFEC8FCB3A9EE07C0AB011FEC0F8080A26DEC
+1F0015806DEBC03E6DEBF0FC6DEBFFF86D6C5B021F5B020313802A4D7ECB34>II<007FB500F090387FFFFEA5C66C48C7000F90C7FC6D6CEC07F8
+6D6D5C6D6D495A6D4B5A6F495A6D6D91C8FC6D6D137E6D6D5B91387FFE014C5A6E6C485A
+6EEB8FE06EEBCFC06EEBFF806E91C9FCA26E5B6E5B6F7E6F7EA26F7F834B7F4B7F92B5FC
+DA01FD7F03F87F4A486C7E4A486C7E020F7FDA1FC0804A486C7F4A486C7F02FE6D7F4A6D
+7F495A49486D7F01076F7E49486E7E49486E7FEBFFF0B500FE49B612C0A542357EB447>
+120 D E
+%EndDVIPSBitmapFont
+%DVIPSBitmapFont: Fj cmsy8 8 1
+/Fj 1 4 df<130C131EA50060EB01800078130739FC0C0FC0007FEB3F80393F8C7F0038
+07CCF83801FFE038007F80011EC7FCEB7F803801FFE03807CCF8383F8C7F397F0C3F8000
+FCEB0FC039781E078000601301000090C7FCA5130C1A1D7C9E23>3
+D E
+%EndDVIPSBitmapFont
+%DVIPSBitmapFont: Fk cmr12 12 16
+/Fk 16 122 df<14FF010713E090381F81F890383E007C01FC133F4848EB1F8049130F48
+48EB07C04848EB03E0A2000F15F0491301001F15F8A2003F15FCA390C8FC4815FEA54815
+FFB3A46C15FEA56D1301003F15FCA3001F15F8A26C6CEB03F0A36C6CEB07E0000315C06D
+130F6C6CEB1F806C6CEB3F00013E137C90381F81F8903807FFE0010090C7FC28447CC131
+>48 D50 D<49B4FC010F13E0013F13FC
+9038FE01FE3A01F0007F80D803C0EB3FC048C7EA1FE0120EED0FF0EA0FE0486C14F8A215
+077F5BA26C48130FEA03C0C813F0A3ED1FE0A2ED3FC01680ED7F0015FE4A5AEC03F0EC1F
+C0D90FFFC7FC15F090380001FCEC007FED3F80ED1FC0ED0FE016F0ED07F816FC150316FE
+A2150116FFA3121EEA7F80487EA416FE491303A2007EC713FC00701407003015F8003814
+0F6C15F06CEC1FE06C6CEB3FC0D803E0EB7F803A01FE01FE0039007FFFF8010F13E00101
+90C7FC28447CC131>I<010FB512FEA3D9000313806E130080B3B3AB123F487E487EA44A
+5A13801300006C495A00705C6C13076C5C6C495A6CEB1F802603E07FC7FC3800FFFCEB1F
+E027467BC332>74 D80 D87 D97 D99
+D101 D105
+D108
+DI<39
+01FC01FE00FF903807FFC091381E07F091383801F8000701707F0003EBE0002601FDC07F
+5C01FF147F91C7FCA25BA35BB3A8486CECFF80B5D8F83F13FEA32F2C7DAB36>I<3903F8
+03F000FFEB1FFCEC3C3EEC707F0007EBE0FF3803F9C000015B13FBEC007E153C01FF1300
+5BA45BB3A748B4FCB512FEA3202C7DAB26>114 D117 D121
+D E
+%EndDVIPSBitmapFont
+%DVIPSBitmapFont: Fl cmr17 17.28 17
+/Fl 17 118 df67 D70 DI73 D<933801FFE0043F13FF4BB612E0
+03079038003FF8DB1FF0EB03FEDB7FC0903800FF804A48C8EA3FE0DA03FCED0FF0DA0FF0
+ED03FC4A486F7E4A486F7E4A48707E4ACA6C7E4948717E4948717E4948717E4948717E49
+48717E013F854A83017F864948727EA24890CC6C7EA24848737EA24848737EA2000F8749
+1907001F87A34848737EA4007F1C80A24985A400FF1CC0AF6C6C4F1380A5003F1C006D61
+A3001F63A26D190F000F63A26C6C4F5AA36C6C4F5AA26C6D4E5A6C636E18FF017F626D6C
+4D90C7FC6E5F011F616D6C4D5A6D6C4D5A0103616E171F6D6C4D5A6D6D4C5ADA3FC04CC8
+FCDA1FF0ED03FE6E6C4B5A6E6C4B5ADA01FFED3FE09126007FC0ECFF80DB1FF0D903FEC9
+FCDB07FFEB3FF8030190B512E0DB003F91CAFC040113E05A667AE367>79
+D<933801FFE0043F13FF4BB612E003079038003FF8DB1FF0EB03FEDB7FC0903800FF804A
+48C8EA3FE0DA03FCED0FF0DA0FF8ED07FCDA1FE0ED01FE4A486F7E4A48707E4ACA6C7E49
+48717E4948717E4948717E010F854948717E4948717EA24948717F01FF864A187F4890CC
+6C7EA2488749191F00078749190F000F87A2001F87491907A2003F87A24985A2007F1C80
+A44985A200FF1CC0AF007F1C806D61A4003F1C00A36D61001F63A36C6C4F5AA20007636D
+191FA26C6C4F5AA26C636C6DDA3F804A5AEFFFE06D6C010301F84A5A6D6C902607C07C49
+90C7FC93380F001E6D6C011E6D495A6D6C6F495A0107021CD903805B6D6C013C6D6C485A
+6E0138151F6D6C0300495A6D01806F485ADA3FC04CC8FCDA1FE0ED71FE91260FF83CEC77
+FC912607FC1CEC7FF8912601FF1EEC3FE09126007FDEECFF80DB1FFFD903FEC9FC030790
+38C03FF8030190B56C1560DB003F143C0401EBE01C93C8121EA21DE0191FA3736C13011D
+C0741303A274130774130F736CEB1F8074133F9738FF01FF7390B51200A264856485745B
+745B745B08071380E001FEC7FC5B807AE367>81 D83 D<003FBC12F8A49126C000
+039038C0000301FCC76C49EB007F01F0190F01C019074848F103FC90C81701007E1A0000
+7C1B7CA300781B3CA400701B1CA600F01B1E481B0EA7C91800B3B3B3A54C7FA2041F13F8
+4AB87EA457627CE160>I97 D<4AB47E020F13F8023F13FE9139FF007F80D903FCEB07E0D907F0EB01F0D91FE0
+EB007849488049488049C87E48485D4915FF00034B138048485CA2485AA2485AA2003F6F
+130049EC007C94C7FC127FA35B12FFAD127F7FA4123F7FA2001FEE01C07F000F16036D16
+8012076C6C15076D160000015E6C6C151E6D6C5C6D6C5C6D6C5CD90FF8495AD903FCEB07
+C0903A00FF803F8091263FFFFEC7FC020F13F80201138032417CBF3A>99
+D<181EEF3FFEEE07FFA4EE000F1703A21701B3AAEDFF80020F13F8023F13FE9139FF803F
+81903A03FC0007C14948EB01E1D91FE0EB00F94948147D4948143D49C8121F4848150F49
+1507120348481503491501120F121F5BA2123F5B127FA45B12FFAD127F7FA3123FA27F12
+1FA26C6C1503A26C6C150712036D150F6C6C151F0000163D137F6D6CECF9FF6D6CEB01F1
+D90FF0D903C113C06D6CD90F81EBFF80D901FFEB7F019039007FFFFC021F13E002010100
+91C7FC41657CE349>II<133C13FF487F487FA66C5B6C90C7FC133C90C8FCB3A2EB03C0EA07FF127FA4
+1201EA007FA2133FB3B3AC497E497EB612E0A41B5F7DDE23>105
+D107 D<9039078003F8D807FFEB0FFFB501
+3F13C092387C0FE0913881F01F9238E03FF00001EB838039007F8700148FEB3F8E029CEB
+1FE0EE0FC00298EB030002B890C7FCA214B014F0A25CA55CB3B0497EEBFFF8B612FCA42C
+3F7CBE33>114 D<1438A71478A414F8A31301A31303A21307130F131FA2137F13FF1203
+000F90B6FCB8FCA3260007F8C8FCB3AE17E0AE6D6CEB01C0A316036D6C148016076D6C14
+006E6C5A91383FC01E91381FF07C6EB45A020313E09138007F802B597FD733>116
+DI
+E
+%EndDVIPSBitmapFont
+end
+%%EndProlog
+%%BeginSetup
+%%Feature: *Resolution 600dpi
+TeXDict begin
+%%PaperSize: A4
+
+%%EndSetup
+%%Page: 1 1
+1 0 bop 1125 937 a Fl(CFITSIO)44 b(Quic)l(k)h(Start)d(Guide)1625
+1190 y Fk(William)28 b(P)m(ence)2277 1154 y Fj(\003)1666
+1394 y Fk(Jan)m(uary)33 b(2003)120 1916 y Fi(Con)l(ten)l(ts)120
+2120 y Fh(1)84 b(In)m(tro)s(duction)2897 b(2)120 2324
+y(2)84 b(Installing)35 b(and)g(Using)h(CFITSIO)2080 b(3)120
+2528 y(3)84 b(Example)34 b(Programs)2600 b(4)120 2731
+y(4)84 b(CFITSIO)33 b(Routines)2603 b(6)256 2844 y Fg(4.1)94
+b(Error)30 b(Rep)s(orting)24 b(.)45 b(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h
+(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)
+g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)174 b(6)256 2957 y(4.2)94
+b(File)30 b(Op)s(en/Close)f(Routines)57 b(.)46 b(.)f(.)h(.)g(.)f(.)h(.)
+g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g
+(.)g(.)f(.)h(.)g(.)174 b(6)256 3070 y(4.3)94 b(HDU-lev)m(el)32
+b(Routines)85 b(.)45 b(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)h(.)g
+(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)
+g(.)174 b(7)256 3183 y(4.4)94 b(Image)32 b(I/O)e(Routines)79
+b(.)45 b(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)
+g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)174
+b(9)256 3296 y(4.5)94 b(T)-8 b(able)30 b(I/O)h(Routines)d(.)46
+b(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g
+(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)129
+b(12)256 3409 y(4.6)94 b(Header)31 b(Keyw)m(ord)f(I/O)h(Routines)78
+b(.)46 b(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)
+h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)129 b(19)256 3522
+y(4.7)94 b(Utilit)m(y)30 b(Routines)c(.)45 b(.)h(.)g(.)f(.)h(.)g(.)g(.)
+f(.)h(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f
+(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)129 b(22)120 3726
+y Fh(5)84 b(CFITSIO)33 b(File)i(Names)f(and)g(Filters)1907
+b(23)256 3839 y Fg(5.1)94 b(Creating)30 b(New)h(Files)43
+b(.)j(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h
+(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)129
+b(23)256 3951 y(5.2)94 b(Op)s(ening)29 b(Existing)f(Files)39
+b(.)46 b(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)
+f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)129
+b(24)256 4064 y(5.3)94 b(Image)32 b(Filtering)53 b(.)45
+b(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f
+(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)129
+b(26)465 4177 y(5.3.1)106 b(Extracting)31 b(a)g(subsection)e(of)i(an)f
+(image)76 b(.)46 b(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g
+(.)g(.)f(.)h(.)g(.)129 b(26)465 4290 y(5.3.2)106 b(Create)32
+b(an)e(Image)h(b)m(y)f(Binning)e(T)-8 b(able)30 b(Columns)i(.)45
+b(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)129
+b(26)256 4403 y(5.4)94 b(T)-8 b(able)30 b(Filtering)74
+b(.)45 b(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)
+g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g
+(.)129 b(28)465 4516 y(5.4.1)106 b(Column)29 b(and)h(Keyw)m(ord)g
+(Filtering)47 b(.)e(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g
+(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)129 b(28)465 4629 y(5.4.2)106
+b(Ro)m(w)31 b(Filtering)39 b(.)45 b(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g
+(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)
+g(.)f(.)h(.)g(.)129 b(29)465 4742 y(5.4.3)106 b(Go)s(o)s(d)30
+b(Time)g(In)m(terv)-5 b(al)30 b(Filtering)59 b(.)46 b(.)f(.)h(.)g(.)g
+(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)
+129 b(32)465 4855 y(5.4.4)106 b(Spatial)29 b(Region)i(Filtering)56
+b(.)46 b(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)
+h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)129 b(32)465 4968
+y(5.4.5)106 b(Example)30 b(Ro)m(w)h(Filters)f(.)45 b(.)h(.)g(.)f(.)h(.)
+g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g
+(.)g(.)f(.)h(.)g(.)129 b(34)256 5081 y(5.5)94 b(Com)m(bined)29
+b(Filtering)g(Examples)44 b(.)i(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h
+(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)f(.)h(.)g(.)g(.)f(.)h(.)g(.)129
+b(36)120 5284 y Fh(6)84 b(CFITSIO)33 b(Error)i(Status)f(Co)s(des)2069
+b(38)p 120 5346 1465 4 v 222 5400 a Ff(\003)258 5431
+y Fe(HEASAR)n(C,)25 b(NASA)f(Go)r(ddard)i(Space)f(Fligh)n(t)h(Cen)n
+(ter,)g Fd(Wil)t(liam.D.Penc)l(e@nasa.gov)1928 5809 y
+Fg(1)p eop
+%%Page: 2 2
+2 1 bop 120 573 a Fi(1)135 b(In)l(tro)t(duction)120 776
+y Fg(This)33 b(do)s(cumen)m(t)h(is)f(in)m(tended)g(to)i(help)e(y)m(ou)i
+(quic)m(kly)e(start)i(writing)d(C)i(programs)g(to)h(read)f(and)g(write)
+120 889 y(FITS)45 b(\014les)f(using)g(the)h(CFITSIO)f(library)-8
+b(.)84 b(It)45 b(co)m(v)m(ers)i(the)f(most)f(imp)s(ortan)m(t)g(CFITSIO)
+e(routines)120 1002 y(that)i(are)f(needed)g(to)h(p)s(erform)d(most)i(t)
+m(yp)s(es)h(of)f(op)s(erations)f(on)h(FITS)f(\014les.)81
+b(F)-8 b(or)45 b(more)f(complete)120 1115 y(information)39
+b(ab)s(out)g(these)i(and)f(all)f(the)h(other)h(a)m(v)-5
+b(ailable)39 b(routines)g(in)g(the)i(library)d(please)i(refer)g(to)120
+1227 y(the)c(\\CFITSIO)e(User's)i(Reference)g(Guide",)h(whic)m(h)d(is)g
+(a)m(v)-5 b(ailable)35 b(from)g(the)h(CFITSIO)e(W)-8
+b(eb)36 b(site)g(at)120 1340 y Fc(http://heasarc.gsfc.nasa)o(.gov)o
+(/fit)o(sio)o Fg(.)261 1453 y(F)-8 b(or)41 b(more)f(general)f
+(information)f(ab)s(out)i(the)g(FITS)f(data)h(format,)j(refer)d(to)g
+(the)g(follo)m(wing)e(w)m(eb)120 1566 y(page:)j(h)m
+(ttp://heasarc.gsfc.nasa.go)m(v/do)s(cs/heasa)q(rc/\014ts.h)m(t)q(ml)
+261 1679 y(FITS)27 b(stands)h(for)g(Flexible)e(Image)j(T)-8
+b(ransp)s(ort)27 b(System)h(and)f(is)g(the)i(standard)e(\014le)g
+(format)h(used)g(to)120 1792 y(store)j(most)g(astronomical)f(data)i
+(\014les.)40 b(There)30 b(are)h(2)g(basic)f(t)m(yp)s(es)g(of)h(FITS)f
+(\014les:)40 b(images)30 b(and)g(tables.)120 1905 y(FITS)j(images)h
+(often)g(con)m(tain)g(a)g(2-dimensional)d(arra)m(y)k(of)e(pixels)f
+(represen)m(ting)h(an)h(image)g(of)f(a)h(piece)120 2018
+y(of)f(the)f(sky)-8 b(,)34 b(but)e(FITS)g(images)g(can)h(also)g(con)m
+(tain)g(1-D)g(arra)m(ys)g(\(i.e,)h(a)f(sp)s(ectrum)e(or)i(ligh)m(t)e
+(curv)m(e\),)j(or)120 2131 y(3-D)40 b(arra)m(ys)f(\(a)g(data)g(cub)s
+(e\),)i(or)d(ev)m(en)i(higher)d(dimensional)f(arra)m(ys)i(of)h(data.)66
+b(An)38 b(image)h(ma)m(y)g(also)120 2244 y(ha)m(v)m(e)30
+b(zero)g(dimensions,)c(in)i(whic)m(h)f(case)j(it)e(is)g(referred)g(to)i
+(as)f(a)g(n)m(ull)e(or)i(empt)m(y)g(arra)m(y)-8 b(.)41
+b(The)28 b(supp)s(orted)120 2357 y(datat)m(yp)s(es)f(for)f(the)h(image)
+g(arra)m(ys)f(are)h(8,)h(16,)g(and)e(32-bit)h(in)m(tegers,)h(and)d(32)j
+(and)d(64-bit)i(\015oating)f(p)s(oin)m(t)120 2469 y(real)k(n)m(um)m(b)s
+(ers.)39 b(Both)31 b(signed)f(and)f(unsigned)g(in)m(tegers)h(are)h
+(supp)s(orted.)261 2582 y(FITS)j(tables)g(con)m(tain)g(ro)m(ws)g(and)g
+(columns)f(of)h(data,)i(similar)c(to)j(a)g(spreadsheet.)52
+b(All)33 b(the)h(v)-5 b(alues)120 2695 y(in)30 b(a)h(particular)e
+(column)h(m)m(ust)g(ha)m(v)m(e)j(the)e(same)g(datat)m(yp)s(e.)43
+b(A)31 b(cell)f(of)h(a)g(column)f(is)g(not)h(restricted)g(to)120
+2808 y(a)h(single)d(n)m(um)m(b)s(er,)i(and)f(instead)h(can)g(con)m
+(tain)h(an)f(arra)m(y)g(or)h(v)m(ector)g(of)g(n)m(um)m(b)s(ers.)41
+b(There)31 b(are)h(actually)120 2921 y(2)43 b(subt)m(yp)s(es)f(of)h
+(FITS)f(tables:)65 b(ASCI)s(I)41 b(and)i(binary)-8 b(.)76
+b(As)43 b(the)g(names)g(imply)-8 b(,)44 b(ASCI)s(I)d(tables)i(store)120
+3034 y(the)37 b(data)h(v)-5 b(alues)36 b(in)g(an)g(ASCI)s(I)g(represen)
+m(tation)h(whereas)f(binary)g(tables)g(store)i(the)f(data)g(v)-5
+b(alues)37 b(in)120 3147 y(a)c(more)f(e\016cien)m(t)h(mac)m
+(hine-readable)e(binary)g(format.)46 b(Binary)32 b(tables)g(are)g
+(generally)g(more)g(compact)120 3260 y(and)25 b(supp)s(ort)e(more)j
+(features)f(\(e.g.,)j(a)e(wider)e(range)h(of)h(datat)m(yp)s(es,)h(and)e
+(v)m(ector)i(columns\))d(than)h(ASCI)s(I)120 3373 y(tables.)261
+3486 y(A)31 b(single)e(FITS)h(\014le)g(man)m(y)h(con)m(tain)g(m)m
+(ultiple)d(images)j(or)g(tables.)41 b(Eac)m(h)31 b(table)g(or)f(image)h
+(is)f(called)120 3599 y(a)k(Header-Data)j(Unit,)d(or)g(HDU.)h(The)f
+(\014rst)f(HDU)i(in)d(a)j(FITS)e(\014le)g(m)m(ust)h(b)s(e)f(an)h(image)
+h(\(but)e(it)h(ma)m(y)120 3711 y(ha)m(v)m(e)c(zero)f(axes\))h(and)e(is)
+g(called)g(the)h(Primary)e(Arra)m(y)-8 b(.)41 b(An)m(y)28
+b(additional)f(HDUs)i(in)f(the)g(\014le)g(\(whic)m(h)g(are)120
+3824 y(also)i(referred)g(to)h(as)g(`extensions'\))f(ma)m(y)h(con)m
+(tain)g(either)f(an)g(image)h(or)f(a)h(table.)261 3937
+y(Ev)m(ery)38 b(HDU)g(con)m(tains)g(a)g(header)g(con)m(taining)f(k)m
+(eyw)m(ord)h(records.)62 b(Eac)m(h)38 b(k)m(eyw)m(ord)g(record)g(is)f
+(80)120 4050 y(ASCI)s(I)29 b(c)m(haracters)j(long)e(and)f(has)i(the)f
+(follo)m(wing)f(format:)120 4263 y Fc(KEYWORD)46 b(=)h(value)g(/)g
+(comment)f(string)261 4475 y Fg(The)23 b(k)m(eyw)m(ord)i(name)f(can)g
+(b)s(e)f(up)g(to)h(8)g(c)m(haracters)i(long)d(\(all)g(upp)s(ercase\).)
+38 b(The)23 b(v)-5 b(alue)24 b(can)g(b)s(e)f(either)120
+4588 y(an)k(in)m(teger)g(or)f(\015oating)h(p)s(oin)m(t)e(n)m(um)m(b)s
+(er,)i(a)g(logical)f(v)-5 b(alue)26 b(\(T)h(or)f(F\),)i(or)e(a)h(c)m
+(haracter)i(string)c(enclosed)i(in)120 4701 y(single)d(quotes.)40
+b(Eac)m(h)26 b(header)f(b)s(egins)f(with)g(a)i(series)f(of)h(required)e
+(k)m(eyw)m(ords)h(to)i(describ)s(e)d(the)h(datat)m(yp)s(e)120
+4814 y(and)35 b(format)h(of)f(the)h(follo)m(wing)e(data)i(unit,)f(if)f
+(an)m(y)-8 b(.)57 b(An)m(y)35 b(n)m(um)m(b)s(er)g(of)g(other)h
+(optional)e(k)m(eyw)m(ords)i(can)120 4927 y(b)s(e)d(included)e(in)h
+(the)h(header)h(to)g(pro)m(vide)e(other)i(descriptiv)m(e)f(information)
+e(ab)s(out)j(the)f(data.)51 b(F)-8 b(or)34 b(the)120
+5040 y(most)g(part,)g(the)g(CFITSIO)d(routines)i(automatically)g(write)
+f(the)i(required)e(FITS)g(k)m(eyw)m(ords)i(for)f(eac)m(h)120
+5153 y(HDU,)e(so)g(y)m(ou,)g(the)g(programmer,)f(usually)e(do)i(not)h
+(need)f(to)h(w)m(orry)f(ab)s(out)g(them.)1928 5809 y(2)p
+eop
+%%Page: 3 3
+3 2 bop 120 573 a Fi(2)135 b(Installing)46 b(and)f(Using)g(CFITSIO)120
+776 y Fg(First,)32 b(y)m(ou)g(should)d(do)m(wnload)i(the)h(CFITSIO)e
+(soft)m(w)m(are)j(and)e(the)h(set)g(of)g(example)g(FITS)f(utilit)m(y)f
+(pro-)120 889 y(grams)h(from)f(the)g(w)m(eb)h(site)f(at)i(h)m
+(ttp://heasarc.gsfc.nasa.go)m(v/\014tsio.)46 b(The)30
+b(example)g(programs)g(illus-)120 1002 y(trate)g(ho)m(w)e(to)h(p)s
+(erform)f(man)m(y)g(common)h(t)m(yp)s(es)f(of)h(op)s(erations)f(on)g
+(FITS)g(\014les)g(using)f(CFITSIO.)g(They)120 1115 y(are)h(also)g
+(useful)d(when)i(writing)f(a)i(new)f(program)g(b)s(ecause)h(it)f(is)g
+(often)h(easier)f(to)i(tak)m(e)g(a)f(cop)m(y)g(of)g(one)g(of)120
+1227 y(these)k(utilit)m(y)e(programs)h(as)g(a)h(template)g(and)f(then)g
+(mo)s(dify)e(it)i(for)g(y)m(our)h(o)m(wn)f(purp)s(oses,)f(rather)i
+(than)120 1340 y(writing)c(the)j(new)f(program)g(completely)g(from)g
+(scratc)m(h.)261 1453 y(T)-8 b(o)28 b(build)c(the)k(CFITSIO)d(library)g
+(on)i(Unix)f(platforms,)i(`un)m(tar')f(the)h(source)f(co)s(de)h
+(distribution)23 b(\014le)120 1566 y(and)30 b(then)g(execute)i(the)e
+(follo)m(wing)f(commands)h(in)f(the)i(directory)e(con)m(taining)h(the)h
+(source)g(co)s(de:)120 1779 y Fc(>)95 b(./configure)45
+b([--prefix=/target/instal)o(lati)o(on/)o(path)o(])120
+1892 y(>)95 b(make)524 b(\(or)47 b('make)f(shared'\))120
+2005 y(>)95 b(make)47 b(install)141 b(\(this)46 b(step)h(is)g
+(optional\))261 2217 y Fg(The)40 b(optional)g('pre\014x')g(argumen)m(t)
+h(to)g(con\014gure)f(giv)m(es)h(the)g(path)f(to)h(the)g(directory)f
+(where)g(the)120 2330 y(CFITSIO)30 b(library)g(and)h(include)f(\014les)
+h(should)f(b)s(e)h(installed)f(via)i(the)g(later)g('mak)m(e)h(install')
+d(command.)120 2443 y(F)-8 b(or)31 b(example,)120 2655
+y Fc(>)95 b(./configure)45 b(--prefix=/usr1/local)261
+2868 y Fg(will)18 b(cause)j(the)g('mak)m(e)h(install')d(command)h(to)h
+(cop)m(y)h(the)e(CFITSIO)f(lib)s(c\014tsio)f(\014le)i(to)h(/usr1/lo)s
+(cal/lib)120 2981 y(and)35 b(the)h(necessary)g(include)e(\014les)h(to)h
+(/usr1/lo)s(cal/include)e(\(assuming)h(of)g(course)h(that)h(the)f(pro)s
+(cess)120 3094 y(has)30 b(p)s(ermission)d(to)32 b(write)d(to)i(these)g
+(directories\).)261 3207 y(Pre-compiled)d(v)m(ersions)h(of)h(the)g
+(CFITSIO)e(DLL)i(library)d(are)j(a)m(v)-5 b(ailable)29
+b(for)g(PCs.)40 b(On)29 b(Macin)m(tosh)120 3320 y(mac)m(hines,)45
+b(refer)d(to)g(the)h(README.MacOS)g(\014le)e(for)h(instructions)e(on)i
+(building)c(CFITSIO)j(using)120 3432 y(Co)s(deW)-8 b(arrior.)261
+3545 y(An)m(y)40 b(programs)g(that)h(use)f(CFITSIO)f(m)m(ust)h(of)g
+(course)h(b)s(e)e(link)m(ed)g(with)g(the)h(CFITSIO)f(library)120
+3658 y(when)e(creating)h(the)g(executable)h(\014le.)63
+b(The)37 b(exact)j(pro)s(cedure)c(for)i(linking)d(a)k(program)e(dep)s
+(ends)f(on)120 3771 y(y)m(our)31 b(soft)m(w)m(are)i(en)m(vironmen)m(t,)
+e(but)g(on)g(Unix)f(platforms,)h(the)g(command)g(line)f(to)i(compile)e
+(and)h(link)e(a)120 3884 y(program)h(will)e(lo)s(ok)i(something)g(lik)m
+(e)f(this:)120 4097 y Fc(gcc)47 b(-o)g(myprog)f(myprog.c)g(-L.)h
+(-lcfitsio)e(-lm)i(-lnsl)f(-lsocket)261 4309 y Fg(Y)-8
+b(ou)37 b(ma)m(y)g(not)f(need)g(to)h(include)e(all)g(of)h(the)h('m',)h
+('nsl',)f(and)f('so)s(c)m(k)m(et')i(system)f(libraries)c(on)j(y)m(our)
+120 4422 y(particular)j(mac)m(hine.)72 b(T)-8 b(o)42
+b(\014nd)d(out)i(what)g(libraries)d(are)j(required)e(on)i(y)m(our)g
+(\(Unix\))f(system,)k(t)m(yp)s(e)120 4535 y Fc('make)i(testprog')28
+b Fg(and)i(see)h(what)f(libraries)e(are)i(then)h(included)c(on)j(the)h
+(resulting)e(link)f(line.)1928 5809 y(3)p eop
+%%Page: 4 4
+4 3 bop 120 573 a Fi(3)135 b(Example)46 b(Programs)120
+776 y Fg(Before)32 b(describing)d(the)j(individual)27
+b(CFITSIO)i(routines)i(in)f(detail,)h(it)f(is)h(instructiv)m(e)f(to)i
+(\014rst)f(lo)s(ok)g(at)120 889 y(an)c(actual)g(program.)40
+b(The)26 b(names)h(of)g(the)g(CFITSIO)f(routines)g(are)h(fairly)e
+(descriptiv)m(e)h(\(they)i(all)e(b)s(egin)120 1002 y(with)j
+Fc(fits)p 525 1002 29 4 v 33 w Fg(,)i(so)g(it)e(should)g(b)s(e)h
+(reasonably)f(clear)i(what)f(this)f(program)h(do)s(es:)120
+1202 y Fc(------------------------)o(----)o(----)o(---)o(----)o(----)o
+(---)o(----)o(----)o(---)o(----)o(---)311 1315 y(#include)45
+b()311 1428 y(#include)g()120 1541
+y(1:)95 b(#include)45 b("fitsio.h")311 1767 y(int)i(main\(int)e(argc,)i
+(char)f(*argv[]\))311 1879 y({)120 1992 y(2:)286 b(fitsfile)45
+b(*fptr;)502 2105 y(char)h(card[FLEN_CARD];)120 2218
+y(3:)286 b(int)47 b(status)f(=)h(0,)95 b(nkeys,)46 b(ii;)95
+b(/*)47 b(MUST)g(initialize)e(status)h(*/)120 2444 y(4:)286
+b(fits_open_file\(&fptr,)42 b(argv[1],)j(READONLY,)h(&status\);)502
+2557 y(fits_get_hdrspace\(fptr,)41 b(&nkeys,)46 b(NULL,)g(&status\);)
+502 2783 y(for)h(\(ii)g(=)g(1;)g(ii)g(<=)h(nkeys;)e(ii++\))94
+b({)597 2896 y(fits_read_record\(fptr,)42 b(ii,)47 b(card,)f
+(&status\);)g(/*)h(read)f(keyword)g(*/)597 3009 y(printf\("\045s\\n",)e
+(card\);)502 3121 y(})502 3234 y(printf\("END\\n\\n"\);)90
+b(/*)48 b(terminate)d(listing)h(with)g(END)h(*/)502 3347
+y(fits_close_file\(fptr,)42 b(&status\);)502 3573 y(if)47
+b(\(status\))475 b(/*)47 b(print)g(any)g(error)f(messages)f(*/)120
+3686 y(5:)477 b(fits_report_error\(stder)o(r,)42 b(status\);)502
+3799 y(return\(status\);)311 3912 y(})120 4025 y
+(------------------------)o(----)o(----)o(---)o(----)o(----)o(---)o
+(----)o(----)o(---)o(----)o(---)261 4225 y Fg(This)28
+b(program)h(op)s(ens)f(the)h(sp)s(eci\014ed)f(FITS)g(\014le)h(and)f
+(prin)m(ts)g(out)h(all)f(the)i(header)f(k)m(eyw)m(ords)g(in)f(the)120
+4338 y(curren)m(t)i(HDU.)i(Some)e(other)h(p)s(oin)m(ts)e(to)i(notice)g
+(ab)s(out)f(the)g(program)g(are:)231 4516 y(1.)46 b(The)30
+b Fc(fitsio.h)e Fg(header)i(\014le)f(m)m(ust)i(b)s(e)e(included)f(to)j
+(de\014ne)e(the)i(v)-5 b(arious)29 b(routines)g(and)h(sym)m(b)s(ols)347
+4629 y(used)g(in)f(CFITSIO.)231 4812 y(2.)46 b(The)37
+b Fc(fitsfile)e Fg(parameter)i(is)f(the)h(\014rst)g(argumen)m(t)g(in)f
+(almost)h(ev)m(ery)h(CFITSIO)d(routine.)60 b(It)347 4925
+y(is)40 b(a)i(p)s(oin)m(ter)e(to)i(a)g(structure)e(\(de\014ned)g(in)g
+Fc(fitsio.h)p Fg(\))g(that)h(stores)h(information)d(ab)s(out)i(the)347
+5038 y(particular)g(FITS)g(\014le)g(that)i(the)g(routine)e(will)e(op)s
+(erate)k(on.)76 b(Memory)43 b(for)f(this)f(structure)g(is)347
+5151 y(automatically)33 b(allo)s(cated)f(when)g(the)h(\014le)f(is)f
+(\014rst)h(op)s(ened)g(or)h(created,)h(and)e(is)g(freed)g(when)g(the)
+347 5264 y(\014le)e(is)f(closed.)231 5447 y(3.)46 b(Almost)40
+b(ev)m(ery)g(CFITSIO)e(routine)g(has)i(a)g Fc(status)d
+Fg(parameter)j(as)g(the)g(last)f(argumen)m(t.)69 b(The)347
+5560 y(status)28 b(v)-5 b(alue)27 b(is)g(also)h(usually)d(returned)i
+(as)h(the)g(v)-5 b(alue)27 b(of)h(the)f(function)g(itself.)38
+b(Normally)27 b(status)1928 5809 y(4)p eop
+%%Page: 5 5
+5 4 bop 347 573 a Fg(=)22 b(0,)i(and)d(a)h(p)s(ositiv)m(e)f(status)h(v)
+-5 b(alue)21 b(indicates)g(an)h(error)f(of)h(some)g(sort.)38
+b(The)22 b(status)g(v)-5 b(ariable)20 b(m)m(ust)347 686
+y(alw)m(a)m(ys)32 b(b)s(e)e(initialized)e(to)k(zero)g(b)s(efore)f(use,)
+g(b)s(ecause)g(if)f(status)h(is)f(greater)j(than)d(zero)i(on)f(input)
+347 799 y(then)e(the)g(CFITSIO)f(routines)g(will)e(simply)g(return)i
+(without)g(doing)g(an)m(ything.)40 b(This)27 b(`inherited)347
+912 y(status')46 b(feature,)j(where)44 b(eac)m(h)i(CFITSIO)e(routine)g
+(inherits)e(the)j(status)g(from)g(the)g(previous)347
+1024 y(routine,)e(mak)m(es)e(it)f(unnecessary)g(to)i(c)m(hec)m(k)g(the)
+f(status)g(v)-5 b(alue)40 b(after)h(ev)m(ery)h(single)d(CFITSIO)347
+1137 y(routine)f(call.)64 b(Generally)38 b(y)m(ou)h(should)e(c)m(hec)m
+(k)j(the)e(status)h(after)g(an)g(esp)s(ecially)e(imp)s(ortan)m(t)g(or)
+347 1250 y(complicated)31 b(routine)f(has)h(b)s(een)g(called,)g(or)g
+(after)h(a)f(blo)s(c)m(k)g(of)g(closely)g(related)g(CFITSIO)f(calls.)
+347 1363 y(This)25 b(example)h(program)h(has)f(tak)m(en)i(this)e
+(feature)h(to)g(the)g(extreme)g(and)f(only)g(c)m(hec)m(ks)i(the)f
+(status)347 1476 y(v)-5 b(alue)30 b(at)h(the)g(v)m(ery)g(end)e(of)i
+(the)f(program.)231 1664 y(4.)46 b(In)37 b(this)e(example)i(program)g
+(the)g(\014le)f(name)h(to)h(b)s(e)e(op)s(ened)h(is)f(giv)m(en)h(as)g
+(an)g(argumen)m(t)g(on)g(the)347 1777 y(command)e(line)f(\()p
+Fc(arg[1])p Fg(\).)53 b(If)35 b(the)g(\014le)g(con)m(tains)g(more)g
+(than)g(1)g(HDU)h(or)f(extension,)i(y)m(ou)e(can)347
+1890 y(sp)s(ecify)19 b(whic)m(h)g(particular)g(HDU)i(to)g(b)s(e)f(op)s
+(ened)f(b)m(y)h(enclosing)g(the)g(name)g(or)h(n)m(um)m(b)s(er)e(of)h
+(the)h(HDU)347 2002 y(in)j(square)i(brac)m(k)m(ets)h(follo)m(wing)d
+(the)h(ro)s(ot)h(name)g(of)f(the)h(\014le.)38 b(F)-8
+b(or)26 b(example,)h Fc(file.fts[0])22 b Fg(op)s(ens)347
+2115 y(the)31 b(primary)d(arra)m(y)-8 b(,)32 b(while)d
+Fc(file.fts[2])e Fg(will)h(mo)m(v)m(e)k(to)f(and)f(op)s(en)f(the)i(2nd)
+f(extension)g(in)f(the)347 2228 y(\014le,)36 b(and)e
+Fc(file.fit[EVENTS])d Fg(will)h(op)s(en)j(the)g(extension)f(that)i(has)
+f(a)g Fc(EXTNAME)46 b(=)i('EVENTS')347 2341 y Fg(k)m(eyw)m(ord)31
+b(in)e(the)h(header.)41 b(Note)31 b(that)g(on)f(the)h(Unix)e(command)h
+(line)f(y)m(ou)h(m)m(ust)g(enclose)h(the)f(\014le)347
+2454 y(name)h(in)e(single)g(or)i(double)e(quote)j(c)m(haracters)g(if)d
+(the)i(name)g(con)m(tains)f(sp)s(ecial)g(c)m(haracters)i(suc)m(h)347
+2567 y(as)f(`[')g(or)f(`]'.)347 2717 y(All)42 b(of)h(the)h(CFITSIO)d
+(routines)h(whic)m(h)g(read)h(or)g(write)g(header)g(k)m(eyw)m(ords,)k
+(image)c(data,)k(or)347 2830 y(table)31 b(data)g(op)s(erate)g(only)f
+(within)e(the)j(curren)m(tly)f(op)s(ened)g(HDU)h(in)e(the)i(\014le.)41
+b(T)-8 b(o)31 b(read)g(or)f(write)347 2943 y(information)36
+b(in)g(a)i(di\013eren)m(t)f(HDU)h(y)m(ou)g(m)m(ust)f(\014rst)g
+(explicitly)e(mo)m(v)m(e)j(to)h(that)f(HDU)g(\(see)g(the)347
+3056 y Fc(fits)p 545 3056 29 4 v 34 w(movabs)p 867 3056
+V 32 w(hdu)30 b Fg(and)g Fc(fits)p 1442 3056 V 33 w(movrel)p
+1763 3056 V 33 w(hdu)f Fg(routines)g(in)g(section)i(4.3\).)231
+3244 y(5.)46 b(The)25 b Fc(fits)p 727 3244 V 33 w(report)p
+1048 3244 V 33 w(error)e Fg(routine)h(pro)m(vides)g(a)h(con)m(v)m
+(enien)m(t)h(w)m(a)m(y)g(to)g(prin)m(t)e(out)h(diagnostic)f(mes-)347
+3357 y(sages)32 b(ab)s(out)e(an)m(y)g(error)g(that)h(ma)m(y)g(ha)m(v)m
+(e)h(o)s(ccurred.)261 3544 y(A)f(set)g(of)f(example)g(FITS)g(utilit)m
+(y)f(programs)h(are)g(a)m(v)-5 b(ailable)30 b(from)g(the)g(CFITSIO)f(w)
+m(eb)i(site)f(at)120 3657 y(h)m(ttp://heasarc.gsfc.nasa.go)m(v/do)s
+(cs/soft)n(w)m(are/)q(\014tsio/cexa)q(mples.h)m(tml.)87
+b(These)45 b(are)g(real)f(w)m(orking)120 3770 y(programs)e(whic)m(h)e
+(illustrate)g(ho)m(w)i(to)h(read,)i(write,)f(and)d(mo)s(dify)f(FITS)i
+(\014les)e(using)h(the)h(CFITSIO)120 3883 y(library)-8
+b(.)36 b(Most)24 b(of)g(these)f(programs)g(are)h(v)m(ery)f(short,)i
+(con)m(taining)e(only)f(a)i(few)f(10s)h(of)f(lines)e(of)j(executable)
+120 3996 y(co)s(de)32 b(or)g(less,)g(y)m(et)h(they)f(p)s(erform)e
+(quite)h(useful)f(op)s(erations)h(on)h(FITS)f(\014les.)44
+b(Running)30 b(eac)m(h)j(program)120 4109 y(without)40
+b(an)m(y)h(command)f(line)f(argumen)m(ts)i(will)d(pro)s(duce)h(a)i
+(short)f(description)f(of)i(ho)m(w)g(to)g(use)f(the)120
+4222 y(program.)g(The)30 b(curren)m(tly)g(a)m(v)-5 b(ailable)29
+b(programs)h(are:)347 4409 y(\014tscop)m(y)h(-)g(cop)m(y)g(a)g(\014le)
+347 4522 y(listhead)e(-)i(list)e(header)h(k)m(eyw)m(ords)347
+4635 y(liststruc)f(-)i(sho)m(w)f(the)g(structure)g(of)h(a)g(FITS)e
+(\014le.)347 4748 y(mo)s(dhead)h(-)g(write)g(or)g(mo)s(dify)f(a)i
+(header)f(k)m(eyw)m(ord)347 4861 y(imarith)f(-)h(add,)g(subtract,)h(m)m
+(ultiply)-8 b(,)28 b(or)j(divide)d(2)j(images)347 4974
+y(imlist)d(-)j(list)e(pixel)g(v)-5 b(alues)29 b(in)g(an)i(image)347
+5087 y(imstat)g(-)f(compute)h(mean,)g(min,)e(and)g(max)i(pixel)e(v)-5
+b(alues)29 b(in)g(an)i(image)347 5200 y(tablist)f(-)g(displa)m(y)f(the)
+h(con)m(ten)m(ts)i(of)f(a)g(FITS)e(table)347 5313 y(tab)s(calc)i(-)g
+(general)f(table)g(calculator)1928 5809 y(5)p eop
+%%Page: 6 6
+6 5 bop 120 573 a Fi(4)135 b(CFITSIO)44 b(Routines)120
+776 y Fg(This)36 b(c)m(hapter)i(describ)s(es)e(the)h(main)g(CFITSIO)f
+(routines)g(that)i(can)g(b)s(e)f(used)g(to)h(p)s(erform)e(the)i(most)
+120 889 y(common)31 b(t)m(yp)s(es)f(of)h(op)s(erations)e(on)i(FITS)e
+(\014les.)120 1136 y Fb(4.1)112 b(Error)37 b(Rep)s(orting)120
+1310 y Fc(void)47 b(fits_report_error\(FILE)41 b(*stream,)46
+b(int)h(status\))120 1423 y(void)g(fits_get_errstatus\(int)41
+b(status,)46 b(char)h(*err_text\))120 1536 y(float)f
+(fits_get_version\(float)c(*version\))261 1748 y Fg(The)24
+b(\014rst)g(routine)f(prin)m(ts)g(out)i(information)e(ab)s(out)h(an)m
+(y)h(error)f(that)h(has)g(o)s(ccurred.)38 b(Whenev)m(er)25
+b(an)m(y)120 1861 y(CFITSIO)f(routine)h(encoun)m(ters)i(an)f(error)f
+(it)h(usually)d(writes)i(a)i(message)g(describing)c(the)j(nature)g(of)g
+(the)120 1974 y(error)g(to)i(an)e(in)m(ternal)f(error)i(message)g(stac)
+m(k)h(and)e(then)h(returns)e(with)g(a)i(p)s(ositiv)m(e)f(in)m(teger)h
+(status)g(v)-5 b(alue.)120 2087 y(P)m(assing)27 b(the)h(error)f(status)
+h(v)-5 b(alue)27 b(to)h(this)f(routine)f(will)f(cause)j(a)g(generic)g
+(description)d(of)j(the)g(error)f(and)120 2200 y(all)e(the)i(messages)h
+(from)e(the)h(in)m(ternal)e(CFITSIO)g(error)h(stac)m(k)i(to)g(b)s(e)e
+(prin)m(ted)f(to)i(the)g(sp)s(eci\014ed)e(stream.)120
+2313 y(The)30 b Fc(stream)f Fg(parameter)h(is)g(usually)e(set)j(equal)f
+(to)h Fc("stdout")d Fg(or)i Fc("stderr")p Fg(.)261 2426
+y(The)25 b(second)g(routine)f(simply)e(returns)i(a)h(30-c)m(haracter)j
+(descriptiv)m(e)c(error)g(message)i(corresp)s(onding)120
+2538 y(to)31 b(the)g(input)d(status)j(v)-5 b(alue.)261
+2651 y(The)30 b(last)g(routine)g(returns)f(the)h(curren)m(t)g(CFITSIO)f
+(library)f(v)m(ersion)i(n)m(um)m(b)s(er.)120 2899 y Fb(4.2)112
+b(File)37 b(Op)s(en/Close)g(Routines)120 3072 y Fc(int)47
+b(fits_open_file\()d(fitsfile)h(**fptr,)h(char)h(*filename,)e(int)h
+(mode,)h(int)g(*status\))120 3185 y(int)g(fits_open_data\()d(fitsfile)h
+(**fptr,)h(char)h(*filename,)e(int)h(mode,)h(int)g(*status\))120
+3298 y(int)g(fits_open_table\(fitsfile)41 b(**fptr,)46
+b(char)h(*filename,)e(int)h(mode,)h(int)g(*status\))120
+3411 y(int)g(fits_open_image\(fitsfile)41 b(**fptr,)46
+b(char)h(*filename,)e(int)h(mode,)h(int)g(*status\))120
+3637 y(int)g(fits_create_file\(fitsfil)o(e)42 b(**fptr,)k(char)g
+(*filename,)f(int)i(*status\))120 3750 y(int)g
+(fits_close_file\(fitsfile)41 b(*fptr,)46 b(int)h(*status\))261
+3962 y Fg(These)38 b(routines)e(op)s(en)i(or)f(close)i(a)f(\014le.)62
+b(The)37 b(\014rst)g Fc(fitsfile)f Fg(parameter)i(in)f(these)h(and)f
+(nearly)120 4075 y(ev)m(ery)28 b(other)g(CFITSIO)f(routine)f(is)h(a)h
+(p)s(oin)m(ter)f(to)h(a)g(structure)g(that)g(CFITSIO)e(uses)h(to)i
+(store)f(relev)-5 b(an)m(t)120 4188 y(parameters)31 b(ab)s(out)f(eac)m
+(h)i(op)s(ened)e(\014le.)41 b(Y)-8 b(ou)31 b(should)e(nev)m(er)i
+(directly)e(read)i(or)f(write)g(an)m(y)h(information)120
+4301 y(in)23 b(this)h(structure.)38 b(Memory)26 b(for)e(this)g
+(structure)g(is)g(allo)s(cated)g(automatically)h(when)f(the)h(\014le)e
+(is)h(op)s(ened)120 4414 y(or)30 b(created,)i(and)e(is)f(freed)h(when)g
+(the)g(\014le)g(is)f(closed.)261 4527 y(The)f Fc(mode)e
+Fg(parameter)j(in)d(the)i Fc(fits)p 1552 4527 29 4 v
+34 w(open)p 1778 4527 V 33 w(xxxx)f Fg(set)h(of)g(routines)f(can)h(b)s
+(e)f(set)i(to)f(either)g Fc(READONLY)120 4640 y Fg(or)i
+Fc(READWRITE)d Fg(to)j(select)g(the)g(t)m(yp)s(e)f(of)h(\014le)f
+(access)i(that)f(will)d(b)s(e)i(allo)m(w)m(ed.)40 b(These)29
+b(sym)m(b)s(olic)f(constan)m(ts)120 4753 y(are)j(de\014ned)e(in)g
+Fc(fitsio.h)p Fg(.)261 4866 y(The)k Fc(fits)p 649 4866
+V 33 w(open)p 874 4866 V 34 w(file)f Fg(routine)h(op)s(ens)f(the)i
+(\014le)f(and)f(p)s(ositions)g(the)i(in)m(ternal)e(\014le)g(p)s(oin)m
+(ter)h(to)h(the)120 4979 y(b)s(eginning)21 b(of)j(the)g(\014le,)g(or)g
+(to)h(the)f(sp)s(eci\014ed)e(extension)h(if)g(an)h(extension)f(name)h
+(or)g(n)m(um)m(b)s(er)e(is)h(app)s(ended)120 5092 y(to)k(the)f(\014le)g
+(name)g(\(see)h(the)g(later)f(section)h(on)f(\\CFITSIO)f(File)g(Names)i
+(and)f(Filters")g(for)g(a)g(description)120 5204 y(of)32
+b(the)f(syn)m(tax\).)45 b Fc(fits)p 945 5204 V 33 w(open)p
+1170 5204 V 33 w(data)31 b Fg(b)s(eha)m(v)m(es)g(similarly)d(except)33
+b(that)f(it)f(will)d(mo)m(v)m(e)33 b(to)f(the)g(\014rst)f(HDU)120
+5317 y(con)m(taining)k(signi\014can)m(t)f(data)i(if)e(a)i(HDU)g(name)g
+(or)f(n)m(um)m(b)s(er)f(to)i(op)s(en)f(is)f(not)i(explicitly)c(sp)s
+(eci\014ed)i(as)120 5430 y(part)23 b(of)h(the)g(\014lename.)38
+b(It)23 b(will)e(mo)m(v)m(e)k(to)g(the)e(\014rst)g(IMA)m(GE)i(HDU)f
+(with)e(NAXIS)i(greater)h(than)e(0,)j(or)d(the)1928 5809
+y(6)p eop
+%%Page: 7 7
+7 6 bop 120 573 a Fg(\014rst)29 b(table)g(that)i(do)s(es)e(not)h(con)m
+(tain)g(the)g(strings)e(`GTI')i(\(a)g(Go)s(o)s(d)g(Time)e(In)m(terv)-5
+b(al)30 b(extension\))f(or)h(`OB-)120 686 y(ST)-8 b(ABLE')37
+b(in)f(the)h(EXTNAME)h(k)m(eyw)m(ord)f(v)-5 b(alue.)61
+b(The)36 b Fc(fits)p 2380 686 29 4 v 34 w(open)p 2606
+686 V 33 w(table)g Fg(and)g Fc(fits)p 3290 686 V 34 w(open)p
+3516 686 V 33 w(image)120 799 y Fg(routines)e(are)i(similar)c(except)37
+b(that)f(they)f(will)e(mo)m(v)m(e)j(to)g(the)g(\014rst)e(signi\014can)m
+(t)h(table)g(HDU)h(or)f(image)120 912 y(HDU,)c(resp)s(ectiv)m(ely)f(if)
+f(a)i(HDU)g(name)g(of)f(n)m(um)m(b)s(er)f(is)h(not)g(sp)s(eci\014ed)f
+(as)i(part)f(of)g(the)h(input)d(\014le)i(name.)261 1024
+y(When)e(op)s(ening)f(an)h(existing)f(\014le,)h(the)h
+Fc(filename)d Fg(can)i(include)e(optional)i(argumen)m(ts,)h(enclosed)f
+(in)120 1137 y(square)h(brac)m(k)m(ets)i(that)f(sp)s(ecify)e
+(\014ltering)g(op)s(erations)g(that)i(should)e(b)s(e)h(applied)e(to)j
+(the)g(input)d(\014le.)40 b(F)-8 b(or)120 1250 y(example,)263
+1428 y Fc(myfile.fit[EVENTS][counts)41 b(>)48 b(0])120
+1605 y Fg(op)s(ens)27 b(the)i(table)f(in)f(the)i(EVENTS)e(extension)h
+(and)g(creates)i(a)e(virtual)f(table)h(b)m(y)g(selecting)g(only)g
+(those)120 1718 y(ro)m(ws)g(where)f(the)i(COUNTS)d(column)h(v)-5
+b(alue)28 b(is)f(greater)i(than)f(0.)40 b(See)28 b(section)g(5)h(for)f
+(more)g(examples)f(of)120 1831 y(these)k(p)s(o)m(w)m(erful)e
+(\014ltering)f(capabilities.)261 1944 y(In)38 b Fc(fits)p
+581 1944 V 33 w(create)p 902 1944 V 33 w(file)p Fg(,)h(the)g
+Fc(filename)d Fg(is)i(simply)e(the)i(ro)s(ot)h(name)f(of)h(the)g
+(\014le)e(to)i(b)s(e)f(created.)120 2057 y(Y)-8 b(ou)36
+b(can)g(o)m(v)m(erwrite)g(an)g(existing)e(\014le)h(b)m(y)g(pre\014xing)
+f(the)i(name)g(with)e(a)i(`!')57 b(c)m(haracter)37 b(\(on)f(the)f(Unix)
+120 2170 y(command)30 b(line)e(this)h(m)m(ust)g(b)s(e)g(pre\014xed)g
+(with)g(a)h(bac)m(kslash,)g(as)g(in)e Fc(`\\!file.fit')p
+Fg(\).)38 b(If)29 b(the)h(\014le)f(name)120 2282 y(ends)f(with)f
+Fc(.gz)h Fg(the)h(\014le)f(will)e(b)s(e)i(compressed)g(using)f(the)i
+(gzip)f(algorithm.)39 b(If)29 b(the)f(\014lename)g(is)g
+Fc(stdout)120 2395 y Fg(or)h Fc("-")e Fg(\(a)j(single)d(dash)h(c)m
+(haracter\))j(then)d(the)h(output)f(\014le)g(will)e(b)s(e)i(pip)s(ed)e
+(to)k(the)f(stdout)f(stream.)41 b(Y)-8 b(ou)120 2508
+y(can)27 b(c)m(hain)f(sev)m(eral)g(tasks)h(together)h(b)m(y)f(writing)d
+(the)j(output)f(from)g(the)g(\014rst)g(task)h(to)g Fc(stdout)e
+Fg(and)h(then)120 2621 y(reading)j(the)i(input)d(\014le)i(in)f(the)i
+(2nd)e(task)i(from)f Fc(stdin)f Fg(or)h Fc("-")p Fg(.)120
+2867 y Fb(4.3)112 b(HDU-lev)m(el)36 b(Routines)261 3040
+y Fg(The)30 b(routines)f(listed)g(in)g(this)h(section)g(op)s(erate)h
+(on)f(Header-Data)j(Units)d(\(HDUs\))h(in)e(a)i(\014le.)120
+3153 y Fc(________________________)o(____)o(____)o(___)o(____)o(____)o
+(___)o(____)o(____)o(___)o(____)o(__)120 3266 y(int)47
+b(fits_get_num_hdus\(fitsfi)o(le)42 b(*fptr,)k(int)h(*hdunum,)e(int)i
+(*status\))120 3379 y(int)g(fits_get_hdu_num\(fitsfil)o(e)42
+b(*fptr,)94 b(int)47 b(*hdunum\))261 3579 y Fg(The)39
+b(\014rst)f(routines)g(returns)g(the)h(total)h(n)m(um)m(b)s(er)e(of)h
+(HDUs)h(in)d(the)j(FITS)e(\014le,)j(and)d(the)h(second)120
+3692 y(routine)32 b(returns)f(the)i(p)s(osition)e(of)i(the)g(curren)m
+(tly)e(op)s(ened)h(HDU)i(in)d(the)i(FITS)f(\014le)g(\(starting)g(with)g
+(1,)120 3805 y(not)f(0\).)120 4005 y Fc(________________________)o
+(____)o(____)o(___)o(____)o(____)o(___)o(____)o(____)o(___)o(____)o
+(____)o(___)o(____)o(__)120 4118 y(int)47 b(fits_movabs_hdu\(fitsfile)
+41 b(*fptr,)46 b(int)h(hdunum,)f(int)h(*hdutype,)e(int)i(*status\))120
+4231 y(int)g(fits_movrel_hdu\(fitsfile)41 b(*fptr,)46
+b(int)h(nmove,)94 b(int)47 b(*hdutype,)e(int)i(*status\))120
+4344 y(int)g(fits_movnam_hdu\(fitsfile)41 b(*fptr,)46
+b(int)h(hdutype,)f(char)g(*extname,)1075 4457 y(int)g(extver,)g(int)h
+(*status\))261 4657 y Fg(These)31 b(routines)f(enable)h(y)m(ou)g(to)h
+(mo)m(v)m(e)g(to)g(a)g(di\013eren)m(t)e(HDU)i(in)e(the)h(\014le.)42
+b(Most)32 b(of)g(the)f(CFITSIO)120 4770 y(functions)g(whic)m(h)h(read)h
+(or)g(write)f(k)m(eyw)m(ords)h(or)g(data)h(op)s(erate)f(only)g(on)f
+(the)h(curren)m(tly)f(op)s(ened)h(HDU)120 4883 y(in)h(the)h(\014le.)53
+b(The)34 b(\014rst)g(routine)g(mo)m(v)m(es)i(to)g(the)f(sp)s(eci\014ed)
+e(absolute)i(HDU)g(n)m(um)m(b)s(er)f(in)f(the)i(FITS)f(\014le)120
+4996 y(\(the)e(\014rst)f(HDU)i(=)e(1\),)i(whereas)f(the)g(second)f
+(routine)g(mo)m(v)m(es)i(a)f(relativ)m(e)g(n)m(um)m(b)s(er)f(of)h(HDUs)
+g(forw)m(ard)120 5109 y(or)f(bac)m(kw)m(ard)h(from)e(the)i(curren)m
+(tly)e(op)s(en)g(HDU.)i(The)f Fc(hdutype)e Fg(parameter)i(returns)f
+(the)i(t)m(yp)s(e)f(of)g(the)120 5222 y(newly)d(op)s(ened)h(HDU,)i(and)
+e(will)e(b)s(e)i(equal)g(to)h(one)g(of)g(these)g(sym)m(b)s(olic)e
+(constan)m(t)j(v)-5 b(alues:)40 b Fc(IMAGE)p 3564 5222
+V 33 w(HDU,)120 5334 y(ASCII)p 366 5334 V 33 w(TBL,)47
+b(or)g(BINARY)p 1069 5334 V 33 w(TBL)p Fg(.)37 b Fc(hdutype)g
+Fg(ma)m(y)h(b)s(e)g(set)h(to)g(NULL)f(if)f(it)h(is)g(not)g(needed.)64
+b(The)38 b(third)120 5447 y(routine)30 b(mo)m(v)m(es)j(to)f(the)f
+(\(\014rst\))h(HDU)g(that)f(matc)m(hes)i(the)e(input)f(extension)h(t)m
+(yp)s(e,)g(name,)h(and)f(v)m(ersion)120 5560 y(n)m(um)m(b)s(er,)23
+b(as)f(giv)m(en)h(b)m(y)f(the)g Fc(XTENSION,)46 b(EXTNAME)20
+b Fg(\(or)j Fc(HDUNAME)p Fg(\))d(and)i Fc(EXTVER)f Fg(k)m(eyw)m(ords.)
+38 b(If)22 b(the)g(input)1928 5809 y(7)p eop
+%%Page: 8 8
+8 7 bop 120 573 a Fg(v)-5 b(alue)33 b(of)g Fc(extver)e
+Fg(=)i(0,)i(then)e(the)g(v)m(ersion)g(n)m(um)m(b)s(er)e(will)g(b)s(e)h
+(ignored)h(when)f(lo)s(oking)g(for)h(a)g(matc)m(hing)120
+686 y(HDU.)120 898 y Fc(________________________)o(____)o(____)o(___)o
+(____)o(____)o(___)o(____)o(____)o(___)o(____)o(____)120
+1011 y(int)47 b(fits_get_hdu_type\(fitsfi)o(le)42 b(*fptr,)93
+b(int)47 b(*hdutype,)f(int)g(*status\))261 1224 y Fg(Get)21
+b(the)g(t)m(yp)s(e)f(of)h(the)f(curren)m(t)g(HDU)h(in)e(the)i(FITS)e
+(\014le:)35 b Fc(IMAGE)p 2435 1224 29 4 v 33 w(HDU,)47
+b(ASCII)p 2947 1224 V 33 w(TBL,)f(or)h(BINARY)p 3649
+1224 V 33 w(TBL)p Fg(.)120 1436 y Fc(________________________)o(____)o
+(____)o(___)o(____)o(____)o(___)o(____)o(____)o(___)o(____)o(____)o
+(___)120 1549 y(int)g(fits_copy_hdu\(fitsfile)42 b(*infptr,)j(fitsfile)
+h(*outfptr,)f(int)i(morekeys,)979 1662 y(int)g(*status\))120
+1775 y(int)g(fits_copy_file\(fitsfile)41 b(*infptr,)46
+b(fitsfile)f(*outfptr,)h(int)h(previous,)979 1888 y(int)g(current,)f
+(int)g(following,)f(>)j(int)f(*status\))261 2100 y Fg(The)34
+b(\014rst)g(routine)f(copies)i(the)f(curren)m(t)g(HDU)i(from)e(the)g
+(FITS)g(\014le)g(asso)s(ciated)g(with)g(infptr)e(and)120
+2213 y(app)s(ends)i(it)i(to)h(the)f(end)f(of)h(the)g(FITS)g(\014le)f
+(asso)s(ciated)h(with)f(outfptr.)57 b(Space)36 b(ma)m(y)h(b)s(e)e
+(reserv)m(ed)i(for)120 2326 y Fc(morekeys)32 b Fg(additional)h(k)m(eyw)
+m(ords)i(in)f(the)g(output)h(header.)53 b(The)35 b(second)f(routine)g
+(copies)h(an)m(y)g(HDUs)120 2439 y(previous)41 b(to)j(the)e(curren)m(t)
+h(HDU,)h(and/or)e(the)h(curren)m(t)f(HDU,)i(and/or)f(an)m(y)g(HDUs)g
+(follo)m(wing)e(the)120 2552 y(curren)m(t)22 b(HDU,)h(dep)s(ending)c
+(on)j(the)g(v)-5 b(alue)22 b(\(T)-8 b(rue)22 b(or)g(F)-8
+b(alse\))23 b(of)f Fc(previous,)45 b(current)p Fg(,)22
+b(and)g Fc(following)p Fg(,)120 2665 y(resp)s(ectiv)m(ely)-8
+b(.)40 b(F)-8 b(or)32 b(example,)215 2853 y Fc(fits_copy_file\(infptr,)
+42 b(outfptr,)k(0,)h(1,)g(1,)g(&status\);)120 3040 y
+Fg(will)32 b(cop)m(y)k(the)f(curren)m(t)g(HDU)g(and)g(an)m(y)g(HDUs)g
+(that)h(follo)m(w)e(it)g(from)h(the)g(input)e(to)i(the)h(output)e
+(\014le,)120 3153 y(but)c(it)g(will)d(not)k(cop)m(y)g(an)m(y)g(HDUs)g
+(preceding)e(the)i(curren)m(t)f(HDU.)1928 5809 y(8)p
+eop
+%%Page: 9 9
+9 8 bop 120 573 a Fb(4.4)112 b(Image)37 b(I/O)h(Routines)120
+744 y Fg(This)29 b(section)h(lists)f(the)i(more)f(imp)s(ortan)m(t)g
+(CFITSIO)e(routines)i(whic)m(h)f(op)s(erate)i(on)f(FITS)g(images.)120
+956 y Fc(________________________)o(____)o(____)o(___)o(____)o(____)o
+(___)o(____)o(____)o(___)o(____)o(__)120 1069 y(int)47
+b(fits_get_img_type\(fitsfi)o(le)42 b(*fptr,)k(int)h(*bitpix,)e(int)i
+(*status\))120 1181 y(int)g(fits_get_img_dim\()c(fitsfile)j(*fptr,)g
+(int)h(*naxis,)93 b(int)47 b(*status\))120 1294 y(int)g
+(fits_get_img_size\(fitsfi)o(le)42 b(*fptr,)k(int)h(maxdim,)93
+b(long)47 b(*naxes,)1170 1407 y(int)g(*status\))120 1520
+y(int)g(fits_get_img_param\(fitsf)o(ile)41 b(*fptr,)46
+b(int)h(maxdim,)94 b(int)47 b(*bitpix,)1218 1633 y(int)g(*naxis,)e
+(long)i(*naxes,)f(int)h(*status\))261 1844 y Fg(Get)38
+b(information)e(ab)s(out)h(the)g(curren)m(tly)f(op)s(ened)h(image)g
+(HDU.)h(The)f(\014rst)f(routine)g(returns)g(the)120 1957
+y(datat)m(yp)s(e)41 b(of)e(the)h(image)g(as)g(\(de\014ned)f(b)m(y)g
+(the)h Fc(BITPIX)e Fg(k)m(eyw)m(ord\),)43 b(whic)m(h)38
+b(can)i(ha)m(v)m(e)h(the)f(follo)m(wing)120 2070 y(sym)m(b)s(olic)29
+b(constan)m(t)i(v)-5 b(alues:)311 2256 y Fc(BYTE_IMG)141
+b(=)i(8)g(\()47 b(8-bit)g(byte)f(pixels,)g(0)h(-)h(255\))311
+2369 y(SHORT_IMG)93 b(=)i(16)143 b(\(16)47 b(bit)g(integer)e(pixels\))
+311 2482 y(LONG_IMG)141 b(=)95 b(32)143 b(\(32-bit)46
+b(integer)f(pixels\))311 2595 y(FLOAT_IMG)93 b(=)47 b(-32)143
+b(\(32-bit)46 b(floating)f(point)h(pixels\))311 2708
+y(DOUBLE_IMG)f(=)i(-64)143 b(\(64-bit)46 b(floating)f(point)h(pixels\))
+261 2895 y Fg(The)34 b(second)g(and)f(third)f(routines)h(return)g(the)h
+(n)m(um)m(b)s(er)e(of)i(dimensions)d(in)i(the)h(image)g(\(from)g(the)
+120 3007 y Fc(NAXIS)25 b Fg(k)m(eyw)m(ord\),)j(and)e(the)h(sizes)f(of)g
+(eac)m(h)i(dimension)c(\(from)i(the)g Fc(NAXIS1,)46 b(NAXIS2)p
+Fg(,)26 b(etc.)40 b(k)m(eyw)m(ords\).)120 3120 y(The)g(last)h(routine)f
+(simply)f(com)m(bines)h(the)i(function)d(of)i(the)h(\014rst)e(3)h
+(routines.)72 b(The)40 b(input)f Fc(maxdim)120 3233 y
+Fg(parameter)28 b(in)f(this)f(routine)h(giv)m(es)h(the)g(maxim)m(um)f
+(n)m(um)m(b)s(er)f(dimensions)f(that)k(ma)m(y)f(b)s(e)f(returned)g
+(\(i.e.,)120 3346 y(the)k(dimension)c(of)k(the)f Fc(naxes)f
+Fg(arra)m(y\))120 3557 y Fc(________________________)o(____)o(____)o
+(___)o(____)o(____)o(___)o(____)o(____)o(___)o(_)120
+3670 y(int)47 b(fits_create_img\(fitsfile)41 b(*fptr,)46
+b(int)h(bitpix,)f(int)h(naxis,)1075 3783 y(long)f(*naxes,)g(int)h
+(*status\))261 3994 y Fg(Create)28 b(an)f(image)h(HDU)g(b)m(y)f
+(writing)e(the)i(required)f(k)m(eyw)m(ords)h(whic)m(h)f(de\014ne)h(the)
+g(structure)g(of)g(the)120 4107 y(image.)50 b(The)33
+b(2nd)f(through)h(4th)h(parameters)f(sp)s(eci\014ed)f(the)h(datat)m(yp)
+s(e,)j(the)d(n)m(um)m(b)s(er)f(of)i(dimensions,)120 4220
+y(and)26 b(the)h(sizes)f(of)g(the)h(dimensions.)37 b(The)26
+b(allo)m(w)m(ed)g(v)-5 b(alues)26 b(of)h(the)f Fc(bitpix)f
+Fg(parameter)i(are)g(listed)e(ab)s(o)m(v)m(e)120 4333
+y(in)32 b(the)h(description)e(of)i(the)g Fc(fits)p 1319
+4333 29 4 v 33 w(get)p 1496 4333 V 33 w(img)p 1673 4333
+V 34 w(type)f Fg(routine.)47 b(If)32 b(the)h(FITS)f(\014le)g(p)s(oin)m
+(ted)g(to)i(b)m(y)f Fc(fptr)e Fg(is)120 4446 y(empt)m(y)c(\(previously)
+d(created)j(with)e Fc(fits)p 1575 4446 V 33 w(create)p
+1896 4446 V 33 w(file)p Fg(\))g(then)h(this)f(routine)g(creates)j(a)f
+(primary)d(arra)m(y)120 4559 y(in)36 b(the)h(\014le,)h(otherwise)f(a)g
+(new)g(IMA)m(GE)h(extension)f(is)f(app)s(ended)f(to)j(end)f(of)g(the)g
+(\014le)g(follo)m(wing)e(the)120 4672 y(other)c(HDUs)g(in)e(the)h
+(\014le.)120 4883 y Fc(________________________)o(____)o(____)o(___)o
+(____)o(____)o(___)o(____)o(____)o(___)o(____)o(_)120
+4996 y(int)47 b(fits_write_pix\(fitsfile)41 b(*fptr,)46
+b(int)h(datatype,)f(long)g(*fpixel,)836 5109 y(long)h(nelements,)e
+(void)h(*array,)g(int)h(*status\);)120 5334 y(int)g
+(fits_write_pixnull\(fitsf)o(ile)41 b(*fptr,)46 b(int)h(datatype,)f
+(long)g(*fpixel,)836 5447 y(long)h(nelements,)e(void)h(*array,)g(void)h
+(*nulval,)e(int)i(*status\);)1928 5809 y Fg(9)p eop
+%%Page: 10 10
+10 9 bop 120 573 a Fc(int)47 b(fits_read_pix\(fitsfile)42
+b(*fptr,)k(int)94 b(datatype,)46 b(long)g(*fpixel,)979
+686 y(long)h(nelements,)e(void)h(*nulval,)g(void)h(*array,)979
+799 y(int)g(*anynul,)f(int)g(*status\))261 1007 y Fg(Read)32
+b(or)f(write)f(all)g(or)h(part)h(of)f(the)g(FITS)g(image.)43
+b(There)31 b(are)h(2)f(di\013eren)m(t)g('write')g(pixel)e(routines:)120
+1120 y(The)23 b(\014rst)g(simply)e(writes)i(the)h(input)e(arra)m(y)i
+(of)g(pixels)e(to)i(the)g(FITS)f(\014le.)38 b(The)23
+b(second)h(is)f(similar,)f(except)120 1233 y(that)30
+b(it)e(substitutes)g(the)h(appropriate)f(n)m(ull)f(pixel)g(v)-5
+b(alue)29 b(in)f(the)h(FITS)f(\014le)g(for)h(an)m(y)g(pixels)e(whic)m
+(h)h(ha)m(v)m(e)120 1346 y(a)j(v)-5 b(alue)29 b(equal)h(to)h
+Fc(*nulval)d Fg(\(note)j(that)g(this)e(parameter)h(giv)m(es)h(the)f
+(address)f(of)i(the)f(n)m(ull)e(pixel)h(v)-5 b(alue,)120
+1459 y(not)35 b(the)g(v)-5 b(alue)34 b(itself)7 b(\).)52
+b(Similarly)-8 b(,)33 b(when)g(reading)h(an)h(image,)h(CFITSIO)d(will)f
+(substitute)h(the)i(v)-5 b(alue)120 1572 y(giv)m(en)29
+b(b)m(y)f Fc(nulval)f Fg(for)i(an)m(y)g(unde\014ned)d(pixels)h(in)h
+(the)h(image,)g(unless)e Fc(nulval)46 b(=)i(NULL)p Fg(,)27
+b(in)h(whic)m(h)f(case)120 1685 y(no)j(c)m(hec)m(ks)i(will)c(b)s(e)i
+(made)g(for)g(unde\014ned)e(pixels)h(when)g(reading)h(the)g(FITS)g
+(image.)261 1798 y(The)35 b Fc(fpixel)f Fg(parameter)i(in)e(these)i
+(routines)e(is)h(an)g(arra)m(y)h(whic)m(h)e(giv)m(es)i(the)g(co)s
+(ordinate)f(in)f(eac)m(h)120 1910 y(dimension)22 b(of)k(the)f(\014rst)f
+(pixel)g(to)h(b)s(e)g(read)g(or)g(written,)g(and)g Fc(nelements)d
+Fg(is)i(the)h(total)h(n)m(um)m(b)s(er)e(of)h(pixels)120
+2023 y(to)i(read)g(or)f(write.)39 b Fc(array)25 b Fg(is)h(the)g
+(address)g(of)h(an)f(arra)m(y)h(whic)m(h)e(either)h(con)m(tains)h(the)g
+(pixel)e(v)-5 b(alues)26 b(to)h(b)s(e)120 2136 y(written,)k(or)g(will)e
+(hold)g(the)j(v)-5 b(alues)30 b(of)i(the)f(pixels)f(that)i(are)f(read.)
+43 b(When)31 b(reading,)g Fc(array)f Fg(m)m(ust)h(ha)m(v)m(e)120
+2249 y(b)s(een)k(allo)s(cated)h(large)g(enough)f(to)i(hold)d(all)h(the)
+h(returned)f(pixel)f(v)-5 b(alues.)56 b(These)36 b(routines)e(starts)j
+(at)120 2362 y(the)e Fc(fpixel)d Fg(lo)s(cation)i(and)g(then)g(read)h
+(or)f(write)g(the)g Fc(nelements)e Fg(pixels,)i(con)m(tin)m(uing)g(on)g
+(successiv)m(e)120 2475 y(ro)m(ws)f(of)g(the)g(image)g(if)f(necessary)
+-8 b(.)49 b(F)-8 b(or)34 b(example,)g(to)f(write)f(an)h(en)m(tire)g(2D)
+h(image,)g(set)f Fc(fpixel[0])46 b(=)120 2588 y(fpixel[1])f(=)j(1)p
+Fg(,)35 b(and)f Fc(nelements)46 b(=)h(NAXIS1)f(*)i(NAXIS2)p
+Fg(.)j(Or)34 b(to)i(read)e(just)g(the)h(10th)h(ro)m(w)e(of)h(the)120
+2701 y(image,)49 b(set)c Fc(fpixel[0])g(=)j(1,)f(fpixel[1])e(=)j(10)p
+Fg(,)g(and)c Fc(nelements)h(=)i(NAXIS1)p Fg(.)82 b(The)45
+b Fc(datatype)120 2814 y Fg(parameter)28 b(sp)s(eci\014es)d(the)j
+(datat)m(yp)s(e)g(of)f(the)g(C)g Fc(array)e Fg(in)h(the)h(program,)h
+(whic)m(h)e(need)h(not)g(b)s(e)g(the)g(same)120 2927
+y(as)32 b(the)f(datat)m(yp)s(e)i(of)e(the)h(FITS)f(image)g(itself.)43
+b(If)31 b(the)h(datat)m(yp)s(es)g(di\013er)e(then)h(CFITSIO)f(will)f
+(con)m(v)m(ert)120 3040 y(the)i(data)h(as)f(it)g(is)f(read)h(or)g
+(written.)41 b(The)31 b(follo)m(wing)e(sym)m(b)s(olic)h(constan)m(ts)i
+(are)f(allo)m(w)m(ed)g(for)g(the)g(v)-5 b(alue)120 3152
+y(of)31 b Fc(datatype)p Fg(:)215 3337 y Fc(TBYTE)238
+b(unsigned)45 b(char)215 3450 y(TSBYTE)190 b(signed)46
+b(char)215 3563 y(TSHORT)190 b(signed)46 b(short)215
+3675 y(TUSHORT)142 b(unsigned)45 b(short)215 3788 y(TINT)286
+b(signed)46 b(int)215 3901 y(TUINT)238 b(unsigned)45
+b(int)215 4014 y(TLONG)238 b(signed)46 b(long)215 4127
+y(TULONG)190 b(unsigned)45 b(long)215 4240 y(TFLOAT)190
+b(float)215 4353 y(TDOUBLE)142 b(double)120 4561 y
+(________________________)o(____)o(____)o(___)o(____)o(____)o(___)o
+(____)o(____)o(___)o(____)o(____)120 4674 y(int)47 b
+(fits_write_subset\(fitsfi)o(le)42 b(*fptr,)k(int)h(datatype,)e(long)h
+(*fpixel,)740 4787 y(long)h(*lpixel,)f(DTYPE)g(*array,)g(>)h(int)g
+(*status\))120 5013 y(int)g(fits_read_subset\(fitsfil)o(e)42
+b(*fptr,)k(int)95 b(datatype,)45 b(long)h(*fpixel,)740
+5126 y(long)h(*lpixel,)f(long)g(*inc,)h(void)f(*nulval,)94
+b(void)46 b(*array,)740 5239 y(int)h(*anynul,)f(int)h(*status\))261
+5447 y Fg(Read)i(or)g(write)e(a)i(rectangular)g(section)g(of)g(the)f
+(FITS)g(image.)96 b(These)49 b(are)g(v)m(ery)g(similar)d(to)120
+5560 y Fc(fits)p 318 5560 29 4 v 33 w(write)p 591 5560
+V 33 w(pix)37 b Fg(and)f Fc(fits)p 1180 5560 V 34 w(read)p
+1406 5560 V 33 w(pix)g Fg(except)j(that)f(y)m(ou)f(sp)s(ecify)f(the)i
+(last)f(pixel)f(co)s(ordinate)h(\(the)1905 5809 y(10)p
+eop
+%%Page: 11 11
+11 10 bop 120 573 a Fg(upp)s(er)22 b(righ)m(t)h(corner)h(of)g(the)h
+(section\))f(instead)f(of)h(the)h(n)m(um)m(b)s(er)d(of)i(pixels)f(to)h
+(b)s(e)g(read.)38 b(The)23 b(read)h(routine)120 686 y(also)38
+b(has)f(an)h Fc(inc)f Fg(parameter)h(whic)m(h)e(can)j(b)s(e)e(used)g
+(to)h(read)g(only)f(ev)m(ery)h Fc(inc-th)e Fg(pixel)g(along)i(eac)m(h)
+120 799 y(dimension)26 b(of)j(the)g(image.)40 b(Normally)28
+b Fc(inc[0])46 b(=)h(inc[1])f(=)i(1)28 b Fg(to)i(read)e(ev)m(ery)i
+(pixel)d(in)g(a)i(2D)h(image.)120 912 y(T)-8 b(o)31 b(read)f(ev)m(ery)h
+(other)g(pixel)e(in)g(the)h(en)m(tire)h(2D)g(image,)g(set)311
+1099 y Fc(fpixel[0])45 b(=)j(fpixel[1])d(=)i(1)311 1212
+y(lpixel[0])e(=)j({NAXIS1})311 1325 y(lpixel[1])d(=)j({NAXIS2})311
+1438 y(inc[0])e(=)h(inc[1])g(=)g(2)261 1626 y Fg(Or,)30
+b(to)h(read)f(the)h(8th)g(ro)m(w)f(of)h(a)f(2D)i(image,)e(set)311
+1813 y Fc(fpixel[0])45 b(=)j(1)311 1926 y(fpixel[1])d(=)j(8)311
+2039 y(lpixel[0])d(=)j({NAXIS1})311 2152 y(lpixel[1])d(=)j(8)311
+2265 y(inc[0])e(=)h(inc[1])g(=)g(1)1905 5809 y Fg(11)p
+eop
+%%Page: 12 12
+12 11 bop 120 573 a Fb(4.5)112 b(T)-9 b(able)37 b(I/O)h(Routines)120
+744 y Fg(This)29 b(section)h(lists)f(the)i(most)f(imp)s(ortan)m(t)g
+(CFITSIO)f(routines)g(whic)m(h)g(op)s(erate)i(on)f(FITS)g(tables.)120
+957 y Fc(________________________)o(____)o(____)o(___)o(____)o(____)o
+(___)o(____)o(____)o(___)o(____)o(____)o(___)o(____)o(__)120
+1070 y(int)47 b(fits_create_tbl\(fitsfile)41 b(*fptr,)46
+b(int)h(tbltype,)f(long)g(nrows,)g(int)h(tfields,)311
+1183 y(char)g(*ttype[],char)d(*tform[],)h(char)i(*tunit[],)e(char)i
+(*extname,)e(int)i(*status\))261 1395 y Fg(Create)e(a)f(new)f(table)h
+(extension)f(b)m(y)h(writing)e(the)i(required)e(k)m(eyw)m(ords)i(that)g
+(de\014ne)f(the)h(table)120 1508 y(structure.)38 b(The)22
+b(required)e(n)m(ull)h(primary)f(arra)m(y)j(will)d(b)s(e)i(created)i
+(\014rst)d(if)h(the)h(\014le)e(is)h(initially)d(completely)120
+1621 y(empt)m(y)-8 b(.)41 b Fc(tbltype)26 b Fg(de\014nes)i(the)g(t)m
+(yp)s(e)h(of)g(table)f(and)g(can)g(ha)m(v)m(e)i(v)-5
+b(alues)28 b(of)g Fc(ASCII)p 2931 1621 29 4 v 33 w(TBL)47
+b(or)g(BINARY)p 3586 1621 V 33 w(TBL)p Fg(.)120 1734
+y(Binary)33 b(tables)h(are)h(generally)e(preferred)g(b)s(ecause)h(they)
+h(are)f(more)h(e\016cien)m(t)f(and)g(supp)s(ort)f(a)h(greater)120
+1847 y(range)d(of)f(column)f(datat)m(yp)s(es)j(than)e(ASCI)s(I)f
+(tables.)261 1960 y(The)c Fc(nrows)f Fg(parameter)i(giv)m(es)g(the)f
+(initial)e(n)m(um)m(b)s(er)h(of)i(empt)m(y)g(ro)m(ws)f(to)h(b)s(e)f
+(allo)s(cated)g(for)h(the)f(table;)120 2073 y(this)g(should)g(normally)
+f(b)s(e)i(set)h(to)g(0.)40 b(The)26 b Fc(tfields)f Fg(parameter)i(giv)m
+(es)f(the)h(n)m(um)m(b)s(er)e(of)i(columns)e(in)g(the)120
+2186 y(table)e(\(maxim)m(um)f(=)g(999\).)40 b(The)22
+b Fc(ttype,)46 b(tform)p Fg(,)24 b(and)e Fc(tunit)f Fg(parameters)i
+(giv)m(e)h(the)f(name,)h(datat)m(yp)s(e,)120 2299 y(and)34
+b(ph)m(ysical)f(units)h(of)g(eac)m(h)i(column,)f(and)f
+Fc(extname)f Fg(giv)m(es)i(the)g(name)g(for)f(the)h(table)g(\(the)g(v)
+-5 b(alue)34 b(of)120 2412 y(the)j Fc(EXTNAME)e Fg(k)m(eyw)m(ord\).)61
+b(The)36 b(FITS)g(Standard)g(recommends)g(that)i(only)e(letters,)j
+(digits,)e(and)f(the)120 2524 y(underscore)27 b(c)m(haracter)h(b)s(e)f
+(used)g(in)f(column)g(names)h(with)f(no)h(em)m(b)s(edded)g(spaces.)40
+b(It)27 b(is)g(recommended)120 2637 y(that)k(all)e(the)i(column)e
+(names)h(in)f(a)i(giv)m(en)f(table)h(b)s(e)e(unique)g(within)f(the)i
+(\014rst)g(8)h(c)m(haracters.)261 2750 y(The)g(follo)m(wing)f(table)i
+(sho)m(ws)f(the)h(TF)m(ORM)g(column)e(format)i(v)-5 b(alues)31
+b(that)h(are)g(allo)m(w)m(ed)g(in)e(ASCI)s(I)120 2863
+y(tables)g(and)g(in)f(binary)g(tables:)502 3051 y Fc(ASCII)46
+b(Table)h(Column)f(Format)g(Codes)502 3164 y(------------------------)o
+(---)o(----)502 3277 y(\(w)h(=)g(column)g(width,)f(d)h(=)h(no.)e(of)i
+(decimal)d(places)i(to)g(display\))693 3390 y(Aw)142
+b(-)48 b(character)d(string)693 3502 y(Iw)142 b(-)48
+b(integer)693 3615 y(Fw.d)e(-)i(fixed)e(floating)g(point)693
+3728 y(Ew.d)g(-)i(exponential)d(floating)g(point)693
+3841 y(Dw.d)h(-)i(exponential)d(floating)g(point)502
+4067 y(Binary)h(Table)g(Column)g(Format)g(Codes)502 4180
+y(------------------------)o(---)o(----)o(-)502 4293
+y(\(r)h(=)g(vector)g(length,)e(default)h(=)i(1\))693
+4406 y(rA)95 b(-)47 b(character)e(string)693 4519 y(rAw)i(-)g(array)f
+(of)i(strings,)d(each)i(of)g(length)f(w)693 4632 y(rL)95
+b(-)47 b(logical)693 4744 y(rX)95 b(-)47 b(bit)693 4857
+y(rB)95 b(-)47 b(unsigned)f(byte)693 4970 y(rS)95 b(-)47
+b(signed)f(byte)h(**)693 5083 y(rI)95 b(-)47 b(signed)f(16-bit)g
+(integer)693 5196 y(rU)95 b(-)47 b(unsigned)f(16-bit)g(integer)g(**)693
+5309 y(rJ)95 b(-)47 b(signed)f(32-bit)g(integer)693 5422
+y(rV)95 b(-)47 b(unsigned)f(32-bit)g(integer)g(**)693
+5535 y(rK)95 b(-)47 b(64-bit)f(integer)g(***)1905 5809
+y Fg(12)p eop
+%%Page: 13 13
+13 12 bop 693 573 a Fc(rE)95 b(-)47 b(32-bit)f(floating)g(point)693
+686 y(rD)95 b(-)47 b(64-bit)f(floating)g(point)693 799
+y(rC)95 b(-)47 b(32-bit)f(complex)g(pair)693 912 y(rM)95
+b(-)47 b(64-bit)f(complex)g(pair)359 1137 y(**)h(The)g(S,)g(U)g(and)g
+(V)h(format)e(codes)g(are)h(not)g(actual)f(legal)g(TFORMn)h(values.)502
+1250 y(CFITSIO)f(substitutes)e(the)j(somewhat)f(more)g(complicated)f
+(set)i(of)502 1363 y(keywords)e(that)i(are)g(used)g(to)g(represent)e
+(unsigned)h(integers)f(or)502 1476 y(signed)h(bytes.)311
+1702 y(***)h(The)g(64-bit)f(integer)g(format)g(is)h(experimental)d(and)
+j(is)g(not)502 1815 y(officially)e(recognized)g(in)i(the)g(FITS)g
+(Standard.)261 2002 y Fg(The)27 b Fc(tunit)e Fg(and)h
+Fc(extname)f Fg(parameters)j(are)f(optional)f(and)h(ma)m(y)g(b)s(e)g
+(set)g(to)h(NULL)f(if)f(they)h(are)g(not)120 2115 y(needed.)261
+2228 y(Note)41 b(that)f(it)e(ma)m(y)i(b)s(e)f(easier)g(to)h(create)h(a)
+f(new)e(table)h(b)m(y)h(cop)m(ying)f(the)g(header)g(from)g(another)120
+2341 y(existing)29 b(table)i(with)e Fc(fits)p 1089 2341
+29 4 v 33 w(copy)p 1314 2341 V 33 w(header)g Fg(rather)h(than)g
+(calling)f(this)g(routine.)120 2554 y Fc(________________________)o
+(____)o(____)o(___)o(____)o(____)o(___)o(____)o(____)o(___)o(____)o(__)
+120 2667 y(int)47 b(fits_get_num_rows\(fitsfi)o(le)42
+b(*fptr,)k(long)g(*nrows,)g(int)h(*status\))120 2780
+y(int)g(fits_get_num_cols\(fitsfi)o(le)42 b(*fptr,)k(int)94
+b(*ncols,)46 b(int)h(*status\))261 2992 y Fg(Get)37 b(the)g(n)m(um)m(b)
+s(er)d(of)j(ro)m(ws)f(or)g(columns)f(in)f(the)j(curren)m(t)e(FITS)h
+(table.)58 b(The)35 b(n)m(um)m(b)s(er)g(of)h(ro)m(ws)g(is)120
+3105 y(giv)m(en)d(b)m(y)g(the)g Fc(NAXIS2)e Fg(k)m(eyw)m(ord)j(and)e
+(the)h(n)m(um)m(b)s(er)f(of)h(columns)f(is)g(giv)m(en)h(b)m(y)g(the)g
+Fc(TFIELDS)e Fg(k)m(eyw)m(ord)120 3218 y(in)e(the)i(header)f(of)g(the)h
+(table.)120 3430 y Fc(________________________)o(____)o(____)o(___)o
+(____)o(____)o(___)o(____)o(____)o(___)o(____)o(__)120
+3543 y(int)47 b(fits_get_colnum\(fitsfile)41 b(*fptr,)46
+b(int)h(casesen,)f(char)g(*template,)1075 3656 y(int)g(*colnum,)g(int)h
+(*status\))120 3769 y(int)g(fits_get_colname\(fitsfil)o(e)42
+b(*fptr,)k(int)h(casesen,)e(char)i(*template,)1075 3882
+y(char)f(*colname,)f(int)i(*colnum,)f(int)h(*status\))261
+4095 y Fg(Get)33 b(the)e(column)f(n)m(um)m(b)s(er)g(\(starting)i(with)e
+(1,)i(not)g(0\))g(of)f(the)h(column)e(whose)h(name)h(matc)m(hes)g(the)
+120 4208 y(sp)s(eci\014ed)37 b(template)i(name.)66 b(The)38
+b(only)g(di\013erence)g(in)f(these)i(2)g(routines)f(is)f(that)j(the)e
+(2nd)g(one)h(also)120 4320 y(returns)29 b(the)i(name)f(of)h(the)f
+(column)f(that)i(matc)m(hed)h(the)e(template)h(string.)261
+4433 y(Normally)-8 b(,)27 b Fc(casesen)d Fg(should)g(b)s(e)i(set)h(to)g
+Fc(CASEINSEN)p Fg(,)d(but)i(it)f(ma)m(y)i(b)s(e)f(set)h(to)g
+Fc(CASESEN)d Fg(to)j(force)g(the)120 4546 y(name)j(matc)m(hing)h(to)g
+(b)s(e)f(case-sensitiv)m(e.)261 4659 y(The)22 b(input)e
+Fc(template)g Fg(string)h(giv)m(es)i(the)f(name)h(of)f(the)h(desired)d
+(column)h(and)h(ma)m(y)h(include)d(wildcard)120 4772
+y(c)m(haracters:)41 b(a)30 b(`*')g(matc)m(hes)g(an)m(y)f(sequence)g(of)
+h(c)m(haracters)g(\(including)c(zero)k(c)m(haracters\),)h(`?')40
+b(matc)m(hes)120 4885 y(an)m(y)45 b(single)e(c)m(haracter,)50
+b(and)44 b(`#')h(matc)m(hes)g(an)m(y)g(consecutiv)m(e)h(string)d(of)i
+(decimal)f(digits)f(\(0-9\).)85 b(If)120 4998 y(more)27
+b(than)g(one)g(column)f(name)h(in)f(the)h(table)g(matc)m(hes)h(the)f
+(template)g(string,)g(then)g(the)g(\014rst)f(matc)m(h)i(is)120
+5111 y(returned)22 b(and)h(the)h(status)f(v)-5 b(alue)23
+b(will)e(b)s(e)i(set)h(to)g Fc(COL)p 1962 5111 V 33 w(NOT)p
+2139 5111 V 34 w(UNIQUE)e Fg(as)h(a)h(w)m(arning)e(that)i(a)g(unique)d
+(matc)m(h)120 5224 y(w)m(as)34 b(not)g(found.)50 b(T)-8
+b(o)35 b(\014nd)d(the)i(next)g(column)f(that)i(matc)m(hes)g(the)f
+(template,)h(call)f(this)e(routine)h(again)120 5337 y(lea)m(ving)d(the)
+h(input)d(status)j(v)-5 b(alue)30 b(equal)g(to)h Fc(COL)p
+1832 5337 V 33 w(NOT)p 2009 5337 V 34 w(UNIQUE)p Fg(.)e(Rep)s(eat)i
+(this)e(pro)s(cess)h(un)m(til)f Fc(status)46 b(=)120
+5450 y(COL)p 270 5450 V 34 w(NOT)p 448 5450 V 33 w(FOUND)29
+b Fg(is)g(returned.)1905 5809 y(13)p eop
+%%Page: 14 14
+14 13 bop 120 573 a Fc(________________________)o(____)o(____)o(___)o
+(____)o(____)o(___)o(____)o(____)o(___)o(____)o(__)120
+686 y(int)47 b(fits_get_coltype\(fitsfil)o(e)42 b(*fptr,)k(int)h
+(colnum,)f(int)h(*typecode,)1122 799 y(long)g(*repeat,)e(long)i
+(*width,)f(int)h(*status\))261 1011 y Fg(Return)41 b(the)h(datat)m(yp)s
+(e,)k(v)m(ector)d(rep)s(eat)f(coun)m(t,)j(and)c(the)h(width)e(in)g(b)m
+(ytes)i(of)g(a)g(single)f(column)120 1124 y(elemen)m(t)i(for)e(column)g
+(n)m(um)m(b)s(er)g Fc(colnum)p Fg(.)74 b(Allo)m(w)m(ed)42
+b(v)-5 b(alues)41 b(for)h(the)g(returned)f(datat)m(yp)s(e)i(in)e(ASCI)s
+(I)120 1237 y(tables)30 b(are:)42 b Fc(TSTRING,)j(TSHORT,)h(TLONG,)g
+(TFLOAT,)g(and)h(TDOUBLE)p Fg(.)29 b(Binary)h(tables)g(supp)s(ort)f
+(these)120 1350 y(additional)19 b(t)m(yp)s(es:)36 b Fc(TLOGICAL,)45
+b(TBIT,)h(TBYTE,)g(TINT32BIT,)f(TCOMPLEX)h(and)h(TDBLCOMPLEX)p
+Fg(.)18 b(The)120 1463 y(negativ)m(e)34 b(of)f(the)h(datat)m(yp)s(e)g
+(co)s(de)f(v)-5 b(alue)32 b(is)h(returned)e(if)h(it)h(is)f(a)h(v)-5
+b(ariable)32 b(length)h(arra)m(y)g(column.)48 b(The)120
+1576 y(rep)s(eat)31 b(coun)m(t)g(is)e(alw)m(a)m(ys)i(1)g(in)e(ASCI)s(I)
+g(tables.)261 1689 y(The)39 b('rep)s(eat')g(parameter)h(returns)e(the)h
+(v)m(ector)h(rep)s(eat)g(coun)m(t)f(on)g(the)g(binary)f(table)g(TF)m
+(ORMn)120 1802 y(k)m(eyw)m(ord)44 b(v)-5 b(alue.)78 b(\(ASCI)s(I)42
+b(table)h(columns)f(alw)m(a)m(ys)i(ha)m(v)m(e)g(rep)s(eat)g(=)e(1\).)80
+b(The)43 b('width')f(parameter)120 1914 y(returns)d(the)i(width)e(in)h
+(b)m(ytes)h(of)g(a)g(single)e(column)h(elemen)m(t)h(\(e.g.,)k(a)c
+('10D')i(binary)c(table)i(column)120 2027 y(will)c(ha)m(v)m(e)42
+b(width)c(=)i(8,)j(an)d(ASCI)s(I)e(table)i('F12.2')j(column)c(will)e
+(ha)m(v)m(e)42 b(width)c(=)i(12,)j(and)d(a)g(binary)120
+2140 y(table'60A')26 b(c)m(haracter)h(string)c(column)h(will)e(ha)m(v)m
+(e)k(width)d(=)h(60\);)k(Note)f(that)e(this)f(routine)f(supp)s(orts)g
+(the)120 2253 y(lo)s(cal)31 b(con)m(v)m(en)m(tion)j(for)e(sp)s
+(ecifying)e(arra)m(ys)i(of)h(\014xed)e(length)h(strings)f(within)f(a)i
+(binary)f(table)h(c)m(haracter)120 2366 y(column)37 b(using)f(the)j
+(syn)m(tax)f(TF)m(ORM)h(=)e('rAw')h(where)g('r')g(is)f(the)h(total)h(n)
+m(um)m(b)s(er)e(of)h(c)m(haracters)h(\(=)120 2479 y(the)d(width)d(of)j
+(the)f(column\))g(and)f('w')i(is)e(the)i(width)d(of)j(a)f(unit)f
+(string)g(within)f(the)j(column.)54 b(Th)m(us)34 b(if)120
+2592 y(the)i(column)f(has)h(TF)m(ORM)g(=)g('60A12')j(then)c(this)g
+(means)h(that)h(eac)m(h)g(ro)m(w)f(of)h(the)f(table)g(con)m(tains)g(5)
+120 2705 y(12-c)m(haracter)h(substrings)31 b(within)h(the)i(60-c)m
+(haracter)j(\014eld,)d(and)f(th)m(us)h(in)e(this)h(case)i(this)e
+(routine)g(will)120 2818 y(return)25 b(t)m(yp)s(eco)s(de)i(=)f
+(TSTRING,)f(rep)s(eat)i(=)e(60,)k(and)c(width)g(=)h(12.)40
+b(The)25 b(n)m(um)m(b)s(er)g(of)i(substings)d(in)h(an)m(y)120
+2931 y(binary)31 b(table)h(c)m(haracter)i(string)e(\014eld)f(can)i(b)s
+(e)f(calculated)h(b)m(y)f(\(rep)s(eat/width\).)47 b(A)33
+b(n)m(ull)d(p)s(oin)m(ter)i(ma)m(y)120 3044 y(b)s(e)e(giv)m(en)g(for)g
+(an)m(y)h(of)g(the)f(output)g(parameters)h(that)g(are)f(not)h(needed.)
+120 3256 y Fc(________________________)o(____)o(____)o(___)o(____)o
+(____)o(___)o(____)o(____)o(___)o(____)o(____)o(___)o(____)o(____)120
+3369 y(int)47 b(fits_insert_rows\(fitsfil)o(e)42 b(*fptr,)k(long)h
+(firstrow,)e(long)h(nrows,)h(int)f(*status\))120 3482
+y(int)h(fits_delete_rows\(fitsfil)o(e)42 b(*fptr,)k(long)h(firstrow,)e
+(long)h(nrows,)h(int)f(*status\))120 3595 y(int)h
+(fits_delete_rowrange\(fit)o(sfil)o(e)42 b(*fptr,)k(char)g(*rangelist,)
+f(int)i(*status\))120 3708 y(int)g(fits_delete_rowlist\(fits)o(file)41
+b(*fptr,)46 b(long)h(*rowlist,)e(long)i(nrows,)f(int)h(*stat\))261
+3920 y Fg(Insert)33 b(or)g(delete)g(ro)m(ws)g(in)e(a)j(table.)48
+b(The)33 b(blank)e(ro)m(ws)i(are)h(inserted)d(immediately)h(follo)m
+(wing)f(ro)m(w)120 4033 y Fc(frow)p Fg(.)54 b(Set)35
+b Fc(frow)f Fg(=)g(0)i(to)f(insert)f(ro)m(ws)h(at)h(the)f(b)s(eginning)
+e(of)i(the)g(table.)55 b(The)34 b(\014rst)h('delete')g(routine)120
+4146 y(deletes)k Fc(nrows)e Fg(ro)m(ws)i(b)s(eginning)d(with)i(ro)m(w)h
+Fc(firstrow)p Fg(.)64 b(The)38 b(2nd)g(delete)h(routine)f(tak)m(es)j
+(an)d(input)120 4259 y(string)26 b(listing)f(the)i(ro)m(ws)f(or)h(ro)m
+(w)g(ranges)g(to)h(b)s(e)e(deleted)h(\(e.g.,)i('2,4-7,)h(9-12'\).)42
+b(The)26 b(last)h(delete)g(routine)120 4372 y(tak)m(es)35
+b(an)f(input)d(long)i(in)m(teger)h(arra)m(y)h(that)f(sp)s(eci\014es)e
+(eac)m(h)j(individual)29 b(ro)m(w)34 b(to)g(b)s(e)f(deleted.)50
+b(The)33 b(ro)m(w)120 4485 y(lists)h(m)m(ust)h(b)s(e)f(sorted)i(in)e
+(ascending)g(order.)55 b(All)34 b(these)i(routines)e(up)s(date)g(the)i
+(v)-5 b(alue)34 b(of)i(the)f Fc(NAXIS2)120 4598 y Fg(k)m(eyw)m(ord)c
+(to)g(re\015ect)g(the)f(new)g(n)m(um)m(b)s(er)f(of)i(ro)m(ws)f(in)f
+(the)i(table.)120 4810 y Fc(________________________)o(____)o(____)o
+(___)o(____)o(____)o(___)o(____)o(____)o(___)o(____)o(____)o(___)o
+(____)o(_)120 4923 y(int)47 b(fits_insert_col\(fitsfile)41
+b(*fptr,)46 b(int)h(colnum,)f(char)h(*ttype,)e(char)i(*tform,)1075
+5036 y(int)f(*status\))120 5149 y(int)h(fits_insert_cols\(fitsfil)o(e)
+42 b(*fptr,)k(int)h(colnum,)f(int)h(ncols,)f(char)g(**ttype,)1122
+5262 y(char)h(**tform,)e(int)i(*status\))120 5488 y(int)g
+(fits_delete_col\(fitsfile)41 b(*fptr,)46 b(int)h(colnum,)f(int)h
+(*status\))1905 5809 y Fg(14)p eop
+%%Page: 15 15
+15 14 bop 261 573 a Fg(Insert)25 b(or)g(delete)g(columns)f(in)f(a)j
+(table.)38 b Fc(colnum)24 b Fg(giv)m(es)h(the)g(p)s(osition)e(of)i(the)
+h(column)d(to)j(b)s(e)f(inserted)120 686 y(or)34 b(deleted)f(\(where)h
+(the)g(\014rst)f(column)g(of)h(the)g(table)g(is)e(at)j(p)s(osition)d
+(1\).)52 b Fc(ttype)32 b Fg(and)h Fc(tform)g Fg(giv)m(e)h(the)120
+799 y(column)j(name)i(and)f(column)f(format,)k(where)d(the)h(allo)m(w)m
+(ed)g(format)f(co)s(des)h(are)g(listed)e(ab)s(o)m(v)m(e)j(in)d(the)120
+912 y(description)43 b(of)i(the)h Fc(fits)p 1088 912
+29 4 v 33 w(create)p 1409 912 V 33 w(table)d Fg(routine.)84
+b(The)45 b(2nd)f('insert')h(routine)f(inserts)g(m)m(ultiple)120
+1024 y(columns,)32 b(where)h Fc(ncols)e Fg(is)h(the)h(n)m(um)m(b)s(er)e
+(of)i(columns)f(to)h(insert,)g(and)f Fc(ttype)f Fg(and)h
+Fc(tform)g Fg(are)h(arra)m(ys)120 1137 y(of)e(string)e(p)s(oin)m(ters)g
+(in)g(this)g(case.)120 1312 y Fc(________________________)o(____)o
+(____)o(___)o(____)o(____)o(___)o(____)o(____)o(___)o(____)o(____)o
+(___)120 1425 y(int)47 b(fits_copy_col\(fitsfile)42 b(*infptr,)j
+(fitsfile)h(*outfptr,)f(int)i(incolnum,)502 1537 y(int)g(outcolnum,)e
+(int)i(create_col,)d(int)j(*status\);)261 1712 y Fg(Cop)m(y)31
+b(a)g(column)f(from)g(one)i(table)e(HDU)i(to)g(another.)42
+b(If)31 b Fc(create)p 2609 1712 V 32 w(col)f Fg(=)h(TR)m(UE)g(\(i.e.,)h
+(not)f(equal)120 1825 y(to)42 b(zero\),)k(then)41 b(a)h(new)f(column)f
+(will)f(b)s(e)i(inserted)f(in)h(the)g(output)g(table)h(at)g(p)s
+(osition)e Fc(outcolumn)p Fg(,)120 1937 y(otherwise)30
+b(the)g(v)-5 b(alues)30 b(in)f(the)i(existing)e(output)h(column)f(will)
+f(b)s(e)i(o)m(v)m(erwritten.)120 2112 y Fc(________________________)o
+(____)o(____)o(___)o(____)o(____)o(___)o(____)o(____)o(___)o(____)o
+(____)o(___)o(____)o(__)120 2225 y(int)47 b(fits_write_col\(fitsfile)41
+b(*fptr,)46 b(int)h(datatype,)f(int)h(colnum,)e(long)i(firstrow,)979
+2337 y(long)g(firstelem,)e(long)h(nelements,)f(void)i(*array,)f(int)h
+(*status\))120 2450 y(int)g(fits_write_colnull\(fitsf)o(ile)41
+b(*fptr,)46 b(int)h(datatype,)f(int)g(colnum,)979 2563
+y(long)h(firstrow,)e(long)i(firstelem,)e(long)h(nelements,)979
+2676 y(void)h(*array,)f(void)g(*nulval,)g(int)h(*status\))120
+2789 y(int)g(fits_write_col_null\(fits)o(file)41 b(*fptr,)46
+b(int)h(colnum,)f(long)g(firstrow,)979 2902 y(long)h(firstelem,)e(long)
+h(nelements,)f(int)i(*status\))120 3128 y(int)g
+(fits_read_col\(fitsfile)42 b(*fptr,)k(int)h(datatype,)e(int)i(colnum,)
+f(long)g(firstrow,)454 3241 y(long)h(firstelem,)e(long)h(nelements,)f
+(void)i(*nulval,)f(void)g(*array,)454 3354 y(int)h(*anynul,)f(int)g
+(*status\))261 3641 y Fg(W)-8 b(rite)44 b(or)f(read)g(elemen)m(ts)h(in)
+e(column)g(n)m(um)m(b)s(er)g Fc(colnum)p Fg(,)j(starting)e(with)f(ro)m
+(w)h Fc(firstsrow)e Fg(and)120 3754 y(elemen)m(t)33 b
+Fc(firstelem)d Fg(\(if)i(it)g(is)g(a)h(v)m(ector)h(column\).)46
+b Fc(firstelem)30 b Fg(is)i(ignored)g(if)f(it)h(is)g(a)h(scalar)f
+(column.)120 3867 y(The)d Fc(nelements)f Fg(n)m(um)m(b)s(er)g(of)i
+(elemen)m(ts)h(are)f(read)g(or)f(written)g(con)m(tin)m(uing)g(on)h
+(successiv)m(e)g(ro)m(ws)g(of)g(the)120 3980 y(table)36
+b(if)f(necessary)-8 b(.)59 b Fc(array)35 b Fg(is)g(the)i(address)e(of)h
+(an)g(arra)m(y)h(whic)m(h)e(either)h(con)m(tains)g(the)h(v)-5
+b(alues)35 b(to)i(b)s(e)120 4092 y(written,)27 b(or)f(will)e(hold)i
+(the)g(returned)g(v)-5 b(alues)26 b(that)h(are)g(read.)39
+b(When)27 b(reading,)g Fc(array)e Fg(m)m(ust)h(ha)m(v)m(e)i(b)s(een)120
+4205 y(allo)s(cated)i(large)h(enough)f(to)h(hold)e(all)g(the)i
+(returned)e(v)-5 b(alues.)261 4318 y(There)40 b(are)h(3)h(di\013eren)m
+(t)e('write')g(column)g(routines:)60 b(The)40 b(\014rst)g(simply)e
+(writes)i(the)h(input)e(arra)m(y)120 4431 y(in)m(to)32
+b(the)g(column.)43 b(The)31 b(second)h(is)f(similar,)e(except)k(that)f
+(it)f(substitutes)g(the)h(appropriate)e(n)m(ull)g(pixel)120
+4544 y(v)-5 b(alue)35 b(in)e(the)j(column)d(for)i(an)m(y)h(input)d
+(arra)m(y)i(v)-5 b(alues)35 b(whic)m(h)f(are)h(equal)g(to)h
+Fc(*nulval)d Fg(\(note)j(that)f(this)120 4657 y(parameter)k(giv)m(es)f
+(the)h(address)e(of)i(the)f(n)m(ull)e(pixel)h(v)-5 b(alue,)40
+b(not)e(the)h(v)-5 b(alue)37 b(itself)7 b(\).)64 b(The)38
+b(third)e(write)120 4770 y(routine)27 b(sets)h(the)g(sp)s(eci\014ed)e
+(table)i(elemen)m(ts)g(to)h(a)f(n)m(ull)e(v)-5 b(alue.)39
+b(New)28 b(ro)m(ws)g(will)d(b)s(e)j(automatical)g(added)120
+4883 y(to)j(the)g(table)f(if)f(the)i(write)e(op)s(eration)h(extends)h
+(b)s(ey)m(ond)e(the)i(curren)m(t)f(size)g(of)h(the)f(table.)261
+4996 y(When)42 b(reading)f(a)i(column,)h(CFITSIO)c(will)g(substitute)h
+(the)h(v)-5 b(alue)42 b(giv)m(en)g(b)m(y)g Fc(nulval)e
+Fg(for)i(an)m(y)120 5109 y(unde\014ned)25 b(elemen)m(ts)i(in)f(the)h
+(FITS)f(column,)h(unless)e Fc(nulval)g Fg(or)i Fc(*nulval)46
+b(=)h(NULL)p Fg(,)26 b(in)g(whic)m(h)f(case)j(no)120
+5222 y(c)m(hec)m(ks)k(will)c(b)s(e)h(made)i(for)f(unde\014ned)e(v)-5
+b(alues)30 b(when)f(reading)h(the)g(column.)261 5334
+y Fc(datatype)i Fg(sp)s(eci\014es)h(the)h(datat)m(yp)s(e)h(of)g(the)f
+(C)g Fc(array)e Fg(in)h(the)h(program,)i(whic)m(h)c(need)i(not)h(b)s(e)
+e(the)120 5447 y(same)42 b(as)f(the)g(in)m(trinsic)e(datat)m(yp)s(e)j
+(of)f(the)h(column)e(in)g(the)h(FITS)g(table.)73 b(The)40
+b(follo)m(wing)g(sym)m(b)s(olic)120 5560 y(constan)m(ts)32
+b(are)e(allo)m(w)m(ed)h(for)f(the)g(v)-5 b(alue)30 b(of)h
+Fc(datatype)p Fg(:)1905 5809 y(15)p eop
+%%Page: 16 16
+16 15 bop 215 573 a Fc(TSTRING)142 b(array)46 b(of)h(character)f
+(string)g(pointers)215 686 y(TBYTE)238 b(unsigned)45
+b(char)215 799 y(TSHORT)190 b(signed)46 b(short)215 912
+y(TUSHORT)142 b(unsigned)45 b(short)215 1024 y(TINT)286
+b(signed)46 b(int)215 1137 y(TUINT)238 b(unsigned)45
+b(int)215 1250 y(TLONG)238 b(signed)46 b(long)215 1363
+y(TULONG)190 b(unsigned)45 b(long)215 1476 y(TFLOAT)190
+b(float)215 1589 y(TDOUBLE)142 b(double)261 1791 y Fg(Note)35
+b(that)e Fc(TSTRING)f Fg(corresp)s(onds)g(to)h(the)h(C)f
+Fc(char**)e Fg(datat)m(yp)s(e,)k(i.e.,)g(a)e(p)s(oin)m(ter)f(to)i(an)f
+(arra)m(y)h(of)120 1904 y(p)s(oin)m(ters)29 b(to)i(an)g(arra)m(y)f(of)h
+(c)m(haracters.)261 2017 y(An)m(y)38 b(column,)i(regardless)d(of)i
+(it's)f(in)m(trinsic)d(datat)m(yp)s(e,)42 b(ma)m(y)d(b)s(e)e(read)h(as)
+h(a)f Fc(TSTRING)f Fg(c)m(haracter)120 2130 y(string.)h(The)24
+b(displa)m(y)g(format)h(of)g(the)h(returned)e(strings)f(will)g(b)s(e)h
+(determined)g(b)m(y)h(the)g Fc(TDISPn)f Fg(k)m(eyw)m(ord,)120
+2243 y(if)i(it)h(exists,)h(otherwise)f(a)g(default)g(format)h(will)c(b)
+s(e)j(used)f(dep)s(ending)f(on)j(the)f(datat)m(yp)s(e)h(of)g(the)f
+(column.)120 2356 y(The)22 b Fc(tablist)e Fg(example)i(utilit)m(y)f
+(program)h(\(a)m(v)-5 b(ailable)22 b(from)g(the)h(CFITSIO)e(w)m(eb)h
+(site\))h(uses)f(this)f(feature)120 2469 y(to)31 b(displa)m(y)e(all)g
+(the)i(v)-5 b(alues)29 b(in)g(a)i(FITS)f(table.)120 2671
+y Fc(________________________)o(____)o(____)o(___)o(____)o(____)o(___)o
+(____)o(____)o(___)o(____)o(____)o(___)o(_)120 2784 y(int)47
+b(fits_select_rows\(fitsfil)o(e)42 b(*infptr,)j(fitsfile)h(*outfptr,)f
+(char)i(*expr,)1122 2897 y(int)g(*status\))120 3010 y(int)g
+(fits_calculator\(fitsfile)41 b(*infptr,)46 b(char)g(*expr,)g(fitsfile)
+g(*outfptr,)1075 3123 y(char)g(*colname,)f(char)i(*tform,)f(int)h
+(*status\))261 3325 y Fg(These)26 b(are)h(2)g(of)g(the)f(most)h(p)s(o)m
+(w)m(erful)e(routines)h(in)f(the)i(CFITSIO)d(library)-8
+b(.)38 b(\(See)27 b(the)g(full)d(CFITSIO)120 3438 y(Reference)37
+b(Guide)e(for)h(a)h(description)d(of)i(sev)m(eral)h(related)f
+(routines\).)57 b(These)36 b(routines)f(can)i(p)s(erform)120
+3551 y(complicated)45 b(transformations)g(on)g(tables)g(based)g(on)g
+(an)g(input)f(arithmetic)g(expression)g(whic)m(h)g(is)120
+3664 y(ev)-5 b(aluated)37 b(for)f(eac)m(h)h(ro)m(w)g(of)g(the)f(table.)
+59 b(The)36 b(\014rst)g(routine)f(will)f(select)j(or)g(cop)m(y)g(ro)m
+(ws)f(of)h(the)f(table)120 3777 y(for)i(whic)m(h)f(the)h(expression)f
+(ev)-5 b(aluates)39 b(to)g(TR)m(UE)g(\(i.e.,)h(not)f(equal)f(to)h
+(zero\).)65 b(The)38 b(second)g(routine)120 3890 y(writes)c(the)h(v)-5
+b(alue)34 b(of)h(the)g(expression)f(to)i(a)f(column)f(in)f(the)i
+(output)g(table.)54 b(Rather)35 b(than)g(supplying)120
+4003 y(the)j(expression)e(directly)g(to)i(these)g(routines,)h(the)e
+(expression)g(ma)m(y)h(also)f(b)s(e)g(written)g(to)h(a)g(text)g(\014le)
+120 4116 y(\(con)m(tin)m(ued)e(o)m(v)m(er)g(m)m(ultiple)e(lines)f(if)i
+(necessary\))h(and)f(the)h(name)f(of)h(the)g(\014le,)g(prep)s(ended)d
+(with)h(a)i('@')120 4229 y(c)m(haracter,)c(ma)m(y)f(b)s(e)f(supplied)d
+(as)k(the)f(v)-5 b(alue)30 b(of)h(the)f('expr')g(parameter)h(\(e.g.)42
+b('@\014lename.txt'\).)261 4342 y(The)26 b(arithmetic)f(expression)g
+(ma)m(y)i(b)s(e)f(a)g(function)f(of)h(an)m(y)h(column)e(or)h(k)m(eyw)m
+(ord)h(in)e(the)h(input)e(table)120 4455 y(as)31 b(sho)m(wn)e(in)g
+(these)i(examples:)120 4657 y Fc(Row)47 b(Selection)e(Expressions:)263
+4770 y(counts)h(>)i(0)1240 b(uses)47 b(COUNTS)f(column)g(value)263
+4883 y(sqrt\()h(X**2)f(+)i(Y**2\))e(<)h(10.)572 b(uses)47
+b(X)g(and)g(Y)h(column)e(values)263 4996 y(\(X)h(>)h(10\))f(||)g(\(X)g
+(<)h(-10\))e(&&)h(\(Y)h(==)f(0\))142 b(used)47 b('or')g(and)g('and')f
+(operators)263 5109 y(gtifilter\(\))1190 b(filter)46
+b(on)i(Good)e(Time)h(Intervals)263 5222 y(regfilter\("myregion.reg"\))
+518 b(filter)46 b(using)h(a)g(region)f(file)263 5334
+y(@select.txt)1190 b(reads)47 b(expression)e(from)h(a)i(text)e(file)120
+5447 y(Calculator)f(Expressions:)263 5560 y(#row)i(\045)g(10)1145
+b(modulus)46 b(of)h(the)g(row)g(number)1905 5809 y Fg(16)p
+eop
+%%Page: 17 17
+17 16 bop 263 573 a Fc(counts/#exposure)807 b(Fn)47 b(of)h(COUNTS)e
+(column)g(and)h(EXPOSURE)e(keyword)263 686 y(dec)i(<)h(85)f(?)g
+(cos\(dec)f(*)h(#deg\))g(:)g(0)143 b(Conditional)45 b(expression:)g
+(evaluates)g(to)1934 799 y(cos\(dec\))g(if)i(dec)g(<)h(85,)f(else)f(0)
+263 912 y(\(count{-1}+count+count{+1)o(}\)/3)o(.)137
+b(running)46 b(mean)h(of)g(the)g(count)f(values)g(in)h(the)1934
+1024 y(previous,)e(current,)g(and)i(next)g(rows)263 1137
+y(max\(0,)f(min\(X,)g(1000\)\))619 b(returns)46 b(a)h(value)g(between)f
+(0)h(-)h(1000)263 1250 y(@calc.txt)1143 b(reads)47 b(expression)e(from)
+h(a)i(text)e(file)261 1463 y Fg(Most)40 b(standard)d(mathematical)i(op)
+s(erators)g(and)f(functions)f(are)i(supp)s(orted.)64
+b(If)38 b(the)h(expression)120 1576 y(includes)32 b(the)j(name)f(of)h
+(a)f(column,)h(than)f(the)h(v)-5 b(alue)34 b(in)f(the)h(curren)m(t)h
+(ro)m(w)f(of)h(the)f(table)h(will)c(b)s(e)j(used)120
+1689 y(when)f(ev)-5 b(aluating)33 b(the)h(expression)f(on)h(eac)m(h)h
+(ro)m(w.)51 b(An)34 b(o\013set)h(to)g(an)e(adjacen)m(t)j(ro)m(w)e(can)g
+(b)s(e)f(sp)s(eci\014ed)120 1802 y(b)m(y)d(including)d(the)j(o\013set)h
+(v)-5 b(alue)29 b(in)g(curly)g(brac)m(k)m(ets)i(after)g(the)f(column)f
+(name)h(as)g(sho)m(wn)g(in)f(one)h(of)g(the)120 1914
+y(examples.)39 b(Keyw)m(ord)27 b(v)-5 b(alues)27 b(can)g(b)s(e)g
+(included)d(in)i(the)i(expression)e(b)m(y)h(preceding)f(the)i(k)m(eyw)m
+(ord)f(name)120 2027 y(with)g(a)i(`#')f(sign.)39 b(See)28
+b(Section)h(5)f(of)h(this)e(do)s(cumen)m(t)h(for)g(more)g(discussion)e
+(of)i(the)h(expression)e(syn)m(tax.)261 2140 y Fc(gtifilter)i
+Fg(is)h(a)i(sp)s(ecial)e(function)g(whic)m(h)g(tests)i(whether)f(the)h
+Fc(TIME)e Fg(column)g(v)-5 b(alue)31 b(in)f(the)h(input)120
+2253 y(table)38 b(falls)f(within)f(one)j(or)f(more)h(Go)s(o)s(d)f(Time)
+f(In)m(terv)-5 b(als.)65 b(By)39 b(default,)g(this)f(function)f(lo)s
+(oks)h(for)g(a)120 2366 y('GTI')27 b(extension)f(in)f(the)i(same)g
+(\014le)e(as)i(the)g(input)d(table.)40 b(The)26 b('GTI')g(table)h(con)m
+(tains)g Fc(START)e Fg(and)h Fc(STOP)120 2479 y Fg(columns)f(whic)m(h)h
+(de\014ne)g(the)g(range)h(of)g(eac)m(h)h(go)s(o)s(d)f(time)f(in)m(terv)
+-5 b(al.)39 b(See)27 b(section)f(5.4.3)j(for)d(more)h(details.)261
+2592 y Fc(regfilter)35 b Fg(is)h(another)h(sp)s(ecial)f(function)g
+(whic)m(h)g(selects)h(ro)m(ws)h(based)e(on)h(whether)g(the)g(spatial)
+120 2705 y(p)s(osition)21 b(asso)s(ciated)j(with)e(eac)m(h)j(ro)m(w)e
+(is)f(lo)s(cated)i(within)d(in)h(a)h(sp)s(eci\014ed)f(region)h(of)g
+(the)h(sky)-8 b(.)38 b(By)24 b(default,)120 2818 y(the)35
+b Fc(X)g Fg(and)f Fc(Y)h Fg(columns)f(in)g(the)h(input)e(table)i(are)g
+(assumed)g(to)h(giv)m(e)f(the)g(p)s(osition)e(of)j(eac)m(h)g(ro)m(w.)55
+b(The)120 2931 y(spatial)35 b(region)g(is)f(de\014ned)h(in)f(an)h(ASCI)
+s(I)f(text)j(\014le)d(whose)i(name)f(is)g(giv)m(en)g(as)h(the)g
+(argumen)m(t)g(to)g(the)120 3044 y Fc(regfilter)28 b
+Fg(function.)39 b(See)31 b(section)f(5.4.4)j(for)d(more)g(details.)261
+3156 y(The)e Fc(infptr)e Fg(and)i Fc(outfptr)e Fg(parameters)j(in)e
+(these)h(routines)f(ma)m(y)i(p)s(oin)m(t)e(to)i(the)g(same)f(table)g
+(or)h(to)120 3269 y(di\013eren)m(t)h(tables.)42 b(In)31
+b Fc(fits)p 1092 3269 29 4 v 33 w(select)p 1413 3269
+V 33 w(rows)p Fg(,)f(if)f(the)j(input)c(and)j(output)f(tables)h(are)g
+(the)g(same)h(then)e(the)120 3382 y(ro)m(ws)e(that)h(do)f(not)g
+(satisfy)g(the)g(selection)g(expression)f(will)e(b)s(e)i(deleted)h
+(from)g(the)g(table.)40 b(Otherwise,)27 b(if)120 3495
+y(the)k(output)g(table)g(is)f(di\013eren)m(t)h(from)g(the)g(input)e
+(table)i(then)g(the)g(selected)h(ro)m(ws)f(will)e(b)s(e)h(copied)h
+(from)120 3608 y(the)g(input)d(table)i(to)h(the)g(output)f(table.)261
+3721 y(The)i(output)g(column)f(in)g Fc(fits)p 1376 3721
+V 33 w(calculator)f Fg(ma)m(y)i(or)h(ma)m(y)g(not)f(already)g(exist.)46
+b(If)32 b(it)g(exists)g(then)120 3834 y(the)44 b(calculated)f(v)-5
+b(alues)43 b(will)e(b)s(e)i(written)g(to)h(that)h(column,)h(o)m(v)m
+(erwriting)d(the)g(existing)g(v)-5 b(alues.)80 b(If)120
+3947 y(the)36 b(column)f(do)s(esn't)h(exist)f(then)h(the)g(new)g
+(column)e(will)g(b)s(e)h(app)s(ended)f(to)j(the)f(output)g(table.)57
+b(The)120 4060 y Fc(tform)37 b Fg(parameter)i(can)f(b)s(e)g(used)g(to)h
+(sp)s(ecify)e(the)h(datat)m(yp)s(e)i(of)e(the)h(new)f(column)f(\(e.g.,)
+42 b(the)d Fc(TFORM)120 4173 y Fg(k)m(eyw)m(ord)26 b(v)-5
+b(alue)25 b(as)h(in)f Fc('1E',)46 b(or)h('1J')p Fg(\).)25
+b(If)h Fc(tform)e Fg(=)h(NULL)h(then)f(a)h(default)f(datat)m(yp)s(e)i
+(will)c(b)s(e)i(used,)120 4286 y(dep)s(ending)j(on)i(the)h(expression.)
+120 4498 y Fc(________________________)o(____)o(____)o(___)o(____)o
+(____)o(___)o(____)o(____)o(___)o(____)o(____)o(___)o(_)120
+4611 y(int)47 b(fits_read_tblbytes\(fitsf)o(ile)41 b(*fptr,)46
+b(long)h(firstrow,)e(long)i(firstchar,)1122 4724 y(long)g(nchars,)f
+(unsigned)f(char)i(*array,)f(int)h(*status\))120 4837
+y(int)g(fits_write_tblbytes)42 b(\(fitsfile)k(*fptr,)g(long)g
+(firstrow,)g(long)g(firstchar,)1122 4950 y(long)h(nchars,)f(unsigned)f
+(char)i(*array,)f(int)h(*status\))261 5162 y Fg(These)35
+b(2)g(routines)e(pro)m(vide)h(lo)m(w-lev)m(el)h(access)h(to)f(tables)g
+(and)f(are)h(mainly)e(useful)g(as)i(an)g(e\016cien)m(t)120
+5275 y(w)m(a)m(y)i(to)g(cop)m(y)g(ro)m(ws)f(of)g(a)h(table)f(from)g
+(one)g(\014le)f(to)i(another.)58 b(These)36 b(routines)f(simply)f(read)
+i(or)g(write)120 5388 y(the)30 b(sp)s(eci\014ed)f(n)m(um)m(b)s(er)g(of)
+h(consecutiv)m(e)i(c)m(haracters)f(\(b)m(ytes\))h(in)d(a)i(table,)f
+(without)f(regard)h(for)h(column)120 5501 y(b)s(oundaries.)83
+b(F)-8 b(or)47 b(example,)i(to)d(read)f(or)h(write)e(the)i(\014rst)f
+(ro)m(w)g(of)h(a)g(table,)j(set)d Fc(firstrow)g(=)h(1,)1905
+5809 y Fg(17)p eop
+%%Page: 18 18
+18 17 bop 120 573 a Fc(firstchar)45 b(=)j(1)p Fg(,)38
+b(and)e Fc(nchars)46 b(=)i(NAXIS1)35 b Fg(where)h(the)h(length)f(of)h
+(a)h(ro)m(w)f(is)e(giv)m(en)i(b)m(y)g(the)g(v)-5 b(alue)36
+b(of)120 686 y(the)31 b Fc(NAXIS1)f Fg(header)h(k)m(eyw)m(ord.)43
+b(When)31 b(reading)g(a)g(table,)h Fc(array)e Fg(m)m(ust)h(ha)m(v)m(e)h
+(b)s(een)f(declared)f(at)i(least)120 799 y Fc(nchars)d
+Fg(b)m(ytes)i(long)f(to)h(hold)e(the)h(returned)f(string)h(of)g(b)m
+(ytes.)1905 5809 y(18)p eop
+%%Page: 19 19
+19 18 bop 120 573 a Fb(4.6)112 b(Header)38 b(Keyw)m(ord)f(I/O)h
+(Routines)120 744 y Fg(The)30 b(follo)m(wing)f(routines)g(read)h(and)g
+(write)g(header)g(k)m(eyw)m(ords)g(in)g(the)g(curren)m(t)g(HDU.)120
+957 y Fc(________________________)o(____)o(____)o(___)o(____)o(____)o
+(___)o(____)o(____)o(___)o(____)o(____)o(___)120 1070
+y(int)47 b(fits_get_hdrspace\(fitsfi)o(le)42 b(*fptr,)k(int)h
+(*keysexist,)d(int)j(*morekeys,)1170 1183 y(int)g(*status\))120
+1395 y Fg(Return)36 b(the)g(n)m(um)m(b)s(er)f(of)i(existing)e(k)m(eyw)m
+(ords)i(\(not)g(coun)m(ting)f(the)h(mandatory)f(END)h(k)m(eyw)m(ord\))g
+(and)120 1508 y(the)29 b(amoun)m(t)h(of)f(empt)m(y)h(space)g(curren)m
+(tly)e(a)m(v)-5 b(ailable)28 b(for)h(more)h(k)m(eyw)m(ords.)40
+b(The)29 b Fc(morekeys)e Fg(parameter)120 1621 y(ma)m(y)k(b)s(e)f(set)h
+(to)g(NULL)f(if)f(it's)h(v)-5 b(alue)30 b(is)g(not)g(needed.)120
+1834 y Fc(________________________)o(____)o(____)o(___)o(____)o(____)o
+(___)o(____)o(____)o(___)o(____)o(____)o(___)o(____)o(___)120
+1947 y(int)47 b(fits_read_record\(fitsfil)o(e)42 b(*fptr,)k(int)h
+(keynum,)f(char)g(*record,)g(int)h(*status\))120 2060
+y(int)g(fits_read_card\(fitsfile)41 b(*fptr,)46 b(char)h(*keyname,)e
+(char)i(*record,)f(int)g(*status\))120 2172 y(int)h
+(fits_read_key\(fitsfile)42 b(*fptr,)k(int)h(datatype,)e(char)i
+(*keyname,)979 2285 y(void)g(*value,)f(char)g(*comment,)f(int)i
+(*status\))120 2511 y(int)g(fits_find_nextkey\(fitsfi)o(le)42
+b(*fptr,)k(char)g(**inclist,)f(int)i(ninc,)1170 2624
+y(char)g(**exclist,)e(int)i(nexc,)f(char)h(*card,)f(int)h(*status\))120
+2850 y(int)g(fits_read_key_unit\(fitsf)o(ile)41 b(*fptr,)46
+b(char)h(*keyname,)e(char)i(*unit,)1218 2963 y(int)g(*status\))261
+3175 y Fg(These)d(routines)g(all)f(read)h(a)h(header)f(record)g(in)f
+(the)i(curren)m(t)f(HDU.)i(The)e(\014rst)f(routine)h(reads)120
+3288 y(k)m(eyw)m(ord)c(n)m(um)m(b)s(er)f Fc(keynum)f
+Fg(\(where)i(the)g(\014rst)f(k)m(eyw)m(ord)i(is)e(at)i(p)s(osition)d
+(1\).)70 b(This)38 b(routine)h(is)g(most)120 3401 y(commonly)28
+b(used)g(when)f(sequen)m(tially)g(reading)g(ev)m(ery)j(record)e(in)f
+(the)i(header)f(from)g(b)s(eginning)e(to)j(end.)120 3514
+y(The)22 b(2nd)f(and)h(3rd)f(routines)g(read)h(the)g(named)g(k)m(eyw)m
+(ord)h(and)e(return)g(either)h(the)g(whole)f(80-b)m(yte)j(record,)120
+3627 y(or)30 b(the)h(k)m(eyw)m(ord)g(v)-5 b(alue)30 b(and)f(commen)m(t)
+j(string.)261 3740 y(Wild)25 b(card)g(c)m(haracters)j(\(*,)g(?,)f(and)e
+(#\))h(ma)m(y)h(b)s(e)e(used)g(when)g(sp)s(ecifying)f(the)i(name)h(of)f
+(the)g(k)m(eyw)m(ord)120 3853 y(to)31 b(b)s(e)f(read,)g(in)f(whic)m(h)h
+(case)h(the)g(\014rst)e(matc)m(hing)i(k)m(eyw)m(ord)f(is)g(returned.)
+261 3966 y(The)41 b Fc(datatype)e Fg(parameter)j(sp)s(eci\014es)e(the)h
+(C)g(datat)m(yp)s(e)h(of)g(the)f(returned)f(k)m(eyw)m(ord)i(v)-5
+b(alue)41 b(and)120 4079 y(can)48 b(ha)m(v)m(e)h(one)f(of)g(the)f
+(follo)m(wing)f(sym)m(b)s(olic)g(constan)m(t)j(v)-5 b(alues:)75
+b Fc(TSTRING,)46 b(TLOGICAL)f Fg(\(==)i(in)m(t\),)120
+4192 y Fc(TBYTE)p Fg(,)d Fc(TSHORT)p Fg(,)f Fc(TUSHORT)p
+Fg(,)g Fc(TINT)p Fg(,)h Fc(TUINT)p Fg(,)f Fc(TLONG)p
+Fg(,)h Fc(TULONG)p Fg(,)f Fc(TFLOAT)p Fg(,)g Fc(TDOUBLE)p
+Fg(,)g Fc(TCOMPLEX)p Fg(,)g(and)120 4304 y Fc(TDBLCOMPLEX)p
+Fg(.)e(Data)k(t)m(yp)s(e)f(con)m(v)m(ersion)g(will)d(b)s(e)i(p)s
+(erformed)f(for)i(n)m(umeric)e(v)-5 b(alues)43 b(if)g(the)h(in)m
+(trinsic)120 4417 y(FITS)32 b(k)m(eyw)m(ord)h(v)-5 b(alue)32
+b(do)s(es)g(not)g(ha)m(v)m(e)i(the)f(same)g(datat)m(yp)s(e.)48
+b(The)32 b Fc(comment)e Fg(parameter)j(ma)m(y)g(b)s(e)f(set)120
+4530 y(equal)e(to)h(NULL)f(if)g(the)g(commen)m(t)i(string)d(is)g(not)i
+(needed.)261 4643 y(The)21 b(4th)h(routine)f(pro)m(vides)g(an)h(easy)g
+(w)m(a)m(y)h(to)f(\014nd)e(all)h(the)h(k)m(eyw)m(ords)g(in)f(the)g
+(header)h(that)g(matc)m(h)h(one)120 4756 y(of)29 b(the)h(name)f
+(templates)g(in)f Fc(inclist)f Fg(and)h(do)h(not)h(matc)m(h)g(an)m(y)f
+(of)g(the)h(name)f(templates)g(in)f Fc(exclist)p Fg(.)120
+4869 y Fc(ninc)37 b Fg(and)h Fc(nexc)f Fg(are)i(the)g(n)m(um)m(b)s(er)e
+(of)h(template)h(strings)e(in)g Fc(inclist)g Fg(and)h
+Fc(exclist)p Fg(,)g(resp)s(ectiv)m(ely)-8 b(.)120 4982
+y(Wild)33 b(cards)h(\(*,)i(?,)f(and)f(#\))g(ma)m(y)h(b)s(e)f(used)f(in)
+g(the)h(templates)h(to)g(matc)m(h)g(m)m(ultiple)d(k)m(eyw)m(ords.)53
+b(Eac)m(h)120 5095 y(time)35 b(this)f(routine)g(is)g(called)g(it)g
+(returns)g(the)h(next)h(matc)m(hing)f(80-b)m(yte)h(k)m(eyw)m(ord)g
+(record.)54 b(It)36 b(returns)120 5208 y(status)31 b(=)f
+Fc(KEY)p 640 5208 29 4 v 33 w(NO)p 769 5208 V 34 w(EXIST)f
+Fg(if)g(there)i(are)g(no)f(more)g(matc)m(hes.)261 5321
+y(The)f(5th)g(routine)f(returns)g(the)i(k)m(eyw)m(ord)g(v)-5
+b(alue)28 b(units)g(string,)g(if)h(an)m(y)-8 b(.)41 b(The)28
+b(units)g(are)i(recorded)f(at)120 5434 y(the)i(b)s(eginning)c(of)k(the)
+f(k)m(eyw)m(ord)h(commen)m(t)h(\014eld)d(enclosed)h(in)f(square)h(brac)
+m(k)m(ets.)1905 5809 y(19)p eop
+%%Page: 20 20
+20 19 bop 120 573 a Fc(________________________)o(____)o(____)o(___)o
+(____)o(____)o(___)o(____)o(____)o(___)o(____)o(__)120
+686 y(int)47 b(fits_write_key\(fitsfile)41 b(*fptr,)46
+b(int)h(datatype,)f(char)g(*keyname,)502 799 y(void)g(*value,)g(char)h
+(*comment,)e(int)i(*status\))120 912 y(int)g(fits_update_key\(fitsfile)
+41 b(*fptr,)46 b(int)h(datatype,)e(char)i(*keyname,)502
+1024 y(void)f(*value,)g(char)h(*comment,)e(int)i(*status\))120
+1137 y(int)g(fits_write_record\(fitsfi)o(le)42 b(*fptr,)k(char)g
+(*card,)g(int)h(*status\))120 1363 y(int)g(fits_modify_comment\(fits)o
+(file)41 b(*fptr,)46 b(char)h(*keyname,)e(char)i(*comment,)502
+1476 y(int)g(*status\))120 1589 y(int)g(fits_write_key_unit\(fits)o
+(file)41 b(*fptr,)46 b(char)h(*keyname,)e(char)i(*unit,)502
+1702 y(int)g(*status\))261 1975 y Fg(W)-8 b(rite)31 b(or)g(mo)s(dify)f
+(a)h(k)m(eyw)m(ord)g(in)f(the)h(header)g(of)g(the)g(curren)m(t)g(HDU.)h
+(The)e(\014rst)g(routine)g(app)s(ends)120 2087 y(the)g(new)g(k)m(eyw)m
+(ord)g(to)h(the)f(end)g(of)g(the)g(header,)h(whereas)e(the)i(second)f
+(routine)f(will)e(up)s(date)j(the)g(v)-5 b(alue)120 2200
+y(and)40 b(commen)m(t)h(\014elds)e(of)i(the)g(k)m(eyw)m(ord)g(if)e(it)h
+(already)h(exists,)h(otherwise)e(it)h(b)s(eha)m(v)m(es)g(lik)m(e)e(the)
+i(\014rst)120 2313 y(routine)32 b(and)g(app)s(ends)f(the)h(new)h(k)m
+(eyw)m(ord.)48 b(Note)34 b(that)f Fc(value)e Fg(giv)m(es)i(the)g
+(address)f(to)h(the)g(v)-5 b(alue)32 b(and)120 2426 y(not)f(the)g(v)-5
+b(alue)31 b(itself.)41 b(The)31 b Fc(datatype)d Fg(parameter)k(sp)s
+(eci\014es)d(the)j(C)e(datat)m(yp)s(e)i(of)f(the)g(k)m(eyw)m(ord)h(v)-5
+b(alue)120 2539 y(and)38 b(ma)m(y)g(ha)m(v)m(e)i(an)m(y)f(of)f(the)g(v)
+-5 b(alues)38 b(listed)f(in)g(the)h(description)e(of)j(the)f(k)m(eyw)m
+(ord)h(reading)e(routines,)120 2652 y(ab)s(o)m(v)m(e.)71
+b(A)40 b(NULL)g(ma)m(y)h(b)s(e)e(en)m(tered)i(for)f(the)g(commen)m(t)h
+(parameter,)i(in)c(whic)m(h)g(case)i(the)f(k)m(eyw)m(ord)120
+2765 y(commen)m(t)31 b(\014eld)e(will)f(b)s(e)i(unmo)s(di\014ed)d(or)j
+(left)h(blank.)261 2878 y(The)25 b(third)f(routine)h(is)g(more)h
+(primitiv)m(e)e(and)h(simply)e(writes)i(the)h(80-c)m(haracter)j
+Fc(card)c Fg(record)h(to)g(the)120 2991 y(header.)40
+b(It)30 b(is)f(the)h(programmer's)f(resp)s(onsibilit)m(y)d(in)i(this)h
+(case)h(to)h(ensure)e(that)h(the)g(record)g(conforms)120
+3104 y(to)h(all)e(the)i(FITS)f(format)g(requiremen)m(ts)g(for)g(a)h
+(header)f(record.)261 3217 y(The)42 b(fourth)f(routine)g(mo)s(di\014es)
+f(the)i(commen)m(t)h(string)e(in)g(an)g(existing)g(k)m(eyw)m(ord,)46
+b(and)41 b(the)h(last)120 3329 y(routine)33 b(writes)g(or)h(up)s(dates)
+f(the)h(k)m(eyw)m(ord)h(units)d(string)h(for)h(an)g(existing)f(k)m(eyw)
+m(ord.)52 b(\(The)34 b(units)e(are)120 3442 y(recorded)e(at)h(the)g(b)s
+(eginning)d(of)i(the)h(k)m(eyw)m(ord)f(commen)m(t)i(\014eld)d(enclosed)
+h(in)f(square)h(brac)m(k)m(ets\).)120 3621 y Fc
+(________________________)o(____)o(____)o(___)o(____)o(____)o(___)o
+(____)o(____)o(___)o(____)o(____)o(__)120 3734 y(int)47
+b(fits_write_comment\(fitsf)o(ile)41 b(*fptr,)46 b(char)h(*comment,)93
+b(int)47 b(*status\))120 3847 y(int)g(fits_write_history\(fitsf)o(ile)
+41 b(*fptr,)46 b(char)h(*history,)93 b(int)47 b(*status\))120
+3960 y(int)g(fits_write_date\(fitsfile)41 b(*fptr,)94
+b(int)47 b(*status\))261 4139 y Fg(W)-8 b(rite)21 b(a)g
+Fc(COMMENT,)46 b(HISTORY)p Fg(,)18 b(or)j Fc(DATE)e Fg(k)m(eyw)m(ord)i
+(to)h(the)f(curren)m(t)f(header.)37 b(The)20 b Fc(COMMENT)f
+Fg(k)m(eyw)m(ord)120 4252 y(is)37 b(t)m(ypically)g(used)g(to)h(write)g
+(a)g(commen)m(t)h(ab)s(out)e(the)i(\014le)e(or)g(the)i(data.)64
+b(The)37 b Fc(HISTORY)f Fg(k)m(eyw)m(ord)i(is)120 4365
+y(t)m(ypically)22 b(used)f(to)j(pro)m(vide)e(information)f(ab)s(out)h
+(the)h(history)e(of)i(the)g(pro)s(cessing)e(pro)s(cedures)h(that)h(ha)m
+(v)m(e)120 4478 y(b)s(een)36 b(applied)f(to)i(the)g(data.)61
+b(The)36 b Fc(comment)f Fg(or)i Fc(history)e Fg(string)h(will)e(b)s(e)i
+(con)m(tin)m(ued)h(o)m(v)m(er)h(m)m(ultiple)120 4591
+y(k)m(eyw)m(ords)31 b(if)e(it)h(is)f(more)i(than)f(70)h(c)m(haracters)h
+(long.)261 4704 y(The)k Fc(DATE)f Fg(k)m(eyw)m(ord)i(is)e(used)h(to)h
+(record)f(the)h(date)g(and)f(time)g(that)h(the)f(FITS)g(\014le)f(w)m
+(as)i(created.)120 4817 y(Note)f(that)f(this)e(\014le)h(creation)h
+(date)g(is)f(usually)e(di\013eren)m(t)i(from)g(the)h(date)g(of)g(the)f
+(observ)-5 b(ation)35 b(whic)m(h)120 4930 y(obtained)e(the)h(data)h(in)
+d(the)j(FITS)e(\014le.)50 b(The)33 b Fc(DATE)g Fg(k)m(eyw)m(ord)h(v)-5
+b(alue)34 b(is)f(a)h(c)m(haracter)i(string)c(in)h('yyyy-)120
+5042 y(mm-ddThh:mm:ss')27 b(format.)40 b(If)29 b(a)g
+Fc(DATE)f Fg(k)m(eyw)m(ord)i(already)e(exists)h(in)f(the)h(header,)h
+(then)e(this)g(routine)120 5155 y(will)g(up)s(date)h(the)i(v)-5
+b(alue)30 b(with)f(the)h(curren)m(t)g(system)h(date.)120
+5334 y Fc(________________________)o(____)o(____)o(___)o(____)o(____)o
+(___)o(____)o(____)o(___)o(____)o(____)o(__)120 5447
+y(int)47 b(fits_delete_record\(fitsf)o(ile)41 b(*fptr,)46
+b(int)h(keynum,)94 b(int)47 b(*status\))120 5560 y(int)g
+(fits_delete_key\(fitsfile)41 b(*fptr,)46 b(char)h(*keyname,)93
+b(int)47 b(*status\))1905 5809 y Fg(20)p eop
+%%Page: 21 21
+21 20 bop 261 573 a Fg(Delete)32 b(a)f(k)m(eyw)m(ord)h(record.)42
+b(The)30 b(\014rst)g(routine)g(deletes)h(a)g(k)m(eyw)m(ord)h(at)f(a)h
+(sp)s(eci\014ed)d(p)s(osition)g(\(the)120 686 y(\014rst)e(k)m(eyw)m
+(ord)h(is)e(at)i(p)s(osition)e(1,)j(not)e(0\),)i(whereas)f(the)f
+(second)h(routine)e(deletes)i(the)g(named)f(k)m(eyw)m(ord.)120
+898 y Fc(________________________)o(____)o(____)o(___)o(____)o(____)o
+(___)o(____)o(____)o(___)o(____)o(____)o(___)o(___)120
+1011 y(int)47 b(fits_copy_header\(fitsfil)o(e)42 b(*infptr,)j(fitsfile)
+h(*outfptr,)93 b(int)47 b(*status\))261 1224 y Fg(Cop)m(y)26
+b(all)f(the)h(header)g(k)m(eyw)m(ords)h(from)e(the)i(curren)m(t)e(HDU)i
+(asso)s(ciated)g(with)d(infptr)g(to)j(the)g(curren)m(t)120
+1337 y(HDU)h(asso)s(ciated)f(with)e(outfptr.)39 b(If)27
+b(the)g(curren)m(t)f(output)h(HDU)g(is)f(not)h(empt)m(y)-8
+b(,)29 b(then)d(a)h(new)f(HDU)i(will)120 1450 y(b)s(e)34
+b(app)s(ended)f(to)j(the)f(output)f(\014le.)53 b(The)35
+b(output)f(HDU)i(will)c(then)i(ha)m(v)m(e)i(the)f(iden)m(tical)f
+(structure)g(as)120 1562 y(the)d(input)d(HDU,)j(but)f(will)e(con)m
+(tain)i(no)h(data.)1905 5809 y(21)p eop
+%%Page: 22 22
+22 21 bop 120 573 a Fb(4.7)112 b(Utilit)m(y)34 b(Routines)120
+744 y Fg(This)29 b(section)h(lists)f(the)i(most)f(imp)s(ortan)m(t)g
+(CFITSIO)f(general)h(utilit)m(y)f(routines.)120 957 y
+Fc(________________________)o(____)o(____)o(___)o(____)o(____)o(___)o
+(____)o(____)o(___)o(____)o(____)o(__)120 1070 y(int)47
+b(fits_write_chksum\()c(fitsfile)i(*fptr,)h(int)h(*status\))120
+1183 y(int)g(fits_verify_chksum\(fitsf)o(ile)41 b(*fptr,)46
+b(int)h(*dataok,)f(int)h(*hduok,)f(int)g(*status\))261
+1395 y Fg(These)35 b(routines)f(compute)h(or)g(v)-5 b(alidate)34
+b(the)i(c)m(hec)m(ksums)f(for)g(the)g(currenrt)f(HDU.)i(The)e
+Fc(DATASUM)120 1508 y Fg(k)m(eyw)m(ord)d(is)e(used)g(to)i(store)g(the)f
+(n)m(umerical)f(v)-5 b(alue)29 b(of)i(the)f(32-bit,)h(1's)f(complemen)m
+(t)g(c)m(hec)m(ksum)h(for)f(the)120 1621 y(data)25 b(unit)e(alone.)39
+b(The)24 b Fc(CHECKSUM)f Fg(k)m(eyw)m(ord)i(is)f(used)f(to)j(store)f
+(the)g(ASCI)s(I)e(enco)s(ded)h(COMPLEMENT)120 1734 y(of)32
+b(the)f(c)m(hec)m(ksum)h(for)f(the)h(en)m(tire)f(HDU.)i(Storing)d(the)i
+(complemen)m(t,)g(rather)f(than)g(the)h(actual)f(c)m(hec)m(k-)120
+1847 y(sum,)26 b(forces)g(the)g(c)m(hec)m(ksum)g(for)f(the)h(whole)f
+(HDU)h(to)g(equal)f(zero.)40 b(If)25 b(the)h(\014le)f(has)g(b)s(een)g
+(mo)s(di\014ed)e(since)120 1960 y(the)31 b(c)m(hec)m(ksums)f(w)m(ere)h
+(computed,)g(then)f(the)g(HDU)i(c)m(hec)m(ksum)f(will)c(usually)h(not)j
+(equal)f(zero.)261 2073 y(The)g(returned)g Fc(dataok)f
+Fg(and)h Fc(hduok)g Fg(parameters)h(will)d(ha)m(v)m(e)k(a)f(v)-5
+b(alue)30 b(=)h(1)g(if)f(the)h(data)g(or)g(HDU)g(is)120
+2186 y(v)m(eri\014ed)c(correctly)-8 b(,)30 b(a)e(v)-5
+b(alue)28 b(=)g(0)g(if)f(the)i Fc(DATASUM)d Fg(or)i Fc(CHECKSUM)e
+Fg(k)m(eyw)m(ord)j(is)e(not)h(presen)m(t,)h(or)f(v)-5
+b(alue)28 b(=)120 2299 y(-1)j(if)e(the)i(computed)f(c)m(hec)m(ksum)h
+(is)f(not)g(correct.)120 2511 y Fc(________________________)o(____)o
+(____)o(___)o(____)o(____)o(___)o(____)o(____)o(___)o(____)o(____)o(__)
+120 2624 y(int)47 b(fits_parse_value\(char)42 b(*card,)k(char)h
+(*value,)e(char)i(*comment,)e(int)i(*status\))120 2737
+y(int)g(fits_get_keytype\(char)42 b(*value,)k(char)g(*dtype,)g(int)h
+(*status\))120 2850 y(int)g(fits_get_keyclass\(char)42
+b(*card\))120 2963 y(int)47 b(fits_parse_template\(char)41
+b(*template,)k(char)i(*card,)f(int)h(*keytype,)e(int)i(*status\))261
+3288 y(fits)p 459 3288 29 4 v 33 w(parse)p 732 3288 V
+33 w(value)29 b Fg(parses)h(the)h(input)d(80-c)m(hararacter)33
+b(header)d(k)m(eyw)m(ord)h(record,)g(returning)d(the)120
+3401 y(v)-5 b(alue)20 b(\(as)i(a)f(literal)e(c)m(haracter)k(string\))d
+(and)g(commen)m(t)i(strings.)37 b(If)20 b(the)h(k)m(eyw)m(ord)h(has)e
+(no)h(v)-5 b(alue)20 b(\(columns)120 3514 y(9-10)38 b(not)e(equal)g(to)
+h('=)f('\),)j(then)d(a)g(n)m(ull)f(v)-5 b(alue)35 b(string)h(is)f
+(returned)g(and)h(the)g(commen)m(t)h(string)f(is)f(set)120
+3627 y(equal)30 b(to)h(column)e(9)i(-)g(80)g(of)f(the)h(input)d
+(string.)261 3740 y Fc(fits)p 459 3740 V 33 w(get)p 636
+3740 V 34 w(keytype)41 b Fg(parses)i(the)g(k)m(eyw)m(ord)h(v)-5
+b(alue)42 b(string)g(to)i(determine)e(its)h(datat)m(yp)s(e.)80
+b Fc(dtype)120 3853 y Fg(returns)34 b(with)f(a)i(v)-5
+b(alue)35 b(of)g('C',)g('L',)g('I',)h('F')f(or)g('X',)h(for)f(c)m
+(haracter)h(string,)f(logical,)h(in)m(teger,)g(\015oating)120
+3966 y(p)s(oin)m(t,)30 b(or)g(complex,)g(resp)s(ectiv)m(ely)-8
+b(.)261 4079 y Fc(fits)p 459 4079 V 33 w(get)p 636 4079
+V 34 w(keyclass)31 b Fg(returns)i(a)h(classi\014cation)e(co)s(de)i
+(that)g(indicates)f(the)h(classi\014cation)e(t)m(yp)s(e)i(of)120
+4192 y(the)41 b(input)d(k)m(eyw)m(ord)j(record)f(\(e.g.,)45
+b(a)40 b(required)f(structural)g(k)m(eyw)m(ord,)44 b(a)d(TDIM)f(k)m
+(eyw)m(ord,)k(a)c(W)m(CS)120 4304 y(k)m(eyw)m(ord,)49
+b(a)d(commen)m(t)g(k)m(eyw)m(ord,)j(etc.)85 b(See)45
+b(the)h(CFITSIO)d(Reference)j(Guide)d(for)i(a)g(list)f(of)h(the)120
+4417 y(di\013eren)m(t)30 b(classi\014cation)f(co)s(des.)261
+4530 y Fc(fits)p 459 4530 V 33 w(parse)p 732 4530 V 33
+w(template)37 b Fg(tak)m(es)j(an)e(input)f(free)h(format)h(k)m(eyw)m
+(ord)g(template)g(string)f(and)g(returns)120 4643 y(a)i(formatted)g
+(80*c)m(har)h(record)e(that)h(satis\014es)f(all)g(the)g(FITS)g
+(requiremen)m(ts)g(for)g(a)h(header)f(k)m(eyw)m(ord)120
+4756 y(record.)65 b(The)38 b(template)h(should)d(generally)i(con)m
+(tain)h(3)g(tok)m(ens:)58 b(the)38 b(k)m(eyw)m(ord)h(name,)i(the)e(k)m
+(eyw)m(ord)120 4869 y(v)-5 b(alue,)28 b(and)f(the)g(k)m(eyw)m(ord)h
+(commen)m(t)h(string.)39 b(The)27 b(returned)f Fc(keytype)g
+Fg(parameter)i(indicates)e(whether)120 4982 y(the)33
+b(k)m(eyw)m(ord)g(is)f(a)h(COMMENT)g(k)m(eyw)m(ord)g(or)g(not.)48
+b(See)33 b(the)g(CFITSIO)e(Reference)j(Guide)e(for)g(more)120
+5095 y(details.)1905 5809 y(22)p eop
+%%Page: 23 23
+23 22 bop 120 573 a Fi(5)135 b(CFITSIO)44 b(File)h(Names)h(and)f
+(Filters)120 779 y Fb(5.1)112 b(Creating)37 b(New)g(Files)120
+951 y Fg(When)43 b(creating)g(a)g(new)g(output)f(\014le)g(on)h
+(magnetic)h(disk)d(with)h Fc(fits)p 2677 951 29 4 v 33
+w(create)p 2998 951 V 33 w(file)g Fg(the)h(follo)m(wing)120
+1064 y(features)31 b(are)f(supp)s(orted.)256 1251 y Fa(\017)46
+b Fg(Ov)m(erwriting,)29 b(or)h('Clobb)s(ering')e(an)j(Existing)d(File)
+347 1402 y(If)f(the)h(\014lename)f(is)g(preceded)g(b)m(y)g(an)h
+(exclamation)g(p)s(oin)m(t)e(\(!\))41 b(then)27 b(if)g(that)h(\014le)e
+(already)i(exists)f(it)347 1514 y(will)f(b)s(e)h(deleted)h(prior)e(to)j
+(creating)f(the)g(new)g(FITS)f(\014le.)39 b(Otherwise)26
+b(if)h(there)h(is)f(an)h(existing)f(\014le)347 1627 y(with)35
+b(the)g(same)h(name,)i(CFITSIO)c(will)f(not)j(o)m(v)m(erwrite)g(the)g
+(existing)e(\014le)h(and)g(will)e(return)h(an)347 1740
+y(error)28 b(status)h(co)s(de.)40 b(Note)30 b(that)f(the)f(exclamation)
+h(p)s(oin)m(t)e(is)g(a)i(sp)s(ecial)e(UNIX)h(c)m(haracter,)j(so)e(if)e
+(it)347 1853 y(is)f(used)g(on)g(the)h(command)g(line)e(rather)h(than)g
+(en)m(tered)i(at)f(a)g(task)g(prompt,)g(it)f(m)m(ust)h(b)s(e)f
+(preceded)347 1966 y(b)m(y)j(a)h(bac)m(kslash)f(to)h(force)g(the)f
+(UNIX)h(shell)d(to)k(pass)d(it)h(v)m(erbatim)g(to)h(the)g(application)d
+(program.)256 2154 y Fa(\017)46 b Fg(Compressed)30 b(Output)f(Files)347
+2304 y(If)g(the)g(output)f(disk)g(\014le)g(name)h(ends)f(with)f(the)i
+(su\016x)f('.gz',)j(then)e(CFITSIO)e(will)f(compress)j(the)347
+2417 y(\014le)38 b(using)g(the)h(gzip)g(compression)f(algorithm)g(b)s
+(efore)g(writing)f(it)i(to)h(disk.)65 b(This)37 b(can)j(reduce)347
+2530 y(the)h(amoun)m(t)g(of)g(disk)e(space)i(used)f(b)m(y)g(the)h
+(\014le.)70 b(Note)42 b(that)f(this)f(feature)h(requires)e(that)i(the)
+347 2643 y(uncompressed)e(\014le)h(b)s(e)g(constructed)h(in)e(memory)h
+(b)s(efore)g(it)g(is)g(compressed)g(and)g(written)g(to)347
+2756 y(disk,)29 b(so)i(it)f(can)h(fail)e(if)g(there)i(is)e
+(insu\016cien)m(t)g(a)m(v)-5 b(ailable)29 b(memory)-8
+b(.)347 2906 y(One)32 b(can)h(also)g(sp)s(ecify)e(that)i(an)m(y)g
+(images)g(written)f(to)h(the)g(output)f(\014le)g(should)f(b)s(e)h
+(compressed)347 3019 y(using)22 b(the)h(newly)f(dev)m(elop)s(ed)h
+(`tile-compression')f(algorithm)g(b)m(y)h(app)s(ending)e(`[compress]')j
+(to)g(the)347 3132 y(name)36 b(of)h(the)f(disk)f(\014le)g(\(as)i(in)e
+Fc(myfile.fits[compress])p Fg(\).)52 b(Refer)36 b(to)h(the)g(CFITSIO)d
+(User's)347 3245 y(Reference)d(Guide)f(for)g(more)g(information)f(ab)s
+(out)h(this)f(new)h(image)h(compression)e(format.)256
+3432 y Fa(\017)46 b Fg(Using)30 b(a)h(T)-8 b(emplate)30
+b(to)h(Create)g(a)g(New)g(FITS)e(File)347 3583 y(The)k(structure)g(of)g
+(an)m(y)h(new)f(FITS)f(\014le)h(that)h(is)e(to)i(b)s(e)f(created)h(ma)m
+(y)g(b)s(e)f(de\014ned)f(in)g(an)h(ASCI)s(I)347 3695
+y(template)c(\014le.)39 b(If)29 b(the)f(name)h(of)g(the)f(template)h
+(\014le)f(is)g(app)s(ended)e(to)k(the)e(name)h(of)g(the)f(FITS)g
+(\014le)347 3808 y(itself,)36 b(enclosed)f(in)f(paren)m(thesis)g
+(\(e.g.,)k Fc('newfile.fits\(template.tx)o(t\)')p Fg(\))29
+b(then)35 b(CFITSIO)347 3921 y(will)28 b(create)33 b(a)e(FITS)f(\014le)
+g(with)f(that)j(structure)e(b)s(efore)h(op)s(ening)e(it)h(for)h(the)g
+(application)e(to)i(use.)347 4034 y(The)h(template)h(\014le)f
+(basically)e(de\014nes)i(the)h(dimensions)c(and)j(data)h(t)m(yp)s(e)g
+(of)g(the)f(primary)f(arra)m(y)347 4147 y(and)23 b(an)m(y)h(IMA)m(GE)g
+(extensions,)h(and)e(the)g(names)g(and)g(data)h(t)m(yp)s(es)g(of)f(the)
+h(columns)e(in)g(an)m(y)i(ASCI)s(I)347 4260 y(or)35 b(binary)f(table)h
+(extensions.)54 b(The)35 b(template)g(\014le)f(can)i(also)f(b)s(e)f
+(used)h(to)g(de\014ne)g(an)m(y)g(optional)347 4373 y(k)m(eyw)m(ords)g
+(that)g(should)d(b)s(e)i(written)f(in)h(an)m(y)g(of)h(the)f(HDU)h
+(headers.)53 b(The)34 b(image)g(pixel)f(v)-5 b(alues)347
+4486 y(and)38 b(table)h(en)m(try)f(v)-5 b(alues)38 b(are)h(all)e
+(initialized)f(to)j(zero.)66 b(The)38 b(application)f(program)h(can)h
+(then)347 4599 y(write)27 b(actual)g(data)h(in)m(to)g(the)f(HDUs.)40
+b(See)28 b(the)f(CFITSIO)f(Reference)i(Guide)e(for)h(for)g(a)h
+(complete)347 4712 y(description)h(of)h(the)h(template)g(\014le)e(syn)m
+(tax.)256 4899 y Fa(\017)46 b Fg(Creating)30 b(a)h(T)-8
+b(emp)s(orary)30 b(Scratc)m(h)h(File)e(in)g(Memory)347
+5050 y(It)38 b(is)f(sometimes)h(useful)e(to)j(create)g(a)f(temp)s
+(orary)g(output)f(\014le)g(when)g(testing)h(an)g(application)347
+5162 y(program.)45 b(If)31 b(the)h(name)g(of)g(the)g(\014le)f(to)i(b)s
+(e)e(created)i(is)e(sp)s(eci\014ed)f(as)i Fc(mem:)42
+b Fg(then)32 b(CFITSIO)e(will)347 5275 y(create)39 b(the)e(\014le)g(in)
+f(memory)h(where)f(it)h(will)e(p)s(ersist)g(only)h(un)m(til)g(the)h
+(program)g(closes)h(the)f(\014le.)347 5388 y(Use)e(of)g(this)f
+Fc(mem:)48 b Fg(output)34 b(\014le)g(usually)e(enables)i(the)h(program)
+f(to)i(run)d(faster,)j(and)e(of)h(course)347 5501 y(the)c(output)f
+(\014le)f(do)s(es)h(not)h(use)f(up)f(an)m(y)i(disk)e(space.)1905
+5809 y(23)p eop
+%%Page: 24 24
+24 23 bop 120 573 a Fb(5.2)112 b(Op)s(ening)38 b(Existing)d(Files)120
+744 y Fg(When)j(op)s(ening)e(a)j(\014le)e(with)g Fc(fits)p
+1392 744 29 4 v 33 w(open)p 1617 744 V 33 w(file)p Fg(,)i(CFITSIO)e
+(can)h(read)g(a)g(v)-5 b(ariet)m(y)39 b(of)f(di\013eren)m(t)f(input)120
+857 y(\014le)30 b(formats)h(and)g(is)f(not)h(restricted)g(to)h(only)e
+(reading)g(FITS)h(format)g(\014les)f(from)h(magnetic)g(disk.)42
+b(The)120 970 y(follo)m(wing)29 b(t)m(yp)s(es)h(of)h(input)d(\014les)i
+(are)g(all)g(supp)s(orted:)256 1183 y Fa(\017)46 b Fg(FITS)30
+b(\014les)f(compressed)h(with)f Fc(zip,)47 b(gzip)29
+b Fg(or)i Fc(compress)347 1333 y Fg(If)36 b(CFITSIO)f(cannot)i(\014nd)e
+(the)i(sp)s(eci\014ed)e(\014le)g(to)i(op)s(en)f(it)g(will)e
+(automatically)i(lo)s(ok)g(for)g(a)h(\014le)347 1446
+y(with)j(the)g(same)h(ro)s(otname)h(but)d(with)h(a)h
+Fc(.gz,)46 b(.zip)p Fg(,)d(or)d Fc(.Z)g Fg(extension.)71
+b(If)41 b(it)f(\014nds)f(suc)m(h)h(a)347 1559 y(compressed)d(\014le,)g
+(it)g(will)d(allo)s(cate)j(a)g(blo)s(c)m(k)f(of)h(memory)g(and)f
+(uncompress)f(the)i(\014le)f(in)m(to)h(that)347 1672
+y(memory)25 b(space.)39 b(The)25 b(application)e(program)h(will)f(then)
+h(transparen)m(tly)g(op)s(en)g(this)g(virtual)f(FITS)347
+1785 y(\014le)35 b(in)f(memory)-8 b(.)56 b(Compressed)35
+b(\014les)f(can)i(only)f(b)s(e)f(op)s(ened)h(with)f('readonly',)j(not)f
+('readwrite')347 1898 y(\014le)30 b(access.)256 2085
+y Fa(\017)46 b Fg(FITS)30 b(\014les)f(on)h(the)h(in)m(ternet,)f(using)f
+Fc(ftp)h Fg(or)g Fc(http)f Fg(URLs)347 2236 y(Simply)20
+b(pro)m(vide)i(the)i(full)c(URL)j(as)g(the)g(name)g(of)h(the)f(\014le)f
+(that)h(y)m(ou)h(w)m(an)m(t)f(to)h(op)s(en.)38 b(F)-8
+b(or)23 b(example,)347 2348 y Fc(ftp://legacy.gsfc.nasa.go)o(v/so)o
+(ftwa)o(re/)o(fits)o(io/c)o(/te)o(stpr)o(og.s)o(td)347
+2461 y Fg(will)34 b(op)s(en)h(the)h(CFITSIO)e(test)j(FITS)e(\014le)g
+(that)i(is)d(lo)s(cated)j(on)e(the)h Fc(legacy)f Fg(mac)m(hine.)57
+b(These)347 2574 y(\014les)30 b(can)g(only)g(b)s(e)f(op)s(ened)h(with)f
+('readonly')h(\014le)g(access.)256 2762 y Fa(\017)46
+b Fg(FITS)30 b(\014les)f(on)h Fc(stdin)f Fg(or)i Fc(stdout)d
+Fg(\014le)i(streams)347 2912 y(If)k(the)g(name)h(of)f(the)h(\014le)e
+(to)i(b)s(e)f(op)s(ened)f(is)g Fc('stdin')g Fg(or)h Fc('-')f
+Fg(\(a)i(single)e(dash)g(c)m(haracter\))k(then)347 3025
+y(CFITSIO)f(will)f(read)j(the)f(\014le)g(from)g(the)h(standard)f(input)
+e(stream.)63 b(Similarly)-8 b(,)36 b(if)h(the)h(output)347
+3138 y(\014le)k(name)h(is)f Fc('stdout')f Fg(or)i Fc('-')p
+Fg(,)j(then)c(the)i(\014le)e(will)e(b)s(e)j(written)f(to)h(the)h
+(standard)e(output)347 3251 y(stream.)54 b(In)34 b(addition,)g(if)f
+(the)i(output)f(\014lename)g(is)g Fc('stdout.gz')d Fg(or)k
+Fc('-.gz')e Fg(then)h(it)g(will)e(b)s(e)347 3364 y(gzip)h(compressed)g
+(b)s(efore)g(b)s(eing)e(written)h(to)i(stdout.)49 b(This)32
+b(mec)m(hanism)g(can)i(b)s(e)e(used)g(to)i(pip)s(e)347
+3477 y(FITS)c(\014les)f(from)h(one)h(task)g(to)g(another)f(without)g
+(ha)m(ving)g(to)h(write)e(an)i(in)m(termediary)e(FITS)g(\014le)347
+3590 y(on)i(magnetic)f(disk.)256 3777 y Fa(\017)46 b
+Fg(FITS)30 b(\014les)f(that)i(exist)f(only)g(in)f(memory)-8
+b(,)31 b(or)f(shared)g(memory)-8 b(.)347 3928 y(In)38
+b(some)i(applications,)f(suc)m(h)g(as)g(real)f(time)h(data)g
+(acquisition,)h(y)m(ou)f(ma)m(y)h(w)m(an)m(t)f(to)h(ha)m(v)m(e)g(one)
+347 4040 y(pro)s(cess)31 b(write)f(a)i(FITS)e(\014le)g(in)m(to)h(a)h
+(certain)f(section)g(of)g(computer)g(memory)-8 b(,)32
+b(and)f(then)f(b)s(e)h(able)347 4153 y(to)26 b(op)s(en)f(that)g(\014le)
+f(in)g(memory)h(with)f(another)h(pro)s(cess.)39 b(There)25
+b(is)f(a)h(sp)s(ecialized)e(CFITSIO)h(op)s(en)347 4266
+y(routine)e(called)h Fc(fits)p 1102 4266 V 33 w(open)p
+1327 4266 V 33 w(memfile)f Fg(that)h(can)h(b)s(e)e(used)h(for)g(this)f
+(purp)s(ose.)37 b(See)23 b(the)g(\\CFITSIO)347 4379 y(User's)31
+b(Reference)g(Guide")f(for)g(more)g(details.)256 4567
+y Fa(\017)46 b Fg(IRAF)31 b(format)g(images)f(\(with)f
+Fc(.imh)h Fg(\014le)f(extensions\))347 4717 y(CFITSIO)38
+b(supp)s(orts)g(reading)h(IRAF)h(format)g(images)g(b)m(y)g(con)m(v)m
+(erting)g(them)g(on)f(the)h(\015y)f(in)m(to)347 4830
+y(FITS)27 b(images)h(in)e(memory)-8 b(.)40 b(The)28 b(application)d
+(program)j(then)f(reads)h(this)e(virtual)g(FITS)h(format)347
+4943 y(image)35 b(in)f(memory)-8 b(.)55 b(There)34 b(is)g(curren)m(tly)
+g(no)h(supp)s(ort)e(for)i(writing)e(IRAF)i(format)g(images,)h(or)347
+5056 y(for)30 b(reading)g(or)g(writing)f(IRAF)h(tables.)256
+5243 y Fa(\017)46 b Fg(Image)31 b(arra)m(ys)g(in)e(ra)m(w)i(binary)d
+(format)347 5394 y(If)23 b(the)h(input)d(\014le)i(is)f(a)i(ra)m(w)f
+(binary)f(data)i(arra)m(y)-8 b(,)26 b(then)d(CFITSIO)f(will)e(con)m(v)m
+(ert)25 b(it)e(on)g(the)h(\015y)f(in)m(to)g(a)347 5507
+y(virtual)f(FITS)g(image)i(with)e(the)i(basic)e(set)i(of)g(required)d
+(header)i(k)m(eyw)m(ords)h(b)s(efore)f(it)g(is)f(op)s(ened)g(b)m(y)1905
+5809 y(24)p eop
+%%Page: 25 25
+25 24 bop 347 573 a Fg(the)31 b(application)d(program.)40
+b(In)30 b(this)f(case)i(the)f(data)h(t)m(yp)s(e)g(and)e(dimensions)e
+(of)k(the)f(image)g(m)m(ust)347 686 y(b)s(e)d(sp)s(eci\014ed)e(in)h
+(square)h(brac)m(k)m(ets)h(follo)m(wing)e(the)h(\014lename)g(\(e.g.)41
+b Fc(rawfile.dat[ib512,512])p Fg(\).)347 799 y(The)30
+b(\014rst)g(c)m(haracter)i(inside)c(the)i(brac)m(k)m(ets)i(de\014nes)e
+(the)g(datat)m(yp)s(e)i(of)e(the)h(arra)m(y:)586 1049
+y Fc(b)429 b(8-bit)47 b(unsigned)e(byte)586 1161 y(i)381
+b(16-bit)47 b(signed)f(integer)586 1274 y(u)381 b(16-bit)47
+b(unsigned)e(integer)586 1387 y(j)381 b(32-bit)47 b(signed)f(integer)
+586 1500 y(r)h(or)h(f)142 b(32-bit)47 b(floating)e(point)586
+1613 y(d)381 b(64-bit)47 b(floating)e(point)347 1863
+y Fg(An)32 b(optional)e(second)i(c)m(haracter)h(sp)s(eci\014es)d(the)i
+(b)m(yte)h(order)e(of)g(the)h(arra)m(y)g(v)-5 b(alues:)43
+b(b)31 b(or)h(B)g(indi-)347 1976 y(cates)27 b(big)d(endian)f(\(as)j(in)
+e(FITS)g(\014les)g(and)g(the)i(nativ)m(e)f(format)g(of)h(SUN)e(UNIX)i
+(w)m(orkstations)f(and)347 2089 y(Mac)35 b(PCs\))d(and)h(l)f(or)h(L)g
+(indicates)f(little)g(endian)g(\(nativ)m(e)i(format)f(of)g(DEC)h(OSF)e
+(w)m(orkstations)347 2202 y(and)41 b(IBM)g(PCs\).)73
+b(If)41 b(this)f(c)m(haracter)i(is)f(omitted)g(then)f(the)i(arra)m(y)f
+(is)g(assumed)f(to)i(ha)m(v)m(e)g(the)347 2315 y(nativ)m(e)29
+b(b)m(yte)g(order)e(of)h(the)h(lo)s(cal)e(mac)m(hine.)40
+b(These)28 b(datat)m(yp)s(e)h(c)m(haracters)g(are)g(then)f(follo)m(w)m
+(ed)f(b)m(y)347 2428 y(a)f(series)f(of)g(one)h(or)f(more)h(in)m(teger)f
+(v)-5 b(alues)25 b(separated)h(b)m(y)f(commas)h(whic)m(h)e(de\014ne)h
+(the)g(size)h(of)f(eac)m(h)347 2540 y(dimension)j(of)j(the)f(ra)m(w)h
+(arra)m(y)-8 b(.)41 b(Arra)m(ys)31 b(with)e(up)g(to)i(5)g(dimensions)d
+(are)i(curren)m(tly)g(supp)s(orted.)347 2691 y(Finally)-8
+b(,)32 b(a)h(b)m(yte)g(o\013set)g(to)g(the)g(p)s(osition)d(of)i(the)h
+(\014rst)f(pixel)e(in)h(the)i(data)g(\014le)e(ma)m(y)i(b)s(e)f(sp)s
+(eci\014ed)347 2804 y(b)m(y)c(separating)g(it)g(with)e(a)j(':')40
+b(from)27 b(the)i(last)e(dimension)f(v)-5 b(alue.)39
+b(If)28 b(omitted,)h(it)e(is)g(assumed)h(that)347 2917
+y(the)h(o\013set)g(=)f(0.)41 b(This)26 b(parameter)j(ma)m(y)g(b)s(e)f
+(used)f(to)i(skip)e(o)m(v)m(er)j(an)m(y)e(header)g(information)f(in)g
+(the)347 3029 y(\014le)j(that)h(precedes)f(the)h(binary)d(data.)42
+b(F)-8 b(urther)30 b(examples:)443 3279 y Fc(raw.dat[b10000])473
+b(1-dimensional)44 b(10000)i(pixel)h(byte)f(array)443
+3392 y(raw.dat[rb400,400,12])185 b(3-dimensional)44 b(floating)i(point)
+g(big-endian)f(array)443 3505 y(img.fits[ib512,512:2880)o(])d(reads)k
+(the)h(512)g(x)h(512)e(short)h(integer)f(array)g(in)h(a)1636
+3618 y(FITS)g(file,)f(skipping)f(over)i(the)g(2880)g(byte)f(header)1905
+5809 y Fg(25)p eop
+%%Page: 26 26
+26 25 bop 120 573 a Fb(5.3)112 b(Image)37 b(Filtering)120
+744 y Fh(5.3.1)105 b(Extracting)35 b(a)g(subsection)h(of)f(an)g(image)
+120 916 y Fg(When)21 b(sp)s(ecifying)e(the)j(name)f(of)g(an)g(image)h
+(to)g(b)s(e)f(op)s(ened,)h(y)m(ou)g(can)f(select)h(a)g(rectangular)f
+(subsection)f(of)120 1029 y(the)29 b(image)f(to)h(b)s(e)f(extracted)i
+(and)e(op)s(ened)f(b)m(y)i(the)f(application)f(program.)40
+b(The)28 b(application)f(program)120 1142 y(then)k(op)s(ens)f(a)i
+(virtual)e(image)h(that)h(only)e(con)m(tains)i(the)f(pixels)f(within)e
+(the)k(sp)s(eci\014ed)d(subsection.)43 b(T)-8 b(o)120
+1255 y(do)33 b(this,)g(sp)s(ecify)f(the)i(the)f(range)h(of)f(pixels)e
+(\(start:end\))k(along)e(eac)m(h)h(axis)f(to)h(b)s(e)f(extracted)h
+(from)f(the)120 1368 y(original)28 b(image)j(enclosed)f(in)f(square)h
+(brac)m(k)m(ets.)42 b(Y)-8 b(ou)31 b(can)f(also)h(sp)s(ecify)e(an)h
+(optional)f(pixel)g(incremen)m(t)120 1481 y(\(start:end:step\))37
+b(for)f(eac)m(h)g(axis)g(of)f(the)h(input)e(image.)57
+b(A)36 b(pixel)e(step)i(=)f(1)h(will)d(b)s(e)i(assumed)g(if)g(it)g(is)
+120 1594 y(not)29 b(sp)s(eci\014ed.)38 b(If)28 b(the)h(starting)f
+(pixel)f(is)g(larger)i(then)f(the)h(end)e(pixel,)h(then)g(the)h(image)g
+(will)c(b)s(e)j(\015ipp)s(ed)120 1706 y(\(pro)s(ducing)33
+b(a)i(mirror)d(image\))j(along)g(that)g(dimension.)51
+b(An)34 b(asterisk,)i('*',)h(ma)m(y)e(b)s(e)f(used)f(to)j(sp)s(ecify)
+120 1819 y(the)25 b(en)m(tire)g(range)g(of)g(an)g(axis,)h(and)e('-*')i
+(will)d(\015ip)g(the)i(en)m(tire)g(axis.)38 b(In)24 b(the)h(follo)m
+(wing)f(examples,)i(assume)120 1932 y(that)31 b Fc(myfile.fits)c
+Fg(con)m(tains)k(a)g(512)g(x)g(512)g(pixel)e(2D)i(image.)215
+2130 y Fc(myfile.fits[201:210,)43 b(251:260])i(-)j(opens)e(a)i(10)f(x)g
+(10)g(pixel)g(subimage.)215 2356 y(myfile.fits[*,)d(512:257])i(-)h
+(opens)g(a)g(512)g(x)h(256)e(image)h(consisting)e(of)406
+2469 y(all)i(the)g(columns)f(in)h(the)g(input)f(image,)h(but)f(only)h
+(rows)g(257)406 2582 y(through)f(512.)95 b(The)46 b(image)h(will)f(be)i
+(flipped)d(along)i(the)g(Y)g(axis)406 2695 y(since)g(the)g(starting)e
+(row)i(is)g(greater)f(than)h(the)g(ending)406 2808 y(row.)215
+3033 y(myfile.fits[*:2,)d(512:257:2])h(-)i(creates)f(a)i(256)e(x)i(128)
+f(pixel)f(image.)406 3146 y(Similar)g(to)h(the)g(previous)f(example,)f
+(but)i(only)g(every)f(other)h(row)406 3259 y(and)g(column)f(is)i(read)e
+(from)h(the)g(input)f(image.)215 3485 y(myfile.fits[-*,)e(*])j(-)h
+(creates)e(an)h(image)f(containing)f(all)i(the)g(rows)g(and)406
+3598 y(columns)f(in)h(the)g(input)g(image,)f(but)h(flips)f(it)h(along)g
+(the)f(X)406 3711 y(axis.)261 3909 y Fg(If)33 b(the)g(arra)m(y)h(to)g
+(b)s(e)f(op)s(ened)f(is)h(in)f(an)h(Image)h(extension,)g(and)f(not)g
+(in)f(the)i(primary)d(arra)m(y)j(of)f(the)120 4022 y(\014le,)c(then)g
+(y)m(ou)g(need)g(to)h(sp)s(ecify)d(the)j(extension)f(name)g(or)g(n)m
+(um)m(b)s(er)f(in)f(square)i(brac)m(k)m(ets)i(b)s(efore)e(giving)120
+4135 y(the)h(subsection)e(range,)i(as)g(in)e Fc(myfile.fits[1][-*,)42
+b(*])29 b Fg(to)h(read)f(the)h(image)g(in)e(the)h(\014rst)g(extension)
+120 4248 y(in)g(the)i(\014le.)120 4485 y Fh(5.3.2)105
+b(Create)34 b(an)h(Image)f(b)m(y)h(Binning)h(T)-9 b(able)34
+b(Columns)120 4657 y Fg(Y)-8 b(ou)40 b(can)f(also)g(create)i(and)d(op)s
+(en)h(a)g(virtual)f(image)h(b)m(y)g(binning)d(the)k(v)-5
+b(alues)38 b(in)g(a)h(pair)f(of)i(columns)120 4770 y(of)f(a)h(FITS)f
+(table)g(\(in)f(other)i(w)m(ords,)h(create)g(a)e(2-D)i(histogram)e(of)g
+(the)g(v)-5 b(alues)39 b(in)f(the)h(2)h(columns\).)120
+4883 y(This)33 b(tec)m(hnique)h(is)g(often)h(used)f(in)g(X-ra)m(y)i
+(astronom)m(y)f(where)f(eac)m(h)i(detected)g(X-ra)m(y)g(photon)f
+(during)120 4996 y(an)29 b(observ)-5 b(ation)28 b(is)g(recorded)h(in)e
+(a)i(FITS)f(table.)40 b(There)29 b(are)g(t)m(ypically)e(2)j(columns)d
+(in)g(the)i(table)g(called)120 5109 y Fc(X)35 b Fg(and)h
+Fc(Y)f Fg(whic)m(h)g(record)g(the)i(pixel)d(lo)s(cation)h(of)h(that)h
+(ev)m(en)m(t)g(in)d(a)j(virtual)d(2D)i(image.)58 b(T)-8
+b(o)36 b(create)h(an)120 5222 y(image)27 b(from)f(this)g(table,)i(one)f
+(just)f(scans)h(the)g(X)g(and)f(Y)h(columns)f(and)g(coun)m(ts)h(up)f
+(ho)m(w)h(man)m(y)g(photons)120 5334 y(w)m(ere)k(recorded)g(in)f(eac)m
+(h)i(pixel)d(of)i(the)g(image.)43 b(When)30 b(table)h(binning)d(is)i
+(sp)s(eci\014ed,)g(CFITSIO)f(creates)120 5447 y(a)38
+b(temp)s(orary)e(FITS)h(primary)e(arra)m(y)j(in)e(memory)h(b)m(y)g
+(computing)g(the)g(histogram)g(of)h(the)f(v)-5 b(alues)37
+b(in)120 5560 y(the)29 b(sp)s(eci\014ed)e(columns.)39
+b(After)29 b(the)g(histogram)f(is)g(computed)h(the)g(original)e(FITS)h
+(\014le)g(con)m(taining)g(the)1905 5809 y(26)p eop
+%%Page: 27 27
+27 26 bop 120 573 a Fg(table)24 b(is)e(closed)i(and)f(the)g(temp)s
+(orary)h(FITS)e(primary)g(arra)m(y)i(is)f(op)s(ened)g(and)g(passed)g
+(to)h(the)g(application)120 686 y(program.)39 b(Th)m(us,)27
+b(the)g(application)e(program)i(nev)m(er)g(sees)h(the)f(original)e
+(FITS)h(table)h(and)f(only)g(sees)h(the)120 799 y(image)k(in)e(the)h
+(new)g(temp)s(orary)g(\014le)g(\(whic)m(h)f(has)h(no)g(extensions\).)
+261 912 y(The)f(table)g(binning)d(sp)s(eci\014er)i(is)h(enclosed)g(in)f
+(square)h(brac)m(k)m(ets)h(follo)m(wing)e(the)i(ro)s(ot)f(\014lename)g
+(and)120 1024 y(table)h(extension)g(name)h(or)f(n)m(um)m(b)s(er)f(and)h
+(b)s(egins)f(with)g(the)h(k)m(eyw)m(ord)h('bin',)f(as)g(in:)120
+1137 y Fc('myfile.fits[events][bin)41 b(\(X,Y\)]')p Fg(.)20
+b(In)h(this)g(case,)k(the)d(X)g(and)f(Y)h(columns)f(in)g(the)h('ev)m
+(en)m(ts')h(table)120 1250 y(extension)29 b(are)h(binned)e(up)g(to)j
+(create)g(the)f(image.)41 b(The)29 b(size)g(of)h(the)g(image)g(is)f
+(usually)e(determined)h(b)m(y)120 1363 y(the)22 b Fc(TLMINn)d
+Fg(and)i Fc(TLMAXn)f Fg(header)h(k)m(eyw)m(ords)h(whic)m(h)e(giv)m(e)i
+(the)f(minim)m(um)e(and)i(maxim)m(um)f(allo)m(w)m(ed)h(pixel)120
+1476 y(v)-5 b(alues)37 b(in)f(the)i(columns.)61 b(F)-8
+b(or)38 b(instance)f(if)g Fc(TLMINn)46 b(=)h(1)37 b Fg(and)g
+Fc(TLMAXn)46 b(=)i(4096)36 b Fg(for)i(b)s(oth)e(columns,)120
+1589 y(this)e(w)m(ould)g(generate)i(a)f(4096)i(x)e(4096)h(pixel)e
+(image)h(b)m(y)g(default.)53 b(This)33 b(is)h(rather)h(large,)i(so)e(y)
+m(ou)g(can)120 1702 y(also)d(sp)s(ecify)f(a)i(pixel)d(binning)f(factor)
+34 b(to)f(reduce)f(the)g(image)h(size.)46 b(F)-8 b(or)33
+b(example)f(sp)s(ecifying)e(,)j Fc('[bin)120 1815 y(\(X,Y\))46
+b(=)i(16]')29 b Fg(will)e(use)i(a)i(binning)26 b(factor)31
+b(of)f(16,)h(whic)m(h)e(will)e(pro)s(duce)i(a)h(256)h(x)f(256)h(pixel)e
+(image)h(in)120 1928 y(the)h(previous)d(example.)261
+2041 y(If)35 b(the)g(TLMIN)g(and)g(TLMAX)g(k)m(eyw)m(ords)g(don't)g
+(exist,)i(or)e(y)m(ou)g(w)m(an)m(t)h(to)g(o)m(v)m(erride)g(their)e(v)-5
+b(alues,)120 2154 y(y)m(ou)36 b(can)h(sp)s(ecify)d(the)i(image)h(range)
+f(and)f(binning)e(factor)k(directly)-8 b(,)37 b(as)f(in)f
+Fc('[bin)46 b(X)i(=)f(1:4096:16,)120 2267 y(Y=1:4096:16]')p
+Fg(.)36 b(Y)-8 b(ou)28 b(can)g(also)f(sp)s(ecify)f(the)i(datat)m(yp)s
+(e)g(of)g(the)g(created)g(image)g(b)m(y)f(app)s(ending)e(a)j(b,)g(i,)
+120 2379 y(j,)f(r,)g(or)f(d)f(\(for)h(8-bit)g(b)m(yte,)i(16-bit)f(in)m
+(tegers,)g(32-bit)f(in)m(teger,)i(32-bit)e(\015oating)g(p)s(oin)m(ts,)g
+(or)g(64-bit)h(double)120 2492 y(precision)34 b(\015oating)h(p)s(oin)m
+(t,)h(resp)s(ectiv)m(ely\))f(to)h(the)g('bin')e(k)m(eyw)m(ord)h(\(e.g.)
+58 b Fc('[binr)46 b(\(X,Y\)]')33 b Fg(creates)k(a)120
+2605 y(\015oating)i(p)s(oin)m(t)e(image\).)66 b(If)38
+b(the)h(datat)m(yp)s(e)h(is)d(not)i(sp)s(eci\014ed)e(then)h(a)h(32-bit)
+g(in)m(teger)g(image)g(will)d(b)s(e)120 2718 y(created)31
+b(b)m(y)g(default.)261 2831 y(If)39 b(the)h(column)e(name)h(is)g(not)g
+(sp)s(eci\014ed,)h(then)f(CFITSIO)f(will)f(\014rst)h(try)i(to)g(use)f
+(the)g('preferred)120 2944 y(column')34 b(as)h(sp)s(eci\014ed)e(b)m(y)i
+(the)g(CPREF)g(k)m(eyw)m(ord)g(if)f(it)h(exists)f(\(e.g.,)k('CPREF)d(=)
+g('DETX,DETY'\),)120 3057 y(otherwise)30 b(column)f(names)h('X',)i('Y')
+e(will)e(b)s(e)i(assumed)g(for)g(the)g(2)h(axes.)261
+3170 y(Note)37 b(that)f(this)e(binning)e(sp)s(eci\014er)i(is)g(not)h
+(restricted)g(to)h(only)f(2D)h(images)f(and)g(can)g(b)s(e)g(used)g(to)
+120 3283 y(create)f(1D,)f(3D,)g(or)g(4D)g(images)f(as)g(w)m(ell.)46
+b(It)32 b(is)f(also)h(p)s(ossible)e(to)j(sp)s(ecify)e(a)h(w)m(eigh)m
+(ting)g(factor)i(that)e(is)120 3396 y(applied)27 b(during)h(the)h
+(binning.)38 b(Please)29 b(refer)h(to)g(the)g(\\CFITSIO)e(User's)i
+(Reference)g(Guide")f(for)g(more)120 3509 y(details)g(on)i(these)f(adv)
+-5 b(anced)31 b(features.)1905 5809 y(27)p eop
+%%Page: 28 28
+28 27 bop 120 573 a Fb(5.4)112 b(T)-9 b(able)37 b(Filtering)120
+744 y Fh(5.4.1)105 b(Column)34 b(and)h(Keyw)m(ord)g(Filtering)120
+916 y Fg(The)29 b(column)f(or)h(k)m(eyw)m(ord)h(\014ltering)e(sp)s
+(eci\014er)g(is)g(used)h(to)h(mo)s(dify)d(the)j(column)e(structure)h
+(and/or)g(the)120 1029 y(header)h(k)m(eyw)m(ords)h(in)e(the)i(HDU)g
+(that)g(w)m(as)g(selected)g(with)e(the)i(previous)e(HDU)i(lo)s(cation)f
+(sp)s(eci\014er.)39 b(It)120 1142 y(can)31 b(b)s(e)e(used)h(to)h(p)s
+(erform)e(the)i(follo)m(wing)d(t)m(yp)s(es)j(of)f(op)s(erations.)256
+1354 y Fa(\017)46 b Fg(App)s(end)35 b(a)h(new)g(column)f(to)i(a)f
+(table)g(b)m(y)h(giving)e(the)h(column)f(name,)j(optionally)d(follo)m
+(w)m(ed)g(b)m(y)347 1467 y(the)e(datat)m(yp)s(e)h(in)d(paren)m(theses,)
+j(follo)m(w)m(ed)e(b)m(y)h(an)g(equals)f(sign)g(and)g(the)h(arithmetic)
+f(expression)347 1580 y(to)e(b)s(e)e(used)g(to)i(compute)f(the)g(v)-5
+b(alue.)40 b(The)28 b(datat)m(yp)s(e)i(is)e(sp)s(eci\014ed)f(using)h
+(the)h(same)g(syn)m(tax)h(that)347 1693 y(is)j(allo)m(w)m(ed)g(for)g
+(the)g(v)-5 b(alue)33 b(of)h(the)f(FITS)g(TF)m(ORMn)g(k)m(eyw)m(ord)h
+(\(e.g.,)i('I',)e('J',)g('E',)f('D',)i(etc.)51 b(for)347
+1806 y(binary)31 b(tables,)h(and)g('I8',)h(F12.3',)i('E20.12',)g(etc.)
+47 b(for)32 b(ASCI)s(I)f(tables\).)46 b(If)32 b(the)g(datat)m(yp)s(e)h
+(is)e(not)347 1919 y(sp)s(eci\014ed)e(then)h(a)h(default)e(datat)m(yp)s
+(e)j(will)27 b(b)s(e)j(c)m(hosen)h(dep)s(ending)d(on)i(the)h
+(expression.)256 2107 y Fa(\017)46 b Fg(Create)33 b(a)g(new)f(header)g
+(k)m(eyw)m(ord)h(b)m(y)f(giving)f(the)i(k)m(eyw)m(ord)g(name,)g
+(preceded)f(b)m(y)g(a)h(p)s(ound)d(sign)347 2220 y('#',)24
+b(follo)m(w)m(ed)c(b)m(y)h(an)g(equals)f(sign)g(and)g(an)h(arithmetic)f
+(expression)g(for)h(the)g(v)-5 b(alue)20 b(of)h(the)h(k)m(eyw)m(ord.)
+347 2332 y(The)k(expression)f(ma)m(y)i(b)s(e)f(a)h(function)e(of)i
+(other)f(header)g(k)m(eyw)m(ord)h(v)-5 b(alues.)39 b(The)26
+b(commen)m(t)h(string)347 2445 y(for)40 b(the)h(k)m(eyw)m(ord)g(ma)m(y)
+g(b)s(e)f(sp)s(eci\014ed)f(in)g(paren)m(theses)i(immediately)d(follo)m
+(wing)h(the)i(k)m(eyw)m(ord)347 2558 y(name.)256 2746
+y Fa(\017)46 b Fg(Ov)m(erwrite)29 b(the)g(v)-5 b(alues)29
+b(in)e(an)i(existing)g(column)e(or)j(k)m(eyw)m(ord)f(b)m(y)g(giving)f
+(the)i(name)f(follo)m(w)m(ed)f(b)m(y)347 2859 y(an)j(equals)e(sign)h
+(and)f(an)i(arithmetic)e(expression.)256 3046 y Fa(\017)46
+b Fg(Select)35 b(a)f(set)g(of)h(columns)d(to)j(b)s(e)e(included)f(in)g
+(the)j(\014ltered)e(\014le)g(b)m(y)h(listing)d(the)k(column)d(names)347
+3159 y(separated)d(with)e(semi-colons.)39 b(Wild)27 b(card)h(c)m
+(haracters)i(ma)m(y)e(b)s(e)g(used)f(in)g(the)i(column)e(names)h(to)347
+3272 y(matc)m(h)33 b(m)m(ultiple)d(columns.)45 b(An)m(y)32
+b(other)h(columns)d(in)h(the)i(input)d(table)i(will)d(not)k(app)s(ear)e
+(in)g(the)347 3385 y(\014ltered)f(\014le.)256 3573 y
+Fa(\017)46 b Fg(Delete)31 b(a)f(column)f(or)g(k)m(eyw)m(ord)h(b)m(y)g
+(listing)d(the)j(name)g(preceded)f(b)m(y)h(a)g(min)m(us)e(sign)g(or)i
+(an)g(excla-)347 3686 y(mation)g(mark)g(\(!\))256 3873
+y Fa(\017)46 b Fg(Rename)31 b(an)f(existing)g(column)f(or)h(k)m(eyw)m
+(ord)h(with)e(the)i(syn)m(tax)g('NewName)g(==)f(OldName'.)261
+4086 y(The)20 b(column)f(\014ltering)g(sp)s(eci\014er)f(is)i(enclosed)g
+(in)f(square)h(brac)m(k)m(ets)h(and)f(b)s(egins)f(with)g(the)h(string)g
+('col'.)120 4199 y(Multiple)29 b(op)s(erations)h(can)h(b)s(e)f(p)s
+(erformed)f(b)m(y)i(separating)f(them)h(with)e(semi-colons.)41
+b(F)-8 b(or)32 b(complex)e(or)120 4312 y(commonly)g(used)h(op)s
+(erations,)f(y)m(ou)i(can)f(write)f(the)h(column)f(\014lter)g(to)i(a)f
+(text)h(\014le,)e(and)h(then)f(use)h(it)f(b)m(y)120 4425
+y(giving)f(the)i(name)f(of)h(the)f(text)i(\014le,)d(preceded)h(b)m(y)h
+(a)f('@')h(c)m(haracter.)261 4538 y(Some)g(examples:)215
+4750 y Fc([col)47 b(PI=PHA)f(*)i(1.1)f(+)g(0.2])285 b(-)48
+b(creates)e(new)g(PI)i(column)e(from)g(PHA)h(values)215
+4976 y([col)g(rate)g(=)g(counts/exposure])91 b(-)48 b(creates)e(or)h
+(overwrites)e(the)i(rate)f(column)g(by)1743 5089 y(dividing)f(the)i
+(counts)f(column)g(by)i(the)1743 5202 y(EXPOSURE)d(keyword)h(value.)215
+5428 y([col)h(TIME;)f(X;)i(Y])667 b(-)48 b(only)e(the)h(listed)f
+(columns)g(will)h(appear)1743 5540 y(in)g(the)g(filtered)e(file)1905
+5809 y Fg(28)p eop
+%%Page: 29 29
+29 28 bop 215 686 a Fc([col)47 b(Time;*raw])713 b(-)48
+b(include)e(the)g(Time)h(column)f(and)h(any)g(other)1743
+799 y(columns)f(whose)g(name)h(ends)f(with)h('raw'.)215
+1024 y([col)g(-TIME;)f(Good)h(==)g(STATUS])141 b(-)48
+b(deletes)e(the)g(TIME)h(column)f(and)1743 1137 y(renames)g(the)g
+(STATUS)h(column)f(to)h(GOOD)215 1363 y([col)g(@colfilt.txt])569
+b(-)48 b(uses)e(the)h(filtering)f(expression)f(in)1743
+1476 y(the)i(colfilt.txt)d(text)j(file)261 1689 y Fg(The)30
+b(original)f(\014le)h(is)g(not)h(c)m(hanged)g(b)m(y)g(this)f
+(\014ltering)f(op)s(eration,)h(and)g(instead)g(the)h(mo)s
+(di\014cations)120 1802 y(are)36 b(made)f(on)h(a)f(temp)s(orary)h(cop)m
+(y)g(of)f(the)h(input)d(FITS)i(\014le)g(\(usually)e(in)h(memory\),)k
+(whic)m(h)c(includes)120 1914 y(a)42 b(cop)m(y)g(of)g(all)e(the)i
+(other)g(HDUs)g(in)e(the)i(input)e(\014le.)73 b(The)41
+b(original)f(input)g(\014le)g(is)h(closed)g(and)g(the)120
+2027 y(application)29 b(program)h(op)s(ens)f(the)i(\014ltered)e(cop)m
+(y)i(of)g(the)g(\014le.)120 2268 y Fh(5.4.2)105 b(Ro)m(w)36
+b(Filtering)120 2439 y Fg(The)22 b(ro)m(w)h(\014lter)f(is)g(used)g(to)h
+(select)g(a)h(subset)e(of)h(the)g(ro)m(ws)f(from)h(a)g(table)f(based)h
+(on)f(a)i(b)s(o)s(olean)d(expression.)120 2552 y(A)37
+b(temp)s(orary)g(new)f(FITS)g(\014le)h(is)f(created)i(on)f(the)g(\015y)
+f(\(usually)f(in)h(memory\))h(whic)m(h)f(con)m(tains)h(only)120
+2665 y(those)30 b(ro)m(ws)g(for)g(whic)m(h)f(the)h(ro)m(w)g(\014lter)f
+(expression)f(ev)-5 b(aluates)31 b(to)f(true)g(\(i.e.,)h(not)f(equal)f
+(to)i(zero\).)42 b(The)120 2778 y(primary)24 b(arra)m(y)j(and)e(an)m(y)
+h(other)h(extensions)e(in)g(the)h(input)e(\014le)h(are)i(also)f(copied)
+f(to)i(the)f(temp)s(orary)g(\014le.)120 2891 y(The)h(original)f(FITS)h
+(\014le)g(is)g(closed)g(and)h(the)g(new)f(temp)s(orary)g(\014le)g(is)g
+(then)g(op)s(ened)g(b)m(y)h(the)g(application)120 3004
+y(program.)261 3117 y(The)f(ro)m(w)g(\014lter)f(expression)g(is)g
+(enclosed)g(in)g(square)h(brac)m(k)m(ets)h(follo)m(wing)e(the)h(\014le)
+f(name)h(and)f(exten-)120 3230 y(sion)31 b(name.)48 b(F)-8
+b(or)33 b(example,)g Fc('file.fits[events][GRAD)o(E==5)o(0]')26
+b Fg(selects)33 b(only)f(those)h(ro)m(ws)f(in)f(the)120
+3342 y(EVENTS)f(table)g(where)g(the)g(GRADE)h(column)f(v)-5
+b(alue)29 b(is)h(equal)g(to)h(50\).)261 3455 y(The)d(ro)m(w)h
+(\014ltering)d(expression)i(can)g(b)s(e)g(an)h(arbitrarily)c(complex)k
+(series)e(of)i(op)s(erations)f(p)s(erformed)120 3568
+y(on)e(constan)m(ts,)i(k)m(eyw)m(ord)e(v)-5 b(alues,)26
+b(and)f(column)g(data)h(tak)m(en)h(from)e(the)h(sp)s(eci\014ed)e(FITS)h
+(T)-8 b(ABLE)26 b(exten-)120 3681 y(sion.)39 b(The)27
+b(expression)f(also)i(can)f(b)s(e)g(written)g(in)m(to)h(a)f(text)i
+(\014le)e(and)g(then)g(used)g(b)m(y)g(giving)f(the)i(\014lename)120
+3794 y(preceded)i(b)m(y)g(a)h('@')g(c)m(haracter,)h(as)e(in)f
+Fc('[@rowfilt.txt]')p Fg(.)261 3907 y(Keyw)m(ord)40 b(and)f(column)g
+(data)h(are)h(referenced)f(b)m(y)f(name.)70 b(An)m(y)40
+b(string)f(of)h(c)m(haracters)h(not)f(sur-)120 4020 y(rounded)30
+b(b)m(y)i(quotes)g(\(ie,)g(a)h(constan)m(t)g(string\))e(or)g(follo)m(w)
+m(ed)h(b)m(y)f(an)h(op)s(en)f(paren)m(theses)h(\(ie,)h(a)f(function)120
+4133 y(name\))e(will)c(b)s(e)j(initially)c(in)m(terpreted)k(as)g(a)h
+(column)e(name)h(and)g(its)g(con)m(ten)m(ts)i(for)e(the)g(curren)m(t)g
+(ro)m(w)g(in-)120 4246 y(serted)e(in)m(to)g(the)h(expression.)38
+b(If)27 b(no)g(suc)m(h)g(column)f(exists,)i(a)f(k)m(eyw)m(ord)h(of)g
+(that)f(name)h(will)c(b)s(e)j(searc)m(hed)120 4359 y(for)34
+b(and)f(its)g(v)-5 b(alue)34 b(used,)g(if)f(found.)50
+b(T)-8 b(o)35 b(force)f(the)g(name)g(to)h(b)s(e)e(in)m(terpreted)g(as)i
+(a)f(k)m(eyw)m(ord)g(\(in)f(case)120 4472 y(there)28
+b(is)f(b)s(oth)g(a)h(column)f(and)g(k)m(eyw)m(ord)h(with)f(the)h(same)g
+(name\),)h(precede)f(the)g(k)m(eyw)m(ord)h(name)e(with)g(a)120
+4584 y(single)j(p)s(ound)f(sign,)i('#',)h(as)g(in)e Fc(#NAXIS2)p
+Fg(.)41 b(Due)32 b(to)g(the)f(generalities)g(of)g(FITS)g(column)f(and)h
+(k)m(eyw)m(ord)120 4697 y(names,)c(if)d(the)i(column)f(or)g(k)m(eyw)m
+(ord)h(name)g(con)m(tains)g(a)g(space)g(or)g(a)g(c)m(haracter)h(whic)m
+(h)e(migh)m(t)g(app)s(ear)g(as)120 4810 y(an)32 b(arithmetic)f(term)h
+(then)g(inclose)f(the)h(name)g(in)e('$')j(c)m(haracters)h(as)e(in)f
+Fc($MAX)46 b(PHA$)31 b Fg(or)h Fc(#$MAX-PHA$)p Fg(.)120
+4923 y(The)e(names)g(are)h(case)g(insensitiv)m(e.)261
+5036 y(T)-8 b(o)37 b(access)g(a)g(table)f(en)m(try)h(in)e(a)h(ro)m(w)h
+(other)f(than)g(the)h(curren)m(t)f(one,)i(follo)m(w)e(the)g(column's)f
+(name)120 5149 y(with)j(a)h(ro)m(w)g(o\013set)g(within)e(curly)g
+(braces.)66 b(F)-8 b(or)40 b(example,)h Fc('PHA)p Fa(f)p
+Fc(-3)p Fa(g)p Fc(')d Fg(will)e(ev)-5 b(aluate)39 b(to)h(the)f(v)-5
+b(alue)120 5262 y(of)40 b(column)e(PHA,)i(3)g(ro)m(ws)f(ab)s(o)m(v)m(e)
+i(the)f(ro)m(w)g(curren)m(tly)e(b)s(eing)g(pro)s(cessed.)68
+b(One)39 b(cannot)h(sp)s(ecify)e(an)120 5375 y(absolute)32
+b(ro)m(w)g(n)m(um)m(b)s(er,)f(only)g(a)i(relativ)m(e)f(o\013set.)47
+b(Ro)m(ws)32 b(that)h(fall)d(outside)h(the)i(table)f(will)d(b)s(e)i
+(treated)120 5488 y(as)g(unde\014ned,)d(or)i(NULLs.)1905
+5809 y(29)p eop
+%%Page: 30 30
+30 29 bop 261 573 a Fg(Bo)s(olean)31 b(op)s(erators)g(can)g(b)s(e)g
+(used)f(in)f(the)i(expression)f(in)f(either)i(their)f(F)-8
+b(ortran)31 b(or)g(C)f(forms.)42 b(The)120 686 y(follo)m(wing)29
+b(b)s(o)s(olean)g(op)s(erators)i(are)g(a)m(v)-5 b(ailable:)311
+886 y Fc("equal")428 b(.eq.)46 b(.EQ.)h(==)95 b("not)46
+b(equal")476 b(.ne.)94 b(.NE.)h(!=)311 999 y("less)46
+b(than")238 b(.lt.)46 b(.LT.)h(<)143 b("less)46 b(than/equal")188
+b(.le.)94 b(.LE.)h(<=)47 b(=<)311 1112 y("greater)e(than")95
+b(.gt.)46 b(.GT.)h(>)143 b("greater)45 b(than/equal")g(.ge.)94
+b(.GE.)h(>=)47 b(=>)311 1225 y("or")572 b(.or.)46 b(.OR.)h(||)95
+b("and")762 b(.and.)46 b(.AND.)h(&&)311 1337 y("negation")236
+b(.not.)46 b(.NOT.)h(!)95 b("approx.)45 b(equal\(1e-7\)")92
+b(~)261 1537 y Fg(Note)34 b(that)g(the)f(exclamation)g(p)s(oin)m(t,)g
+(')10 b(!',)34 b(is)e(a)i(sp)s(ecial)d(UNIX)i(c)m(haracter,)j(so)d(if)f
+(it)g(is)g(used)g(on)h(the)120 1650 y(command)f(line)f(rather)h(than)g
+(en)m(tered)h(at)g(a)g(task)g(prompt,)f(it)g(m)m(ust)g(b)s(e)g
+(preceded)g(b)m(y)g(a)h(bac)m(kslash)f(to)120 1763 y(force)f(the)g
+(UNIX)f(shell)f(to)i(ignore)f(it.)261 1876 y(The)d(expression)e(ma)m(y)
+j(also)f(include)e(arithmetic)h(op)s(erators)h(and)f(functions.)38
+b(T)-8 b(rigonometric)27 b(func-)120 1989 y(tions)g(use)h(radians,)g
+(not)g(degrees.)40 b(The)28 b(follo)m(wing)f(arithmetic)g(op)s(erators)
+h(and)g(functions)e(can)j(b)s(e)e(used)120 2102 y(in)i(the)i
+(expression)e(\(function)g(names)h(are)h(case)h(insensitiv)m(e\):)311
+2302 y Fc("addition")522 b(+)477 b("subtraction")d(-)311
+2415 y("multiplication")234 b(*)477 b("division")618
+b(/)311 2528 y("negation")522 b(-)477 b("exponentiation")330
+b(**)143 b(^)311 2641 y("absolute)45 b(value")237 b(abs\(x\))g
+("cosine")714 b(cos\(x\))311 2754 y("sine")g(sin\(x\))237
+b("tangent")666 b(tan\(x\))311 2867 y("arc)47 b(cosine")427
+b(arccos\(x\))93 b("arc)47 b(sine")619 b(arcsin\(x\))311
+2979 y("arc)47 b(tangent")379 b(arctan\(x\))93 b("arc)47
+b(tangent")475 b(arctan2\(x,y\))311 3092 y("exponential")378
+b(exp\(x\))237 b("square)46 b(root")476 b(sqrt\(x\))311
+3205 y("natural)45 b(log")381 b(log\(x\))237 b("common)46
+b(log")524 b(log10\(x\))311 3318 y("modulus")570 b(i)48
+b(\045)f(j)286 b("random)46 b(#)h([0.0,1.0\)")141 b(random\(\))311
+3431 y("minimum")570 b(min\(x,y\))141 b("maximum")666
+b(max\(x,y\))311 3544 y("if-then-else")330 b(b?x:y)261
+3744 y Fg(The)37 b(follo)m(wing)f(t)m(yp)s(e)i(casting)f(op)s(erators)h
+(are)g(a)m(v)-5 b(ailable,)38 b(where)f(the)h(inclosing)d(paren)m
+(theses)j(are)120 3857 y(required)22 b(and)i(tak)m(en)h(from)f(the)h(C)
+f(language)g(usage.)40 b(Also,)25 b(the)f(in)m(teger)h(to)g(real)f
+(casts)h(v)-5 b(alues)24 b(to)h(double)120 3970 y(precision:)884
+4170 y Fc("real)46 b(to)h(integer")189 b(\(int\))46 b(x)239
+b(\(INT\))46 b(x)884 4283 y("integer)f(to)i(real")190
+b(\(float\))46 b(i)143 b(\(FLOAT\))45 b(i)261 4483 y
+Fg(Sev)m(eral)31 b(constan)m(ts)g(are)g(built)d(in)h(for)h(use)g(in)f
+(n)m(umerical)g(expressions:)502 4683 y Fc(#pi)667 b(3.1415...)284
+b(#e)620 b(2.7182...)502 4796 y(#deg)f(#pi/180)380 b(#row)524
+b(current)46 b(row)h(number)502 4909 y(#null)428 b(undefined)45
+b(value)142 b(#snull)428 b(undefined)45 b(string)261
+5109 y Fg(A)d(string)f(constan)m(t)i(m)m(ust)f(b)s(e)f(enclosed)h(in)f
+(quotes)h(as)g(in)f('Crab'.)75 b(The)41 b("n)m(ull")g(constan)m(ts)i
+(are)120 5222 y(useful)36 b(for)h(conditionally)e(setting)j(table)f(v)
+-5 b(alues)37 b(to)h(a)g(NULL,)g(or)f(unde\014ned,)h(v)-5
+b(alue)37 b(\(F)-8 b(or)38 b(example,)120 5334 y Fc("col1==-99)45
+b(?)95 b(#NULL)47 b(:)g(col1")p Fg(\).)261 5447 y(There)33
+b(is)g(also)g(a)h(function)f(for)g(testing)h(if)e(t)m(w)m(o)j(v)-5
+b(alues)33 b(are)h(close)g(to)g(eac)m(h)h(other,)g(i.e.,)g(if)d(they)i
+(are)120 5560 y("near")29 b(eac)m(h)g(other)f(to)g(within)e(a)i(user)f
+(sp)s(eci\014ed)f(tolerance.)41 b(The)27 b(argumen)m(ts,)i
+Fc(value)p 3184 5560 29 4 v 33 w(1)e Fg(and)h Fc(value)p
+3707 5560 V 33 w(2)1905 5809 y Fg(30)p eop
+%%Page: 31 31
+31 30 bop 120 573 a Fg(can)39 b(b)s(e)g(in)m(teger)g(or)g(real)f(and)h
+(represen)m(t)g(the)g(t)m(w)m(o)h(v)-5 b(alues)38 b(who's)h(pro)m
+(ximit)m(y)f(is)g(b)s(eing)g(tested)h(to)h(b)s(e)120
+686 y(within)28 b(the)i(sp)s(eci\014ed)f(tolerance,)i(also)g(an)f(in)m
+(teger)h(or)f(real:)1075 880 y Fc(near\(value_1,)44 b(value_2,)h
+(tolerance\))261 1074 y Fg(When)30 b(a)h(NULL,)f(or)h(unde\014ned,)d(v)
+-5 b(alue)30 b(is)f(encoun)m(tered)i(in)e(the)h(FITS)g(table,)g(the)h
+(expression)e(will)120 1186 y(ev)-5 b(aluate)42 b(to)g(NULL)g(unless)d
+(the)j(unde\014ned)d(v)-5 b(alue)41 b(is)g(not)g(actually)g(required)f
+(for)h(ev)-5 b(aluation,)44 b(e.g.)120 1299 y("TR)m(UE)e(.or.)76
+b(NULL")42 b(ev)-5 b(aluates)43 b(to)g(TR)m(UE.)f(The)f(follo)m(wing)g
+(t)m(w)m(o)i(functions)e(allo)m(w)g(some)h(NULL)120 1412
+y(detection)31 b(and)f(handling:)1027 1606 y Fc(ISNULL\(x\))1027
+1719 y(DEFNULL\(x,y\))261 1913 y Fg(The)43 b(former)g(returns)f(a)i(b)s
+(o)s(olean)e(v)-5 b(alue)43 b(of)g(TR)m(UE)h(if)e(the)i(argumen)m(t)f
+(x)h(is)e(NULL.)i(The)e(later)120 2026 y("de\014nes")e(a)g(v)-5
+b(alue)39 b(to)h(b)s(e)g(substituted)e(for)h(NULL)h(v)-5
+b(alues;)44 b(it)39 b(returns)f(the)i(v)-5 b(alue)40
+b(of)f(x)h(if)f(x)g(is)g(not)120 2139 y(NULL,)31 b(otherwise)e(it)h
+(returns)f(the)i(v)-5 b(alue)30 b(of)g(y)-8 b(.)261 2252
+y(Bit)31 b(masks)g(can)g(b)s(e)f(used)g(to)h(select)h(out)f(ro)m(ws)g
+(from)f(bit)g(columns)f(\()p Fc(TFORMn)47 b(=)g(#X)p
+Fg(\))31 b(in)e(FITS)h(\014les.)120 2365 y(T)-8 b(o)31
+b(represen)m(t)f(the)h(mask,)f(binary)-8 b(,)30 b(o)s(ctal,)h(and)e
+(hex)i(formats)f(are)h(allo)m(w)m(ed:)931 2558 y Fc(binary:)142
+b(b0110xx1010000101xxxx00)o(01)931 2671 y(octal:)190
+b(o720x1)46 b(->)h(\(b111010000xxx001\))931 2784 y(hex:)286
+b(h0FxD)94 b(->)47 b(\(b00001111xxxx1101\))261 2978 y
+Fg(In)28 b(all)g(the)i(represen)m(tations,)f(an)g(x)g(or)g(X)g(is)f
+(allo)m(w)m(ed)h(in)f(the)h(mask)g(as)h(a)f(wild)e(card.)40
+b(Note)30 b(that)g(the)120 3091 y(x)i(represen)m(ts)f(a)h(di\013eren)m
+(t)f(n)m(um)m(b)s(er)g(of)h(wild)d(card)j(bits)e(in)g(eac)m(h)j
+(represen)m(tation.)45 b(All)30 b(represen)m(tations)120
+3204 y(are)h(case)g(insensitiv)m(e.)261 3317 y(T)-8 b(o)38
+b(construct)f(the)h(b)s(o)s(olean)e(expression)g(using)g(the)h(mask)h
+(as)f(the)h(b)s(o)s(olean)e(equal)h(op)s(erator)g(de-)120
+3430 y(scrib)s(ed)29 b(ab)s(o)m(v)m(e)i(on)g(a)g(bit)f(table)g(column.)
+40 b(F)-8 b(or)32 b(example,)e(if)g(y)m(ou)h(had)f(a)h(7)g(bit)e
+(column)h(named)g(\015ags)h(in)120 3543 y(a)36 b(FITS)e(table)i(and)f
+(w)m(an)m(ted)h(all)e(ro)m(ws)h(ha)m(ving)g(the)h(bit)e(pattern)i
+(0010011,)k(the)35 b(selection)h(expression)120 3656
+y(w)m(ould)29 b(b)s(e:)1456 3850 y Fc(flags)47 b(==)g(b0010011)311
+3962 y(or)1456 4075 y(flags)g(.eq.)f(b10011)261 4269
+y Fg(It)32 b(is)e(also)i(p)s(ossible)d(to)j(test)g(if)f(a)h(range)f(of)
+h(bits)e(is)h(less)g(than,)g(less)g(than)h(equal,)f(greater)i(than)e
+(and)120 4382 y(greater)h(than)e(equal)g(to)h(a)f(particular)f(b)s(o)s
+(olean)h(v)-5 b(alue:)1456 4576 y Fc(flags)47 b(<=)g(bxxx010xx)1456
+4689 y(flags)g(.gt.)f(bxxx100xx)1456 4802 y(flags)h(.le.)f(b1xxxxxxx)
+261 4996 y Fg(Notice)31 b(the)g(use)f(of)h(the)f(x)h(bit)e(v)-5
+b(alue)30 b(to)h(limit)d(the)j(range)f(of)h(bits)e(b)s(eing)g
+(compared.)261 5109 y(It)k(is)f(not)h(necessary)g(to)g(sp)s(ecify)f
+(the)g(leading)g(\(most)h(signi\014can)m(t\))f(zero)i(\(0\))g(bits)d
+(in)h(the)h(mask,)g(as)120 5222 y(sho)m(wn)d(in)f(the)h(second)h
+(expression)e(ab)s(o)m(v)m(e.)261 5334 y(Bit)h(wise)f(AND,)h(OR)g(and)f
+(NOT)g(op)s(erations)g(are)h(also)g(p)s(ossible)d(on)i(t)m(w)m(o)i(or)f
+(more)g(bit)f(\014elds)f(using)120 5447 y(the)38 b('&'\(AND\),)h(')p
+Fa(j)p Fg('\(OR\),)g(and)e(the)h(')10 b(!'\(NOT\))38
+b(op)s(erators.)63 b(All)36 b(of)i(these)g(op)s(erators)g(result)e(in)g
+(a)i(bit)120 5560 y(\014eld)29 b(whic)m(h)g(can)i(then)f(b)s(e)g(used)f
+(with)g(the)i(equal)f(op)s(erator.)41 b(F)-8 b(or)31
+b(example:)1905 5809 y(31)p eop
+%%Page: 32 32
+32 31 bop 1361 573 a Fc(\(!flags\))45 b(==)j(b1101100)1361
+686 y(\(flags)e(&)h(b1000001\))f(==)h(bx000001)261 887
+y Fg(Bit)35 b(\014elds)e(can)h(b)s(e)g(app)s(ended)f(as)i(w)m(ell)e
+(using)g(the)i('+')g(op)s(erator.)53 b(Strings)33 b(can)i(b)s(e)f
+(concatenated)120 1000 y(this)29 b(w)m(a)m(y)-8 b(,)32
+b(to)s(o.)120 1238 y Fh(5.4.3)105 b(Go)s(o)s(d)36 b(Time)e(In)m(terv)-6
+b(al)34 b(Filtering)120 1410 y Fg(A)27 b(common)g(\014ltering)e(metho)s
+(d)i(in)m(v)m(olv)m(es)g(selecting)g(ro)m(ws)f(whic)m(h)g(ha)m(v)m(e)i
+(a)g(time)e(v)-5 b(alue)27 b(whic)m(h)e(lies)h(within)120
+1523 y(what)38 b(is)e(called)h(a)h(Go)s(o)s(d)g(Time)e(In)m(terv)-5
+b(al)38 b(or)f(GTI.)h(The)f(time)h(in)m(terv)-5 b(als)36
+b(are)i(de\014ned)f(in)f(a)i(separate)120 1636 y(FITS)31
+b(table)h(extension)f(whic)m(h)g(con)m(tains)h(2)g(columns)e(giving)h
+(the)h(start)g(and)f(stop)h(time)g(of)g(eac)m(h)g(go)s(o)s(d)120
+1749 y(in)m(terv)-5 b(al.)59 b(The)37 b(\014ltering)e(op)s(eration)h
+(accepts)i(only)e(those)h(ro)m(ws)g(of)g(the)g(input)e(table)i(whic)m
+(h)e(ha)m(v)m(e)j(an)120 1861 y(asso)s(ciated)31 b(time)g(whic)m(h)f
+(falls)f(within)f(one)k(of)f(the)g(time)f(in)m(terv)-5
+b(als)30 b(de\014ned)g(in)g(the)h(GTI)f(extension.)42
+b(A)120 1974 y(high)28 b(lev)m(el)i(function,)f
+(gti\014lter\(a,b,c,d\),)i(is)d(a)m(v)-5 b(ailable)30
+b(whic)m(h)e(ev)-5 b(aluates)31 b(eac)m(h)g(ro)m(w)f(of)g(the)g(input)d
+(table)120 2087 y(and)j(returns)g(TR)m(UE)g(or)h(F)-10
+b(ALSE)30 b(dep)s(ending)f(whether)h(the)g(ro)m(w)h(is)f(inside)e(or)j
+(outside)f(the)h(go)s(o)s(d)g(time)120 2200 y(in)m(terv)-5
+b(al.)40 b(The)30 b(syn)m(tax)h(is)406 2401 y Fc(gtifilter\()45
+b([)j("gtifile")d([,)i(expr)g([,)g("STARTCOL",)e("STOPCOL")g(])j(])f(])
+g(\))120 2603 y Fg(where)35 b(eac)m(h)i("[]")g(demarks)e(optional)f
+(parameters.)57 b(Note)37 b(that)f(the)g(quotes)g(around)e(the)i
+(gti\014le)f(and)120 2716 y(ST)-8 b(AR)g(T/STOP)31 b(column)h(are)h
+(required.)45 b(Either)32 b(single)f(or)h(double)g(quote)h(c)m
+(haracters)h(ma)m(y)f(b)s(e)f(used.)120 2828 y(The)c(gti\014le,)g(if)f
+(sp)s(eci\014ed,)h(can)g(b)s(e)g(blank)f(\(""\))j(whic)m(h)d(will)f
+(mean)j(to)g(use)f(the)g(\014rst)g(extension)g(with)f(the)120
+2941 y(name)c("*GTI*")i(in)d(the)h(curren)m(t)g(\014le,)h(a)f(plain)e
+(extension)i(sp)s(eci\014er)f(\(eg,)k("+2",)f("[2]",)i(or)c
+("[STDGTI]"\))120 3054 y(whic)m(h)f(will)f(b)s(e)i(used)g(to)i(select)f
+(an)g(extension)f(in)f(the)i(curren)m(t)g(\014le,)g(or)g(a)g(regular)e
+(\014lename)h(with)g(or)g(with-)120 3167 y(out)j(an)g(extension)g(sp)s
+(eci\014er)e(whic)m(h)h(in)f(the)j(latter)f(case)h(will)c(mean)j(to)h
+(use)f(the)g(\014rst)f(extension)h(with)e(an)120 3280
+y(extension)29 b(name)h("*GTI*".)42 b(Expr)28 b(can)i(b)s(e)f(an)m(y)g
+(arithmetic)g(expression,)g(including)d(simply)h(the)j(time)120
+3393 y(column)j(name.)52 b(A)34 b(v)m(ector)i(time)e(expression)f(will)
+e(pro)s(duce)i(a)i(v)m(ector)g(b)s(o)s(olean)f(result.)50
+b(ST)-8 b(AR)g(TCOL)120 3506 y(and)33 b(STOPCOL)f(are)j(the)f(names)g
+(of)g(the)g(ST)-8 b(AR)g(T/STOP)33 b(columns)g(in)f(the)j(GTI)e
+(extension.)52 b(If)33 b(one)120 3619 y(of)e(them)f(is)f(sp)s
+(eci\014ed,)g(they)i(b)s(oth)e(m)m(ust)i(b)s(e.)261 3732
+y(In)37 b(its)h(simplest)e(form,)k(no)e(parameters)g(need)g(to)h(b)s(e)
+e(pro)m(vided)g({)h(default)g(v)-5 b(alues)37 b(will)e(b)s(e)j(used.)
+120 3845 y(The)30 b(expression)f Fc("gtifilter\(\)")e
+Fg(is)i(equiv)-5 b(alen)m(t)30 b(to)454 4046 y Fc(gtifilter\()45
+b("",)i(TIME,)f("*START*",)f("*STOP*")h(\))120 4247 y
+Fg(This)30 b(will)g(searc)m(h)j(the)f(curren)m(t)g(\014le)g(for)g(a)g
+(GTI)g(extension,)h(\014lter)e(the)h(TIME)g(column)f(in)g(the)i(curren)
+m(t)120 4360 y(table,)47 b(using)c(ST)-8 b(AR)g(T/STOP)43
+b(times)g(tak)m(en)i(from)e(columns)g(in)g(the)h(GTI)f(extension)h
+(with)e(names)120 4473 y(con)m(taining)30 b(the)h(strings)e("ST)-8
+b(AR)g(T")31 b(and)f("STOP".)41 b(The)30 b(wildcards)e(\('*'\))k(allo)m
+(w)e(sligh)m(t)f(v)-5 b(ariations)30 b(in)120 4586 y(naming)h(con)m(v)m
+(en)m(tions)h(suc)m(h)g(as)g("TST)-8 b(AR)g(T")32 b(or)g("ST)-8
+b(AR)g(TTIME".)45 b(The)31 b(same)i(default)e(v)-5 b(alues)31
+b(apply)120 4699 y(for)g(unsp)s(eci\014ed)e(parameters)j(when)e(the)i
+(\014rst)e(one)i(or)f(t)m(w)m(o)i(parameters)f(are)g(sp)s(eci\014ed.)42
+b(The)31 b(function)120 4812 y(automatically)41 b(searc)m(hes)g(for)g
+(TIMEZER)m(O/I/F)g(k)m(eyw)m(ords)g(in)e(the)i(curren)m(t)g(and)f(GTI)h
+(extensions,)120 4924 y(applying)28 b(a)j(relativ)m(e)f(time)h
+(o\013set,)g(if)f(necessary)-8 b(.)120 5163 y Fh(5.4.4)105
+b(Spatial)35 b(Region)h(Filtering)120 5334 y Fg(Another)f(common)h
+(\014ltering)e(metho)s(d)h(selects)g(ro)m(ws)h(based)f(on)g(whether)g
+(the)h(spatial)e(p)s(osition)f(asso-)120 5447 y(ciated)40
+b(with)f(eac)m(h)j(ro)m(w)e(is)f(lo)s(cated)h(within)e(a)i(giv)m(en)h
+(2-dimensional)d(region.)69 b(The)40 b(syn)m(tax)h(for)e(this)120
+5560 y(high-lev)m(el)29 b(\014lter)h(is)1905 5809 y(32)p
+eop
+%%Page: 33 33
+33 32 bop 454 573 a Fc(regfilter\()45 b("regfilename")f([)k(,)f(Xexpr,)
+f(Yexpr)h([)g(,)h("wcs)e(cols")h(])g(])g(\))120 757 y
+Fg(where)28 b(eac)m(h)i("[)g(]")f(demarks)g(optional)f(parameters.)40
+b(The)29 b(region)f(\014le)g(name)h(is)f(required)f(and)h(m)m(ust)h(b)s
+(e)120 870 y(enclosed)g(in)f(quotes.)41 b(The)29 b(remaining)e
+(parameters)j(are)f(optional.)40 b(The)29 b(region)g(\014le)f(is)g(an)i
+(ASCI)s(I)d(text)120 983 y(\014le)36 b(whic)m(h)f(con)m(tains)i(a)g
+(list)f(of)g(one)h(or)g(more)g(geometric)h(shap)s(es)d(\(circle,)k
+(ellipse,)d(b)s(o)m(x,)i(etc.\))62 b(whic)m(h)120 1096
+y(de\014nes)30 b(a)i(region)f(on)g(the)h(celestial)f(sphere)g(or)g(an)g
+(area)h(within)d(a)j(particular)e(2D)i(image.)44 b(The)31
+b(region)120 1209 y(\014le)37 b(is)g(t)m(ypically)g(generated)i(using)e
+(an)h(image)g(displa)m(y)f(program)h(suc)m(h)f(as)i(fv/PO)m(W)f
+(\(distribute)e(b)m(y)120 1322 y(the)c(HEASAR)m(C\),)g(or)g(ds9)g
+(\(distributed)d(b)m(y)i(the)h(Smithsonian)e(Astroph)m(ysical)g(Observ)
+-5 b(atory\).)46 b(Users)120 1435 y(should)40 b(refer)i(to)h(the)f(do)s
+(cumen)m(tation)g(pro)m(vided)f(with)g(these)h(programs)g(for)g(more)g
+(details)f(on)h(the)120 1548 y(syn)m(tax)31 b(used)e(in)h(the)g(region)
+g(\014les.)261 1661 y(In)35 b(its)g(simpliest)e(form,)k(\(e.g.,)i
+Fc(regfilter\("region.reg"\))30 b Fg(\))36 b(the)f(co)s(ordinates)h(in)
+e(the)i(default)120 1774 y('X')24 b(and)e('Y')i(columns)d(will)g(b)s(e)
+h(used)g(to)i(determine)e(if)g(eac)m(h)i(ro)m(w)f(is)f(inside)f(or)i
+(outside)f(the)h(area)h(sp)s(eci\014ed)120 1886 y(in)g(the)i(region)f
+(\014le.)38 b(Alternate)26 b(p)s(osition)d(column)h(names,)j(or)e
+(expressions,)g(ma)m(y)h(b)s(e)f(en)m(tered)h(if)e(needed,)120
+1999 y(as)31 b(in)502 2184 y Fc(regfilter\("region.reg",)41
+b(XPOS,)47 b(YPOS\))120 2368 y Fg(Region)38 b(\014ltering)f(can)h(b)s
+(e)g(applied)e(most)i(unam)m(biguously)e(if)h(the)h(p)s(ositions)f(in)f
+(the)j(region)f(\014le)f(and)120 2481 y(in)e(the)i(table)g(to)h(b)s(e)e
+(\014ltered)g(are)h(b)s(oth)f(giv)m(e)h(in)f(terms)g(of)h(absolute)g
+(celestial)f(co)s(ordinate)h(units.)58 b(In)120 2594
+y(this)37 b(case)i(the)g(lo)s(cations)f(and)f(sizes)h(of)h(the)f
+(geometric)h(shap)s(es)f(in)f(the)h(region)g(\014le)f(are)i(sp)s
+(eci\014ed)e(in)120 2707 y(angular)d(units)f(on)h(the)h(sky)f(\(e.g.,)j
+(p)s(ositions)c(giv)m(en)i(in)e(R.A.)i(and)f(Dec.)54
+b(and)34 b(sizes)g(in)f(arcseconds)i(or)120 2820 y(arcmin)m(utes\).)j
+(Similarly)-8 b(,)20 b(eac)m(h)j(ro)m(w)g(of)f(the)g(\014ltered)f
+(table)h(will)d(ha)m(v)m(e)k(a)g(celestial)e(co)s(ordinate)h(asso)s
+(ciated)120 2933 y(with)32 b(it.)50 b(This)32 b(asso)s(ciation)h(is)g
+(usually)e(implemen)m(ted)h(using)h(a)g(set)i(of)e(so-called)h('W)-8
+b(orld)33 b(Co)s(ordinate)120 3046 y(System')j(\(or)h(W)m(CS\))f(FITS)f
+(k)m(eyw)m(ords)i(that)f(de\014ne)g(the)g(co)s(ordinate)g
+(transformation)f(that)i(m)m(ust)f(b)s(e)120 3159 y(applied)28
+b(to)j(the)g(v)-5 b(alues)30 b(in)f(the)h('X')h(and)f('Y')h(columns)e
+(to)i(calculate)g(the)f(co)s(ordinate.)261 3272 y(Alternativ)m(ely)-8
+b(,)37 b(one)f(can)f(p)s(erform)f(spatial)h(\014ltering)e(using)h
+(unitless)g('pixel')g(co)s(ordinates)h(for)g(the)120
+3385 y(regions)30 b(and)g(ro)m(w)h(p)s(ositions.)40 b(In)30
+b(this)f(case)j(the)f(user)f(m)m(ust)h(b)s(e)f(careful)g(to)h(ensure)f
+(that)h(the)g(p)s(ositions)120 3498 y(in)h(the)h(2)h(\014les)e(are)i
+(self-consisten)m(t.)49 b(A)34 b(t)m(ypical)e(problem)g(is)g(that)i
+(the)g(region)f(\014le)f(ma)m(y)i(b)s(e)e(generated)120
+3610 y(using)22 b(a)i(binned)e(image,)j(but)e(the)h(un)m(binned)d(co)s
+(ordinates)i(are)i(giv)m(en)e(in)g(the)h(ev)m(en)m(t)h(table.)38
+b(The)24 b(R)m(OSA)-8 b(T)120 3723 y(ev)m(en)m(ts)34
+b(\014les,)f(for)g(example,)h(ha)m(v)m(e)g(X)f(and)g(Y)g(pixel)e(co)s
+(ordinates)i(that)h(range)f(from)g(1)g(-)h(15360.)51
+b(These)120 3836 y(co)s(ordinates)32 b(are)h(t)m(ypically)f(binned)e(b)
+m(y)j(a)g(factor)g(of)g(32)h(to)f(pro)s(duce)f(a)h(480x480)i(pixel)c
+(image.)48 b(If)32 b(one)120 3949 y(then)f(uses)g(a)g(region)g(\014le)f
+(generated)j(from)d(this)g(image)i(\(in)e(image)h(pixel)f(units\))g(to)
+i(\014lter)e(the)i(R)m(OSA)-8 b(T)120 4062 y(ev)m(en)m(ts)33
+b(\014le,)e(then)g(the)g(X)g(and)g(Y)h(column)e(v)-5
+b(alues)30 b(m)m(ust)i(b)s(e)e(con)m(v)m(erted)j(to)f(corresp)s(onding)
+d(pixel)h(units)120 4175 y(as)h(in:)502 4360 y Fc
+(regfilter\("rosat.reg",)42 b(X/32.+.5,)j(Y/32.+.5\))120
+4544 y Fg(Note)30 b(that)f(this)e(binning)f(con)m(v)m(ersion)i(is)g
+(not)h(necessary)g(if)e(the)i(region)f(\014le)f(is)h(sp)s(eci\014ed)f
+(using)g(celestial)120 4657 y(co)s(ordinate)g(units)f(instead)g(of)h
+(pixel)f(units)f(b)s(ecause)i(CFITSIO)f(is)g(then)h(able)g(to)h
+(directly)d(compare)j(the)120 4770 y(celestial)34 b(co)s(ordinate)g(of)
+g(eac)m(h)h(ro)m(w)f(in)f(the)h(table)g(with)f(the)h(celestial)g(co)s
+(ordinates)g(in)e(the)j(region)e(\014le)120 4883 y(without)c(ha)m(ving)
+h(to)h(kno)m(w)g(an)m(ything)f(ab)s(out)g(ho)m(w)g(the)h(image)f(ma)m
+(y)h(ha)m(v)m(e)h(b)s(een)d(binned.)261 4996 y(The)k(last)g("w)m(cs)g
+(cols")h(parameter)f(should)e(rarely)h(b)s(e)h(needed.)48
+b(If)33 b(supplied,)d(this)i(string)g(con)m(tains)120
+5109 y(the)39 b(names)g(of)h(the)f(2)g(columns)f(\(space)i(or)f(comma)h
+(separated\))g(whic)m(h)e(ha)m(v)m(e)i(the)g(asso)s(ciated)f(W)m(CS)120
+5222 y(k)m(eyw)m(ords.)k(If)30 b(not)h(supplied,)d(the)j(\014lter)f
+(will)e(scan)j(the)g(X)g(and)g(Y)g(expressions)e(for)i(column)f(names.)
+42 b(If)120 5334 y(only)32 b(one)g(is)g(found)f(in)g(eac)m(h)j
+(expression,)e(those)h(columns)e(will)f(b)s(e)i(used,)h(otherwise)f(an)
+g(error)g(will)e(b)s(e)120 5447 y(returned.)261 5560
+y(These)g(region)g(shap)s(es)g(are)g(supp)s(orted)f(\(names)i(are)f
+(case)i(insensitiv)m(e\):)1905 5809 y(33)p eop
+%%Page: 34 34
+34 33 bop 454 573 a Fc(Point)428 b(\()48 b(X1,)f(Y1)g(\))715
+b(<-)48 b(One)f(pixel)f(square)g(region)454 686 y(Line)476
+b(\()48 b(X1,)f(Y1,)g(X2,)f(Y2)i(\))333 b(<-)48 b(One)f(pixel)f(wide)h
+(region)454 799 y(Polygon)332 b(\()48 b(X1,)f(Y1,)g(X2,)f(Y2,)h(...)g
+(\))95 b(<-)48 b(Rest)e(are)h(interiors)e(with)454 912
+y(Rectangle)236 b(\()48 b(X1,)f(Y1,)g(X2,)f(Y2,)h(A)h(\))334
+b(|)47 b(boundaries)e(considered)454 1024 y(Box)524 b(\()48
+b(Xc,)f(Yc,)g(Wdth,)f(Hght,)g(A)i(\))143 b(V)47 b(within)f(the)h
+(region)454 1137 y(Diamond)332 b(\()48 b(Xc,)f(Yc,)g(Wdth,)f(Hght,)g(A)
+i(\))454 1250 y(Circle)380 b(\()48 b(Xc,)f(Yc,)g(R)g(\))454
+1363 y(Annulus)332 b(\()48 b(Xc,)f(Yc,)g(Rin,)f(Rout)h(\))454
+1476 y(Ellipse)332 b(\()48 b(Xc,)f(Yc,)g(Rx,)f(Ry,)h(A)h(\))454
+1589 y(Elliptannulus)c(\()k(Xc,)f(Yc,)g(Rinx,)f(Riny,)g(Routx,)g
+(Routy,)g(Ain,)h(Aout)g(\))454 1702 y(Sector)380 b(\()48
+b(Xc,)f(Yc,)g(Amin,)f(Amax)h(\))120 1914 y Fg(where)33
+b(\(Xc,Yc\))j(is)d(the)i(co)s(ordinate)e(of)i(the)f(shap)s(e's)f(cen)m
+(ter;)k(\(X#,Y#\))e(are)f(the)g(co)s(ordinates)g(of)g(the)120
+2027 y(shap)s(e's)22 b(edges;)k(Rxxx)d(are)g(the)h(shap)s(es')e(v)-5
+b(arious)22 b(Radii)f(or)i(semima)5 b(jor/minor)21 b(axes;)27
+b(and)22 b(Axxx)h(are)g(the)120 2140 y(angles)i(of)g(rotation)g(\(or)g
+(b)s(ounding)d(angles)j(for)g(Sector\))h(in)d(degrees.)40
+b(F)-8 b(or)26 b(rotated)g(shap)s(es,)f(the)g(rotation)120
+2253 y(angle)36 b(can)h(b)s(e)e(left)h(o\013,)i(indicating)c(no)i
+(rotation.)59 b(Common)35 b(alternate)i(names)f(for)g(the)g(regions)g
+(can)120 2366 y(also)27 b(b)s(e)f(used:)39 b(rotb)s(o)m(x)27
+b(=)f(b)s(o)m(x;)j(rotrectangle)f(=)f(rectangle;)i(\(rot\)rhom)m(bus)d
+(=)h(\(rot\)diamond;)h(and)e(pie)120 2479 y(=)h(sector.)41
+b(When)28 b(a)g(shap)s(e's)f(name)g(is)g(preceded)g(b)m(y)h(a)g(min)m
+(us)e(sign,)i('-',)h(the)f(de\014ned)e(region)h(is)g(instead)120
+2592 y(the)36 b(area)g(*outside*)g(its)f(b)s(oundary)e(\(ie,)k(the)f
+(region)f(is)g(in)m(v)m(erted\).)56 b(All)34 b(the)i(shap)s(es)f
+(within)e(a)j(single)120 2705 y(region)e(\014le)g(are)h(OR'd)g
+(together)h(to)f(create)i(the)e(region,)g(and)g(the)g(order)f(is)g
+(signi\014can)m(t.)53 b(The)34 b(o)m(v)m(erall)120 2818
+y(w)m(a)m(y)g(of)g(lo)s(oking)e(at)i(region)f(\014les)f(is)g(that)i(if)
+e(the)i(\014rst)e(region)h(is)g(an)g(excluded)f(region)h(then)g(a)g
+(dumm)m(y)120 2931 y(included)27 b(region)j(of)g(the)g(whole)f
+(detector)j(is)d(inserted)f(in)h(the)h(fron)m(t.)41 b(Then)29
+b(eac)m(h)i(region)f(sp)s(eci\014cation)120 3044 y(as)h(it)g(is)f(pro)s
+(cessed)h(o)m(v)m(errides)g(an)m(y)g(selections)g(inside)e(of)i(that)h
+(region)f(sp)s(eci\014ed)e(b)m(y)i(previous)f(regions.)120
+3156 y(Another)f(w)m(a)m(y)i(of)e(thinking)e(ab)s(out)i(this)f(is)h
+(that)g(if)g(a)g(previous)f(excluded)g(region)h(is)g(completely)g
+(inside)120 3269 y(of)i(a)f(subsequen)m(t)g(included)e(region)i(the)g
+(excluded)f(region)h(is)g(ignored.)261 3382 y(The)20
+b(p)s(ositional)e(co)s(ordinates)i(ma)m(y)h(b)s(e)e(giv)m(en)i(either)e
+(in)g(pixel)g(units,)i(decimal)e(degrees)i(or)f(hh:mm:ss.s,)120
+3495 y(dd:mm:ss.s)25 b(units.)37 b(The)26 b(shap)s(e)f(sizes)g(ma)m(y)i
+(b)s(e)e(giv)m(en)h(in)e(pixels,)i(degrees,)h(arcmin)m(utes,)g(or)f
+(arcseconds.)120 3608 y(Lo)s(ok)k(at)i(examples)d(of)i(region)f(\014le)
+f(pro)s(duced)g(b)m(y)h(fv/PO)m(W)h(or)f(ds9)g(for)g(further)f(details)
+h(of)g(the)h(region)120 3721 y(\014le)e(format.)120 3961
+y Fh(5.4.5)105 b(Example)34 b(Ro)m(w)h(Filters)311 4133
+y Fc([double)46 b(&&)h(mag)g(<=)g(5.0])381 b(-)95 b(Extract)46
+b(all)h(double)f(stars)g(brighter)1886 4246 y(than)94
+b(fifth)47 b(magnitude)311 4472 y([#row)f(>=)h(125)g(&&)h(#row)e(<=)h
+(175])142 b(-)48 b(Extract)e(row)h(numbers)e(125)i(through)f(175)311
+4697 y([abs\(sin\(theta)e(*)j(#deg\)\))f(<)i(0.5])e(-)i(Extract)e(all)h
+(rows)f(having)g(the)1886 4810 y(absolute)f(value)i(of)g(the)g(sine)g
+(of)g(theta)1886 4923 y(less)94 b(than)47 b(a)g(half)g(where)f(the)h
+(angles)1886 5036 y(are)g(tabulated)e(in)i(degrees)311
+5262 y([@rowFilter.txt])711 b(-)48 b(Extract)e(rows)g(using)h(the)g
+(expression)1886 5375 y(contained)e(within)h(the)h(text)g(file)1886
+5488 y(rowFilter.txt)1905 5809 y Fg(34)p eop
+%%Page: 35 35
+35 34 bop 311 686 a Fc([gtifilter\(\)])855 b(-)48 b(Search)e(the)h
+(current)f(file)g(for)h(a)h(GTI)359 799 y(extension,)92
+b(filter)i(the)47 b(TIME)359 912 y(column)f(in)h(the)g(current)f
+(table,)g(using)359 1024 y(START/STOP)f(times)h(taken)g(from)359
+1137 y(columns)f(in)j(the)f(GTI)94 b(extension)311 1363
+y([regfilter\("pow.reg"\)])423 b(-)48 b(Extract)e(rows)g(which)h(have)f
+(a)i(coordinate)1886 1476 y(\(as)f(given)f(in)h(the)g(X)h(and)f(Y)g
+(columns\))1886 1589 y(within)f(the)h(spatial)f(region)g(specified)1886
+1702 y(in)h(the)g(pow.reg)f(region)g(file.)1905 5809
+y Fg(35)p eop
+%%Page: 36 36
+36 35 bop 120 573 a Fb(5.5)112 b(Com)m(bined)37 b(Filtering)e(Examples)
+120 744 y Fg(The)29 b(previous)g(sections)g(describ)s(ed)f(all)h(the)h
+(individual)25 b(t)m(yp)s(es)30 b(of)g(\014lters)e(that)j(ma)m(y)f(b)s
+(e)f(applied)f(to)i(the)120 857 y(input)i(\014le.)49
+b(In)33 b(this)f(section)i(w)m(e)g(sho)m(w)g(examples)f(whic)m(h)f(com)
+m(bine)i(sev)m(eral)f(di\013eren)m(t)h(\014lters)e(at)i(once.)120
+970 y(These)h(examples)f(all)g(use)h(the)g Fc(fitscopy)e
+Fg(program)i(that)g(is)f(distributed)e(with)i(the)h(CFITSIO)f(co)s(de.)
+120 1083 y(It)c(simply)e(copies)j(the)f(input)f(\014le)g(to)i(the)g
+(output)f(\014le.)120 1268 y Fc(fitscopy)46 b(rosat.fit)f(out.fit)261
+1453 y Fg(This)25 b(trivial)g(example)h(simply)f(mak)m(es)i(an)g(iden)m
+(tical)f(cop)m(y)h(of)g(the)g(input)e(rosat.\014t)i(\014le)f(without)g
+(an)m(y)120 1566 y(\014ltering.)120 1751 y Fc(fitscopy)46
+b('rosat.fit[events][col)41 b(Time;X;Y][#row)j(<)k(1000]')e(out.fit)261
+1936 y Fg(The)34 b(output)g(\014le)g(con)m(tains)h(only)e(the)i(Time,)g
+(X,)g(and)e(Y)i(columns,)g(and)e(only)h(the)h(\014rst)f(999)h(ro)m(ws)
+120 2049 y(from)g(the)g('EVENTS')f(table)h(extension)g(of)g(the)g
+(input)e(\014le.)54 b(All)33 b(the)j(other)f(HDUs)g(in)f(the)h(input)e
+(\014le)120 2162 y(are)e(copied)f(to)h(the)f(output)g(\014le)g(without)
+f(an)m(y)i(mo)s(di\014cation.)120 2346 y Fc(fitscopy)46
+b('rosat.fit[events][PI)c(<)47 b(50][bin)f(\(Xdet,Ydet\))f(=)i(16]')g
+(image.fit)261 2531 y Fg(This)29 b(creates)i(an)f(output)g(image)h(b)m
+(y)f(binning)d(the)j(Xdet)h(and)f(Ydet)g(columns)f(of)h(the)h(ev)m(en)m
+(ts)g(table)120 2644 y(with)25 b(a)i(pixel)e(binning)e(factor)k(of)g
+(16.)40 b(Only)25 b(the)i(ro)m(ws)f(whic)m(h)f(ha)m(v)m(e)j(a)e(PI)h
+(energy)f(less)g(than)g(50)h(are)g(used)120 2757 y(to)33
+b(construct)f(this)e(image.)45 b(The)32 b(output)f(image)h(\014le)f
+(con)m(tains)h(a)g(primary)e(arra)m(y)i(image)g(without)f(an)m(y)120
+2870 y(extensions.)120 3055 y Fc(fitscopy)46 b('rosat.fit[events][gtif)
+o(ilt)o(er\(\))41 b(&&)47 b(regfilter\("pow.reg"\)]')42
+b(out.fit)261 3240 y Fg(The)29 b(\014ltering)f(expression)g(in)g(this)h
+(example)g(uses)g(the)h Fc(gtifilter)d Fg(function)h(to)i(test)g
+(whether)f(the)120 3353 y(TIME)e(column)f(v)-5 b(alue)26
+b(in)g(eac)m(h)j(ro)m(w)e(is)f(within)f(one)i(of)g(the)h(Go)s(o)s(d)f
+(Time)f(In)m(terv)-5 b(als)26 b(de\014ned)g(in)g(the)i(GTI)120
+3466 y(extension)h(in)f(the)i(same)g(input)d(\014le,)i(and)g(also)h
+(uses)f(the)g Fc(regfilter)e Fg(function)i(to)h(test)g(if)f(the)g(p)s
+(osition)120 3579 y(asso)s(ciated)i(with)d(eac)m(h)j(ro)m(w)g(\(deriv)m
+(ed)e(b)m(y)h(default)f(from)h(the)g(v)-5 b(alues)29
+b(in)g(the)h(X)h(and)e(Y)h(columns)f(of)h(the)120 3692
+y(ev)m(en)m(ts)38 b(table\))e(is)g(lo)s(cated)h(within)c(the)k(area)g
+(de\014ned)e(in)h(the)g Fc(pow.reg)f Fg(text)i(region)f(\014le)f
+(\(whic)m(h)h(w)m(as)120 3804 y(previously)g(created)k(with)e(the)g
+Fc(fv/POW)f Fg(image)i(displa)m(y)e(program\).)66 b(Only)37
+b(the)i(ro)m(ws)f(whic)m(h)g(satisfy)120 3917 y(b)s(oth)30
+b(tests)h(are)g(copied)e(to)i(the)g(output)f(table.)120
+4102 y Fc(fitscopy)46 b('r.fit[evt][PI<50]')c(stdout)k(|)i(fitscopy)d
+(stdin[evt][col)f(X,Y])j(out.fit)261 4287 y Fg(In)25
+b(this)f(somewhat)i(con)m(v)m(oluted)f(example,)i(\014tscop)m(y)e(is)g
+(used)f(to)i(\014rst)f(select)h(the)f(ro)m(ws)g(from)g(the)h(evt)120
+4400 y(extension)j(whic)m(h)g(ha)m(v)m(e)i(PI)e(less)g(than)h(50)g(and)
+f(write)g(the)h(resulting)e(table)i(out)g(to)g(the)g(stdout)g(stream.)
+120 4513 y(This)36 b(is)g(pip)s(ed)f(to)j(a)g(2nd)f(instance)g(of)h
+(\014tscop)m(y)g(\(with)e(the)i(Unix)e(`)p Fa(j)p Fg(')i(pip)s(e)e
+(command\))h(whic)m(h)g(reads)120 4626 y(that)31 b(\014ltered)f(FITS)f
+(\014le)h(from)g(the)h(stdin)e(stream)i(and)f(copies)g(only)g(the)h(X)f
+(and)g(Y)h(columns)e(from)h(the)120 4739 y(evt)h(table)f(to)h(the)g
+(output)f(\014le.)120 4924 y Fc(fitscopy)46 b('r.fit[evt][col)d
+(RAD=sqrt\(\(X-#XCEN\)**2+\(Y-)o(#YCE)o(N\)*)o(*2\)])o([rad)o(<10)o
+(0]')e(out.fit)261 5109 y Fg(This)23 b(example)i(\014rst)f(creates)i(a)
+f(new)f(column)g(called)g(RAD)h(whic)m(h)e(giv)m(es)i(the)g(distance)g
+(b)s(et)m(w)m(een)g(the)120 5222 y(X,Y)k(co)s(ordinate)f(of)g(eac)m(h)i
+(ev)m(en)m(t)g(and)d(the)i(co)s(ordinate)f(de\014ned)f(b)m(y)h(the)h(X)
+m(CEN)f(and)g(YCEN)g(k)m(eyw)m(ords)120 5334 y(in)j(the)i(header.)47
+b(Then,)32 b(only)g(those)h(ro)m(ws)g(whic)m(h)e(ha)m(v)m(e)j(a)f
+(distance)f(less)g(than)g(100)i(are)f(copied)f(to)h(the)120
+5447 y(output)e(table.)45 b(In)31 b(other)h(w)m(ords,)f(only)g(the)h
+(ev)m(en)m(ts)h(whic)m(h)e(are)h(lo)s(cated)g(within)d(100)k(pixel)d
+(units)g(from)120 5560 y(the)h(\(X)m(CEN,)g(YCEN\))f(co)s(ordinate)h
+(are)f(copied)g(to)h(the)g(output)f(table.)1905 5809
+y(36)p eop
+%%Page: 37 37
+37 36 bop 120 573 a Fc(fitscopy)46 b('ftp://heasarc.gsfc.nas)o(a.g)o
+(ov/r)o(osat)o(.fi)o(t[ev)o(ents)o(][b)o(in)c(\(X,Y\)=16]')j(img.fit)
+261 785 y Fg(This)22 b(example)h(bins)e(the)i(X)h(and)f(Y)g(columns)f
+(of)h(the)h(h)m(yp)s(othetical)e(R)m(OSA)-8 b(T)24 b(\014le)e(at)i(the)
+f(HEASAR)m(C)120 898 y(ftp)30 b(site)g(to)h(create)h(the)f(output)f
+(image.)120 1111 y Fc(fitscopy)46 b('raw.fit[i512,512][101:)o(110)o
+(,51:)o(60]')41 b(image.fit)261 1323 y Fg(This)28 b(example)h(con)m(v)m
+(erts)i(the)e(512)i(x)e(512)i(pixel)d(ra)m(w)h(binary)f(16-bit)h(in)m
+(teger)h(image)g(to)g(a)g(FITS)e(\014le)120 1436 y(and)i(copies)g(a)h
+(10)g(x)f(10)h(pixel)e(subimage)h(from)g(it)f(to)j(the)e(output)g(FITS)
+g(image.)1905 5809 y(37)p eop
+%%Page: 38 38
+38 37 bop 120 573 a Fi(6)135 b(CFITSIO)44 b(Error)h(Status)g(Co)t(des)
+120 776 y Fg(The)34 b(follo)m(wing)e(table)i(lists)f(all)g(the)h(error)
+g(status)g(co)s(des)h(used)e(b)m(y)h(CFITSIO.)f(Programmers)h(are)g
+(en-)120 889 y(couraged)f(to)g(use)g(the)f(sym)m(b)s(olic)f(mnemonics)h
+(\(de\014ned)f(in)g(the)i(\014le)f(\014tsio.h\))g(rather)g(than)g(the)h
+(actual)120 1002 y(in)m(teger)e(status)f(v)-5 b(alues)30
+b(to)h(impro)m(v)m(e)f(the)h(readabilit)m(y)e(of)h(their)g(co)s(de.)168
+1214 y Fc(Symbolic)45 b(Const)190 b(Value)237 b(Meaning)168
+1327 y(--------------)187 b(-----)94 b(------------------------)o(----)
+o(---)o(----)o(----)o(--)1122 1440 y(0)191 b(OK,)47 b(no)g(error)168
+1553 y(SAME_FILE)427 b(101)190 b(input)46 b(and)h(output)f(files)h(are)
+g(the)f(same)168 1666 y(TOO_MANY_FILES)187 b(103)j(tried)46
+b(to)h(open)g(too)g(many)g(FITS)f(files)h(at)g(once)168
+1779 y(FILE_NOT_OPENED)139 b(104)190 b(could)46 b(not)h(open)g(the)g
+(named)f(file)168 1892 y(FILE_NOT_CREATED)91 b(105)190
+b(could)46 b(not)h(create)f(the)h(named)g(file)168 2005
+y(WRITE_ERROR)331 b(106)190 b(error)46 b(writing)g(to)h(FITS)g(file)168
+2117 y(END_OF_FILE)331 b(107)190 b(tried)46 b(to)h(move)g(past)g(end)g
+(of)g(file)168 2230 y(READ_ERROR)379 b(108)190 b(error)46
+b(reading)g(from)h(FITS)f(file)168 2343 y(FILE_NOT_CLOSED)139
+b(110)190 b(could)46 b(not)h(close)g(the)f(file)168 2456
+y(ARRAY_TOO_BIG)235 b(111)190 b(array)46 b(dimensions)f(exceed)h
+(internal)g(limit)168 2569 y(READONLY_FILE)235 b(112)190
+b(Cannot)46 b(write)g(to)i(readonly)d(file)168 2682 y
+(MEMORY_ALLOCATION)e(113)190 b(Could)46 b(not)h(allocate)f(memory)168
+2795 y(BAD_FILEPTR)331 b(114)190 b(invalid)46 b(fitsfile)f(pointer)168
+2908 y(NULL_INPUT_PTR)187 b(115)j(NULL)47 b(input)f(pointer)g(to)h
+(routine)168 3021 y(SEEK_ERROR)379 b(116)190 b(error)46
+b(seeking)g(position)g(in)h(file)168 3247 y(BAD_URL_PREFIX)235
+b(121)142 b(invalid)46 b(URL)h(prefix)f(on)h(file)g(name)168
+3359 y(TOO_MANY_DRIVERS)139 b(122)j(tried)46 b(to)h(register)f(too)h
+(many)g(IO)g(drivers)168 3472 y(DRIVER_INIT_FAILED)c(123)142
+b(driver)46 b(initialization)e(failed)168 3585 y(NO_MATCHING_DRIVER)f
+(124)142 b(matching)45 b(driver)i(is)g(not)g(registered)168
+3698 y(URL_PARSE_ERROR)187 b(125)142 b(failed)46 b(to)h(parse)g(input)f
+(file)h(URL)168 3924 y(SHARED_BADARG)235 b(151)190 b(bad)47
+b(argument)e(in)j(shared)e(memory)g(driver)168 4037 y(SHARED_NULPTR)235
+b(152)190 b(null)47 b(pointer)e(passed)h(as)i(an)f(argument)168
+4150 y(SHARED_TABFULL)187 b(153)j(no)47 b(more)g(free)f(shared)g
+(memory)h(handles)168 4263 y(SHARED_NOTINIT)187 b(154)j(shared)46
+b(memory)g(driver)g(is)h(not)g(initialized)168 4376 y(SHARED_IPCERR)235
+b(155)190 b(IPC)47 b(error)f(returned)g(by)h(a)g(system)f(call)168
+4489 y(SHARED_NOMEM)283 b(156)190 b(no)47 b(memory)f(in)h(shared)f
+(memory)h(driver)168 4601 y(SHARED_AGAIN)283 b(157)190
+b(resource)45 b(deadlock)h(would)g(occur)168 4714 y(SHARED_NOFILE)235
+b(158)190 b(attempt)46 b(to)h(open/create)e(lock)h(file)h(failed)168
+4827 y(SHARED_NORESIZE)139 b(159)190 b(shared)46 b(memory)g(block)g
+(cannot)h(be)g(resized)f(at)h(the)g(moment)168 5053 y(HEADER_NOT_EMPTY)
+91 b(201)190 b(header)46 b(already)g(contains)f(keywords)168
+5166 y(KEY_NO_EXIST)283 b(202)190 b(keyword)46 b(not)h(found)f(in)h
+(header)168 5279 y(KEY_OUT_BOUNDS)187 b(203)j(keyword)46
+b(record)g(number)g(is)h(out)g(of)g(bounds)168 5392 y(VALUE_UNDEFINED)
+139 b(204)190 b(keyword)46 b(value)g(field)g(is)i(blank)168
+5505 y(NO_QUOTE)475 b(205)190 b(string)46 b(is)h(missing)f(the)h
+(closing)f(quote)1905 5809 y Fg(38)p eop
+%%Page: 39 39
+39 38 bop 168 573 a Fc(BAD_KEYCHAR)331 b(207)190 b(illegal)46
+b(character)f(in)i(keyword)f(name)h(or)g(card)168 686
+y(BAD_ORDER)427 b(208)190 b(required)45 b(keywords)h(out)h(of)g(order)
+168 799 y(NOT_POS_INT)331 b(209)190 b(keyword)46 b(value)g(is)h(not)g
+(a)h(positive)d(integer)168 912 y(NO_END)571 b(210)190
+b(couldn't)45 b(find)i(END)g(keyword)168 1024 y(BAD_BITPIX)379
+b(211)190 b(illegal)46 b(BITPIX)g(keyword)g(value)168
+1137 y(BAD_NAXIS)427 b(212)190 b(illegal)46 b(NAXIS)g(keyword)g(value)
+168 1250 y(BAD_NAXES)427 b(213)190 b(illegal)46 b(NAXISn)g(keyword)g
+(value)168 1363 y(BAD_PCOUNT)379 b(214)190 b(illegal)46
+b(PCOUNT)g(keyword)g(value)168 1476 y(BAD_GCOUNT)379
+b(215)190 b(illegal)46 b(GCOUNT)g(keyword)g(value)168
+1589 y(BAD_TFIELDS)331 b(216)190 b(illegal)46 b(TFIELDS)g(keyword)f
+(value)168 1702 y(NEG_WIDTH)427 b(217)190 b(negative)45
+b(table)i(row)g(size)168 1815 y(NEG_ROWS)475 b(218)190
+b(negative)45 b(number)i(of)g(rows)f(in)i(table)168 1928
+y(COL_NOT_FOUND)235 b(219)190 b(column)46 b(with)h(this)f(name)h(not)g
+(found)f(in)h(table)168 2041 y(BAD_SIMPLE)379 b(220)190
+b(illegal)46 b(value)g(of)h(SIMPLE)f(keyword)168 2154
+y(NO_SIMPLE)427 b(221)190 b(Primary)46 b(array)g(doesn't)g(start)g
+(with)h(SIMPLE)168 2267 y(NO_BITPIX)427 b(222)190 b(Second)46
+b(keyword)g(not)h(BITPIX)168 2379 y(NO_NAXIS)475 b(223)190
+b(Third)46 b(keyword)g(not)h(NAXIS)168 2492 y(NO_NAXES)475
+b(224)190 b(Couldn't)45 b(find)i(all)g(the)g(NAXISn)f(keywords)168
+2605 y(NO_XTENSION)331 b(225)190 b(HDU)47 b(doesn't)f(start)g(with)h
+(XTENSION)e(keyword)168 2718 y(NOT_ATABLE)379 b(226)190
+b(the)47 b(CHDU)f(is)i(not)f(an)g(ASCII)f(table)g(extension)168
+2831 y(NOT_BTABLE)379 b(227)190 b(the)47 b(CHDU)f(is)i(not)f(a)g
+(binary)f(table)g(extension)168 2944 y(NO_PCOUNT)427
+b(228)190 b(couldn't)45 b(find)i(PCOUNT)f(keyword)168
+3057 y(NO_GCOUNT)427 b(229)190 b(couldn't)45 b(find)i(GCOUNT)f(keyword)
+168 3170 y(NO_TFIELDS)379 b(230)190 b(couldn't)45 b(find)i(TFIELDS)f
+(keyword)168 3283 y(NO_TBCOL)475 b(231)190 b(couldn't)45
+b(find)i(TBCOLn)f(keyword)168 3396 y(NO_TFORM)475 b(232)190
+b(couldn't)45 b(find)i(TFORMn)f(keyword)168 3509 y(NOT_IMAGE)427
+b(233)190 b(the)47 b(CHDU)f(is)i(not)f(an)g(IMAGE)f(extension)168
+3621 y(BAD_TBCOL)427 b(234)190 b(TBCOLn)46 b(keyword)g(value)g(<)i(0)f
+(or)g(>)h(rowlength)168 3734 y(NOT_TABLE)427 b(235)190
+b(the)47 b(CHDU)f(is)i(not)f(a)g(table)168 3847 y(COL_TOO_WIDE)283
+b(236)190 b(column)46 b(is)h(too)g(wide)g(to)g(fit)g(in)g(table)168
+3960 y(COL_NOT_UNIQUE)187 b(237)j(more)47 b(than)f(1)i(column)e(name)g
+(matches)g(template)168 4073 y(BAD_ROW_WIDTH)235 b(241)190
+b(sum)47 b(of)g(column)f(widths)g(not)h(=)h(NAXIS1)168
+4186 y(UNKNOWN_EXT)331 b(251)190 b(unrecognizable)44
+b(FITS)i(extension)g(type)168 4299 y(UNKNOWN_REC)331
+b(252)190 b(unknown)46 b(record;)g(1st)g(keyword)g(not)h(SIMPLE)f(or)h
+(XTENSION)168 4412 y(END_JUNK)475 b(253)190 b(END)47
+b(keyword)f(is)h(not)g(blank)168 4525 y(BAD_HEADER_FILL)139
+b(254)190 b(Header)46 b(fill)h(area)f(contains)g(non-blank)f(chars)168
+4638 y(BAD_DATA_FILL)235 b(255)190 b(Illegal)46 b(data)g(fill)h(bytes)f
+(\(not)h(zero)g(or)g(blank\))168 4751 y(BAD_TFORM)427
+b(261)190 b(illegal)46 b(TFORM)g(format)g(code)168 4863
+y(BAD_TFORM_DTYPE)139 b(262)190 b(unrecognizable)44 b(TFORM)i(datatype)
+g(code)168 4976 y(BAD_TDIM)475 b(263)190 b(illegal)46
+b(TDIMn)g(keyword)g(value)168 5089 y(BAD_HEAP_PTR)283
+b(264)190 b(invalid)46 b(BINTABLE)f(heap)i(pointer)f(is)h(out)g(of)g
+(range)168 5315 y(BAD_HDU_NUM)331 b(301)190 b(HDU)47
+b(number)f(<)h(1)h(or)f(>)g(MAXHDU)168 5428 y(BAD_COL_NUM)331
+b(302)190 b(column)46 b(number)g(<)i(1)f(or)g(>)h(tfields)168
+5541 y(NEG_FILE_POS)283 b(304)190 b(tried)46 b(to)h(move)g(to)g
+(negative)f(byte)g(location)g(in)h(file)1905 5809 y Fg(39)p
+eop
+%%Page: 40 40
+40 39 bop 168 573 a Fc(NEG_BYTES)427 b(306)190 b(tried)46
+b(to)h(read)g(or)g(write)g(negative)e(number)h(of)h(bytes)168
+686 y(BAD_ROW_NUM)331 b(307)190 b(illegal)46 b(starting)f(row)i(number)
+f(in)h(table)168 799 y(BAD_ELEM_NUM)283 b(308)190 b(illegal)46
+b(starting)f(element)h(number)g(in)h(vector)168 912 y(NOT_ASCII_COL)235
+b(309)190 b(this)47 b(is)g(not)g(an)g(ASCII)f(string)g(column)168
+1024 y(NOT_LOGICAL_COL)139 b(310)190 b(this)47 b(is)g(not)g(a)g
+(logical)f(datatype)f(column)168 1137 y(BAD_ATABLE_FORMAT)e(311)190
+b(ASCII)46 b(table)h(column)f(has)h(wrong)f(format)168
+1250 y(BAD_BTABLE_FORMAT)d(312)190 b(Binary)46 b(table)g(column)g(has)h
+(wrong)g(format)168 1363 y(NO_NULL)523 b(314)190 b(null)47
+b(value)f(has)h(not)g(been)f(defined)168 1476 y(NOT_VARI_LEN)283
+b(317)190 b(this)47 b(is)g(not)g(a)g(variable)f(length)g(column)168
+1589 y(BAD_DIMEN)427 b(320)190 b(illegal)46 b(number)g(of)h(dimensions)
+e(in)i(array)168 1702 y(BAD_PIX_NUM)331 b(321)190 b(first)46
+b(pixel)h(number)f(greater)g(than)g(last)h(pixel)168
+1815 y(ZERO_SCALE)379 b(322)190 b(illegal)46 b(BSCALE)g(or)h(TSCALn)f
+(keyword)g(=)h(0)168 1928 y(NEG_AXIS)475 b(323)190 b(illegal)46
+b(axis)g(length)g(<)i(1)168 2154 y(NOT_GROUP_TABLE)330
+b(340)142 b(Grouping)46 b(function)f(error)168 2267 y
+(HDU_ALREADY_MEMBER)186 b(341)168 2379 y(MEMBER_NOT_FOUND)282
+b(342)168 2492 y(GROUP_NOT_FOUND)330 b(343)168 2605 y(BAD_GROUP_ID)474
+b(344)168 2718 y(TOO_MANY_HDUS_TRACKED)42 b(345)168 2831
+y(HDU_ALREADY_TRACKED)138 b(346)168 2944 y(BAD_OPTION)570
+b(347)168 3057 y(IDENTICAL_POINTERS)186 b(348)168 3170
+y(BAD_GROUP_ATTACH)282 b(349)168 3283 y(BAD_GROUP_DETACH)g(350)168
+3509 y(NGP_NO_MEMORY)426 b(360)238 b(malloc)46 b(failed)168
+3621 y(NGP_READ_ERR)474 b(361)238 b(read)46 b(error)h(from)f(file)168
+3734 y(NGP_NUL_PTR)522 b(362)238 b(null)46 b(pointer)g(passed)g(as)h
+(an)g(argument.)1695 3847 y(Passing)f(null)g(pointer)g(as)h(a)h(name)f
+(of)1695 3960 y(template)f(file)g(raises)g(this)h(error)168
+4073 y(NGP_EMPTY_CURLINE)234 b(363)k(line)46 b(read)h(seems)f(to)h(be)h
+(empty)e(\(used)1695 4186 y(internally\))168 4299 y
+(NGP_UNREAD_QUEUE_FULL)c(364)238 b(cannot)46 b(unread)g(more)g(then)h
+(1)g(line)g(\(or)g(single)1695 4412 y(line)g(twice\))168
+4525 y(NGP_INC_NESTING)330 b(365)238 b(too)46 b(deep)h(include)f(file)h
+(nesting)e(\(infinite)1695 4638 y(loop,)h(template)g(includes)f(itself)
+i(?\))168 4751 y(NGP_ERR_FOPEN)426 b(366)238 b(fopen\(\))45
+b(failed,)h(cannot)g(open)h(template)e(file)168 4863
+y(NGP_EOF)714 b(367)238 b(end)46 b(of)i(file)e(encountered)f(and)i(not)
+g(expected)168 4976 y(NGP_BAD_ARG)522 b(368)238 b(bad)46
+b(arguments)g(passed.)g(Usually)f(means)1695 5089 y(internal)h(parser)g
+(error.)g(Should)g(not)h(happen)168 5202 y(NGP_TOKEN_NOT_EXPECT)90
+b(369)238 b(token)46 b(not)h(expected)e(here)168 5428
+y(BAD_I2C)523 b(401)190 b(bad)47 b(int)g(to)g(formatted)e(string)h
+(conversion)168 5541 y(BAD_F2C)523 b(402)190 b(bad)47
+b(float)f(to)h(formatted)f(string)g(conversion)1905 5809
+y Fg(40)p eop
+%%Page: 41 41
+41 40 bop 168 573 a Fc(BAD_INTKEY)379 b(403)190 b(can't)46
+b(interpret)g(keyword)f(value)i(as)g(integer)168 686
+y(BAD_LOGICALKEY)187 b(404)j(can't)46 b(interpret)g(keyword)f(value)i
+(as)g(logical)168 799 y(BAD_FLOATKEY)283 b(405)190 b(can't)46
+b(interpret)g(keyword)f(value)i(as)g(float)168 912 y(BAD_DOUBLEKEY)235
+b(406)190 b(can't)46 b(interpret)g(keyword)f(value)i(as)g(double)168
+1024 y(BAD_C2I)523 b(407)190 b(bad)47 b(formatted)e(string)h(to)h(int)g
+(conversion)168 1137 y(BAD_C2F)523 b(408)190 b(bad)47
+b(formatted)e(string)h(to)h(float)g(conversion)168 1250
+y(BAD_C2D)523 b(409)190 b(bad)47 b(formatted)e(string)h(to)h(double)f
+(conversion)168 1363 y(BAD_DATATYPE)283 b(410)190 b(illegal)46
+b(datatype)f(code)i(value)168 1476 y(BAD_DECIM)427 b(411)190
+b(bad)47 b(number)f(of)h(decimal)f(places)g(specified)168
+1589 y(NUM_OVERFLOW)283 b(412)190 b(overflow)45 b(during)i(datatype)e
+(conversion)168 1702 y(DATA_COMPRESSION_ERR)137 b(413)95
+b(error)46 b(compressing)f(image)168 1815 y(DATA_DECOMPRESSION_ERR)c
+(414)95 b(error)46 b(uncompressing)f(image)168 2041 y(BAD_DATE)475
+b(420)190 b(error)46 b(in)h(date)g(or)g(time)g(conversion)168
+2267 y(PARSE_SYNTAX_ERR)91 b(431)190 b(syntax)46 b(error)g(in)i(parser)
+e(expression)168 2379 y(PARSE_BAD_TYPE)187 b(432)j(expression)45
+b(did)i(not)g(evaluate)e(to)i(desired)f(type)168 2492
+y(PARSE_LRG_VECTOR)91 b(433)190 b(vector)46 b(result)g(too)h(large)f
+(to)i(return)e(in)h(array)168 2605 y(PARSE_NO_OUTPUT)139
+b(434)190 b(data)47 b(parser)f(failed)g(not)h(sent)f(an)h(out)g(column)
+168 2718 y(PARSE_BAD_COL)235 b(435)190 b(bad)47 b(data)f(encounter)g
+(while)g(parsing)g(column)168 2831 y(PARSE_BAD_OUTPUT)91
+b(436)190 b(Output)46 b(file)h(not)g(of)g(proper)f(type)168
+3057 y(ANGLE_TOO_BIG)235 b(501)190 b(celestial)45 b(angle)i(too)f
+(large)h(for)g(projection)168 3170 y(BAD_WCS_VAL)331
+b(502)190 b(bad)47 b(celestial)e(coordinate)g(or)i(pixel)g(value)168
+3283 y(WCS_ERROR)427 b(503)190 b(error)46 b(in)h(celestial)f
+(coordinate)f(calculation)168 3396 y(BAD_WCS_PROJ)283
+b(504)190 b(unsupported)45 b(type)h(of)h(celestial)f(projection)168
+3509 y(NO_WCS_KEY)379 b(505)190 b(celestial)45 b(coordinate)g(keywords)
+h(not)h(found)168 3621 y(APPROX_WCS_KEY)187 b(506)j(approximate)45
+b(wcs)i(keyword)e(values)h(were)h(returned)1905 5809
+y Fg(41)p eop
+%%Trailer
+end
+userdict /end-hook known{end-hook}if
+%%EOF
diff --git a/external/cfitsio/quick.tex b/external/cfitsio/quick.tex
new file mode 100644
index 0000000..109d04e
--- /dev/null
+++ b/external/cfitsio/quick.tex
@@ -0,0 +1,2159 @@
+\documentclass[11pt]{article}
+\input{html.sty}
+\htmladdtonavigation
+   {\begin{rawhtml}
+ FITSIO Home
+    \end{rawhtml}}
+
+\oddsidemargin=0.20in
+\evensidemargin=0.20in
+\textwidth=15.5truecm
+\textheight=21.5truecm
+
+\title{CFITSIO Quick Start Guide}
+\author{William Pence \thanks{HEASARC, NASA Goddard Space Flight Center,
+{\it William.D.Pence@nasa.gov}}}
+
+\date{January 2003}
+
+\begin{document}
+
+\maketitle
+\tableofcontents
+
+% ===================================================================
+\section{Introduction}
+
+This document is intended to help you quickly start writing C programs
+to read and write FITS files using the CFITSIO library.  It covers the
+most important CFITSIO routines that are needed to perform most types
+of operations on FITS files. For more complete information about these
+and all the other available routines in the library please refer to
+the  ``CFITSIO User's Reference Guide'', which is available from the
+CFITSIO Web site at {\tt http://heasarc.gsfc.nasa.gov/fitsio}.
+
+For more general information about the FITS data format, refer to the
+following web page:
+http://heasarc.gsfc.nasa.gov/docs/heasarc/fits.html
+
+FITS stands for Flexible Image Transport System and is the standard
+file format used to store most astronomical data files.  There are 2
+basic types of FITS files: images and tables.  FITS images often
+contain a 2-dimensional array of pixels representing an image of a
+piece of the sky, but  FITS images can also contain 1-D arrays (i.e,
+a spectrum or light curve), or  3-D arrays (a data cube), or
+even higher dimensional arrays of data.   An image may also have zero
+dimensions, in which case it is referred to as a null or empty array.
+The supported datatypes for the image arrays are 8, 16, and 32-bit
+integers, and 32 and 64-bit floating point real numbers.  Both signed
+and unsigned integers are supported.
+
+FITS tables contain rows and columns of data, similar to a
+spreadsheet.  All the values in a particular column must have the same
+datatype.  A cell of a column is not restricted to a single number, and
+instead can contain an array or vector of numbers.  There are actually
+2 subtypes of FITS tables: ASCII and binary. As the names imply,  ASCII
+tables store the data values in an ASCII representation whereas binary
+tables store the data values in a more efficient machine-readable
+binary format.  Binary tables are generally more compact and support
+more features (e.g., a wider range of datatypes, and vector columns)
+than ASCII tables.
+
+A single FITS file many contain multiple images or tables.  Each table
+or image is called a Header-Data Unit, or HDU.  The first HDU in a FITS
+file must be an image (but it may have zero axes) and is called the
+Primary Array.  Any additional HDUs in the file (which are also
+referred to as `extensions') may contain either an image or a table.
+
+Every HDU contains a header containing keyword records.  Each keyword
+record is 80 ASCII characters long and has the following format:
+
+\begin{verbatim}
+KEYWORD = value / comment string
+\end{verbatim}
+
+The keyword name can be up to 8 characters long (all uppercase).  The
+value can be either an integer or floating point number, a logical
+value (T or F), or a character string enclosed in single quotes.  Each
+header begins with a series of required keywords to describe the
+datatype and format of the following data unit, if any.  Any number of
+other optional keywords can be included in  the header to provide other
+descriptive information about the data.  For the most part, the CFITSIO
+routines automatically write the required FITS keywords for each HDU,
+so you, the programmer, usually do not need to worry about them.
+
+% ===================================================================
+\section{Installing and Using CFITSIO}
+
+First, you should download the CFITSIO software and the set of example
+FITS utility programs from the web site at
+http://heasarc.gsfc.nasa.gov/fitsio.  The example programs illustrate
+how to perform many common types of operations on FITS files using
+CFITSIO.  They are also useful when writing a new program because it is
+often easier to take a copy of one of these utility programs as a
+template and then modify it for your own purposes, rather than writing
+the new program completely from scratch.
+
+To build the CFITSIO library on Unix platforms, `untar' the source code
+distribution file and then execute the following commands in the
+directory containing the source code:
+
+\begin{verbatim}
+>  ./configure [--prefix=/target/installation/path]
+>  make           (or 'make shared')
+>  make install   (this step is optional)
+\end{verbatim}
+
+The optional
+'prefix' argument to configure gives the path to the directory where
+the CFITSIO library and include files should be installed via the later
+'make install' command. For example,
+
+\begin{verbatim}
+>  ./configure --prefix=/usr1/local
+\end{verbatim}
+
+will cause the 'make install' command to copy the CFITSIO libcfitsio file 
+to /usr1/local/lib and the necessary include files to /usr1/local/include
+(assuming of course that the  process has permission to write to these 
+directories).
+
+Pre-compiled versions of the CFITSIO DLL library are available for
+PCs.  On Macintosh machines, refer to the README.MacOS file for
+instructions on building CFITSIO using CodeWarrior.
+
+Any programs that use CFITSIO must of course be linked with the CFITSIO
+library when creating the executable file.  The exact procedure for
+linking a program depends on your software environment, but on Unix
+platforms, the command line to compile and link a program will look
+something like this:
+
+\begin{verbatim}
+gcc -o myprog myprog.c -L. -lcfitsio -lm -lnsl -lsocket
+\end{verbatim}
+
+You may not need to include all of the 'm', 'nsl', and 'socket' system
+libraries on your particular machine.  To find out what libraries are
+required on your (Unix) system, type {\tt'make testprog'} and see what
+libraries are then included on the resulting link line.
+
+\newpage
+% ===================================================================
+\section{Example Programs}
+
+Before describing the individual CFITSIO routines in detail, it is
+instructive to first look at an actual program.  The names of the
+CFITSIO routines are fairly descriptive (they all begin with {\tt
+fits\_}, so it should be reasonably clear what this program does:
+
+\begin{verbatim}
+----------------------------------------------------------------
+    #include 
+    #include 
+1:  #include "fitsio.h"
+
+    int main(int argc, char *argv[])
+    {
+2:      fitsfile *fptr;         
+        char card[FLEN_CARD]; 
+3:      int status = 0,  nkeys, ii;  /* MUST initialize status */
+
+4:      fits_open_file(&fptr, argv[1], READONLY, &status);
+        fits_get_hdrspace(fptr, &nkeys, NULL, &status);
+
+        for (ii = 1; ii <= nkeys; ii++)  { 
+          fits_read_record(fptr, ii, card, &status); /* read keyword */
+          printf("%s\n", card);
+        }
+        printf("END\n\n");  /* terminate listing with END */
+        fits_close_file(fptr, &status);
+
+        if (status)          /* print any error messages */
+5:          fits_report_error(stderr, status);
+        return(status);
+    }
+----------------------------------------------------------------
+\end{verbatim}
+
+This program opens the specified FITS file and prints
+out all the header keywords in the current HDU.
+Some other points to notice about the program are:
+\begin{enumerate}
+
+\item
+The {\tt fitsio.h} header file must be included to define the 
+various routines and symbols used in CFITSIO.
+
+\item
+
+The {\tt fitsfile}  parameter is the first argument in almost every
+CFITSIO routine.  It is a pointer to a structure (defined in {\tt
+fitsio.h}) that stores information about the particular FITS file that
+the routine will operate on.  Memory for this structure is
+automatically allocated when the file is first opened or created, and
+is freed when the file is closed.
+
+\item
+Almost every CFITSIO routine has a {\tt status} parameter as the last
+argument. The status value is also usually returned as the value of the
+function itself.  Normally status = 0, and a positive status value
+indicates an error of some sort.  The status variable must always be
+initialized to zero before use, because if status is greater than zero
+on input then the CFITSIO routines will simply return without doing
+anything.  This `inherited status' feature, where each CFITSIO routine
+inherits the status from the previous routine, makes it unnecessary to
+check the status value after every single CFITSIO routine call.
+Generally you should check the status after an especially important or
+complicated routine has been called, or after a block of
+closely related CFITSIO calls.  This example program has taken this
+feature to the extreme and only checks the status value at the 
+very end of the program.
+
+\item
+
+In this example program the file name to be opened is given as an
+argument on the command line ({\tt arg[1]}).  If the file contains more
+than 1 HDU or extension, you can specify which particular HDU to be
+opened by enclosing the name or number of the HDU in square brackets
+following the root name of the file.  For example, {\tt file.fts[0]}
+opens the  primary array, while {\tt file.fts[2]} will move to and open
+the 2nd extension in the file, and {\tt file.fit[EVENTS]} will open the
+extension that has a {\tt EXTNAME = 'EVENTS'} keyword in the header.
+Note that on the Unix command line you must enclose the file name in
+single or double quote characters if the name contains special
+characters such as `[' or `]'.
+
+All of the CFITSIO routines which read or write header keywords,
+image data, or table data operate only within the currently opened
+HDU in the file. To read or write information in a different HDU you must
+first explicitly move to that HDU (see the {\tt fits\_movabs\_hdu} and
+{\tt fits\_movrel\_hdu} routines in section 4.3).
+
+\item
+
+The {\tt fits\_report\_error} routine provides a convenient way to print out
+diagnostic messages about any error that may have occurred. 
+
+\end{enumerate}
+
+A set of example FITS utility programs are  available from the CFITSIO
+web site at \newline
+http://heasarc.gsfc.nasa.gov/docs/software/fitsio/cexamples.html.
+These are real working programs which illustrate how to read, write,
+and modify FITS files using the CFITSIO library.  Most of these
+programs are very short, containing only a few 10s of lines of
+executable code or less, yet they perform quite useful operations on
+FITS files. Running each program without any command line arguments
+will produce a short description of how to use the program.
+The currently available programs are:
+\begin{quote}
+fitscopy - copy a file
+\newline
+listhead - list header keywords
+\newline
+liststruc - show the structure of a FITS file.
+\newline
+modhead  - write or modify a header keyword
+\newline
+imarith  - add, subtract, multiply, or divide 2 images
+\newline
+imlist  - list pixel values in an image
+\newline
+imstat  - compute mean, min, and max pixel values in an image
+\newline
+tablist - display the contents of a FITS table
+\newline
+tabcalc  - general table calculator
+\end{quote}
+
+\newpage
+
+% ===================================================================
+\section{CFITSIO Routines}
+
+This chapter describes the main CFITSIO routines that can be used to
+perform the most common types of operations on FITS files.
+
+% ===================================================================
+{\bf \subsection{Error Reporting}}
+
+\begin{verbatim}
+void fits_report_error(FILE *stream, int status)
+void fits_get_errstatus(int status, char *err_text)
+float fits_get_version(float *version)
+\end{verbatim}
+
+The first routine prints out information about any error that
+has occurred.  Whenever any CFITSIO routine encounters an error it
+usually writes a message describing the nature of the error to an
+internal error message stack and then returns with a positive integer
+status value. Passing the error status value to this routine will
+cause  a generic description of the error and all the messages
+from the internal CFITSIO error stack to be printed to the specified
+stream.  The {\tt stream} parameter is usually set equal to
+{\tt "stdout"} or {\tt "stderr"}.
+
+The second routine simply returns a 30-character descriptive
+error message corresponding to the input status value.
+
+The last routine returns the current CFITSIO library version number.
+
+% ===================================================================
+{\bf \subsection{File Open/Close Routines}}
+
+\begin{verbatim}
+int fits_open_file( fitsfile **fptr, char *filename, int mode, int *status)
+int fits_open_data( fitsfile **fptr, char *filename, int mode, int *status)
+int fits_open_table(fitsfile **fptr, char *filename, int mode, int *status)
+int fits_open_image(fitsfile **fptr, char *filename, int mode, int *status)
+
+int fits_create_file(fitsfile **fptr, char *filename, int *status)
+int fits_close_file(fitsfile *fptr, int *status)
+\end{verbatim}
+
+These routines open or close a file.  The first {\tt fitsfile}
+parameter  in these and nearly every other CFITSIO routine is a pointer
+to a structure that CFITSIO uses to store relevant parameters about
+each opened file.  You should never directly read or write any
+information in this structure.  Memory for this structure is allocated
+automatically when the file is opened or created, and is freed when the
+file is closed.
+
+The {\tt mode} parameter in the {\tt fits\_open\_xxxx} set of routines
+can be set to either {\tt READONLY} or {\tt READWRITE} to select the
+type of file access that will be allowed. These symbolic constants are
+defined in {\tt fitsio.h}.
+
+The {\tt fits\_open\_file} routine opens the file and positions the internal
+file pointer to the beginning of the file, or to the specified
+extension if an extension name or number is appended to the file name
+(see the later section on ``CFITSIO File Names and Filters'' for a
+description of the syntax). {\tt fits\_open\_data} behaves similarly except
+that it will move to the first HDU containing significant data if a HDU
+name or number to open is not explicitly specified as part of the
+filename.  It will move to the first IMAGE HDU with NAXIS greater than
+0, or the first table that does not contain the strings `GTI' (a Good
+Time Interval extension) or `OBSTABLE' in the EXTNAME keyword value.
+The {\tt fits\_open\_table} and {\tt fits\_open\_image}  routines are similar
+except that they will move to the first significant table HDU or image
+HDU, respectively if a HDU name of number is not specified as part of
+the input file name.
+
+When opening an existing file, the {\tt filename} can include optional
+arguments, enclosed in square brackets that specify filtering
+operations that should be applied to the input file.  For example,
+\begin{verbatim}
+   myfile.fit[EVENTS][counts > 0]
+\end{verbatim}
+opens the table in the EVENTS extension and creates a virtual table by
+selecting only those rows where the COUNTS column value is greater than
+0.  See section 5 for more examples of these powerful filtering
+capabilities.
+
+In {\tt fits\_create\_file},  the {\tt filename} is simply the root name of
+the file to be created.  You can overwrite an existing file by
+prefixing the name with a `!' character (on the Unix command line this
+must be prefixed with a backslash, as in \verb+`\!file.fit'+).  
+If the file name ends with {\tt .gz} the file will be compressed
+using the gzip algorithm.  If the
+filename is {\tt stdout} or {\tt "-"} (a single dash character)
+then the output file will be piped to the stdout stream.  You can
+chain several tasks together by writing the output from the first task
+to {\tt stdout} and then reading the input file in the 2nd task from
+{\tt stdin} or {\tt "-"}.
+
+
+% ===================================================================
+{\bf \subsection{HDU-level Routines}}
+
+The routines listed in this section operate on Header-Data Units (HDUs) in a file.
+
+\begin{verbatim}
+_______________________________________________________________
+int fits_get_num_hdus(fitsfile *fptr, int *hdunum, int *status)
+int fits_get_hdu_num(fitsfile *fptr,  int *hdunum)
+\end{verbatim}
+
+The first routines returns the total number of HDUs in the FITS file,
+and the second routine returns the position of the currently opened HDU in
+the FITS file (starting with 1, not 0).
+
+\begin{verbatim}
+__________________________________________________________________________
+int fits_movabs_hdu(fitsfile *fptr, int hdunum, int *hdutype, int *status)
+int fits_movrel_hdu(fitsfile *fptr, int nmove,  int *hdutype, int *status)
+int fits_movnam_hdu(fitsfile *fptr, int hdutype, char *extname,
+                    int extver, int *status)
+\end{verbatim}
+
+These routines enable you to move to a different HDU in the file.
+Most of the CFITSIO functions which read or write keywords or data
+operate only on the currently opened HDU in the file.  The first
+routine moves to the specified absolute HDU number in the FITS
+file (the first HDU = 1), whereas the second routine moves a relative
+number of HDUs forward or backward from the currently open HDU.  The
+{\tt hdutype} parameter returns the type of the newly opened HDU, and will
+be equal to one of these symbolic constant values: {\tt IMAGE\_HDU,
+ASCII\_TBL, or BINARY\_TBL}.  {\tt hdutype} may be set to NULL
+if it is not needed.  The third routine moves to the (first) HDU
+that matches the input extension type, name, and version number,
+as given by the {\tt XTENSION, EXTNAME} (or {\tt HDUNAME}) and {\tt EXTVER} keywords.
+If the input value of {\tt extver} = 0, then the version number will
+be ignored when looking for a matching HDU.
+
+\begin{verbatim}
+_________________________________________________________________
+int fits_get_hdu_type(fitsfile *fptr,  int *hdutype, int *status)
+\end{verbatim}
+
+Get the type of the current HDU in the FITS file:  {\tt IMAGE\_HDU,
+ASCII\_TBL, or BINARY\_TBL}.
+
+\begin{verbatim}
+____________________________________________________________________
+int fits_copy_hdu(fitsfile *infptr, fitsfile *outfptr, int morekeys,
+                  int *status)
+int fits_copy_file(fitsfile *infptr, fitsfile *outfptr, int previous,
+                  int current, int following, > int *status)
+\end{verbatim}
+
+The first routine copies the current HDU from the FITS file associated
+with infptr and appends it to the end of the FITS file associated with
+outfptr.  Space may be reserved for {\tt morekeys} additional keywords
+in the output header.   The second routine copies any HDUs previous
+to the current HDU, and/or the current HDU, and/or any HDUs following the
+current HDU, depending on the value (True or False) of {\tt previous,
+current}, and {\tt following}, respectively.  For example,
+\begin{verbatim}
+  fits_copy_file(infptr, outfptr, 0, 1, 1, &status);
+\end{verbatim}
+will copy the current HDU and any HDUs that follow it from the input
+to the output file, but it will not copy any HDUs preceding the 
+current HDU.
+
+
+\newpage
+% ===================================================================
+\subsection{Image I/O Routines}
+
+This section lists the more important CFITSIO routines which operate on
+FITS images.
+
+\begin{verbatim}
+_______________________________________________________________
+int fits_get_img_type(fitsfile *fptr, int *bitpix, int *status)
+int fits_get_img_dim( fitsfile *fptr, int *naxis,  int *status)
+int fits_get_img_size(fitsfile *fptr, int maxdim,  long *naxes,
+                      int *status)
+int fits_get_img_param(fitsfile *fptr, int maxdim,  int *bitpix,
+                       int *naxis, long *naxes, int *status)
+\end{verbatim}
+
+Get information about the currently opened image HDU. The first routine
+returns the datatype of the image as (defined by the {\tt BITPIX}
+keyword), which can have the following symbolic constant values: 
+\begin{verbatim}
+    BYTE_IMG   =   8   ( 8-bit byte pixels, 0 - 255)
+    SHORT_IMG  =  16   (16 bit integer pixels)
+    LONG_IMG   =  32   (32-bit integer pixels)
+    FLOAT_IMG  = -32   (32-bit floating point pixels)
+    DOUBLE_IMG = -64   (64-bit floating point pixels)
+\end{verbatim}
+
+The second and third routines return the number of dimensions in the
+image (from the {\tt NAXIS} keyword), and the sizes of each dimension
+(from the {\tt NAXIS1, NAXIS2}, etc. keywords).  The last routine
+simply combines the function of the first 3 routines.  The input {\tt
+maxdim} parameter in this routine gives the maximum number dimensions
+that may be returned (i.e., the dimension of the {\tt naxes}
+array)
+
+\begin{verbatim}
+__________________________________________________________
+int fits_create_img(fitsfile *fptr, int bitpix, int naxis, 
+                    long *naxes, int *status)
+\end{verbatim}
+
+Create an image HDU by writing the required keywords which define the
+structure of the image.  The 2nd through 4th parameters  specified the
+datatype, the number of dimensions, and the sizes of the dimensions.
+The allowed values of the {\tt bitpix} parameter are listed above in
+the description of the {\tt fits\_get\_img\_type} routine.  If the FITS
+file pointed to by {\tt fptr} is empty (previously created with
+{\tt fits\_create\_file}) then this routine creates a primary array in
+the file, otherwise a new IMAGE extension is appended to end of the
+file following the other HDUs in the file.
+
+\begin{verbatim}
+______________________________________________________________
+int fits_write_pix(fitsfile *fptr, int datatype, long *fpixel,
+               long nelements, void *array, int *status);
+
+int fits_write_pixnull(fitsfile *fptr, int datatype, long *fpixel,
+               long nelements, void *array, void *nulval, int *status);
+
+int fits_read_pix(fitsfile *fptr, int  datatype, long *fpixel, 
+                  long nelements, void *nulval, void *array, 
+                  int *anynul, int *status)
+\end{verbatim}
+
+Read or write all or part of the FITS image.  There are 2 different
+'write' pixel routines:  The first simply writes the input array of pixels
+to the FITS file.  The second is similar, except that it substitutes
+the appropriate null pixel value in the FITS file for any pixels 
+which have a value equal to {\tt *nulval} (note that this parameter
+gives the address of the null pixel value, not the value itself).
+Similarly,  when reading an image, CFITSIO will substitute the value
+given by {\tt nulval}  for  any undefined pixels in the image, unless
+{\tt nulval = NULL}, in which case no checks will be made for undefined
+pixels when reading the FITS image.
+
+The {\tt fpixel} parameter in these routines is an array which gives
+the coordinate in each dimension of the first pixel to be read or
+written, and {\tt nelements} is the total number of pixels to read or
+write.  {\tt array} is the address of an array which either contains
+the pixel values to be written, or will hold the values of the pixels
+that are read.  When reading, {\tt array} must have been allocated
+large enough to hold all the returned pixel values.  These routines
+starts at the {\tt fpixel} location and then read or write the {\tt
+nelements} pixels, continuing on successive rows of the image if
+necessary.  For example, to write an entire 2D image, set {\tt
+fpixel[0] = fpixel[1] = 1}, and {\tt nelements = NAXIS1 * NAXIS2}.  Or
+to read just the 10th row of the image, set {\tt fpixel[0] = 1,
+fpixel[1] = 10}, and {\tt nelements = NAXIS1}.  The {\tt datatype}
+parameter specifies the datatype of the C {\tt array} in the program,
+which need not be the same as the datatype of the FITS image itself.
+If the datatypes differ then CFITSIO will convert the data as it is
+read or written.  The following symbolic constants are allowed for the
+value of {\tt datatype}:
+\begin{verbatim}
+  TBYTE     unsigned char
+  TSBYTE    signed char
+  TSHORT    signed short
+  TUSHORT   unsigned short
+  TINT      signed int
+  TUINT     unsigned int
+  TLONG     signed long
+  TLONGLONG signed 8-byte integer
+  TULONG    unsigned long
+  TFLOAT    float
+  TDOUBLE   double
+\end{verbatim}
+
+
+\begin{verbatim}
+_________________________________________________________________
+int fits_write_subset(fitsfile *fptr, int datatype, long *fpixel,
+             long *lpixel, DTYPE *array, > int *status)
+
+int fits_read_subset(fitsfile *fptr, int  datatype, long *fpixel,
+             long *lpixel, long *inc, void *nulval,  void *array,
+             int *anynul, int *status)
+\end{verbatim}
+
+Read or write a rectangular section of the FITS image.  These are very
+similar to {\tt fits\_write\_pix} and {\tt fits\_read\_pix} except that
+you specify the last pixel coordinate (the upper right corner of the
+section) instead of the number of pixels to be read.  The read routine
+also has an {\tt inc} parameter which can be used to read only every
+{\tt inc-th} pixel along each dimension of the image.  Normally  {\tt
+inc[0] = inc[1] = 1} to read every pixel in a 2D image.  To read every
+other pixel in the entire 2D image, set
+\begin{verbatim}
+    fpixel[0] = fpixel[1] = 1
+    lpixel[0] = {NAXIS1}
+    lpixel[1] = {NAXIS2}  
+    inc[0] = inc[1] = 2  
+\end{verbatim}
+
+Or, to read the 8th row of a 2D image, set 
+\begin{verbatim}
+    fpixel[0] = 1
+    fpixel[1] = 8
+    lpixel[0] = {NAXIS1}
+    lpixel[1] = 8
+    inc[0] = inc[1] = 1
+\end{verbatim}
+
+\newpage
+% ===================================================================
+\subsection{Table I/O Routines}
+
+This section lists the most important CFITSIO routines which operate on
+FITS tables.
+
+\begin{verbatim}
+__________________________________________________________________________
+int fits_create_tbl(fitsfile *fptr, int tbltype, long nrows, int tfields,
+    char *ttype[],char *tform[], char *tunit[], char *extname, int *status)
+\end{verbatim}
+
+Create a new  table extension by writing the required keywords that
+define the table structure. The required null primary array
+will be created first if the file is initially completely empty.  {\tt
+tbltype} defines the type of table and can have values of {\tt
+ASCII\_TBL or BINARY\_TBL}.  Binary tables are generally preferred
+because they are more efficient and support a greater range of column
+datatypes than ASCII tables.
+
+The {\tt nrows} parameter gives the initial number of empty rows to be
+allocated for the table; this should normally be set to 0.  The {\tt tfields}
+parameter gives the number of columns in the table (maximum = 999).
+The {\tt
+ttype, tform}, and {\tt tunit} parameters give the name, datatype, and
+physical units of each column, and {\tt extname} gives the name for the
+table (the value of the {\tt EXTNAME} keyword).  
+The FITS Standard recommends that only
+letters, digits, and the underscore character be used in column names
+with no embedded spaces.  It is recommended that all the column names
+in a given table be unique within the first 8 characters.
+
+The following table
+shows the TFORM column format values that are allowed in ASCII tables
+and in binary tables:
+\begin{verbatim}
+        ASCII Table Column Format Codes
+        -------------------------------
+        (w = column width, d = no. of decimal places to display)
+            Aw   - character string
+            Iw   - integer
+            Fw.d - fixed floating point
+            Ew.d - exponential floating point
+            Dw.d - exponential floating point
+
+        Binary Table Column Format Codes
+        --------------------------------
+        (r = vector length, default = 1)
+            rA  - character string
+            rAw - array of strings, each of length w
+            rL  - logical
+            rX  - bit
+            rB  - unsigned byte
+            rS  - signed byte **
+            rI  - signed 16-bit integer
+            rU  - unsigned 16-bit integer **
+            rJ  - signed 32-bit integer
+            rV  - unsigned 32-bit integer **
+            rK  - 64-bit integer ***
+            rE  - 32-bit floating point
+            rD  - 64-bit floating point
+            rC  - 32-bit complex pair
+            rM  - 64-bit complex pair
+
+     ** The S, U and V format codes are not actual legal TFORMn values.
+        CFITSIO substitutes the somewhat more complicated set of
+        keywords that are used to represent unsigned integers or
+        signed bytes.
+
+    *** The 64-bit integer format is experimental and is not 
+        officially recognized in the FITS Standard.
+\end{verbatim}
+  
+The {\tt tunit} and {\tt extname} parameters are optional and
+may be set to NULL
+if they are not needed.  
+
+Note that it may be easier to create a new table by copying the
+header from another existing table with {\tt fits\_copy\_header} rather
+than calling this routine.
+
+\begin{verbatim}
+_______________________________________________________________
+int fits_get_num_rows(fitsfile *fptr, long *nrows, int *status)
+int fits_get_num_cols(fitsfile *fptr, int  *ncols, int *status)
+\end{verbatim}
+
+Get the number of rows or columns in the current FITS table.  The
+number of rows is given by the {\tt NAXIS2} keyword and the number of columns
+is given by the {\tt TFIELDS} keyword in the header of the table.
+
+\begin{verbatim}
+_______________________________________________________________
+int fits_get_colnum(fitsfile *fptr, int casesen, char *template,
+                    int *colnum, int *status)
+int fits_get_colname(fitsfile *fptr, int casesen, char *template,
+                    char *colname, int *colnum, int *status)
+\end{verbatim}
+
+Get the  column number (starting with 1, not 0) of the column whose
+name matches the specified template name.  The only difference in
+these 2 routines is that the 2nd one also returns the name of the
+column that matched the template string.
+
+Normally, {\tt casesen} should
+be set to {\tt CASEINSEN}, but it may be set to {\tt CASESEN} to force
+the name matching to be case-sensitive.
+
+The input {\tt template} string gives the name of the desired column and
+may include wildcard characters:  a `*' matches any sequence of
+characters (including zero characters), `?' matches any single
+character, and `\#' matches any consecutive string of decimal digits
+(0-9).  If more than one column name in the table matches the template
+string, then the first match is returned and the status value will be
+set to {\tt COL\_NOT\_UNIQUE}  as a warning that a unique match was not
+found.  To find the next column that matches the template, call this
+routine again leaving the input status value equal to {\tt
+COL\_NOT\_UNIQUE}.  Repeat this process until {\tt status =
+COL\_NOT\_FOUND}  is returned.
+
+\begin{verbatim}
+_______________________________________________________________
+int fits_get_coltype(fitsfile *fptr, int colnum, int *typecode,
+                     long *repeat, long *width, int *status)
+
+int fits_get_eqcoltype(fitsfile *fptr, int colnum, int *typecode,
+                     long *repeat, long *width, int *status)
+\end{verbatim}
+
+Return the datatype, vector repeat count, and the width in bytes of a
+single column element for column number {\tt colnum}.  Allowed values
+for the returned datatype in ASCII tables are:  {\tt TSTRING, TSHORT,
+TLONG, TFLOAT, and TDOUBLE}.  Binary tables support these additional
+types: {\tt TLOGICAL, TBIT, TBYTE, TINT32BIT, TCOMPLEX and TDBLCOMPLEX}.  The
+negative of the datatype code value is returned if it is a variable
+length array column.
+
+These 2 routines are similar, except that in the case of scaled
+integer columns the 2nd routine, fit\_get\_eqcoltype, returns the
+'equivalent' datatype that is needed to store the scaled values, which
+is not necessarily the same as the physical datatype of the unscaled values
+as stored in the FITS table.  For example if a '1I' column in a binary
+table has TSCALn = 1 and TZEROn = 32768, then this column effectively
+contains unsigned short integer values, and thus the returned value of
+typecode will be TUSHORT, not TSHORT.  Or, if TSCALn or TZEROn are not
+integers, then the equivalent datatype will be returned as TFLOAT or
+TDOUBLE, depending on the size of the integer.
+
+The repeat count is always 1 in ASCII tables.
+The 'repeat' parameter returns the vector repeat count on the binary
+table TFORMn keyword value. (ASCII table columns always have repeat
+= 1).  The 'width' parameter returns the width in bytes of a single
+column element (e.g., a '10D' binary table column will have width =
+8, an ASCII table 'F12.2' column will have width = 12, and a binary
+table'60A' character string  column will have width = 60);  Note that
+this routine supports the local convention for specifying arrays of
+fixed length strings within a binary table character column using
+the syntax TFORM = 'rAw' where 'r' is the total number of
+characters (= the width of the column) and 'w' is the width of a
+unit string within the column.  Thus if the column has TFORM =
+'60A12' then this means that each row of the table contains
+5 12-character substrings within the 60-character field, and thus 
+in this case this routine will return typecode = TSTRING, repeat =
+60, and width = 12.  The number of substings in any binary table
+character string field can be calculated by (repeat/width). 
+A null pointer may be given for any of the output parameters that
+ are not needed.
+
+\begin{verbatim}
+____________________________________________________________________________
+int fits_insert_rows(fitsfile *fptr, long firstrow, long nrows, int *status)
+int fits_delete_rows(fitsfile *fptr, long firstrow, long nrows, int *status)
+int fits_delete_rowrange(fitsfile *fptr, char *rangelist, int *status)
+int fits_delete_rowlist(fitsfile *fptr, long *rowlist, long nrows, int *stat)
+\end{verbatim}
+
+Insert or delete rows in a table.  The blank rows are inserted
+immediately following row {\tt frow}. Set {\tt frow} = 0 to insert rows
+at the beginning of the table.  The first 'delete' routine deletes {\tt
+nrows} rows beginning with row {\tt firstrow}.   The 2nd delete routine
+takes an input string listing the rows or row ranges to be deleted
+(e.g., '2,4-7, 9-12').  The last delete routine takes an input long
+integer array that specifies each individual row to be deleted.  The
+row lists must be sorted in ascending order.  All these routines update
+the value of the {\tt NAXIS2} keyword to reflect the new number of rows
+in the table.
+
+\begin{verbatim}
+_________________________________________________________________________
+int fits_insert_col(fitsfile *fptr, int colnum, char *ttype, char *tform,
+                    int *status)
+int fits_insert_cols(fitsfile *fptr, int colnum, int ncols, char **ttype,
+                     char **tform, int *status)
+
+int fits_delete_col(fitsfile *fptr, int colnum, int *status)
+\end{verbatim}
+
+Insert or delete columns in a table.  {\tt colnum} gives the position
+of the column to be inserted or deleted (where the first column of the
+table is at position 1).  {\tt ttype} and {\tt tform} give the column
+name and column format, where the allowed format codes are listed above
+in the description of the {\tt fits\_create\_table} routine.  The 2nd
+'insert' routine inserts multiple columns, where {\tt ncols} is the
+number of columns to insert, and  {\tt ttype} and {\tt tform} are
+arrays of string pointers in this case.
+
+\begin{verbatim}
+____________________________________________________________________
+int fits_copy_col(fitsfile *infptr, fitsfile *outfptr, int incolnum,
+        int outcolnum, int create_col, int *status);
+\end{verbatim}
+
+Copy a column from one table HDU to another.  If {\tt create\_col} = TRUE (i.e., not equal to zero),
+then a new column will be inserted in the output table at position
+{\tt outcolumn}, otherwise the values in the existing output column will be
+overwritten. 
+
+\begin{verbatim}
+__________________________________________________________________________
+int fits_write_col(fitsfile *fptr, int datatype, int colnum, long firstrow,
+                  long firstelem, long nelements, void *array, int *status)
+int fits_write_colnull(fitsfile *fptr, int datatype, int colnum, 
+                  long firstrow, long firstelem, long nelements, 
+                  void *array, void *nulval, int *status)
+int fits_write_col_null(fitsfile *fptr, int colnum, long firstrow,
+                  long firstelem, long nelements, int *status)
+
+int fits_read_col(fitsfile *fptr, int datatype, int colnum, long firstrow,
+       long firstelem, long nelements, void *nulval, void *array, 
+       int *anynul, int *status)
+
+\end{verbatim}
+
+Write or read elements in column number {\tt colnum}, starting with row
+{\tt firstsrow} and element {\tt firstelem} (if it is a vector
+column).  {\tt firstelem} is ignored if it is a scalar column. The {\tt
+nelements} number of elements are read or written continuing on
+successive rows of the table if necessary. {\tt array} is the address
+of an array which either contains the  values to be written, or will
+hold the returned values that are read.  When reading, {\tt array} must
+have been allocated large enough to hold all the returned values.
+
+There are 3 different 'write' column routines:  The first simply writes
+the input array into the column.  The second is similar, except that it
+substitutes the appropriate null pixel value in the column for any
+input array values which are equal to {\tt *nulval} (note that this
+parameter gives the address of the null pixel value, not the value
+itself).  The third write routine sets the specified table elements
+to a null value.  New rows will be automatical added to the table
+if the write operation extends beyond the current size of the table.
+
+When reading a column, CFITSIO will substitute the value given by {\tt
+nulval}  for  any undefined elements in the FITS column, unless {\tt
+nulval} or {\tt *nulval = NULL}, in which case no checks will be made
+for undefined values when reading the column.
+
+{\tt datatype} specifies the datatype of the C {\tt array} in the program,
+which need not be the same as the intrinsic datatype of the column in
+the FITS table.   The following symbolic constants are allowed for the
+value of {\tt datatype}:
+
+\begin{verbatim}
+  TSTRING   array of character string pointers
+  TBYTE     unsigned char
+  TSHORT    signed short
+  TUSHORT   unsigned short
+  TINT      signed int
+  TUINT     unsigned int
+  TLONG     signed long
+  TLONGLONG signed 8-byte integer
+  TULONG    unsigned long
+  TFLOAT    float
+  TDOUBLE   double
+\end{verbatim}
+
+Note that {\tt TSTRING} corresponds to the C {\tt
+char**} datatype, i.e., a pointer to an array of pointers to an array
+of characters.
+
+Any column, regardless of it's intrinsic datatype, may be read as a
+{\tt TSTRING} character string. The display format of the returned
+strings will be determined by the {\tt TDISPn} keyword, if it exists,
+otherwise a default format will be used depending on the datatype of
+the column.  The {\tt tablist} example utility program (available from
+the CFITSIO web site) uses this feature to display all the values in a
+FITS table.
+
+\begin{verbatim}
+_____________________________________________________________________
+int fits_select_rows(fitsfile *infptr, fitsfile *outfptr, char *expr,
+                     int *status)
+int fits_calculator(fitsfile *infptr, char *expr, fitsfile *outfptr,
+                    char *colname, char *tform, int *status) 
+\end{verbatim}
+
+These are 2 of the most powerful routines in the CFITSIO library.  (See
+the full CFITSIO Reference Guide for a description of several related
+routines).  These routines can perform complicated transformations on
+tables based on an input arithmetic expression which is evaluated for
+each row of the table.  The first routine will select or copy rows of
+the table for which the expression evaluates to TRUE (i.e., not equal
+to zero).  The second routine writes the value of the expression to a
+column in the output table.  Rather than supplying the expression
+directly to these routines, the expression may also be written to a
+text file (continued over multiple lines if necessary) and the name of
+the file, prepended with a '@' character, may be supplied as the value
+of the 'expr' parameter (e.g.  '@filename.txt').
+
+The arithmetic expression may be a function of any column or keyword in
+the input table as shown in these examples:
+
+\begin{verbatim}
+Row Selection Expressions:
+   counts > 0                          uses COUNTS column value
+   sqrt( X**2 + Y**2) < 10.            uses X and Y column values
+   (X > 10) || (X < -10) && (Y == 0)   used 'or' and 'and' operators  
+   gtifilter()                         filter on Good Time Intervals
+   regfilter("myregion.reg")           filter using a region file
+   @select.txt                         reads expression from a text file
+Calculator Expressions:
+   #row % 10                        modulus of the row number
+   counts/#exposure                 Fn of COUNTS column and EXPOSURE keyword
+   dec < 85 ? cos(dec * #deg) : 0   Conditional expression: evaluates to
+                                      cos(dec) if dec < 85, else 0
+   (count{-1}+count+count{+1})/3.   running mean of the count values in the
+                                      previous, current, and next rows
+   max(0, min(X, 1000))             returns a value between 0 - 1000
+   @calc.txt                        reads expression from a text file
+\end{verbatim}
+
+Most standard mathematical operators and functions are supported.  If
+the expression includes the name of a column, than the value in the
+current row of the table will be used when evaluating the expression on
+each row.   An offset to an adjacent row can be specified by including
+the offset value in curly brackets after the column name as shown in
+one of the examples.  Keyword values can be included in the expression
+by preceding the keyword name with a `\#' sign.   See Section 5 of this
+document for more discussion of the expression syntax.
+
+{\tt gtifilter} is a special function which tests whether the {\tt
+TIME} column value in the input table falls within one or more Good
+Time Intervals.  By default, this function looks for a 'GTI' extension
+in the same file as the input table.  The 'GTI' table contains {\tt START} 
+and {\tt STOP} columns which define the range of
+each good time interval. See section 5.4.3 for more details.
+
+{\tt regfilter} is another special function which selects rows based on
+whether the spatial position associated with each row is located within
+in a specified region of the sky.  By default, the {\tt X} and {\tt Y}
+columns in the input table are assumed to give the position of each row.
+The spatial region is defined in an ASCII text file whose name is given
+as the argument to the {\tt regfilter} function. See section 5.4.4 for
+more details.
+
+The {\tt infptr} and {\tt outfptr} parameters in these routines may
+point to the same table or to different tables.  In {\tt
+fits\_select\_rows}, if the input and output tables are the same then
+the rows that do not satisfy the selection expression will be deleted
+from the table.  Otherwise, if the output table is different from the
+input table then the selected rows will be copied from the input table
+to the output table.
+
+The output column in {\tt fits\_calculator} may or may not already
+exist.  If it exists then the calculated values will be written to that
+column, overwriting the existing values.  If the column doesn't exist
+then the new column will be appended to the output table. The {\tt tform}
+parameter can be used to specify the datatype of the new column (e.g.,
+the {\tt TFORM} keyword value as in {\tt '1E', or '1J'}). If {\tt
+tform} = NULL then a default datatype will be used, depending on the
+expression.
+
+\begin{verbatim}
+_____________________________________________________________________
+int fits_read_tblbytes(fitsfile *fptr, long firstrow, long firstchar,
+                     long nchars, unsigned char *array, int *status)
+int fits_write_tblbytes (fitsfile *fptr, long firstrow, long firstchar,
+                     long nchars, unsigned char *array, int *status)
+\end{verbatim}
+
+These 2 routines provide low-level access to tables and are mainly
+useful as an efficient way to copy rows of a table from one file to
+another.  These routines simply read or write the specified number of
+consecutive characters (bytes) in a table, without regard for column
+boundaries.  For example, to read or write the first row of a table,
+set {\tt firstrow = 1, firstchar = 1}, and {\tt nchars = NAXIS1} where
+the length of a row is given by the value of the {\tt NAXIS1} header
+keyword.  When reading a table, {\tt array} must have been declared at
+least {\tt nchars} bytes long to hold the returned string of bytes.
+
+\newpage
+% ===================================================================
+\subsection{Header Keyword I/O Routines}
+\nopagebreak
+The following routines read and write header keywords in the current HDU.
+\nopagebreak
+
+\begin{verbatim}
+____________________________________________________________________
+int fits_get_hdrspace(fitsfile *fptr, int *keysexist, int *morekeys,
+                      int *status)
+\end{verbatim}
+\nopagebreak
+Return the number of existing keywords (not counting the mandatory END
+keyword) and the amount of empty space currently available for more
+keywords. The {\tt morekeys} parameter may be set to NULL if it's value is
+not needed.
+
+\begin{verbatim}
+___________________________________________________________________________
+int fits_read_record(fitsfile *fptr, int keynum, char *record, int *status)
+int fits_read_card(fitsfile *fptr, char *keyname, char *record, int *status)
+int fits_read_key(fitsfile *fptr, int datatype, char *keyname,
+                  void *value, char *comment, int *status)
+
+int fits_find_nextkey(fitsfile *fptr, char **inclist, int ninc,
+                      char **exclist, int nexc, char *card, int *status)
+
+int fits_read_key_unit(fitsfile *fptr, char *keyname, char *unit, 
+                       int *status)
+\end{verbatim}
+
+These routines all read a header record in the current HDU. The first
+routine reads keyword number {\tt keynum} (where the first keyword is
+at position 1).  This routine is most commonly used when sequentially
+reading every record in the header from beginning to end.  The 2nd and
+3rd routines read the named keyword and return either the whole
+record, or the keyword value and comment string.  In each case any 
+non-significant trailing blank characters in the strings are truncated.
+
+Wild card characters (*, ?, and \#) may be used when specifying the name
+of the keyword to be read, in which case the first matching keyword is
+returned.
+
+The {\tt datatype} parameter specifies the C datatype of the returned
+keyword value and can have one of the following symbolic constant
+values:  {\tt TSTRING, TLOGICAL} (== int), {\tt TBYTE}, {\tt TSHORT},
+{\tt TUSHORT}, {\tt TINT}, {\tt TUINT}, {\tt TLONG}, {\tt TULONG}, {\tt
+TFLOAT}, {\tt TDOUBLE}, {\tt TCOMPLEX}, and {\tt TDBLCOMPLEX}.  Data
+type conversion will be performed for numeric values if the intrinsic
+FITS keyword value does not have the same datatype.  The {\tt comment}
+parameter may be set equal to NULL if the comment string is not
+needed.
+
+The 4th routine provides an easy way to find all the keywords in the
+header that match one of the name templates in {\tt inclist} and do not
+match any of the name templates in {\tt exclist}.  {\tt ninc} and {\tt
+nexc} are the number of template strings in {\tt inclist} and {\tt
+exclist}, respectively.  Wild cards (*, ?, and \#) may be used in the
+templates to match multiple keywords.  Each time this routine is called
+it returns the next matching 80-byte keyword record.  It returns status
+= {\tt KEY\_NO\_EXIST} if there are no more matches.
+
+The 5th routine returns the keyword value units string, if any.
+The units are recorded at the beginning of the keyword comment field
+enclosed in square brackets.
+\begin{verbatim}
+_______________________________________________________________
+int fits_write_key(fitsfile *fptr, int datatype, char *keyname, 
+        void *value, char *comment, int *status)
+int fits_update_key(fitsfile *fptr, int datatype, char *keyname,
+        void *value, char *comment, int *status)
+int fits_write_record(fitsfile *fptr, char *card, int *status)
+
+int fits_modify_comment(fitsfile *fptr, char *keyname, char *comment,
+        int *status)
+int fits_write_key_unit(fitsfile *fptr, char *keyname, char *unit,
+        int *status)
+
+\end{verbatim}
+
+Write or modify a keyword  in the header of the current HDU.  The
+first routine appends the new keyword to the end of the header, whereas
+the second routine will update the value and comment fields of the
+keyword if it already exists, otherwise it behaves like the first
+routine and appends the new keyword.  Note that {\tt value} gives the
+address to the value and not the value itself.  The {\tt datatype}
+parameter specifies the C datatype of the keyword value and may have
+any of the values listed in the description of the keyword reading
+routines, above.  A NULL may be entered for the comment parameter, in
+which case the  keyword comment field will be unmodified or left
+blank.
+
+The third routine is more primitive and simply writes the 80-character
+{\tt card} record to the header.  It is the programmer's responsibility
+in this case to ensure that the record conforms to all the FITS format
+requirements for a header record.
+
+The fourth routine modifies the comment string in an existing keyword,
+and the last routine writes or updates the keyword units string for an
+existing keyword.  (The units are recorded at the beginning of the
+keyword comment field enclosed in square brackets).
+
+\begin{verbatim}
+___________________________________________________________________
+int fits_write_comment(fitsfile *fptr, char *comment,  int *status)
+int fits_write_history(fitsfile *fptr, char *history,  int *status)
+int fits_write_date(fitsfile *fptr,  int *status)
+\end{verbatim}
+
+Write a {\tt COMMENT, HISTORY}, or {\tt DATE} keyword to the current
+header.  The {\tt COMMENT} keyword is typically used to write a comment
+about the file or the data.  The {\tt HISTORY} keyword is typically
+used to provide information about the history of the processing
+procedures that have been applied to the data.  The {\tt comment} or
+{\tt history} string will be continued over multiple keywords if it is
+more than 70 characters long.
+
+The {\tt DATE} keyword is used to record the date and time that the
+FITS file was created.  Note that this file creation date is usually
+different from the date of the observation which obtained the data in
+the FITS file.  The {\tt DATE} keyword value is a character string in
+'yyyy-mm-ddThh:mm:ss' format. If a {\tt DATE} keyword already exists in
+the header, then this routine will update the value with the current
+system date.
+
+\begin{verbatim}
+___________________________________________________________________
+int fits_delete_record(fitsfile *fptr, int keynum,  int *status)
+int fits_delete_key(fitsfile *fptr, char *keyname,  int *status)
+\end{verbatim}
+
+Delete a keyword record. The first routine deletes a keyword at a
+specified position (the first keyword is at position 1, not 0),
+whereas the second routine deletes the named keyword.
+
+\begin{verbatim}
+_______________________________________________________________________
+int fits_copy_header(fitsfile *infptr, fitsfile *outfptr,  int *status)
+\end{verbatim}
+
+Copy all the header keywords from the current HDU associated with
+infptr to the current HDU associated with outfptr.  If the current
+output HDU is not empty, then a new HDU will be appended to the output
+file. The output HDU will then have the identical structure as the
+input HDU, but will contain no data.
+
+\newpage
+% ===================================================================
+\subsection{Utility Routines}
+
+This section lists the most important CFITSIO general utility routines.
+
+\begin{verbatim}
+___________________________________________________________________
+int fits_write_chksum( fitsfile *fptr, int *status)
+int fits_verify_chksum(fitsfile *fptr, int *dataok, int *hduok, int *status)
+\end{verbatim}
+
+These routines  compute or validate the checksums for the currenrt
+HDU.  The {\tt DATASUM} keyword is used to store the numerical value of
+the 32-bit, 1's complement checksum for the data unit alone.  The {\tt
+CHECKSUM} keyword is used to store the ASCII encoded COMPLEMENT of the
+checksum for the entire HDU.  Storing the complement, rather than the
+actual checksum, forces the checksum for the whole HDU to equal zero.
+If the file has been modified since the checksums were computed, then
+the HDU checksum will usually not equal zero.
+
+The returned {\tt dataok} and {\tt hduok} parameters will have a value
+= 1 if the data or HDU is verified correctly, a value = 0 if the
+{\tt DATASUM} or {\tt CHECKSUM} keyword is not present, or value = -1 if the
+computed checksum is not correct.
+
+
+\begin{verbatim}
+___________________________________________________________________
+int fits_parse_value(char *card, char *value, char *comment, int *status)
+int fits_get_keytype(char *value, char *dtype, int *status)
+int fits_get_keyclass(char *card)
+int fits_parse_template(char *template, char *card, int *keytype, int *status)
+
+\end{verbatim}
+
+{\tt fits\_parse\_value} parses the input 80-chararacter header keyword record, returning
+the value (as a literal character string) and comment strings.  If the
+keyword has no value (columns 9-10 not equal to '= '), then a null
+value string is returned and the comment string is set equal to column
+9 - 80 of the input string.
+
+{\tt fits\_get\_keytype} parses the keyword value string to determine its
+datatype.  {\tt dtype} returns with a value of 'C', 'L', 'I', 'F' or
+'X', for character string, logical, integer, floating point, or
+complex, respectively.
+
+{\tt fits\_get\_keyclass} returns a classification code that indicates
+the classification type of the input keyword record (e.g., a required
+structural keyword, a TDIM keyword, a WCS keyword, a comment keyword,
+etc.  See the CFITSIO Reference Guide for a list of the different
+classification codes.
+
+{\tt fits\_parse\_template} takes an input free format keyword template
+string and returns a formatted 80*char record that satisfies all the
+FITS requirements for a header keyword record.  The template should
+generally contain 3 tokens: the keyword name, the keyword value, and
+the keyword comment string.  The returned {\tt keytype} parameter
+indicates whether the keyword is a COMMENT keyword or not.   See the
+CFITSIO Reference Guide for more details.
+
+\newpage
+% ===================================================================
+\section{CFITSIO File Names and Filters}
+
+\subsection{Creating New Files}
+
+When creating a new output file on magnetic disk  with {\tt
+fits\_create\_file} the following features are supported.
+\begin{itemize}
+\item Overwriting, or 'Clobbering' an Existing File
+
+If the filename is preceded by an exclamation
+point (!) then if that file already exists it will be deleted prior to
+creating the new FITS file.  Otherwise if there is an existing file
+with the same name, CFITSIO will not overwrite the existing file and
+will return an error status code.  Note  that the exclamation point is
+a special UNIX character, so if it is used on the command line rather
+than entered at a task prompt, it must be preceded by a backslash to
+force the UNIX shell to pass it verbatim to the application program.
+
+\item Compressed Output Files
+
+If the output disk file name ends with the suffix '.gz', then CFITSIO
+will compress the file using the gzip compression algorithm before
+writing it to disk.  This can reduce the amount of disk space used by
+the file.  Note that this feature requires that the uncompressed file
+be constructed in memory before it is compressed and written to disk,
+so it can fail if there is insufficient available memory.
+
+One can also specify that any images written to the output file should
+be compressed using the newly developed `tile-compression' algorithm by
+appending `[compress]' to the name of the disk file (as in
+{\tt myfile.fits[compress]}).   Refer to the CFITSIO User's Reference Guide
+for more information about this new image compression format.
+
+\item Using a Template to Create a New FITS File
+
+The structure of any new FITS file that is to be created may be defined
+in an ASCII template file.  If the name of the template file is
+appended to the name of the FITS file itself, enclosed in parenthesis
+(e.g., {\tt 'newfile.fits(template.txt)'}) then CFITSIO will create a
+FITS file with that structure before opening it for the application to
+use.  The template file basically defines the dimensions and data type
+of the primary array and any IMAGE extensions, and the names and data
+types of the columns in any ASCII or binary table extensions.  The
+template file can also be used to define any optional keywords that
+should be written in any of the HDU headers.  The image pixel values
+and table entry values are all initialized to zero.  The application
+program can then write actual data into the HDUs.  See the CFITSIO
+Reference Guide for for a complete description of the template file
+syntax.
+
+\item Creating a Temporary Scratch File in Memory
+
+It is sometimes useful to create a temporary output file when testing
+an application program.  If the name of the file to be created is
+specified as {\tt mem:} then CFITSIO will create the file in
+memory where it will persist only until the program closes the file.
+Use of this {\tt mem:} output file usually enables the program to run
+faster, and of course the output file does not use up any disk space.
+
+
+\end{itemize}
+
+\subsection{Opening Existing Files}
+
+When opening a file with {\tt fits\_open\_file}, CFITSIO can read a
+variety of different input file formats and is not restricted to only
+reading FITS format files from magnetic disk. The following types of
+input files are all supported:
+
+\begin{itemize}
+\item FITS files compressed with {\tt zip, gzip} or {\tt compress}
+
+If CFITSIO cannot find the specified file to open it will automatically
+look for a file with the same rootname but with a {\tt .gz, .zip}, or
+{\tt .Z} extension.  If it finds such a compressed file, it will
+allocate a block of memory and uncompress the file into that memory
+space.  The application program will then transparently open this
+virtual FITS file in memory.  Compressed
+files can only be opened with 'readonly', not 'readwrite' file access.
+
+\item  FITS files on the internet, using {\tt ftp} or {\tt http} URLs
+
+Simply provide the full URL as the name of the file that you want to
+open.  For example,\linebreak {\tt
+ftp://legacy.gsfc.nasa.gov/software/fitsio/c/testprog.std}\linebreak
+will open the CFITSIO test FITS file that is located on the {\tt
+legacy} machine.  These files can only be opened with 'readonly' file
+access.
+
+\item  FITS files on {\tt stdin} or {\tt stdout} file streams
+
+If the name of the file to be opened is {\tt 'stdin'} or {\tt '-'} (a
+single dash character) then CFITSIO will read the file from the
+standard input stream.  Similarly, if the output file name is {\tt
+'stdout'} or {\tt '-'}, then the file will be written to the standard
+output stream.  In addition, if the output filename is {\tt
+'stdout.gz'} or {\tt '-.gz'} then it will be gzip compressed before
+being written to stdout.  This mechanism can be used to pipe FITS files
+from one task to another without having to write an intermediary FITS
+file on magnetic disk.
+
+\item FITS files that exist only in memory, or shared memory.
+
+In some applications, such as real time data acquisition, you may want
+to have one process write a FITS file into a certain section of
+computer memory, and then be able to open that file in memory with
+another process.  There is a specialized CFITSIO open routine called
+{\tt fits\_open\_memfile} that can be used for this purpose.  See the
+``CFITSIO User's Reference Guide'' for more details.
+
+\item  IRAF format images (with {\tt .imh} file extensions)
+
+CFITSIO supports reading IRAF format images by converting them on the
+fly into FITS images in memory.  The application program then reads
+this virtual FITS format image in memory.  There is currently no
+support for writing IRAF format images, or for reading or writing IRAF
+tables.
+
+\item Image arrays in raw binary format
+
+If the input file is a raw binary data array, then CFITSIO will convert
+it on the fly into a virtual FITS image with the basic set of required
+header keywords before it is opened by the application program.  In
+this case the data type and dimensions of the image must be specified
+in square brackets following the filename (e.g. {\tt
+rawfile.dat[ib512,512]}). The first character inside the brackets
+defines the datatype of the array:
+
+\begin{verbatim}
+     b         8-bit unsigned byte
+     i        16-bit signed integer
+     u        16-bit unsigned integer
+     j        32-bit signed integer
+     r or f   32-bit floating point
+     d        64-bit floating point
+\end{verbatim}
+An optional second character specifies the byte order of the array
+values: b or B indicates big endian (as in FITS files and the native
+format of SUN UNIX workstations and Mac PCs) and l or L indicates
+little endian (native format of DEC OSF workstations and IBM PCs).  If
+this character is omitted then the array is assumed to have the native
+byte order of the local machine.  These datatype characters are then
+followed by a series of one or more integer values separated by commas
+which define the size of each dimension of the raw array.  Arrays with
+up to 5 dimensions are currently supported.  
+
+Finally, a byte offset to the position of the first pixel in the data
+file may be specified by separating it with a ':' from the last
+dimension value.  If omitted, it is assumed that the offset = 0.  This
+parameter may be used to skip over any header information in the file
+that precedes the binary data.  Further examples:
+ 
+\begin{verbatim}
+  raw.dat[b10000]          1-dimensional 10000 pixel byte array
+  raw.dat[rb400,400,12]    3-dimensional floating point big-endian array
+  img.fits[ib512,512:2880] reads the 512 x 512 short integer array in a
+                           FITS file, skipping over the 2880 byte header
+\end{verbatim}
+
+\end{itemize}
+\newpage
+
+\subsection{Image Filtering}
+
+\subsubsection{Extracting a subsection of an image}
+
+When specifying the name of an image to be opened, you can select a
+rectangular subsection of the image to be extracted and opened by the
+application program.  The application program then opens a virtual
+image that only contains the pixels within the specified subsection.
+To do this, specify the the range of pixels (start:end) along each axis
+to be extracted from the original image enclosed in square brackets.
+You can also specify an optional pixel increment (start:end:step) for
+each axis of the input image.  A pixel step = 1 will be assumed if it
+is not specified.  If the starting pixel is larger then the end pixel,
+then the image will be flipped (producing a mirror image) along that
+dimension.  An asterisk, '*', may be used to specify the entire range
+of an axis, and '-*' will flip the entire axis.  In the following
+examples, assume that {\tt myfile.fits} contains a 512 x 512 pixel 2D
+image.
+
+\begin{verbatim}
+  myfile.fits[201:210, 251:260] - opens a 10 x 10 pixel subimage.
+
+  myfile.fits[*, 512:257] - opens a 512 x 256 image consisting of
+	      all the columns in the input image, but only rows 257
+	      through 512.  The image will be flipped along the Y axis
+	      since the starting row is greater than the ending
+	      row.
+
+  myfile.fits[*:2, 512:257:2] - creates a 256 x 128 pixel image.
+	      Similar to the previous example, but only every other row
+	      and column is read from the input image.
+
+  myfile.fits[-*, *] - creates an image containing all the rows and
+	      columns in the input image, but flips it along the X
+	      axis.
+\end{verbatim}
+
+If the array to be opened is in an Image extension, and not in the
+primary array of the file, then you need to specify the extension
+name or number in square brackets before giving the subsection range,
+as in {\tt  myfile.fits[1][-*, *]} to read the image in the
+first extension in the file.
+
+\subsubsection{Create an Image by Binning Table Columns}
+
+You can also create and open a virtual image by binning the values in a
+pair of columns of a FITS table (in other words, create a 2-D histogram
+of the values in the 2 columns).  This technique is often used in X-ray
+astronomy where each detected X-ray photon during an observation is
+recorded in a FITS table.  There are typically 2 columns in the table
+called  {\tt X} and {\tt Y} which record the pixel location of that
+event in a virtual 2D image.  To create an image from this table, one
+just scans the X and Y columns and counts up how many photons were
+recorded in each pixel of the image.  When table binning is specified,
+CFITSIO creates a temporary FITS primary array in memory by computing
+the histogram of the values in the specified columns.  After the
+histogram is computed the original FITS file containing the table is
+closed and the temporary FITS primary array is opened and passed to the
+application program.  Thus, the application program never sees the
+original FITS table and only sees the image in the new temporary file
+(which has no extensions).
+
+The table binning specifier is enclosed in square brackets following
+the root filename and table extension name or number and begins with
+the keyword 'bin', as in: \newline  
+{\tt 'myfile.fits[events][bin (X,Y)]'}. In
+this case, the X and Y columns in the 'events' table extension are
+binned up to create the image.  The size of the image is usually
+determined by the {\tt TLMINn} and {\tt TLMAXn} header keywords which
+give the minimum and maximum allowed pixel values in the columns.  For
+instance if {\tt TLMINn = 1} and {\tt TLMAXn = 4096} for both columns, this would
+generate a 4096 x 4096 pixel image by default.  This is rather large,
+so you can also specify a pixel binning factor to reduce the image
+size.  For example specifying ,  {\tt '[bin (X,Y) = 16]'} will use a
+binning factor of 16, which will produce a 256 x 256 pixel image in the
+previous example.
+
+If the TLMIN and TLMAX keywords don't exist, or you want to override
+their values,  you can specify the image range and binning factor
+directly, as in {\tt '[bin X = 1:4096:16, Y=1:4096:16]'}.  You can also
+specify the datatype of the created image by appending a b, i, j, r, or
+d (for 8-bit byte, 16-bit integers, 32-bit integer, 32-bit floating
+points, or 64-bit double precision floating point, respectively)  to
+the 'bin' keyword (e.g. {\tt '[binr (X,Y)]'} creates a floating point
+image).  If the datatype is not specified then a 32-bit integer image
+will be created by default.
+
+If the column name is not specified, then CFITSIO will first try to use
+the 'preferred column' as specified by the CPREF keyword if it exists
+(e.g., 'CPREF = 'DETX,DETY'), otherwise column names 'X', 'Y' will be
+assumed for the 2 axes.
+
+Note that this binning specifier is not restricted to only 2D images
+and can be used to create 1D, 3D, or 4D images as well.  It is also
+possible to specify a weighting factor that is applied during the
+binning.  Please refer to the ``CFITSIO User's Reference Guide'' for
+more details on these advanced features.
+\newpage
+
+\subsection{Table Filtering}
+
+\subsubsection{Column and Keyword Filtering}
+
+The column or keyword filtering specifier is used to modify the
+column structure and/or the header keywords in the HDU that was
+selected with the previous HDU location specifier.   It can
+be used to perform the following types of operations. 
+
+\begin{itemize}
+\item
+Append a new column to a table by giving the column name, optionally
+followed by the datatype in parentheses, followed by an equals sign and
+the arithmetic  expression to be used to compute the value.  The
+datatype is specified using the same syntax that is allowed for the
+value of the FITS TFORMn keyword (e.g., 'I', 'J', 'E', 'D', etc. for
+binary tables, and 'I8', F12.3', 'E20.12', etc.  for ASCII tables).  If
+the datatype is not specified then a default datatype will be chosen
+depending on the expression.
+
+\item
+Create a new header keyword by giving the keyword name, preceded by a
+pound sign '\#', followed by an equals sign and an arithmetic
+expression for the value of the keyword.  The expression may be a
+function of other header keyword values.  The comment string for the
+keyword may be specified in parentheses immediately following the
+keyword name.
+
+\item
+Overwrite the values in an existing column or keyword by giving the
+name followed by an equals sign and an arithmetic expression.
+
+\item
+Select a set of columns to be included in the filtered file by listing
+the column names separated with semi-colons.   Wild card characters may
+be used in the column names to match multiple columns.  Any other
+columns in the input table will not appear in the filtered file.
+
+\item
+Delete a column or keyword by listing the name preceded by a minus sign
+or an exclamation mark (!)
+
+\item
+Rename an existing column or keyword with the syntax 'NewName ==
+OldName'.
+
+\end{itemize}
+
+The column filtering specifier is enclosed in square brackets and
+begins with the string 'col'.   Multiple operations can be performed
+by separating them with semi-colons.  For  complex  or commonly used
+operations,  you can write the column filter to a text file, and then
+use it by giving the name of the text file, preceded by a '@'
+character.
+
+Some examples:
+
+\begin{verbatim}
+  [col PI=PHA * 1.1 + 0.2]      - creates new PI column from PHA values
+
+  [col rate = counts/exposure]  - creates or overwrites the rate column by
+                                  dividing the counts column by the
+                                  EXPOSURE keyword value.
+
+  [col TIME; X; Y]              - only the listed columns will appear
+                                  in the filtered file
+
+  [col Time;*raw]               - include the Time column and any other
+                                  columns whose name ends with 'raw'.
+
+  [col -TIME; Good == STATUS]   - deletes the TIME column and
+                                  renames the STATUS column to GOOD
+
+  [col @colfilt.txt]            - uses the filtering expression in
+                                  the colfilt.txt text file
+\end{verbatim}
+
+The original file is not changed by this filtering operation, and
+instead the modifications are made on a temporary copy of the input
+FITS file (usually in memory), which includes a copy of all the other
+HDUs in the input file.   The original input file is closed and the
+application program opens the filtered copy of the file.
+
+\subsubsection{Row Filtering}
+
+The row filter is used to select a subset of the rows from a table
+based on a boolean expression.  A temporary new FITS file is created on
+the fly (usually in memory) which contains only those rows for which
+the row filter expression evaluates to true (i.e., not equal to zero).
+The primary array and any other extensions in the input file are also
+copied to the temporary file.  The original FITS file is closed and the
+new temporary file is then opened by the application program.
+
+The row filter expression is enclosed in square brackets following the
+file name and extension name.  For example, {\tt
+'file.fits[events][GRADE==50]'}  selects only those rows in the EVENTS
+table where the GRADE column value is equal to 50).
+
+The row filtering  expression can be an arbitrarily  complex series of
+operations performed  on constants,  keyword values,  and column data
+taken from the specified FITS TABLE extension.  The expression 
+also can be written into a text file and then used by giving the
+filename preceded by a '@' character, as in
+{\tt '[@rowfilt.txt]'}.
+
+Keyword and column data  are referenced by   name.  Any  string of
+characters not surrounded by    quotes (ie, a constant  string)   or
+followed by   an open parentheses (ie,   a  function name)   will be
+initially interpreted   as a column  name and  its contents for the
+current row inserted into the expression.  If no such column exists,
+a keyword of that  name will be searched for  and its value used, if
+found.  To force the  name to be  interpreted as a keyword (in case
+there is both a column and keyword with the  same name), precede the
+keyword name with a single pound sign, '\#', as in {\tt \#NAXIS2}.  Due to
+the generalities of FITS column and  keyword names, if the column or
+keyword name  contains a space or a  character which might appear as
+an arithmetic  term then inclose  the  name in '\$'  characters as in
+{\tt \$MAX PHA\$} or {\tt \#\$MAX-PHA\$}.  The names are case insensitive.
+
+To access a table entry in a row other  than the current one, follow
+the  column's name  with  a row  offset  within  curly  braces.  For
+example, {\tt'PHA\{-3\}'} will evaluate to the value  of column PHA, 3 rows
+above  the  row currently  being processed.   One  cannot specify an
+absolute row number, only a relative offset.  Rows that fall outside
+the table will be treated as undefined, or NULLs.
+
+Boolean   operators can be  used in  the expression  in either their
+Fortran or C forms.  The following boolean operators are available:
+
+\begin{verbatim}
+    "equal"         .eq. .EQ. ==  "not equal"          .ne.  .NE.  !=
+    "less than"     .lt. .LT. <   "less than/equal"    .le.  .LE.  <= =<
+    "greater than"  .gt. .GT. >   "greater than/equal" .ge.  .GE.  >= =>
+    "or"            .or. .OR. ||  "and"                .and. .AND. &&
+    "negation"     .not. .NOT. !  "approx. equal(1e-7)"  ~
+\end{verbatim}
+
+Note  that the exclamation point,  '!', is a special UNIX character, so
+if it is used  on the command line rather than entered at a task
+prompt, it must be  preceded by a backslash to force the UNIX shell to
+ignore it.
+
+The expression may  also include arithmetic operators and functions.
+Trigonometric  functions use  radians,  not degrees.  The  following
+arithmetic  operators and  functions  can be  used in the expression
+(function names are case insensitive):
+
+ 
+\begin{verbatim}
+    "addition"           +          "subtraction"          -
+    "multiplication"     *          "division"             /
+    "negation"           -          "exponentiation"       **   ^
+    "absolute value"     abs(x)     "cosine"               cos(x)
+    "sine"               sin(x)     "tangent"              tan(x)
+    "arc cosine"         arccos(x)  "arc sine"             arcsin(x)
+    "arc tangent"        arctan(x)  "arc tangent"          arctan2(x,y)
+    "exponential"        exp(x)     "square root"          sqrt(x)
+    "natural log"        log(x)     "common log"           log10(x)
+    "modulus"            i % j      "random # [0.0,1.0)"   random()
+    "minimum"            min(x,y)   "maximum"              max(x,y)
+    "if-then-else"       b?x:y
+\end{verbatim}
+
+
+The  following  type  casting  operators  are  available,  where the
+inclosing parentheses are required and taken  from  the  C  language
+usage. Also, the integer to real casts values to double precision:
+
+\begin{verbatim}
+                "real to integer"    (int) x     (INT) x
+                "integer to real"    (float) i   (FLOAT) i
+\end{verbatim}
+
+
+Several constants are built in  for  use  in  numerical
+expressions:
+
+ 
+\begin{verbatim}
+        #pi              3.1415...      #e             2.7182...
+        #deg             #pi/180        #row           current row number
+        #null         undefined value   #snull         undefined string
+\end{verbatim}
+
+A  string constant must  be enclosed  in quotes  as in  'Crab'.  The
+"null" constants  are useful for conditionally  setting table values to
+a NULL, or undefined, value (For example,  {\tt "col1==-99 ? \#NULL :
+col1"}).
+
+There is also a function for testing if  two  values  are  close  to
+each  other,  i.e.,  if  they are "near" each other to within a user
+specified tolerance. The  arguments,  {\tt value\_1}  and  {\tt value\_2}  can  be
+integer  or  real  and  represent  the two values who's proximity is
+being tested to be within the specified tolerance, also  an  integer
+or real:
+
+\begin{verbatim}
+                    near(value_1, value_2, tolerance)
+\end{verbatim}
+
+When  a  NULL, or undefined, value is encountered in the FITS table,
+the expression will evaluate to NULL unless the undefined  value  is
+not   actually   required  for  evaluation,  e.g. "TRUE  .or.  NULL"
+evaluates to TRUE. The  following  two  functions  allow  some  NULL
+detection  and  handling:  
+
+\begin{verbatim}
+                   ISNULL(x)
+                   DEFNULL(x,y)
+\end{verbatim}
+
+The former returns a boolean value of TRUE if the  argument  x  is
+NULL.   The later  "defines"  a  value  to  be  substituted  for NULL
+values; it returns the value of x if x is not NULL, otherwise  it
+returns  the value of y.
+
+Bit  masks can be used to select out rows from bit columns ({\tt TFORMn =
+\#X}) in FITS files. To represent the mask,  binary,  octal,  and  hex
+formats are allowed:
+ 
+\begin{verbatim}
+                 binary:   b0110xx1010000101xxxx0001
+                 octal:    o720x1 -> (b111010000xxx001)
+                 hex:      h0FxD  -> (b00001111xxxx1101)
+\end{verbatim}
+
+In  all  the  representations, an x or X is allowed in the mask as a
+wild card. Note that the x represents a  different  number  of  wild
+card  bits  in  each  representation.  All  representations are case
+insensitive.
+
+To construct the boolean expression using the mask  as  the  boolean
+equal  operator  described above on a bit table column. For example,
+if you had a 7 bit column named flags in a  FITS  table  and  wanted
+all  rows  having  the bit pattern 0010011, the selection expression
+would be:
+
+ 
+\begin{verbatim}
+                            flags == b0010011
+    or
+                            flags .eq. b10011
+\end{verbatim}
+
+It is also possible to test if a range of bits is  less  than,  less
+than  equal,  greater  than  and  greater than equal to a particular
+boolean value:
+
+ 
+\begin{verbatim}
+                            flags <= bxxx010xx
+                            flags .gt. bxxx100xx
+                            flags .le. b1xxxxxxx
+\end{verbatim}
+
+Notice the use of the x bit value to limit the range of  bits  being
+compared.
+
+It  is  not necessary to specify the leading (most significant) zero
+(0) bits in the mask, as shown in the second expression above.
+
+Bit wise AND, OR and NOT operations are  also  possible  on  two  or
+more  bit  fields  using  the  '\&'(AND),  '$|$'(OR),  and the '!'(NOT)
+operators. All of these operators result in a bit  field  which  can
+then be used with the equal operator. For example:
+
+ 
+\begin{verbatim}
+                          (!flags) == b1101100
+                          (flags & b1000001) == bx000001
+\end{verbatim}
+
+Bit  fields can be appended as well using the '+' operator.  Strings
+can be concatenated this way, too.
+
+\subsubsection{Good Time Interval Filtering}
+
+    A common filtering method involves selecting rows which have a time
+    value which lies within what is called a Good Time Interval or GTI.
+    The time intervals are defined in a separate FITS table extension
+    which contains 2 columns giving the start and stop time of each
+    good interval.  The filtering operation accepts only those rows of
+    the input table which have an associated time which falls within
+    one of the time intervals defined in the GTI extension. A high
+    level function, gtifilter(a,b,c,d), is available which evaluates
+    each row of the input table  and returns TRUE  or FALSE depending
+    whether the row is inside or outside the  good time interval.  The
+    syntax is
+ 
+\begin{verbatim}
+      gtifilter( [ "gtifile" [, expr [, "STARTCOL", "STOPCOL" ] ] ] )
+\end{verbatim}
+    where  each "[]" demarks optional parameters.  Note that  the quotes
+    around the gtifile and START/STOP column are required.  Either single
+    or double quote characters may be used.  The gtifile,
+    if specified,  can be blank  ("") which will  mean to use  the first
+    extension  with   the name "*GTI*"  in   the current  file,  a plain
+    extension  specifier (eg, "+2",  "[2]", or "[STDGTI]") which will be
+    used  to  select  an extension  in  the current  file, or  a regular
+    filename with or without an extension  specifier which in the latter
+    case  will mean to  use the first  extension  with an extension name
+    "*GTI*".  Expr can be   any arithmetic expression, including  simply
+    the time  column  name.  A  vector  time expression  will  produce a
+    vector boolean  result.  STARTCOL and  STOPCOL are the  names of the
+    START/STOP   columns in the    GTI extension.  If   one  of them  is
+    specified, they both  must be.
+
+    In  its  simplest form, no parameters need to be provided -- default
+    values will be used.  The expression {\tt "gtifilter()"} is equivalent to
+ 
+\begin{verbatim}
+       gtifilter( "", TIME, "*START*", "*STOP*" )
+\end{verbatim}
+    This will search the current file for a GTI  extension,  filter  the
+    TIME  column in the current table, using START/STOP times taken from
+    columns in the GTI  extension  with  names  containing  the  strings
+    "START"  and "STOP".  The wildcards ('*') allow slight variations in
+    naming conventions  such  as  "TSTART"  or  "STARTTIME".   The  same
+    default  values  apply for unspecified parameters when the first one
+    or  two  parameters  are  specified.   The  function   automatically
+    searches   for   TIMEZERO/I/F   keywords  in  the  current  and  GTI
+    extensions, applying a relative time offset, if necessary.
+
+\subsubsection{Spatial Region Filtering}
+
+    Another common  filtering method selects rows based on whether the
+    spatial position associated with each row is located within a given
+    2-dimensional region.  The syntax for this high-level filter is
+ 
+\begin{verbatim}
+       regfilter( "regfilename" [ , Xexpr, Yexpr [ , "wcs cols" ] ] )
+\end{verbatim}
+    where each "[ ]" demarks optional parameters. The region file name
+    is required and must be  enclosed in quotes.  The remaining
+    parameters are optional.  The region file is an ASCII text file
+    which contains a list of one or more geometric shapes (circle,
+    ellipse, box, etc.) which defines a region on the celestial sphere
+    or an area within a particular 2D image.  The region file is
+    typically generated using an image display program such as fv/POW
+    (distribute by the HEASARC), or ds9 (distributed by the Smithsonian
+    Astrophysical Observatory).  Users should refer to the documentation
+    provided with these programs for more details on the syntax used in
+    the region files.
+
+    In its simpliest form, (e.g., {\tt regfilter("region.reg")} ) the
+    coordinates in the default 'X' and 'Y' columns will be used to
+    determine if each row is inside or outside the area specified in
+    the region file.  Alternate position column names, or expressions,
+    may be entered if needed, as in
+ 
+\begin{verbatim}
+        regfilter("region.reg", XPOS, YPOS)
+\end{verbatim}
+    Region filtering can be applied most unambiguously if the positions
+    in the region file and in the table to be filtered are both give in
+    terms of absolute celestial coordinate units.  In this case the
+    locations and sizes of the geometric shapes in the region file are
+    specified in angular units on the sky (e.g., positions given in
+    R.A. and Dec.  and sizes in arcseconds or arcminutes).  Similarly,
+    each row of the filtered table will have a celestial coordinate
+    associated with it.  This association is usually implemented using
+    a set of so-called 'World Coordinate System' (or WCS) FITS keywords
+    that define the coordinate transformation that must be applied to
+    the values in the 'X' and 'Y' columns to calculate the coordinate.
+
+    Alternatively, one can perform spatial filtering using unitless
+    'pixel' coordinates for the regions and row positions.  In this
+    case the user must be careful to ensure that the positions in the 2
+    files are self-consistent.  A typical problem is that the region
+    file may be generated using a binned image, but the unbinned
+    coordinates are given in the event table.  The ROSAT events files,
+    for example, have X and Y pixel coordinates that range from 1 -
+    15360.  These coordinates are typically binned by a factor of 32 to
+    produce a 480x480 pixel image.  If one then uses a region file
+    generated from this image (in image pixel units) to filter the
+    ROSAT events file, then the X and Y column values must be converted
+    to corresponding pixel units as in:
+ 
+\begin{verbatim}
+        regfilter("rosat.reg", X/32.+.5, Y/32.+.5)
+\end{verbatim}
+    Note that this binning conversion is not necessary if the region
+    file is specified using celestial coordinate units instead of pixel
+    units because CFITSIO is then able to directly compare the
+    celestial coordinate of each row in the table with the celestial
+    coordinates in the region file without having to know anything
+    about how the image may have been binned.
+
+    The last "wcs cols" parameter should rarely be needed. If supplied,
+    this  string contains the names of the 2 columns (space or comma
+    separated) which have the associated WCS keywords. If not supplied,
+    the filter  will scan the X  and Y expressions for column names.
+    If only one is found in each  expression, those columns will be
+    used, otherwise an error will be returned.
+
+    These region shapes are supported (names are case insensitive):
+ 
+\begin{verbatim}
+       Point         ( X1, Y1 )               <- One pixel square region
+       Line          ( X1, Y1, X2, Y2 )       <- One pixel wide region
+       Polygon       ( X1, Y1, X2, Y2, ... )  <- Rest are interiors with
+       Rectangle     ( X1, Y1, X2, Y2, A )       | boundaries considered
+       Box           ( Xc, Yc, Wdth, Hght, A )   V within the region
+       Diamond       ( Xc, Yc, Wdth, Hght, A )
+       Circle        ( Xc, Yc, R )
+       Annulus       ( Xc, Yc, Rin, Rout )
+       Ellipse       ( Xc, Yc, Rx, Ry, A )
+       Elliptannulus ( Xc, Yc, Rinx, Riny, Routx, Routy, Ain, Aout )
+       Sector        ( Xc, Yc, Amin, Amax )
+\end{verbatim}
+    where (Xc,Yc) is  the coordinate of  the shape's center; (X\#,Y\#) are
+    the coordinates  of the shape's edges;  Rxxx are the shapes' various
+    Radii or semimajor/minor  axes; and Axxx  are the angles of rotation
+    (or bounding angles for Sector) in degrees.  For rotated shapes, the
+    rotation angle  can  be left  off, indicating  no rotation.   Common
+    alternate  names for the regions  can also be  used: rotbox = box;
+    rotrectangle = rectangle;  (rot)rhombus = (rot)diamond;  and pie
+    = sector.  When a  shape's name is  preceded by a minus sign, '-',
+    the defined region  is instead the area  *outside* its boundary (ie,
+    the region is inverted).  All the shapes within a single region
+    file are OR'd together to create the region, and the order is
+    significant. The overall way of looking at region files is that if
+    the first region is an excluded region then a dummy included region
+    of the whole detector is inserted in the front. Then each region
+    specification as it is processed overrides any selections inside of
+    that region specified by previous regions. Another way of thinking
+    about this is that if a previous excluded region is completely
+    inside of a subsequent included region the excluded region is
+    ignored.
+
+    The positional coordinates may be given either in pixel units,
+    decimal degrees or hh:mm:ss.s, dd:mm:ss.s units.  The shape sizes
+    may be given in pixels, degrees, arcminutes, or arcseconds.  Look
+    at examples of region file produced by fv/POW or ds9 for further
+    details of the region file format.
+
+\subsubsection{Example Row Filters}
+ 
+\begin{verbatim}
+    [double && mag <= 5.0]        -  Extract all double stars brighter
+                                     than  fifth magnitude 
+
+    [#row >= 125 && #row <= 175]   - Extract row numbers 125 through 175
+
+    [abs(sin(theta * #deg)) < 0.5] - Extract all rows having the
+                                     absolute value of the sine of theta
+                                     less  than a half where the angles
+                                     are tabulated in degrees
+
+    [@rowFilter.txt]               - Extract rows using the expression
+                                     contained within the text file
+                                     rowFilter.txt
+
+    [gtifilter()]                  - Search the current file for a GTI
+				     extension,  filter  the TIME
+				     column in the current table, using
+				     START/STOP times taken from
+				     columns in the GTI  extension
+
+    [regfilter("pow.reg")]         - Extract rows which have a coordinate
+                                     (as given in the X and Y columns)
+                                     within the spatial region specified
+                                     in the pow.reg region file.
+\end{verbatim}
+
+\newpage
+\subsection{Combined Filtering Examples}
+
+The previous sections described all the individual types of filters
+that may be applied to the input file.  In this section we show
+examples which combine several different filters at once.  These
+examples all use the {\tt fitscopy} program that is distributed with
+the CFITSIO code.  It simply copies the input file to the output file.
+
+\begin{verbatim}
+fitscopy rosat.fit out.fit
+\end{verbatim}
+
+This trivial example simply makes an identical copy of the input
+rosat.fit file without any filtering.
+
+\begin{verbatim}
+fitscopy 'rosat.fit[events][col Time;X;Y][#row < 1000]' out.fit
+\end{verbatim}
+
+The output file contains only the Time, X, and Y columns, and only
+the first 999 rows from the 'EVENTS' table extension of the input file.
+All the other HDUs in the input file are copied to the output file
+without any modification.
+
+\begin{verbatim}
+fitscopy 'rosat.fit[events][PI < 50][bin (Xdet,Ydet) = 16]' image.fit
+\end{verbatim}
+
+This creates an output image by binning the Xdet and Ydet columns of
+the events table with a pixel binning factor of 16.  Only the rows
+which have a PI energy less than 50 are used to construct this image.
+The output image file contains a primary array image without any
+extensions.
+
+\begin{verbatim}
+fitscopy 'rosat.fit[events][gtifilter() && regfilter("pow.reg")]' out.fit
+\end{verbatim}
+
+The filtering expression in this example uses the {\tt gtifilter}
+function to test whether the TIME column value in each row is within
+one of the Good Time Intervals defined in the GTI extension in the same
+input file, and also uses the {\tt regfilter} function to test if the
+position associated with each row (derived by default from the values
+in the X and Y columns of the events table) is located within the area
+defined in the {\tt pow.reg} text region file (which was previously
+created with the {\tt fv/POW} image display program).  Only the rows
+which satisfy both tests are copied to the output table.
+
+\begin{verbatim}
+fitscopy 'r.fit[evt][PI<50]' stdout | fitscopy stdin[evt][col X,Y] out.fit
+\end{verbatim}
+
+In this somewhat convoluted example, fitscopy is used to first select
+the rows from the evt extension which have PI less than 50 and write the
+resulting table out to the stdout stream.  This is piped to a 2nd
+instance of fitscopy (with the Unix `$|$' pipe command) which reads that
+filtered FITS file from the stdin stream and copies only the X and Y
+columns from the evt table to the output file.
+
+\begin{verbatim}
+fitscopy 'r.fit[evt][col RAD=sqrt((X-#XCEN)**2+(Y-#YCEN)**2)][rad<100]' out.fit
+\end{verbatim}
+
+This example first creates a new column called RAD which gives the
+distance between the X,Y coordinate of each event and the coordinate
+defined by the XCEN and YCEN keywords in the header.  Then, only those
+rows which have a distance less than 100 are copied to the output
+table.  In other words, only the events which are located within 100
+pixel units from the (XCEN, YCEN) coordinate are copied to the output
+table.
+
+\begin{verbatim}
+fitscopy 'ftp://heasarc.gsfc.nasa.gov/rosat.fit[events][bin (X,Y)=16]' img.fit
+\end{verbatim}
+
+This example bins the X and Y columns of the hypothetical ROSAT file 
+at the HEASARC ftp site to create the output image.
+
+\begin{verbatim}
+fitscopy 'raw.fit[i512,512][101:110,51:60]' image.fit
+\end{verbatim}
+
+This example converts the 512 x 512 pixel raw binary 16-bit integer
+image to a FITS file and copies a 10 x 10 pixel subimage from it to the
+output FITS image.
+
+\newpage
+\section{CFITSIO Error Status Codes}
+
+The following table lists all the error status codes used by CFITSIO.
+Programmers are encouraged to use the symbolic mnemonics (defined in
+the file fitsio.h) rather than the actual integer status values to
+improve the readability of their code.
+
+\begin{verbatim}
+ Symbolic Const    Value     Meaning
+ --------------    -----  -----------------------------------------
+                     0    OK, no error
+ SAME_FILE         101    input and output files are the same
+ TOO_MANY_FILES    103    tried to open too many FITS files at once
+ FILE_NOT_OPENED   104    could not open the named file
+ FILE_NOT_CREATED  105    could not create the named file
+ WRITE_ERROR       106    error writing to FITS file
+ END_OF_FILE       107    tried to move past end of file
+ READ_ERROR        108    error reading from FITS file
+ FILE_NOT_CLOSED   110    could not close the file
+ ARRAY_TOO_BIG     111    array dimensions exceed internal limit
+ READONLY_FILE     112    Cannot write to readonly file
+ MEMORY_ALLOCATION 113    Could not allocate memory
+ BAD_FILEPTR       114    invalid fitsfile pointer
+ NULL_INPUT_PTR    115    NULL input pointer to routine 
+ SEEK_ERROR        116    error seeking position in file 
+
+ BAD_URL_PREFIX     121   invalid URL prefix on file name 
+ TOO_MANY_DRIVERS   122   tried to register too many IO drivers 
+ DRIVER_INIT_FAILED 123   driver initialization failed 
+ NO_MATCHING_DRIVER 124   matching driver is not registered 
+ URL_PARSE_ERROR    125   failed to parse input file URL
+
+ SHARED_BADARG     151    bad argument in shared memory driver
+ SHARED_NULPTR     152    null pointer passed as an argument
+ SHARED_TABFULL    153    no more free shared memory handles
+ SHARED_NOTINIT    154    shared memory driver is not initialized
+ SHARED_IPCERR     155    IPC error returned by a system call
+ SHARED_NOMEM      156    no memory in shared memory driver
+ SHARED_AGAIN      157    resource deadlock would occur
+ SHARED_NOFILE     158    attempt to open/create lock file failed
+ SHARED_NORESIZE   159    shared memory block cannot be resized at the moment
+
+ HEADER_NOT_EMPTY  201    header already contains keywords
+ KEY_NO_EXIST      202    keyword not found in header
+ KEY_OUT_BOUNDS    203    keyword record number is out of bounds
+ VALUE_UNDEFINED   204    keyword value field is blank 
+ NO_QUOTE          205    string is missing the closing quote
+ BAD_KEYCHAR       207    illegal character in keyword name or card
+ BAD_ORDER         208    required keywords out of order
+ NOT_POS_INT       209    keyword value is not a positive integer
+ NO_END            210    couldn't find END keyword
+ BAD_BITPIX        211    illegal BITPIX keyword value
+ BAD_NAXIS         212    illegal NAXIS keyword value
+ BAD_NAXES         213    illegal NAXISn keyword value
+ BAD_PCOUNT        214    illegal PCOUNT keyword value
+ BAD_GCOUNT        215    illegal GCOUNT keyword value
+ BAD_TFIELDS       216    illegal TFIELDS keyword value
+ NEG_WIDTH         217    negative table row size
+ NEG_ROWS          218    negative number of rows in table
+ COL_NOT_FOUND     219    column with this name not found in table
+ BAD_SIMPLE        220    illegal value of SIMPLE keyword
+ NO_SIMPLE         221    Primary array doesn't start with SIMPLE
+ NO_BITPIX         222    Second keyword not BITPIX
+ NO_NAXIS          223    Third keyword not NAXIS
+ NO_NAXES          224    Couldn't find all the NAXISn keywords
+ NO_XTENSION       225    HDU doesn't start with XTENSION keyword
+ NOT_ATABLE        226    the CHDU is not an ASCII table extension
+ NOT_BTABLE        227    the CHDU is not a binary table extension
+ NO_PCOUNT         228    couldn't find PCOUNT keyword
+ NO_GCOUNT         229    couldn't find GCOUNT keyword
+ NO_TFIELDS        230    couldn't find TFIELDS keyword
+ NO_TBCOL          231    couldn't find TBCOLn keyword
+ NO_TFORM          232    couldn't find TFORMn keyword
+ NOT_IMAGE         233    the CHDU is not an IMAGE extension
+ BAD_TBCOL         234    TBCOLn keyword value < 0 or > rowlength
+ NOT_TABLE         235    the CHDU is not a table
+ COL_TOO_WIDE      236    column is too wide to fit in table
+ COL_NOT_UNIQUE    237    more than 1 column name matches template
+ BAD_ROW_WIDTH     241    sum of column widths not = NAXIS1
+ UNKNOWN_EXT       251    unrecognizable FITS extension type
+ UNKNOWN_REC       252    unknown record; 1st keyword not SIMPLE or XTENSION
+ END_JUNK          253    END keyword is not blank
+ BAD_HEADER_FILL   254    Header fill area contains non-blank chars
+ BAD_DATA_FILL     255    Illegal data fill bytes (not zero or blank)
+ BAD_TFORM         261    illegal TFORM format code
+ BAD_TFORM_DTYPE   262    unrecognizable TFORM datatype code
+ BAD_TDIM          263    illegal TDIMn keyword value
+ BAD_HEAP_PTR      264    invalid BINTABLE heap pointer is out of range
+
+ BAD_HDU_NUM       301    HDU number < 1 or > MAXHDU
+ BAD_COL_NUM       302    column number < 1 or > tfields
+ NEG_FILE_POS      304    tried to move to negative byte location in file
+ NEG_BYTES         306    tried to read or write negative number of bytes
+ BAD_ROW_NUM       307    illegal starting row number in table
+ BAD_ELEM_NUM      308    illegal starting element number in vector
+ NOT_ASCII_COL     309    this is not an ASCII string column
+ NOT_LOGICAL_COL   310    this is not a logical datatype column
+ BAD_ATABLE_FORMAT 311    ASCII table column has wrong format
+ BAD_BTABLE_FORMAT 312    Binary table column has wrong format
+ NO_NULL           314    null value has not been defined
+ NOT_VARI_LEN      317    this is not a variable length column
+ BAD_DIMEN         320    illegal number of dimensions in array
+ BAD_PIX_NUM       321    first pixel number greater than last pixel
+ ZERO_SCALE        322    illegal BSCALE or TSCALn keyword = 0
+ NEG_AXIS          323    illegal axis length < 1
+
+ NOT_GROUP_TABLE       340   Grouping function error
+ HDU_ALREADY_MEMBER    341
+ MEMBER_NOT_FOUND      342
+ GROUP_NOT_FOUND       343
+ BAD_GROUP_ID          344
+ TOO_MANY_HDUS_TRACKED 345
+ HDU_ALREADY_TRACKED   346
+ BAD_OPTION            347
+ IDENTICAL_POINTERS    348
+ BAD_GROUP_ATTACH      349
+ BAD_GROUP_DETACH      350
+
+ NGP_NO_MEMORY         360     malloc failed
+ NGP_READ_ERR          361     read error from file
+ NGP_NUL_PTR           362     null pointer passed as an argument.
+                                 Passing null pointer as a name of
+                                 template file raises this error
+ NGP_EMPTY_CURLINE     363     line read seems to be empty (used
+                                 internally)
+ NGP_UNREAD_QUEUE_FULL 364     cannot unread more then 1 line (or single
+                                 line twice)
+ NGP_INC_NESTING       365     too deep include file nesting (infinite
+                                 loop, template includes itself ?)
+ NGP_ERR_FOPEN         366     fopen() failed, cannot open template file
+ NGP_EOF               367     end of file encountered and not expected
+ NGP_BAD_ARG           368     bad arguments passed. Usually means
+                                 internal parser error. Should not happen
+ NGP_TOKEN_NOT_EXPECT  369     token not expected here
+
+ BAD_I2C           401    bad int to formatted string conversion
+ BAD_F2C           402    bad float to formatted string conversion
+ BAD_INTKEY        403    can't interpret keyword value as integer
+ BAD_LOGICALKEY    404    can't interpret keyword value as logical
+ BAD_FLOATKEY      405    can't interpret keyword value as float
+ BAD_DOUBLEKEY     406    can't interpret keyword value as double
+ BAD_C2I           407    bad formatted string to int conversion
+ BAD_C2F           408    bad formatted string to float conversion
+ BAD_C2D           409    bad formatted string to double conversion
+ BAD_DATATYPE      410    illegal datatype code value
+ BAD_DECIM         411    bad number of decimal places specified
+ NUM_OVERFLOW      412    overflow during datatype conversion
+ DATA_COMPRESSION_ERR   413  error compressing image 
+ DATA_DECOMPRESSION_ERR 414  error uncompressing image 
+
+ BAD_DATE          420    error in date or time conversion 
+
+ PARSE_SYNTAX_ERR  431    syntax error in parser expression 
+ PARSE_BAD_TYPE    432    expression did not evaluate to desired type 
+ PARSE_LRG_VECTOR  433    vector result too large to return in array 
+ PARSE_NO_OUTPUT   434    data parser failed not sent an out column 
+ PARSE_BAD_COL     435    bad data encounter while parsing column 
+ PARSE_BAD_OUTPUT  436    Output file not of proper type          
+
+ ANGLE_TOO_BIG     501    celestial angle too large for projection 
+ BAD_WCS_VAL       502    bad celestial coordinate or pixel value 
+ WCS_ERROR         503    error in celestial coordinate calculation  
+ BAD_WCS_PROJ      504    unsupported type of celestial projection 
+ NO_WCS_KEY        505    celestial coordinate keywords not found
+ APPROX_WCS_KEY    506    approximate wcs keyword values were returned
+\end{verbatim}
+
+\end{document}
diff --git a/external/cfitsio/quick.toc b/external/cfitsio/quick.toc
new file mode 100644
index 0000000..9d7c7da
--- /dev/null
+++ b/external/cfitsio/quick.toc
@@ -0,0 +1,25 @@
+\contentsline {section}{\numberline {1}Introduction}{2}
+\contentsline {section}{\numberline {2}Installing and Using CFITSIO}{3}
+\contentsline {section}{\numberline {3}Example Programs}{4}
+\contentsline {section}{\numberline {4}CFITSIO Routines}{6}
+\contentsline {subsection}{\numberline {4.1}Error Reporting}{6}
+\contentsline {subsection}{\numberline {4.2}File Open/Close Routines}{6}
+\contentsline {subsection}{\numberline {4.3}HDU-level Routines}{7}
+\contentsline {subsection}{\numberline {4.4}Image I/O Routines}{9}
+\contentsline {subsection}{\numberline {4.5}Table I/O Routines}{12}
+\contentsline {subsection}{\numberline {4.6}Header Keyword I/O Routines}{19}
+\contentsline {subsection}{\numberline {4.7}Utility Routines}{22}
+\contentsline {section}{\numberline {5}CFITSIO File Names and Filters}{23}
+\contentsline {subsection}{\numberline {5.1}Creating New Files}{23}
+\contentsline {subsection}{\numberline {5.2}Opening Existing Files}{24}
+\contentsline {subsection}{\numberline {5.3}Image Filtering}{26}
+\contentsline {subsubsection}{\numberline {5.3.1}Extracting a subsection of an image}{26}
+\contentsline {subsubsection}{\numberline {5.3.2}Create an Image by Binning Table Columns}{26}
+\contentsline {subsection}{\numberline {5.4}Table Filtering}{28}
+\contentsline {subsubsection}{\numberline {5.4.1}Column and Keyword Filtering}{28}
+\contentsline {subsubsection}{\numberline {5.4.2}Row Filtering}{29}
+\contentsline {subsubsection}{\numberline {5.4.3}Good Time Interval Filtering}{32}
+\contentsline {subsubsection}{\numberline {5.4.4}Spatial Region Filtering}{32}
+\contentsline {subsubsection}{\numberline {5.4.5}Example Row Filters}{34}
+\contentsline {subsection}{\numberline {5.5}Combined Filtering Examples}{36}
+\contentsline {section}{\numberline {6}CFITSIO Error Status Codes}{38}
diff --git a/external/cfitsio/region.c b/external/cfitsio/region.c
new file mode 100644
index 0000000..3ec5bc2
--- /dev/null
+++ b/external/cfitsio/region.c
@@ -0,0 +1,1747 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#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; iexists ) {
+                     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( ; ixrefval, 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; inShapes; 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( Xxmin || X>Shapes->xmax
+             || Yymin || 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; inShapes; 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; iprevY && y>=nextY) || (yprevX && 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 ( inShapes ) {
+
+    /* 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; inShapes; 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], ®wcs->xrefval,  ®wcs->yrefval,
+		®wcs->xrefpix, ®wcs->yrefpix, ®wcs->xinc, ®wcs->yinc,
+		®wcs->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; jparam.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; jxrefval, 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; jxrefval, 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; jcomp, &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/external/cfitsio/region.h b/external/cfitsio/region.h
new file mode 100644
index 0000000..516c4fd
--- /dev/null
+++ b/external/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/external/cfitsio/ricecomp.c b/external/cfitsio/ricecomp.c
new file mode 100644
index 0000000..d5a11a4
--- /dev/null
+++ b/external/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 
+#include 
+#include 
+
+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<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> 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; jbitbuffer;
+	    lbits_to_go = buffer->bits_to_go;
+	    for (j=0; j> 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<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> 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; jbitbuffer;
+	    lbits_to_go = buffer->bits_to_go;
+	    for (j=0; j> 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<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> 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; jbitbuffer;
+	    lbits_to_go = buffer->bits_to_go;
+	    for (j=0; j> 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<>(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<>(-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<bits_to_go,buffer);
+	
+/*	if (putcbuf(buffer->bitbuffer<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 
+#include 
+*/
+
+/*---------------------------------------------------------------------------*/
+/* 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<=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> nbits) - 1;
+
+	b &= (1< nx) imax = nx;
+	if (fs<0) {
+	    /* low-entropy case, all zero differences */
+	    for ( ; i= 0; k -= 8) {
+		    b = *c++;
+		    diff |= b<0) {
+		    b = *c++;
+		    diff |= b>>(-k);
+		    b &= (1<>1;
+		} else {
+		    diff = ~(diff>>1);
+		}
+		array[i] = diff+lastpix;
+		lastpix = array[i];
+	    }
+	} else {
+	    /* normal case, Rice coding */
+	    for ( ; i>nbits);
+		b &= (1<>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<=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> nbits) - 1;
+
+	b &= (1< nx) imax = nx;
+	if (fs<0) {
+	    /* low-entropy case, all zero differences */
+	    for ( ; i= 0; k -= 8) {
+		    b = *c++;
+		    diff |= b<0) {
+		    b = *c++;
+		    diff |= b>>(-k);
+		    b &= (1<>1;
+		} else {
+		    diff = ~(diff>>1);
+		}
+		array[i] = diff+lastpix;
+		lastpix = array[i];
+	    }
+	} else {
+	    /* normal case, Rice coding */
+	    for ( ; i>nbits);
+		b &= (1<>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<=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> nbits) - 1;
+
+	b &= (1< nx) imax = nx;
+	if (fs<0) {
+	    /* low-entropy case, all zero differences */
+	    for ( ; i= 0; k -= 8) {
+		    b = *c++;
+		    diff |= b<0) {
+		    b = *c++;
+		    diff |= b>>(-k);
+		    b &= (1<>1;
+		} else {
+		    diff = ~(diff>>1);
+		}
+		array[i] = diff+lastpix;
+		lastpix = array[i];
+	    }
+	} else {
+	    /* normal case, Rice coding */
+	    for ( ; i>nbits);
+		b &= (1<>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/external/cfitsio/sample.tpl b/external/cfitsio/sample.tpl
new file mode 100644
index 0000000..8cfca14
--- /dev/null
+++ b/external/cfitsio/sample.tpl
@@ -0,0 +1,121 @@
+# sample template - create 9 HDUs in one FITS file
+
+# syntax :
+
+# everything which starts with a hashmark is ignored
+# the same for empty lines
+
+# one can use \include filename to include other files
+# equal sign after keyword name is optional
+# \group must be terminated by \end
+# xtension is terminated by \group, xtension or EOF
+# First HDU of type image may be defined using "SIMPLE T"
+# group may contain other groups and xtensions
+# keywords may be indented, but indentation is limited to max 7chars.
+
+# template parser processes all keywords, makes substitutions
+# when necessary (hashmarks -> index), converts keyword names
+# to uppercase and writes keywords to file.
+# For string keywords, parser uses CFITSIO long string routines
+# to store string values longer than 72 characters. Parser can
+# read/process lines of any length, as long as there is enough memory.
+# For a very limited set of keywords (like NAXIS1 for binary tables)
+# template parser ignores values specified in template file
+# (one should not specify NAXIS1 for binary tables) and computes and
+# writes values respective to table structure.
+# number of rows in binary/ascii tables can be specified with NAXIS2
+
+# if the 1st HDU is not defined with "SIMPLE T" and is defined with
+# xtension image/asciitable/bintable then dummy primary HDU is
+# created by parser.
+
+simple	t
+ bitpix		16
+ naxis		1
+ naxis1		10
+COMMENT
+ comment  
+ sdsdf / keyword without value (null type)
+        if line begins with 8+ spaces everything is a comment
+
+xtension image
+ bitpix		16
+ naxis		1
+ naxis1		10
+ QWERW		F / dfg dfgsd fg - boolean keyword
+ FFFSDS45	3454345 /integer_or_real keyword
+ SSSDFS34	32345.453   / real keyword
+ adsfd34	(234234.34,2342342.3) / complex keyword - no space between ()
+ SDFDF#		adfasdfasdfdfcvxccvzxcvcvcxv / autoindexed keyword, here idx=1
+ SDFD#		'asf dfa dfad df dfad f ad fadfdaf dfdfa df loooooong keyyywoooord - reaaalllly verrrrrrrrrryy loooooooooong' / comment is max 80 chars
+ history        history record, spaces (all but 1st) after keyname are copied
+ SDFDF#		strg_value_without_spaces / autoindexed keyword, here idx=2
+ comment        comment record, spaces (all but 1st) after keyname are copied
+ strg45		'sdfasdfadfffdfasdfasdfasdf &'
+ continue   'sdfsdfsdfsd fsdf' / 3 spaces must follow CONTINUE keyword
+
+
+xtension image
+ bitpix		16
+ naxis		1
+ naxis1		10
+
+\group
+ 
+ xtension image
+  bitpix	16
+  naxis		1
+  naxis1	10
+
+# create group inside group
+
+ \group
+
+# one can specify additional columns in group HDU. The first column
+# specified will have index 7 however, since the first 6 columns are occupied
+# by grouping table itself.
+# Please note, that it is not allowed to specify EXTNAME keyword as an
+# additional keyword for group HDU, since parser automatically writes
+# EXTNAME = GROUPING keyword.
+
+  TFORM#	13A
+  TTYPE#	ADDIT_COL_IN_GRP_HDU
+  TFORM#	1E
+  TTYPE#	REAL_COLUMN
+  COMMENT sure, there is always place for comments
+
+# the following specifies empty ascii table (0 cols / 0 rows)
+
+  xtension asciitable
+
+ \end
+ 
+\end
+
+# one do not have to specify all NAXISn keywords. If not specified
+# NAXISn equals zero.
+
+xtension image
+ bitpix	16
+ naxis	1
+# naxis1	10
+
+# the following tells how to set number of rows in binary table
+# note also that the last line in template file does not have to
+# have LineFeed character as the last one.
+
+xtension bintable
+naxis2	 10
+EXTNAME	asdjfhsdkf
+TTYPE#   MEMBER_XTENSION
+TFORM#   8A
+TTYPE#   MEMBER_2
+TFORM#   8U
+TTYPE#   MEMBER_3
+TFORM#   8V
+TTYPE#   MEMBER_NAME
+TFORM#   32A
+TDIM#	 '(8,4)'
+TTYPE#   MEMBER_VERSION
+TFORM#   1J
+TNULL#   0
\ No newline at end of file
diff --git a/external/cfitsio/scalnull.c b/external/cfitsio/scalnull.c
new file mode 100644
index 0000000..d2f2924
--- /dev/null
+++ b/external/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 
+#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/external/cfitsio/smem.c b/external/cfitsio/smem.c
new file mode 100644
index 0000000..bf82370
--- /dev/null
+++ b/external/cfitsio/smem.c
@@ -0,0 +1,77 @@
+#include 
+#include 
+#include 
+#ifdef __APPLE__
+#include 
+#else
+#include 
+#endif
+#include "fitsio.h"     /* needed to define LONGLONG */
+#include "drvrsmem.h"   /* uses LONGLONG */
+
+int	main(int argc, char **argv)
+{ int cmdok, listmode, longlistmode, recovermode, deletemode, id;
+int status;
+char *address;
+
+listmode = longlistmode = recovermode = deletemode = 0;
+id = -1;
+cmdok = 1;
+
+switch (argc)
+ { case 1:	listmode = 1;
+		break;
+   case 2:
+		if (0 == strcmp("-l", argv[1])) longlistmode = 1;
+		else if (0 == strcmp("-r", argv[1])) recovermode = 1;
+		else if (0 == strcmp("-d", argv[1])) deletemode = 1;
+		else cmdok = 0;
+		break;
+   case 3:
+		if (0 == strcmp("-r", argv[1])) recovermode = 1;
+		else if (0 == strcmp("-d", argv[1])) deletemode = 1;
+		else
+		 { cmdok = 0;		/* signal invalid cmd line syntax */
+		   break;
+		 }
+		if (1 != sscanf(argv[2], "%d", &id)) cmdok = 0;
+		break;
+   default:
+		cmdok = 0;
+		break;
+ }
+
+if (0 == cmdok)
+  { printf("usage :\n\n");
+    printf("smem            - list all shared memory segments\n");
+    printf("\t!\tcouldn't obtain RDONLY lock - info unreliable\n");
+    printf("\tIdx\thandle of shared memory segment (visible by application)\n");
+    printf("\tKey\tcurrent system key of shared memory segment. Key\n");
+    printf("\t\tchanges whenever shmem segment is reallocated. Use\n");
+    printf("\t\tipcs (or ipcs -a) to view all shmem segments\n");
+    printf("\tNproc\tnumber of processes attached to segment\n");
+    printf("\tSize\tsize of shmem segment in bytes\n");
+    printf("\tFlags\tRESIZABLE - realloc allowed, PERSIST - segment is not\n");
+    printf("\t\tdeleted after shared_free called by last process attached\n");
+    printf("\t\tto it.\n");
+    printf("smem -d         - delete all shared memory segments (may block)\n");
+    printf("smem -d id      - delete specific shared memory segment (may block)\n");
+    printf("smem -r         - unconditionally reset all shared memory segments\n\t\t(does not block, recovers zombie handles left by kill -9)\n");
+    printf("smem -r id      - unconditionally reset specific shared memory segment\n");
+  }
+
+if (shared_init(0))
+  { printf("couldn't initialize shared memory, aborting ...\n");
+    return(10);
+  }
+
+if (listmode) shared_list(id);
+else if (recovermode) shared_recover(id);
+else if (deletemode) shared_uncond_delete(id);
+
+for (id = 0; id <16; id++) {
+  status = shared_getaddr(id, &address);
+  if (!status)printf("id, status, address %d %d %ld %.30s\n", id, status, address, address);
+}
+return(0);
+}
diff --git a/external/cfitsio/speed.c b/external/cfitsio/speed.c
new file mode 100644
index 0000000..75fbf05
--- /dev/null
+++ b/external/cfitsio/speed.c
@@ -0,0 +1,511 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+
+/*
+  Every program which uses the CFITSIO interface must include the
+  the fitsio.h header file.  This contains the prototypes for all
+  the routines and defines the error status values and other symbolic
+  constants used in the interface.  
+*/
+#include "fitsio.h"
+
+#define minvalue(A,B) ((A) < (B) ? (A) : (B))
+
+/* size of the image */
+#define XSIZE 3000
+#define YSIZE 3000
+
+/* size of data buffer */
+#define SHTSIZE 20000
+static long sarray[ SHTSIZE ] = {SHTSIZE * 0};
+
+/* no. of rows in binary table */
+#define BROWS 2500000
+
+/* no. of rows in ASCII table */
+#define AROWS 400000
+
+/*  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
+#define difftime(A,B) ((double) A - (double) B)
+#endif
+
+/* define variables for measuring elapsed time */
+clock_t scpu, ecpu;
+time_t start, finish;
+long startsec;   /* start of elapsed time interval */
+int startmilli;  /* start of elapsed time interval */
+
+int writeimage(fitsfile *fptr, int *status);
+int writebintable(fitsfile *fptr, int *status);
+int writeasctable(fitsfile *fptr, int *status);
+int readimage(fitsfile *fptr, int *status);
+int readatable(fitsfile *fptr, int *status);
+int readbtable(fitsfile *fptr, int *status);
+void printerror( int status);
+int marktime(int *status);
+int gettime(double *elapse, float *elapscpu, int *status);
+int main(void);
+
+int main()
+{
+/*************************************************************************
+    This program tests the speed of writing/reading FITS files with cfitsio
+**************************************************************************/
+
+    FILE *diskfile;
+    fitsfile *fptr;        /* pointer to the FITS file, defined in fitsio.h */
+    int status, ii;
+    long rawloop;
+    char filename[] = "speedcc.fit";           /* name for new FITS file */
+    char buffer[2880] = {2880 * 0};
+    time_t tbegin, tend;
+    float rate, size, elapcpu, cpufrac;
+    double elapse;
+
+    tbegin = time(0);
+
+    remove(filename);               /* Delete old file if it already exists */
+
+    diskfile =  fopen(filename,"w+b");
+    rawloop = XSIZE * YSIZE / 720;
+
+    printf("                                                ");
+    printf(" SIZE / ELAPSE(%%CPU) = RATE\n");
+    printf("RAW fwrite (2880 bytes/loop)...                 ");
+    marktime(&status);
+
+    for (ii = 0; ii < rawloop; ii++)
+      if (fwrite(buffer, 1, 2880, diskfile) != 2880)
+        printf("write error \n");
+
+    gettime(&elapse, &elapcpu, &status);
+
+    cpufrac = elapcpu / elapse * 100.;
+    size = 2880. * rawloop / 1000000.;
+    rate = size / elapse;
+    printf(" %4.1fMB/%4.1fs(%3.0f) = %5.2fMB/s\n", size, elapse, cpufrac,rate);
+
+    /* read back the binary records */
+    fseek(diskfile, 0, 0);
+
+    printf("RAW fread  (2880 bytes/loop)...                 ");
+    marktime(&status);
+
+    for (ii = 0; ii < rawloop; ii++)
+      if (fread(buffer, 1, 2880, diskfile) != 2880)
+        printf("read error \n");
+
+    gettime(&elapse, &elapcpu, &status);
+
+    cpufrac = elapcpu / elapse * 100.;
+    size = 2880. * rawloop / 1000000.;
+    rate = size / elapse;
+    printf(" %4.1fMB/%4.1fs(%3.0f) = %5.2fMB/s\n", size, elapse, cpufrac,rate);
+
+    fclose(diskfile);
+    remove(filename);
+
+    status = 0;     
+    fptr = 0;
+
+    if (fits_create_file(&fptr, filename, &status)) /* create new FITS file */
+       printerror( status);          
+   
+    if (writeimage(fptr, &status))
+       printerror( status);     
+
+    if (writebintable(fptr, &status))
+       printerror( status);     
+
+    if (writeasctable(fptr, &status))
+       printerror( status);     
+
+    if (readimage(fptr, &status))
+       printerror( status);     
+
+    if (readbtable(fptr, &status))
+       printerror( status);     
+
+    if (readatable(fptr, &status))
+       printerror( status);     
+
+    if (fits_close_file(fptr, &status))     
+         printerror( status );
+
+    tend = time(0);
+    elapse = difftime(tend, tbegin) + 0.5;
+    printf("Total elapsed time = %.1fs, status = %d\n",elapse, status);
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int writeimage(fitsfile *fptr, int *status)
+
+    /**************************************************/
+    /* write the primary array containing a 2-D image */
+    /**************************************************/
+{
+    long  nremain, ii;
+    float rate, size, elapcpu, cpufrac;
+    double elapse;
+
+    /* initialize FITS image parameters */
+    int bitpix   =  32;   /* 32-bit  signed integer pixel values       */
+    long naxis    =   2;  /* 2-dimensional image                            */    
+    long naxes[2] = {XSIZE, YSIZE }; /* image size */
+
+    /* write the required keywords for the primary array image */
+    if ( fits_create_img(fptr, bitpix, naxis, naxes, status) )
+         printerror( *status );          
+
+    printf("\nWrite %dx%d I*4 image, %d pixels/loop:   ",XSIZE,YSIZE,SHTSIZE);
+    marktime(status);
+
+    nremain = XSIZE * YSIZE;
+    for (ii = 1; ii <= nremain; ii += SHTSIZE)
+    {
+      ffpprj(fptr, 0, ii, SHTSIZE, sarray, status);
+    }
+
+    ffflus(fptr, status);  /* flush all buffers to disk */
+
+    gettime(&elapse, &elapcpu, status);
+
+    cpufrac = elapcpu / elapse * 100.;
+    size = XSIZE * 4. * YSIZE / 1000000.;
+    rate = size / elapse;
+    printf(" %4.1fMB/%4.1fs(%3.0f) = %5.2fMB/s\n", size, elapse, cpufrac,rate);
+
+    return( *status );
+}
+/*--------------------------------------------------------------------------*/
+int writebintable (fitsfile *fptr, int *status)
+
+    /*********************************************************/
+    /* Create a binary table extension containing 3 columns  */
+    /*********************************************************/
+{
+    int tfields = 2;
+    long nremain, ntodo, firstrow = 1, firstelem = 1, nrows;
+    float rate, size, elapcpu, cpufrac;
+    double elapse;
+
+    char extname[] = "Speed_Test";           /* extension name */
+
+    /* define the name, datatype, and physical units for the columns */
+    char *ttype[] = { "first", "second" };
+    char *tform[] = {"1J",       "1J"   };
+    char *tunit[] = { " ",       " "    };
+
+    /* append a new empty binary table onto the FITS file */
+
+    if ( fits_create_tbl( fptr, BINARY_TBL, BROWS, tfields, ttype, tform,
+                tunit, extname, status) )
+         printerror( *status );
+
+    /* get table row size and optimum number of rows to write per loop */
+    fits_get_rowsize(fptr, &nrows, status);
+    nrows = minvalue(nrows, SHTSIZE);
+    nremain = BROWS;
+
+    printf("Write %7drow x %dcol bintable %4ld rows/loop:", BROWS, tfields,
+       nrows);
+    marktime(status);
+
+    while(nremain)
+    {
+      ntodo = minvalue(nrows, nremain);
+      ffpclj(fptr, 1, firstrow, firstelem, ntodo, sarray, status);
+      ffpclj(fptr, 2, firstrow, firstelem, ntodo, sarray, status);
+      firstrow += ntodo;
+      nremain -= ntodo;
+    }
+
+    ffflus(fptr, status);  /* flush all buffers to disk */
+
+    gettime(&elapse, &elapcpu, status);
+
+    cpufrac = elapcpu / elapse * 100.;
+    size = BROWS * 8. / 1000000.;
+    rate = size / elapse;
+    printf(" %4.1fMB/%4.1fs(%3.0f) = %5.2fMB/s\n", size, elapse, cpufrac,rate);
+
+    return( *status );
+}
+/*--------------------------------------------------------------------------*/
+int writeasctable (fitsfile *fptr, int *status)
+
+    /*********************************************************/
+    /* Create an ASCII table extension containing 2 columns  */
+    /*********************************************************/
+{
+    int tfields = 2;
+    long nremain, ntodo, firstrow = 1, firstelem = 1;
+    long nrows;
+    float rate, size, elapcpu, cpufrac;
+    double elapse;
+
+    char extname[] = "Speed_Test";           /* extension name */
+
+    /* define the name, datatype, and physical units for the columns */
+    char *ttype[] = { "first", "second" };
+    char *tform[] = {"I6",       "I6"   };
+    char *tunit[] = { " ",      " "     };
+
+    /* append a new empty ASCII table onto the FITS file */
+    if ( fits_create_tbl( fptr, ASCII_TBL, AROWS, tfields, ttype, tform,
+                tunit, extname, status) )
+         printerror( *status );
+
+    /* get table row size and optimum number of rows to write per loop */
+    fits_get_rowsize(fptr, &nrows, status);
+    nrows = minvalue(nrows, SHTSIZE);
+    nremain = AROWS;
+
+    printf("Write %7drow x %dcol asctable %4ld rows/loop:", AROWS, tfields,
+           nrows);
+    marktime(status);
+
+    while(nremain)
+    {
+      ntodo = minvalue(nrows, nremain);
+      ffpclj(fptr, 1, firstrow, firstelem, ntodo, sarray, status);
+      ffpclj(fptr, 2, firstrow, firstelem, ntodo, sarray, status);
+      firstrow += ntodo;
+      nremain -= ntodo;
+    }
+
+    ffflus(fptr, status);  /* flush all buffers to disk */
+
+    gettime(&elapse, &elapcpu, status);
+
+    cpufrac = elapcpu / elapse * 100.;
+    size = AROWS * 13. / 1000000.;
+    rate = size / elapse;
+    printf(" %4.1fMB/%4.1fs(%3.0f) = %5.2fMB/s\n", size, elapse, cpufrac,rate);
+
+    return( *status );
+}
+/*--------------------------------------------------------------------------*/
+int readimage( fitsfile *fptr, int *status )
+
+    /*********************/
+    /* Read a FITS image */
+    /*********************/
+{
+    int anynull, hdutype;
+    long nremain, ii;
+    long longnull = 0;
+    float rate, size, elapcpu, cpufrac;
+    double elapse;
+
+    /* move to the primary array */
+    if ( fits_movabs_hdu(fptr, 1, &hdutype, status) ) 
+         printerror( *status );
+
+    printf("\nRead back image                                 ");
+    marktime(status);
+
+    nremain = XSIZE * YSIZE;
+    for (ii=1; ii <= nremain; ii += SHTSIZE)
+    {
+      ffgpvj(fptr, 0, ii, SHTSIZE, longnull, sarray, &anynull, status);
+    }
+
+    gettime(&elapse, &elapcpu, status);
+
+    cpufrac = elapcpu / elapse * 100.;
+    size = XSIZE * 4. * YSIZE / 1000000.;
+    rate = size / elapse;
+    printf(" %4.1fMB/%4.1fs(%3.0f) = %5.2fMB/s\n", size, elapse, cpufrac,rate);
+
+    return( *status );
+}
+/*--------------------------------------------------------------------------*/
+int readbtable( fitsfile *fptr, int *status )
+
+    /************************************************************/
+    /* read and print data values from the binary table */
+    /************************************************************/
+{
+    int hdutype, anynull;
+    long nremain, ntodo, firstrow = 1, firstelem = 1;
+    long nrows;
+    long lnull = 0;
+    float rate, size, elapcpu, cpufrac;
+    double elapse;
+
+    /* move to the table */
+    if ( fits_movrel_hdu(fptr, 1, &hdutype, status) ) 
+           printerror( *status );
+
+    /* get table row size and optimum number of rows to read per loop */
+    fits_get_rowsize(fptr, &nrows, status);
+    nrows = minvalue(nrows, SHTSIZE);
+    
+    /*  read the columns */  
+    nremain = BROWS;
+
+    printf("Read back BINTABLE                              ");
+    marktime(status);
+
+    while(nremain)
+    {
+      ntodo = minvalue(nrows, nremain);
+      ffgcvj(fptr, 1, firstrow, firstelem, ntodo,
+                     lnull, sarray, &anynull, status);
+      ffgcvj(fptr, 2, firstrow, firstelem, ntodo,
+                     lnull, sarray, &anynull, status);
+      firstrow += ntodo; 
+      nremain  -= ntodo;
+    }
+
+    gettime(&elapse, &elapcpu, status);
+
+    cpufrac = elapcpu / elapse * 100.;
+    size = BROWS * 8. / 1000000.;
+    rate = size / elapse;
+    printf(" %4.1fMB/%4.1fs(%3.0f) = %5.2fMB/s\n", size, elapse, cpufrac,rate);
+
+    return( *status );
+}
+/*--------------------------------------------------------------------------*/
+int readatable( fitsfile *fptr, int *status )
+
+    /************************************************************/
+    /* read and print data values from an ASCII or binary table */
+    /************************************************************/
+{
+    int hdutype, anynull;
+    long nremain, ntodo, firstrow = 1, firstelem = 1;
+    long nrows;
+    long lnull = 0;
+    float rate, size, elapcpu, cpufrac;
+    double elapse;
+
+    /* move to the table */
+    if ( fits_movrel_hdu(fptr, 1, &hdutype, status) ) 
+           printerror( *status );
+
+    /* get table row size and optimum number of rows to read per loop */
+    fits_get_rowsize(fptr, &nrows, status);
+    nrows = minvalue(nrows, SHTSIZE);
+ 
+    /*  read the columns */  
+    nremain = AROWS;
+
+    printf("Read back ASCII Table                           ");
+    marktime(status);
+
+    while(nremain)
+    {
+      ntodo = minvalue(nrows, nremain);
+      ffgcvj(fptr, 1, firstrow, firstelem, ntodo,
+                     lnull, sarray, &anynull, status);
+      ffgcvj(fptr, 2, firstrow, firstelem, ntodo,
+                     lnull, sarray, &anynull, status);
+      firstrow += ntodo;
+      nremain  -= ntodo;
+    }
+
+    gettime(&elapse, &elapcpu, status);
+
+    cpufrac = elapcpu / elapse * 100.;
+    size = AROWS * 13. / 1000000.;
+    rate = size / elapse;
+    printf(" %4.1fMB/%4.1fs(%3.0f) = %5.2fMB/s\n", size, elapse, cpufrac,rate);
+
+    return( *status );
+}
+/*--------------------------------------------------------------------------*/
+void printerror( int status)
+{
+    /*****************************************************/
+    /* Print out cfitsio error messages and exit program */
+    /*****************************************************/
+
+    char status_str[FLEN_STATUS], errmsg[FLEN_ERRMSG];
+  
+    if (status)
+      fprintf(stderr, "\n*** Error occurred during program execution ***\n");
+
+    fits_get_errstatus(status, status_str);   /* get the error description */
+    fprintf(stderr, "\nstatus = %d: %s\n", status, status_str);
+
+    /* get first message; null if stack is empty */
+    if ( fits_read_errmsg(errmsg) ) 
+    {
+         fprintf(stderr, "\nError message stack:\n");
+         fprintf(stderr, " %s\n", errmsg);
+
+         while ( fits_read_errmsg(errmsg) )  /* get remaining messages */
+             fprintf(stderr, " %s\n", errmsg);
+    }
+
+    exit( status );       /* terminate the program, returning error status */
+}
+/*--------------------------------------------------------------------------*/
+int marktime( int *status)
+{
+    double telapse;
+    time_t temp;
+    struct  timeval tv;
+    struct  timezone tz;
+
+    temp = time(0);
+
+    /* Since elapsed time is only measured to the nearest second */
+    /* keep getting the time until the seconds tick just changes. */
+    /* This provides more consistent timing measurements since the */
+    /* intervals all start on an integer seconds. */
+
+    telapse = 0.;
+
+        scpu = clock();
+        start = time(0);
+/*
+    while (telapse == 0.)
+    {
+        scpu = clock();
+        start = time(0);
+        telapse = difftime( start, temp );
+    }
+*/
+        gettimeofday (&tv, &tz);
+
+	startsec = tv.tv_sec;
+        startmilli = tv.tv_usec/1000;
+
+    return( *status );
+}
+/*--------------------------------------------------------------------------*/
+int gettime(double *elapse, float *elapscpu, int *status)
+{
+        struct  timeval tv;
+        struct  timezone tz;
+	int stopmilli;
+	long stopsec;
+
+
+        gettimeofday (&tv, &tz);
+    ecpu = clock();
+    finish = time(0);
+
+        stopmilli = tv.tv_usec/1000;
+	stopsec = tv.tv_sec;
+	
+
+	*elapse = (stopsec - startsec) + (stopmilli - startmilli)/1000.;
+
+/*    *elapse = difftime(finish, start) + 0.5; */
+    *elapscpu = (ecpu - scpu) * 1.0 / CLOCKTICKS;
+
+    return( *status );
+}
diff --git a/external/cfitsio/swapproc.c b/external/cfitsio/swapproc.c
new file mode 100644
index 0000000..cc69d6e
--- /dev/null
+++ b/external/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 
+#include 
+#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 
+/* 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 
+/* 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/external/cfitsio/testf77.f b/external/cfitsio/testf77.f
new file mode 100644
index 0000000..ac3bc21
--- /dev/null
+++ b/external/cfitsio/testf77.f
@@ -0,0 +1,2488 @@
+C     This is a big and complicated program that tests most of
+C     the fitsio routines.  This code does not represent
+C     the most efficient method of reading or writing FITS files 
+C     because this code is primarily designed to stress the fitsio
+C     library routines.
+
+      character asciisum*17
+      character*3 cval
+      character*1 xinarray(21), binarray(21), boutarray(21), bnul
+      character colname*70, tdisp*40, nulstr*40
+      character oskey*15
+      character iskey*21
+      character lstr*200   
+      character  comm*73
+      character*30 inskey(21)
+      character*30 onskey(3)
+      character filename*40, card*78, card2*78
+      character keyword*8
+      character value*68, comment*72
+      character uchars*78
+      character*15 ttype(10), tform(10), tunit(10)
+      character*15 tblname
+      character*15 binname
+      character errmsg*75
+      character*8  inclist(2),exclist(2)
+      character*8 xctype,yctype,ctype
+      character*18 kunit
+
+      logical simple,extend,larray(42), larray2(42)
+      logical olkey, ilkey, onlkey(3), inlkey(3), anynull
+
+      integer*2 imgarray(19,30), imgarray2(10,20)
+      integer*2         iinarray(21), ioutarray(21), inul
+
+      integer naxes(3), pcount, gcount, npixels, nrows, rowlen
+      integer existkeys, morekeys, keynum
+      integer datastatus, hdustatus
+      integer status, bitpix, naxis, block
+      integer ii, jj, jjj, hdutype, hdunum, tfields
+      integer nkeys, nfound, colnum, typecode, signval,nmsg
+      integer repeat, offset, width, jnulval
+      integer kinarray(21), koutarray(21), knul
+      integer jinarray(21), joutarray(21), jnul
+      integer ojkey, ijkey, otint
+      integer onjkey(3), injkey(3)
+      integer tbcol(5)
+      integer iunit, tmpunit
+      integer fpixels(2), lpixels(2), inc(2)
+
+      real estatus, vers
+      real einarray(21), eoutarray(21), enul, cinarray(42)
+      real ofkey, oekey, iekey, onfkey(3),onekey(3), inekey(3)
+
+      double precision dinarray(21),doutarray(21),dnul, minarray(42)
+      double precision scale, zero
+      double precision ogkey, odkey, idkey, otfrac, ongkey(3)
+      double precision ondkey(3), indkey(3)
+      double precision checksum, datsum
+      double precision xrval,yrval,xrpix,yrpix,xinc,yinc,rot
+      double precision xpos,ypos,xpix,ypix
+
+      tblname = 'Test-ASCII'
+      binname = 'Test-BINTABLE'
+      onskey(1) = 'first string'
+      onskey(2) = 'second string'
+      onskey(3) = '        '
+      oskey = 'value_string'
+      inclist(1)='key*'
+      inclist(2)='newikys'
+      exclist(1)='key_pr*'
+      exclist(2)='key_pkls'
+      xctype='RA---TAN'
+      yctype='DEC--TAN'
+
+      olkey = .true.
+      ojkey = 11
+      otint = 12345678
+      ofkey = 12.121212
+      oekey = 13.131313
+      ogkey = 14.1414141414141414D+00
+      odkey = 15.1515151515151515D+00
+      otfrac = .1234567890123456D+00
+      onlkey(1) = .true.
+      onlkey(2) = .false.
+      onlkey(3) = .true.
+      onjkey(1) = 11
+      onjkey(2) = 12
+      onjkey(3) = 13
+      onfkey(1) = 12.121212
+      onfkey(2) = 13.131313
+      onfkey(3) = 14.141414
+      onekey(1) = 13.131313
+      onekey(2) = 14.141414
+      onekey(3) = 15.151515
+      ongkey(1) = 14.1414141414141414D+00
+      ongkey(2) = 15.1515151515151515D+00
+      ongkey(3) = 16.1616161616161616D+00
+      ondkey(1) = 15.1515151515151515D+00
+      ondkey(2) = 16.1616161616161616D+00
+      ondkey(3) = 17.1717171717171717D+00
+
+      tbcol(1) = 1
+      tbcol(2) =  17
+      tbcol(3) =  28
+      tbcol(4) =  43
+      tbcol(5) =  56
+      status = 0
+
+      call ftvers(vers)
+      write(*,'(1x,A,F7.3)') 'FITSIO TESTPROG, v', vers
+      write(*, '(1x,A)')' '
+
+      iunit = 15
+      tmpunit = 16
+
+      write(*,'(1x,A)') 'Try opening then closing a nonexistent file: '
+      call ftopen(iunit, 'tq123x.kjl', 1, block, status)
+      write(*,'(1x,A,2i4)')'  ftopen iunit, status (expect an error) ='
+     & ,iunit, status
+      call ftclos(iunit, status)
+      write(*,'(1x,A,i4)')'  ftclos status = ', status
+      write(*,'(1x,A)')' '
+
+      call ftcmsg
+      status = 0
+
+      filename = 'testf77.fit'
+
+C delete previous version of the file, if it exists 
+
+      call ftopen(iunit, filename, 1, block, status)
+      if (status .eq. 0)then
+         call ftdelt(iunit, status)
+      else
+C        clear the error message stack
+         call ftcmsg
+      end if
+
+      status = 0
+
+C
+C        #####################
+C        #  create FITS file #
+C        #####################
+      
+
+      call ftinit(iunit, filename, 1, status)
+      write(*,'(1x,A,i4)')'ftinit create new file status = ', status
+      write(*,'(1x,A)')' '
+
+      if (status .ne. 0)go to 999
+
+      simple = .true.
+      bitpix = 32
+      naxis = 2
+      naxes(1) = 10
+      naxes(2) = 2
+      npixels = 20
+      pcount = 0
+      gcount = 1
+      extend = .true.
+      
+C        ############################
+C        #  write single keywords   #
+C        ############################
+      
+      call ftphpr(iunit,simple, bitpix, naxis, naxes, 
+     & 0,1,extend,status)
+
+      call ftprec(iunit, 
+     &'key_prec= ''This keyword was written by fxprec'' / '//
+     & 'comment goes here',  status)
+
+      write(*,'(1x,A)') 'test writing of long string keywords: '
+      card = '1234567890123456789012345678901234567890'//
+     & '12345678901234567890123456789012345'
+      call ftpkys(iunit, 'card1', card, ' ', status)
+      call ftgkey(iunit, 'card1', card2, comment, status)
+
+      write(*,'(1x,A)') card
+      write(*,'(1x,A)') card2
+      
+      card = '1234567890123456789012345678901234567890'//
+     &  '123456789012345678901234''6789012345'
+      call ftpkys(iunit, 'card2', card, ' ', status)
+      call ftgkey(iunit, 'card2', card2, comment, status)
+      write(*,'(1x,A)') card
+      write(*,'(1x,A)') card2
+      
+      card = '1234567890123456789012345678901234567890'//
+     &  '123456789012345678901234''''789012345'
+      call ftpkys(iunit, 'card3', card, ' ', status)
+      call ftgkey(iunit, 'card3', card2, comment, status)
+      write(*,'(1x,A)') card
+      write(*,'(1x,A)') card2
+      
+      card = '1234567890123456789012345678901234567890'//
+     & '123456789012345678901234567''9012345'
+      call ftpkys(iunit, 'card4', card, ' ', status)
+      call ftgkey(iunit, 'card4', card2, comment, status)
+      write(*,'(1x,A)') card
+      write(*,'(1x,A)') card2
+
+      call ftpkys(iunit, 'key_pkys', oskey, 'fxpkys comment', status)
+      call ftpkyl(iunit, 'key_pkyl', olkey, 'fxpkyl comment', status)
+      call ftpkyj(iunit, 'key_pkyj', ojkey, 'fxpkyj comment', status)
+      call ftpkyf(iunit,'key_pkyf',ofkey,5, 'fxpkyf comment', status)
+      call ftpkye(iunit,'key_pkye',oekey,6, 'fxpkye comment', status)
+      call ftpkyg(iunit,'key_pkyg',ogkey,14, 'fxpkyg comment',status)
+      call ftpkyd(iunit,'key_pkyd',odkey,14, 'fxpkyd comment',status)
+
+      lstr='This is a very long string '//
+     &   'value that is continued over more than one keyword.'
+
+      call ftpkls(iunit,'key_pkls',lstr,'fxpkls comment',status)
+
+      call ftplsw(iunit, status)
+      call ftpkyt(iunit,'key_pkyt',otint,otfrac,'fxpkyt comment',
+     & status)
+      call ftpcom(iunit, 'This keyword was written by fxpcom.',
+     &  status)
+      call ftphis(iunit, 
+     &'  This keyword written by fxphis (w/ 2 leading spaces).',
+     &    status)
+
+      call ftpdat(iunit, status)
+      
+      if (status .gt. 0)go to 999   
+
+C
+C        ###############################
+C        #  write arrays of keywords   #
+C        ###############################
+      
+      nkeys = 3
+
+      comm = 'fxpkns comment&'
+      call ftpkns(iunit, 'ky_pkns', 1, nkeys, onskey, comm, status)
+      comm = 'fxpknl comment&'
+      call ftpknl(iunit, 'ky_pknl', 1, nkeys, onlkey, comm, status)
+
+      comm = 'fxpknj comment&'
+      call ftpknj(iunit, 'ky_pknj', 1, nkeys, onjkey, comm, status)
+
+      comm = 'fxpknf comment&'
+      call ftpknf(iunit, 'ky_pknf', 1, nkeys, onfkey,5,comm,status)
+
+      comm = 'fxpkne comment&'
+      call ftpkne(iunit, 'ky_pkne', 1, nkeys, onekey,6,comm,status)
+
+      comm = 'fxpkng comment&'
+      call ftpkng(iunit, 'ky_pkng', 1, nkeys, ongkey,13,comm,status)
+
+      comm = 'fxpknd comment&'
+      call ftpknd(iunit, 'ky_pknd', 1, nkeys, ondkey,14,comm,status)
+      
+      if (status .gt. 0)go to 999
+      
+C        ############################
+C        #  write generic keywords  #
+C        ############################
+      
+
+      oskey = '1'
+      call ftpkys(iunit, 'tstring', oskey, 'tstring comment',status)
+
+      olkey = .true.
+      call ftpkyl(iunit, 'tlogical', olkey, 'tlogical comment',
+     &    status)
+
+      ojkey = 11
+      call ftpkyj(iunit, 'tbyte', ojkey, 'tbyte comment', status)
+
+      ojkey = 21
+      call ftpkyj(iunit, 'tshort', ojkey, 'tshort comment', status)
+
+      ojkey = 31
+      call ftpkyj(iunit, 'tint', ojkey, 'tint comment', status)
+
+      ojkey = 41
+      call ftpkyj(iunit, 'tlong', ojkey, 'tlong comment', status)
+
+      oekey = 42
+      call ftpkye(iunit, 'tfloat', oekey, 6,'tfloat comment', status)
+
+      odkey = 82.D+00
+      call ftpkyd(iunit, 'tdouble', odkey, 14, 'tdouble comment',
+     &          status)
+
+      if (status .gt. 0)go to 999
+      write(*,'(1x,A)') 'Wrote all Keywords successfully '
+
+
+C        ############################
+C        #  write data              #
+C        ############################
+      
+      
+C define the null value (must do this before writing any data) 
+      call ftpkyj(iunit,'BLANK',-99,
+     & 'value to use for undefined pixels',   status)
+      
+C initialize arrays of values to write to primary array 
+      do ii = 1, npixels
+          boutarray(ii) = char(ii)
+          ioutarray(ii) = ii
+          joutarray(ii) = ii
+          eoutarray(ii) = ii
+          doutarray(ii) = ii
+      end do      
+
+C write a few pixels with each datatype 
+C set the last value in each group of 4 as undefined 
+      call ftpprb(iunit, 1,  1, 2, boutarray(1),  status)
+      call ftppri(iunit, 1,  5, 2, ioutarray(5),  status)
+      call ftpprj(iunit, 1,  9, 2, joutarray(9),  status)
+      call ftppre(iunit, 1, 13, 2, eoutarray(13), status)
+      call ftpprd(iunit, 1, 17, 2, doutarray(17), status)
+      bnul = char(4)
+      call ftppnb(iunit, 1,  3, 2, boutarray(3),   bnul, status)
+      inul = 8
+      call ftppni(iunit, 1,  7, 2, ioutarray(7),  inul, status)
+      call ftppnj(iunit, 1, 11, 2, joutarray(11),  12, status)
+      call ftppne(iunit, 1, 15, 2, eoutarray(15), 16., status)
+      dnul = 20.
+      call ftppnd(iunit, 1, 19, 2, doutarray(19), dnul, status)
+      call ftppru(iunit, 1, 1, 1, status)
+
+      if (status .gt. 0)then
+          write(*,'(1x,A,I4)')'ftppnx status = ', status
+          goto 999
+      end if
+
+      call ftflus(iunit, status)   
+C flush all data to the disk file  
+      write(*,'(1x,A,I4)')'ftflus status = ', status
+      write(*,'(1x,A)')' '
+
+      call ftghdn(iunit, hdunum)
+      write(*,'(1x,A,I4)')'HDU number = ', hdunum
+
+C        ############################
+C        #  read data               #
+C        ############################
+      
+     
+C read back the data, setting null values = 99 
+      write(*,'(1x,A)')
+     &   'Values read back from primary array (99 = null pixel)'
+      write(*,'(1x,A)') 
+     &  'The 1st, and every 4th pixel should be undefined: '
+
+      anynull = .false.
+      bnul = char(99)
+      call ftgpvb(iunit, 1,  1, 10, bnul, binarray, anynull, status)
+      call ftgpvb(iunit, 1, 11, 10, bnul, binarray(11),anynull,status)
+
+      do ii = 1,npixels
+           iinarray(ii) = ichar(binarray(ii))
+      end do
+
+      write(*,1101) (iinarray(ii), ii = 1, npixels), anynull,
+     &  ' (ftgpvb) '
+1101  format(1x,20i3,l3,a)
+
+      inul = 99
+      call ftgpvi(iunit, 1, 1, npixels, inul, iinarray,anynull,status)
+
+      write(*,1101) (iinarray(ii), ii = 1, npixels), anynull,
+     &  ' (ftgpvi) '
+
+      call ftgpvj(iunit, 1, 1, npixels, 99,  jinarray,anynull,status)
+
+      write(*,1101) (jinarray(ii), ii = 1, npixels), anynull,
+     &  ' (ftgpvj) '
+
+      call ftgpve(iunit, 1, 1, npixels, 99., einarray,anynull,status)
+
+      write(*,1102) (einarray(ii), ii = 1, npixels), anynull,
+     &  ' (ftgpve) '
+
+1102  format(2x,20f3.0,l2,a)
+
+      dnul = 99.
+      call ftgpvd(iunit, 1,  1, 10, dnul,  dinarray, anynull, status)
+      call ftgpvd(iunit, 1, 11, 10, dnul,dinarray(11),anynull,status)
+
+      write(*,1102) (dinarray(ii), ii = 1, npixels), anynull,
+     &  ' (ftgpvd) '
+
+      if (status .gt. 0)then
+          write(*,'(1x,A,I4)')'ERROR: ftgpv_ status = ', status
+          goto 999
+      end if
+      
+      if (.not. anynull)then
+         write(*,'(1x,A)') 'ERROR: ftgpv_ did not detect null values '
+         go to 999
+      end if
+      
+C reset the output null value to the expected input value 
+
+      do ii = 4, npixels, 4      
+          boutarray(ii) = char(99)
+          ioutarray(ii) = 99
+          joutarray(ii) = 99
+          eoutarray(ii) = 99.
+          doutarray(ii) = 99.
+      end do
+
+          ii = 1
+          boutarray(ii) = char(99)
+          ioutarray(ii) = 99
+          joutarray(ii) = 99
+          eoutarray(ii) = 99.
+          doutarray(ii) = 99.
+
+      
+C compare the output with the input flag any differences 
+      do ii = 1, npixels
+     
+         if (boutarray(ii) .ne. binarray(ii))then
+             write(*,'(1x,A,2A2)') 'bout != bin ', boutarray(ii), 
+     &      binarray(ii)
+         end if
+
+         if (ioutarray(ii) .ne. iinarray(ii))then
+             write(*,'(1x,A,2I8)') 'bout != bin ', ioutarray(ii), 
+     &      iinarray(ii)
+         end if
+
+         if (joutarray(ii) .ne. jinarray(ii))then
+             write(*,'(1x,A,2I12)') 'bout != bin ', joutarray(ii), 
+     &       jinarray(ii)
+         end if
+
+         if (eoutarray(ii) .ne. einarray(ii))then
+             write(*,'(1x,A,2E15.3)') 'bout != bin ', eoutarray(ii),
+     &       einarray(ii)
+         end if
+    
+         if (doutarray(ii) .ne. dinarray(ii))then
+             write(*,'(1x,A,2D20.6)') 'bout != bin ', doutarray(ii), 
+     &       dinarray(ii)
+         end if
+      end do
+
+      do ii = 1, npixels
+        binarray(ii) = char(0)
+        iinarray(ii) = 0
+        jinarray(ii) = 0
+        einarray(ii) = 0.
+        dinarray(ii) = 0.
+      end do      
+
+      anynull = .false.
+      call ftgpfb(iunit, 1,  1, 10, binarray, larray, anynull,status)
+      call ftgpfb(iunit, 1, 11, 10, binarray(11), larray(11),
+     & anynull, status)
+
+      do ii = 1, npixels
+        if (larray(ii))binarray(ii) = char(0)
+      end do
+
+      do ii = 1,npixels
+           iinarray(ii) = ichar(binarray(ii))
+      end do
+
+      write(*,1101)(iinarray(ii),ii = 1,npixels),anynull,' (ftgpfb)'
+
+      call ftgpfi(iunit, 1, 1, npixels, iinarray, larray, anynull,
+     & status)
+
+      do ii = 1, npixels
+        if (larray(ii))iinarray(ii) = 0
+      end do
+
+      write(*,1101)(iinarray(ii),ii = 1,npixels),anynull,' (ftgpfi)'
+
+      call ftgpfj(iunit, 1, 1, npixels, jinarray, larray, anynull,
+     & status)
+
+      do ii = 1, npixels
+        if (larray(ii))jinarray(ii) = 0
+      end do
+
+      write(*,1101)(jinarray(ii),ii = 1,npixels),anynull,' (ftgpfj)'
+
+      call ftgpfe(iunit, 1, 1, npixels, einarray, larray, anynull,
+     & status)
+
+      do ii = 1, npixels
+        if (larray(ii))einarray(ii) = 0.
+      end do
+
+      write(*,1102)(einarray(ii),ii = 1,npixels),anynull,' (ftgpfe)'
+
+      call ftgpfd(iunit, 1,  1, 10, dinarray, larray, anynull,status)
+      call ftgpfd(iunit, 1, 11, 10, dinarray(11), larray(11),
+     & anynull, status)
+
+      do ii = 1, npixels
+        if (larray(ii))dinarray(ii) = 0.
+      end do
+
+      write(*,1102)(dinarray(ii),ii = 1,npixels),anynull,' (ftgpfd)'
+
+      if (status .gt. 0)then
+          write(*,'(1x,A,I4)')'ERROR: ftgpf_ status = ', status
+          go to 999
+      end if
+
+      if (.not. anynull)then
+         write(*,'(1x,A)') 'ERROR: ftgpf_ did not detect null values'
+         go to 999
+      end if
+
+
+C        ##########################################
+C        #  close and reopen file multiple times  #
+C        ##########################################
+      
+
+      do ii = 1, 10
+         call ftclos(iunit, status)
+        
+         if (status .gt. 0)then
+            write(*,'(1x,A,I4)')'ERROR in ftclos (1) = ', status
+            go to 999
+         end if
+
+         call ftopen(iunit, filename, 1, block, status)
+
+         if (status .gt. 0)then
+            write(*,'(1x,A,I4)')'ERROR: ftopen open file status = ',
+     &      status
+            go to 999
+         end if
+      end do
+      
+      write(*,'(1x,A)') ' '
+      write(*,'(1x,A)') 'Closed then reopened the FITS file 10 times.'
+      write(*,'(1x,A)')' '
+
+      call ftghdn(iunit, hdunum)
+      write(*,'(1x,A,I4)')'HDU number = ', hdunum
+
+
+C        ############################
+C        #  read single keywords    #
+C        ############################
+      
+
+      simple = .false.
+      bitpix = 0
+      naxis = 0
+      naxes(1) = 0
+      naxes(2) = 0
+      pcount = -99
+      gcount =  -99
+      extend = .false.
+      write(*,'(1x,A)') 'Read back keywords: '
+      call ftghpr(iunit, 3, simple, bitpix, naxis, naxes, pcount,
+     &       gcount, extend, status)
+      write(*,'(1x,A,L4,4I4)')'simple, bitpix, naxis, naxes = ',
+     &       simple, bitpix, naxis, naxes(1), naxes(2)
+      write(*,'(1x,A,2I4,L4)')'  pcount, gcount, extend = ',
+     &           pcount, gcount, extend
+
+      call ftgrec(iunit, 9, card, status)
+      write(*,'(1x,A)') card
+      if (card(1:15) .ne. 'KEY_PREC= ''This')
+     &    write(*,'(1x,A)') 'ERROR in ftgrec '
+
+      call ftgkyn(iunit, 9, keyword, value, comment, status)
+      write(*,'(1x,5A)') keyword,' ', value(1:35),' ', comment(1:20)
+
+      if (keyword(1:8) .ne. 'KEY_PREC' )
+     &    write(*,'(1x,2A)') 'ERROR in ftgkyn: ', keyword
+
+      call ftgcrd(iunit, keyword, card, status)
+      write(*,'(1x,A)') card
+
+      if (keyword(1:8) .ne.  card(1:8) )
+     &    write(*,'(1x,2A)') 'ERROR in ftgcrd: ', keyword
+
+      call ftgkey(iunit, 'KY_PKNS1', value, comment, status)
+      write(*,'(1x,5A)') 'KY_PKNS1 ',':', value(1:15),':', comment(1:16)
+
+      if (value(1:14) .ne. '''first string''')
+     &  write(*,'(1x,2A)') 'ERROR in ftgkey: ', value
+
+      call ftgkys(iunit, 'key_pkys', iskey, comment, status)
+      write(*,'(1x,5A,I4)')'KEY_PKYS ',':',iskey,':',comment(1:16),
+     & status
+
+      call ftgkyl(iunit, 'key_pkyl', ilkey, comment, status)
+      write(*,'(1x,2A,L4,2A,I4)') 'KEY_PKYL ',':', ilkey,':', 
+     &comment(1:16), status
+
+      call ftgkyj(iunit, 'KEY_PKYJ', ijkey, comment, status)
+      write(*,'(1x,2A,I4,2A,I4)') 'KEY_PKYJ ',':',ijkey,':', 
+     &  comment(1:16), status
+
+      call ftgkye(iunit, 'KEY_PKYJ', iekey, comment, status)
+      write(*,'(1x,2A,f12.5,2A,I4)') 'KEY_PKYE ',':',iekey,':',
+     & comment(1:16), status
+
+      call ftgkyd(iunit, 'KEY_PKYJ', idkey, comment, status)
+      write(*,'(1x,2A,F12.5,2A,I4)') 'KEY_PKYD ',':',idkey,':', 
+     & comment(1:16), status
+
+      if (ijkey .ne. 11 .or. iekey .ne. 11. .or. idkey .ne. 11.)
+     &   write(*,'(1x,A,I4,2F5.1)') 'ERROR in ftgky(jed): ',
+     & ijkey, iekey, idkey
+
+      iskey= ' '
+      call ftgkys(iunit, 'key_pkys', iskey, comment, status)
+      write(*,'(1x,5A,I4)') 'KEY_PKYS ',':', iskey,':', comment(1:16),
+     &  status
+
+      ilkey = .false.
+      call ftgkyl(iunit, 'key_pkyl', ilkey, comment, status)
+      write(*,'(1x,2A,L4,2A,I4)') 'KEY_PKYL ',':', ilkey,':',
+     &  comment(1:16), status
+
+      ijkey = 0
+      call ftgkyj(iunit, 'KEY_PKYJ', ijkey, comment, status)
+      write(*,'(1x,2A,I4,2A,I4)') 'KEY_PKYJ ',':',ijkey,':', 
+     & comment(1:16), status
+
+      iekey = 0
+      call ftgkye(iunit, 'KEY_PKYE', iekey, comment, status)
+      write(*,'(1x,2A,f12.5,2A,I4)') 'KEY_PKYE ',':',iekey,':', 
+     & comment(1:16), status
+
+      idkey = 0
+      call ftgkyd(iunit, 'KEY_PKYD', idkey, comment, status)
+      write(*,'(1x,2A,F12.5,2A,I4)') 'KEY_PKYD ',':',idkey,':',
+     & comment(1:16), status
+
+      iekey = 0
+      call ftgkye(iunit, 'KEY_PKYF', iekey, comment, status)
+      write(*,'(1x,2A,f12.5,2A,I4)') 'KEY_PKYF ',':',iekey,':',
+     & comment(1:16), status
+
+      iekey = 0
+      call ftgkye(iunit, 'KEY_PKYE', iekey, comment, status)
+      write(*,'(1x,2A,f12.5,2A,I4)') 'KEY_PKYE ',':',iekey,':', 
+     & comment(1:16), status
+
+      idkey = 0
+      call ftgkyd(iunit, 'KEY_PKYG', idkey, comment, status)
+      write(*,'(1x,2A,f16.12,2A,I4)') 'KEY_PKYG ',':',idkey,':',
+     & comment(1:16), status
+
+      idkey = 0
+      call ftgkyd(iunit, 'KEY_PKYD', idkey, comment, status)
+      write(*,'(1x,2A,f16.12,2A,I4)') 'KEY_PKYD ',':',idkey,':', 
+     & comment(1:16), status
+
+      call ftgkyt(iunit, 'KEY_PKYT', ijkey, idkey, comment, status)
+      write(*,'(1x,2A,i10,A,f16.14,A,I4)') 'KEY_PKYT  ',':',
+     & ijkey,':', idkey, comment(1:16), status
+
+      call ftpunt(iunit, 'KEY_PKYJ', 'km/s/Mpc', status)
+      ijkey = 0
+      call ftgkyj(iunit, 'KEY_PKYJ', ijkey, comment, status)
+      write(*,'(1x,2A,I4,2A,I4)') 'KEY_PKYJ ',':',ijkey,':', 
+     & comment(1:38), status
+      call ftgunt(iunit,'KEY_PKYJ',kunit,status)
+      write(*,'(1x,2A)') 'keyword unit=', kunit
+
+      call ftpunt(iunit, 'KEY_PKYJ', ' ', status)
+      ijkey = 0
+      call ftgkyj(iunit, 'KEY_PKYJ', ijkey, comment, status)
+      write(*,'(1x,2A,I4,2A,I4)') 'KEY_PKYJ ',':',ijkey,':', 
+     & comment(1:38), status
+      call ftgunt(iunit,'KEY_PKYJ',kunit,status)
+      write(*,'(1x,2A)') 'keyword unit=', kunit
+
+      call ftpunt(iunit, 'KEY_PKYJ', 'feet/second/second', status)
+      ijkey = 0
+      call ftgkyj(iunit, 'KEY_PKYJ', ijkey, comment, status)
+      write(*,'(1x,2A,I4,2A,I4)') 'KEY_PKYJ ',':',ijkey,':', 
+     & comment(1:38), status
+      call ftgunt(iunit,'KEY_PKYJ',kunit,status)
+      write(*,'(1x,2A)') 'keyword unit=', kunit
+
+      call ftgkys(iunit, 'key_pkls', lstr, comment, status)
+      write(*,'(1x,2A)') 'KEY_PKLS long string value = ', lstr(1:50)
+      write(*,'(1x,A)')lstr(51:120)
+
+C get size and position in header 
+      call ftghps(iunit, existkeys, keynum, status)
+      write(*,'(1x,A,I4,A,I4)') 'header contains ', existkeys,
+     & ' keywords; located at keyword ', keynum
+
+C        ############################
+C        #  read array keywords     #
+C        ############################
+      
+      call ftgkns(iunit, 'ky_pkns', 1, 3, inskey, nfound, status)
+      write(*,'(1x,4A)') 'ftgkns: ', inskey(1)(1:14), inskey(2)(1:14),
+     &  inskey(3)(1:14)
+      if (nfound .ne. 3 .or. status .gt. 0)
+     &   write(*,'(1x,A,2I4)') ' ERROR in ftgkns ', nfound, status
+
+      call ftgknl(iunit, 'ky_pknl', 1, 3, inlkey, nfound, status)
+      write(*,'(1x,A,3L4)') 'ftgknl: ', inlkey(1), inlkey(2), inlkey(3)
+      if (nfound .ne. 3 .or. status .gt. 0)
+     &   write(*,'(1x,A,2I4)') ' ERROR in ftgknl ', nfound, status
+
+      call ftgknj(iunit, 'ky_pknj', 1, 3, injkey, nfound, status)
+      write(*,'(1x,A,3I4)') 'ftgknj: ', injkey(1), injkey(2), injkey(3)
+      if (nfound .ne. 3 .or. status .gt. 0)
+     &   write(*,'(1x,A,2I4)') ' ERROR in ftgknj ', nfound, status
+
+      call ftgkne(iunit, 'ky_pkne', 1, 3, inekey, nfound, status)
+      write(*,'(1x,A,3F10.5)') 'ftgkne: ',inekey(1),inekey(2),inekey(3)
+      if (nfound .ne. 3 .or. status .gt. 0)
+     &   write(*,'(1x,A,2I4)') ' ERROR in ftgkne ', nfound, status
+
+      call ftgknd(iunit, 'ky_pknd', 1, 3, indkey, nfound, status)
+      write(*,'(1x,A,3F10.5)') 'ftgknd: ',indkey(1),indkey(2),indkey(3)
+      if (nfound .ne. 3 .or. status .gt. 0)
+     &   write(*,'(1x,A,2I4)') ' ERROR in ftgknd ', nfound, status
+
+      write(*,'(1x,A)')' '
+      write(*,'(1x,A)')
+     & 'Before deleting the HISTORY and DATE keywords...'
+      do ii = 29, 32     
+          call ftgrec(iunit, ii, card, status)
+          write(*,'(1x,A)') card(1:8)
+      end do
+
+C don't print date value, so that 
+C the output will always be the same 
+      
+
+C        ############################
+C        #  delete keywords         #
+C        ############################
+      
+
+      call ftdrec(iunit, 30, status)
+      call ftdkey(iunit, 'DATE', status)
+
+      write(*,'(1x,A)')' '
+      write(*,'(1x,A)') 'After deleting the keywords... '
+      do ii = 29, 30            
+          call ftgrec(iunit, ii, card, status)
+          write(*,'(1x,A)') card
+      end do      
+
+      if (status .gt. 0)
+     &   write(*,'(1x,A)') ' ERROR deleting keywords '
+      
+
+C        ############################
+C        #  insert keywords         #
+C        ############################
+      
+      call ftirec(iunit,26,
+     & 'KY_IREC = ''This keyword inserted by fxirec''',
+     &   status)
+      call ftikys(iunit, 'KY_IKYS', 'insert_value_string',
+     &  'ikys comment', status)
+      call ftikyj(iunit, 'KY_IKYJ', 49, 'ikyj comment', status)
+      call ftikyl(iunit, 'KY_IKYL', .true., 'ikyl comment', status)
+      call ftikye(iunit, 'KY_IKYE',12.3456,4,'ikye comment',status)
+      odkey = 12.345678901234567D+00
+      call ftikyd(iunit, 'KY_IKYD', odkey, 14,
+     &  'ikyd comment', status)
+      call ftikyf(iunit, 'KY_IKYF', 12.3456, 4, 'ikyf comment',
+     & status)
+      call ftikyg(iunit, 'KY_IKYG', odkey, 13,
+     & 'ikyg comment', status)
+
+      write(*,'(1x,A)')' '
+      write(*,'(1x,A)') 'After inserting the keywords... '
+      do ii = 25, 34
+          call ftgrec(iunit, ii, card, status)
+          write(*,'(1x,A)') card
+      end do
+
+      if (status .gt. 0)
+     &   write(*,'(1x,A)') ' ERROR inserting keywords '
+      
+
+C        ############################
+C        #  modify keywords         #
+C        ############################
+      
+      call ftmrec(iunit, 25,
+     & 'COMMENT   This keyword was modified by fxmrec', status)
+      call ftmcrd(iunit, 'KY_IREC', 
+     & 'KY_MREC = ''This keyword was modified by fxmcrd''', status)
+      call ftmnam(iunit, 'KY_IKYS', 'NEWIKYS', status)
+
+      call ftmcom(iunit,'KY_IKYJ','This is a modified comment',
+     & status)
+      call ftmkyj(iunit, 'KY_IKYJ', 50, '&', status)
+      call ftmkyl(iunit, 'KY_IKYL', .false., '&', status)
+      call ftmkys(iunit, 'NEWIKYS', 'modified_string', '&', status)
+      call ftmkye(iunit, 'KY_IKYE', -12.3456, 4, '&', status)
+      odkey = -12.345678901234567D+00
+
+      call ftmkyd(iunit, 'KY_IKYD', odkey, 14, 
+     & 'modified comment', status)
+      call ftmkyf(iunit, 'KY_IKYF', -12.3456, 4, '&', status)
+      call ftmkyg(iunit,'KY_IKYG', odkey,13,'&',status)
+
+      write(*,'(1x,A)')' '
+      write(*,'(1x,A)') 'After modifying the keywords... '
+      do ii = 25, 34
+          call ftgrec(iunit, ii, card, status)
+          write(*,'(1x,A)') card
+      end do
+      
+      if (status .gt. 0)then
+         write(*,'(1x,A)') ' ERROR modifying keywords '
+         go to 999
+      end if
+      
+C        ############################
+C        #  update keywords         #
+C        ############################
+      
+      call ftucrd(iunit, 'KY_MREC', 
+     & 'KY_UCRD = ''This keyword was updated by fxucrd''',
+     &         status)
+
+      call ftukyj(iunit, 'KY_IKYJ', 51, '&', status)
+      call ftukyl(iunit, 'KY_IKYL', .true., '&', status)
+      call ftukys(iunit, 'NEWIKYS', 'updated_string', '&', status)
+      call ftukye(iunit, 'KY_IKYE', -13.3456, 4, '&', status)
+      odkey = -13.345678901234567D+00
+
+      call ftukyd(iunit, 'KY_IKYD',odkey , 14, 
+     & 'modified comment', status)
+      call ftukyf(iunit, 'KY_IKYF', -13.3456, 4, '&', status)
+      call ftukyg(iunit, 'KY_IKYG', odkey, 13, '&', status)
+
+      write(*,'(1x,A)')' '
+      write(*,'(1x,A)') 'After updating the keywords... '
+      do ii = 25, 34
+          call ftgrec(iunit, ii, card, status)
+          write(*,'(1x,A)') card
+      end do
+
+      if (status .gt. 0)then
+         write(*,'(1x,A)') ' ERROR modifying keywords '
+         go to 999
+      end if
+
+C     move to top of header and find keywords using wild cards 
+      call ftgrec(iunit, 0, card, status)
+
+      write(*,'(1x,A)')' '
+      write(*,'(1x,A)')
+     &  'Keywords found using wildcard search (should be 9)...'
+      nfound = -1
+91    nfound = nfound +1
+      call ftgnxk(iunit, inclist, 2, exclist, 2, card, status)
+      if (status .eq. 0)then
+          write(*,'(1x,A)') card
+          go to 91
+      end if
+
+      if (nfound .ne. 9)then
+          write(*,'(1x,A)')
+     &    'ERROR reading keywords using wildcards (ftgnxk)'
+         go to 999
+      end if
+      status = 0
+
+C        ############################
+C        #  create binary table     #
+C        ############################
+      
+      tform(1) = '15A'
+      tform(2) = '1L'
+      tform(3) = '16X'
+      tform(4) = '1B'
+      tform(5) = '1I'
+      tform(6) = '1J'
+      tform(7) = '1E'
+      tform(8) = '1D'
+      tform(9) = '1C'
+      tform(10)= '1M'
+
+      ttype(1) = 'Avalue'
+      ttype(2) = 'Lvalue'
+      ttype(3) = 'Xvalue'
+      ttype(4) = 'Bvalue'
+      ttype(5) = 'Ivalue'
+      ttype(6) = 'Jvalue'
+      ttype(7) = 'Evalue'
+      ttype(8) = 'Dvalue'
+      ttype(9) = 'Cvalue'
+      ttype(10)= 'Mvalue'
+
+      tunit(1) = ' '
+      tunit(2) = 'm**2'
+      tunit(3) = 'cm'
+      tunit(4) = 'erg/s'
+      tunit(5) = 'km/s'
+      tunit(6) = ' '
+      tunit(7) = ' '
+      tunit(8) = ' '
+      tunit(9) = ' '
+      tunit(10)= ' '
+
+      nrows = 21
+      tfields = 10
+      pcount = 0
+
+      call ftibin(iunit, nrows, tfields, ttype, tform, tunit, 
+     & binname, pcount, status)
+      write(*,'(1x,A)')' '
+      write(*,'(1x,A,I4)') 'ftibin status = ', status
+      call ftghdn(iunit, hdunum)
+      write(*,'(1x,A,I4)') 'HDU number = ', hdunum
+
+C get size and position in header, and reserve space for more keywords 
+      call ftghps(iunit, existkeys, keynum, status)
+      write(*,'(1x,A,I4,A,I4)') 'header contains ',existkeys,
+     & ' keywords located at keyword ', keynum
+
+      morekeys = 40
+      call fthdef(iunit, morekeys, status)
+      call ftghsp(iunit, existkeys, morekeys, status)
+      write(*,'(1x,A,I4,A,I4,A)') 'header contains ', existkeys,
+     &' keywords with room for ', morekeys,' more'
+
+C define null value for int cols 
+      call fttnul(iunit, 4, 99, status)   
+      call fttnul(iunit, 5, 99, status)
+      call fttnul(iunit, 6, 99, status)
+
+      call ftpkyj(iunit, 'TNULL4', 99, 'value for undefined pixels',
+     &  status)
+      call ftpkyj(iunit, 'TNULL5', 99, 'value for undefined pixels',
+     &  status)
+      call ftpkyj(iunit, 'TNULL6', 99, 'value for undefined pixels',
+     & status)
+
+      naxis = 3
+      naxes(1) = 1
+      naxes(2) = 2
+      naxes(3) = 8
+      call ftptdm(iunit, 3, naxis, naxes, status)
+
+      naxis = 0
+      naxes(1) = 0
+      naxes(2) = 0
+      naxes(3) = 0
+      call ftgtdm(iunit, 3, 3, naxis, naxes, status)
+      call ftgkys(iunit, 'TDIM3', iskey, comment, status)
+      write(*,'(1x,2A,4I4)') 'TDIM3 = ', iskey, naxis, naxes(1),
+     &      naxes(2), naxes(3)
+
+C force header to be scanned (not required) 
+      call ftrdef(iunit, status)  
+    
+C        ############################
+C        #  write data to columns   #
+C        ############################
+         
+C initialize arrays of values to write to table 
+      signval = -1
+      do ii = 1, 21
+          signval = signval * (-1)
+          boutarray(ii) = char(ii)
+          ioutarray(ii) = (ii) * signval
+          joutarray(ii) = (ii) * signval
+          koutarray(ii) = (ii) * signval
+          eoutarray(ii) = (ii) * signval
+          doutarray(ii) = (ii) * signval
+      end do
+
+      call ftpcls(iunit, 1, 1, 1, 3, onskey, status)  
+C write string values 
+      call ftpclu(iunit, 1, 4, 1, 1, status)  
+C write null value 
+
+      larray(1) = .false.
+      larray(2) =.true.
+      larray(3) = .false.
+      larray(4) = .false.
+      larray(5) =.true.
+      larray(6) =.true.
+      larray(7) = .false.
+      larray(8) = .false.
+      larray(9) = .false.
+      larray(10) =.true.
+      larray(11) =.true.
+      larray(12) = .true.
+      larray(13) = .false.
+      larray(14) = .false.
+      larray(15) =.false.
+      larray(16) =.false.
+      larray(17) = .true.
+      larray(18) = .true.
+      larray(19) = .true.
+      larray(20) = .true.
+      larray(21) =.false.
+      larray(22) =.false.
+      larray(23) =.false.
+      larray(24) =.false.
+      larray(25) =.false.
+      larray(26) = .true.
+      larray(27) = .true.
+      larray(28) = .true.
+      larray(29) = .true.
+      larray(30) = .true.
+      larray(31) =.false.
+      larray(32) =.false.
+      larray(33) =.false.
+      larray(34) =.false.
+      larray(35) =.false.
+      larray(36) =.false.
+
+C write bits
+      call ftpclx(iunit, 3, 1, 1, 36, larray, status) 
+
+C loop over cols 4 - 8 
+      do ii = 4, 8   
+          call ftpclb(iunit, ii, 1, 1, 2, boutarray, status)
+          if (status .eq. 412) status = 0
+
+          call ftpcli(iunit, ii, 3, 1, 2, ioutarray(3), status) 
+          if (status .eq. 412) status = 0
+
+          call ftpclj(iunit, ii, 5, 1, 2, koutarray(5), status) 
+          if (status .eq. 412) status = 0
+
+          call ftpcle(iunit, ii, 7, 1, 2, eoutarray(7), status)
+          if (status .eq. 412)status = 0
+
+          call ftpcld(iunit, ii, 9, 1, 2, doutarray(9), status)
+          if (status .eq. 412)status = 0
+
+C write null value 
+          call ftpclu(iunit, ii, 11, 1, 1, status)  
+      end do
+
+      call ftpclc(iunit,  9, 1, 1, 10, eoutarray, status)
+      call ftpclm(iunit, 10, 1, 1, 10, doutarray, status)
+
+C loop over cols 4 - 8 
+      do ii = 4, 8
+          bnul = char(13)
+          call ftpcnb(iunit, ii, 12, 1, 2, boutarray(12),bnul,status)
+          if (status .eq. 412) status = 0
+          inul=15
+          call ftpcni(iunit, ii, 14, 1, 2, ioutarray(14),inul,status) 
+          if (status .eq. 412) status = 0
+          call ftpcnj(iunit, ii, 16, 1, 2, koutarray(16), 17, status) 
+          if (status .eq. 412) status = 0
+          call ftpcne(iunit, ii, 18, 1, 2, eoutarray(18), 19.,status)
+          if (status .eq. 412) status = 0
+          dnul = 21.
+          call ftpcnd(iunit, ii, 20, 1, 2, doutarray(20),dnul,status)
+          if (status .eq. 412) status = 0
+      end do
+      
+C write logicals
+      call ftpcll(iunit, 2, 1, 1, 21, larray, status) 
+C write null value 
+      call ftpclu(iunit, 2, 11, 1, 1, status)  
+      write(*,'(1x,A,I4)') 'ftpcl_ status = ', status
+      if (status .gt. 0)go to 999
+      
+C        #########################################
+C        #  get information about the columns    #
+C        #########################################
+      
+      write(*,'(1x,A)')' '
+      write(*,'(1x,A)')
+     & 'Find the column numbers a returned status value'//
+     & ' of 237 is'
+      write(*,'(1x,A)') 
+     & 'expected and indicates that more than one column'//
+     & ' name matches'
+      write(*,'(1x,A)')'the input column name template.'//
+     & '  Status = 219 indicates that'
+      write(*,'(1x,A)') 'there was no matching column name.'
+
+      call ftgcno(iunit, 0, 'Xvalue', colnum, status)
+      write(*,'(1x,A,I4,A,I4)') 'Column Xvalue is number', colnum,
+     &' status =',status
+
+219   continue
+      if (status .ne. 219)then
+        call ftgcnn(iunit, 1, '*ue', colname, colnum, status)
+        write(*,'(1x,3A,I4,A,I4)') 'Column ',colname(1:6),' is number', 
+     &   colnum,' status = ',  status
+        go to 219
+      end if
+
+      status = 0
+
+      write(*,'(1x,A)')' '
+      write(*,'(1x,A)') 'Information about each column: '
+
+      do ii = 1, tfields
+        call ftgtcl(iunit, ii, typecode, repeat, width, status)
+        call ftgbcl(iunit,ii,ttype,tunit,cval,repeat,scale,
+     &        zero, jnulval, tdisp, status)
+
+        write(*,'(1x,A,3I4,5A,2F8.2,I12,A)')
+     &  tform(ii)(1:3), typecode, repeat, width,' ',
+     &  ttype(1)(1:6),' ',tunit(1)(1:6), cval, scale, zero, jnulval,
+     &  tdisp(1:8)
+      end do
+
+      write(*,'(1x,A)') ' '
+
+C        ###############################################
+C        #  insert ASCII table before the binary table #
+C        ###############################################
+
+      call ftmrhd(iunit, -1, hdutype, status)
+      if (status .gt. 0)goto 999
+
+      tform(1) = 'A15'
+      tform(2) = 'I10'
+      tform(3) = 'F14.6'
+      tform(4) = 'E12.5'
+      tform(5) = 'D21.14'
+
+      ttype(1) = 'Name'
+      ttype(2) = 'Ivalue'
+      ttype(3) = 'Fvalue'
+      ttype(4) = 'Evalue'
+      ttype(5) = 'Dvalue'
+
+      tunit(1) = ' '
+      tunit(2) = 'm**2'
+      tunit(3) = 'cm'
+      tunit(4) = 'erg/s'
+      tunit(5) = 'km/s'
+
+      rowlen = 76
+      nrows = 11
+      tfields = 5
+
+      call ftitab(iunit, rowlen, nrows, tfields, ttype, tbcol, 
+     & tform, tunit, tblname, status)
+      write(*,'(1x,A,I4)') 'ftitab status = ', status
+      call ftghdn(iunit, hdunum)
+      write(*,'(1x,A,I4)') 'HDU number = ', hdunum
+
+C define null value for int cols 
+      call ftsnul(iunit, 1, 'null1', status)   
+      call ftsnul(iunit, 2, 'null2', status)
+      call ftsnul(iunit, 3, 'null3', status)
+      call ftsnul(iunit, 4, 'null4', status)
+      call ftsnul(iunit, 5, 'null5', status)
+ 
+      call ftpkys(iunit, 'TNULL1', 'null1',
+     & 'value for undefined pixels', status)
+      call ftpkys(iunit, 'TNULL2', 'null2',
+     & 'value for undefined pixels', status)
+      call ftpkys(iunit, 'TNULL3', 'null3',
+     & 'value for undefined pixels', status)
+      call ftpkys(iunit, 'TNULL4', 'null4',
+     & 'value for undefined pixels', status)
+      call ftpkys(iunit, 'TNULL5', 'null5',
+     & 'value for undefined pixels', status)
+
+      if (status .gt. 0) goto 999
+      
+C        ############################
+C        #  write data to columns   #
+C        ############################
+           
+C initialize arrays of values to write to table 
+      do ii = 1,21     
+          boutarray(ii) = char(ii)
+          ioutarray(ii) = ii
+          joutarray(ii) = ii
+          eoutarray(ii) = ii
+          doutarray(ii) = ii
+      end do      
+
+C write string values 
+      call ftpcls(iunit, 1, 1, 1, 3, onskey, status)  
+C write null value 
+      call ftpclu(iunit, 1, 4, 1, 1, status)  
+
+      do ii = 2,5 
+C loop over cols 2 - 5       
+          call ftpclb(iunit, ii, 1, 1, 2, boutarray, status)  
+C char array 
+          if (status .eq. 412) status = 0
+             
+          call ftpcli(iunit, ii, 3, 1, 2, ioutarray(3), status)  
+C short array 
+          if (status .eq. 412) status = 0
+             
+          call ftpclj(iunit, ii, 5, 1, 2, joutarray(5), status)  
+C long array 
+          if (status .eq. 412)status = 0
+              
+          call ftpcle(iunit, ii, 7, 1, 2, eoutarray(7), status)  
+C float array 
+          if (status .eq. 412) status = 0
+             
+          call ftpcld(iunit, ii, 9, 1, 2, doutarray(9), status)  
+C double array 
+          if (status .eq. 412) status = 0
+
+          call ftpclu(iunit, ii, 11, 1, 1, status)  
+C write null value 
+      end do
+      write(*,'(1x,A,I4)') 'ftpcl_ status = ', status
+      write(*,'(1x,A)')' '
+
+C        ################################
+C        #  read data from ASCII table  #
+C        ################################
+      
+      call ftghtb(iunit, 99, rowlen, nrows, tfields, ttype, tbcol, 
+     &       tform, tunit, tblname, status)
+
+      write(*,'(1x,A,3I3,2A)')
+     & 'ASCII table: rowlen, nrows, tfields, extname:',
+     & rowlen, nrows, tfields,' ',tblname
+
+      do ii = 1,tfields
+        write(*,'(1x,A,I4,3A)') 
+     & ttype(ii)(1:7), tbcol(ii),' ',tform(ii)(1:7), tunit(ii)(1:7)
+      end do
+
+      nrows = 11
+      call ftgcvs(iunit, 1, 1, 1, nrows, 'UNDEFINED', inskey,
+     &   anynull, status)
+      bnul = char(99)
+      call ftgcvb(iunit, 2, 1, 1, nrows, bnul, binarray,
+     & anynull, status)
+      inul = 99
+      call ftgcvi(iunit, 2, 1, 1, nrows, inul, iinarray,
+     & anynull, status)
+      call ftgcvj(iunit, 3, 1, 1, nrows, 99, jinarray,
+     & anynull, status)
+      call ftgcve(iunit, 4, 1, 1, nrows, 99., einarray,
+     & anynull, status)
+      dnul = 99.
+      call ftgcvd(iunit, 5, 1, 1, nrows, dnul, dinarray,
+     & anynull, status)
+
+      write(*,'(1x,A)')' '
+      write(*,'(1x,A)') 'Data values read from ASCII table: '
+      do ii = 1, nrows
+        jj = ichar(binarray(ii))
+        write(*,1011) inskey(ii), jj,
+     &   iinarray(ii), jinarray(ii), einarray(ii), dinarray(ii)
+1011    format(1x,a15,3i3,1x,2f3.0)
+      end do
+
+      call ftgtbs(iunit, 1, 20, 78, uchars, status)
+      write(*,'(1x,A)')' '
+      write(*,'(1x,A)') uchars
+      call ftptbs(iunit, 1, 20, 78, uchars, status)
+      
+C        #########################################
+C        #  get information about the columns    #
+C        #########################################
+
+      call ftgcno(iunit, 0, 'name', colnum, status)
+      write(*,'(1x,A)')' '
+      write(*,'(1x,A,I4,A,I4)')
+     &  'Column name is number',colnum,' status = ', status
+
+2190  continue
+      if (status .ne. 219)then
+        if (status .gt. 0 .and. status .ne. 237)go to 999
+
+        call ftgcnn(iunit, 1, '*ue', colname, colnum, status)
+        write(*,'(1x,3A,I4,A,I4)')
+     & 'Column ',colname(1:6),' is number',colnum,' status = ',status
+        go to 2190
+      end if
+   
+      status = 0
+
+      do ii = 1, tfields       
+        call ftgtcl(iunit, ii, typecode, repeat, width, status)
+        call ftgacl(iunit, ii, ttype, tbcol,tunit,tform, 
+     &   scale,zero, nulstr, tdisp, status)
+
+        write(*,'(1x,A,3I4,2A,I4,2A,2F10.2,3A)')
+     & tform(ii)(1:7), typecode, repeat, width,' ',
+     &  ttype(1)(1:6), tbcol(1), ' ',tunit(1)(1:5),
+     &  scale, zero, ' ', nulstr(1:6), tdisp(1:2)
+
+      end do
+
+      write(*,'(1x,A)') ' '
+
+C        ###############################################
+C        #  test the insert/delete row/column routines #
+C        ###############################################
+      
+      call ftirow(iunit, 2, 3, status)
+      if (status .gt. 0) goto 999
+
+      nrows = 14
+      call ftgcvs(iunit, 1, 1, 1, nrows, 'UNDEFINED',
+     & inskey,   anynull, status)
+      call ftgcvb(iunit, 2, 1, 1, nrows, bnul, binarray,
+     & anynull, status)
+      call ftgcvi(iunit, 2, 1, 1, nrows, inul, iinarray,
+     & anynull, status)
+      call ftgcvj(iunit, 3, 1, 1, nrows, 99, jinarray,
+     & anynull, status)
+      call ftgcve(iunit, 4, 1, 1, nrows, 99., einarray,
+     & anynull, status)
+      call ftgcvd(iunit, 5, 1, 1, nrows, dnul, dinarray,
+     & anynull, status)
+
+      write(*,'(1x,A)')' '
+      write(*,'(1x,A)')'Data values after inserting 3 rows after row 2:'
+      do ii = 1, nrows     
+        jj = ichar(binarray(ii))
+        write(*,1011) inskey(ii), jj,
+     &   iinarray(ii), jinarray(ii), einarray(ii), dinarray(ii)
+      end do
+
+      call ftdrow(iunit, 10, 2, status)
+
+      nrows = 12
+      call ftgcvs(iunit, 1, 1, 1, nrows, 'UNDEFINED', inskey,  
+     & anynull, status)
+      call ftgcvb(iunit, 2, 1, 1, nrows, bnul, binarray, anynull,
+     & status)
+      call ftgcvi(iunit, 2, 1, 1, nrows, inul, iinarray, anynull,
+     & status)
+      call ftgcvj(iunit, 3, 1, 1, nrows, 99, jinarray, anynull,
+     & status)
+      call ftgcve(iunit, 4, 1, 1, nrows, 99., einarray, anynull,
+     & status)
+      call ftgcvd(iunit, 5, 1, 1, nrows, dnul, dinarray, anynull,
+     & status)
+
+      write(*,'(1x,A)')' '
+      write(*,'(1x,A)') 'Data values after deleting 2 rows at row 10: '
+      do ii = 1, nrows    
+        jj = ichar(binarray(ii))
+        write(*,1011)  inskey(ii), jj,
+     &       iinarray(ii), jinarray(ii), einarray(ii), dinarray(ii)
+      end do
+      call ftdcol(iunit, 3, status)
+
+      call ftgcvs(iunit, 1, 1, 1, nrows, 'UNDEFINED', inskey, 
+     &  anynull, status)
+      call ftgcvb(iunit, 2, 1, 1, nrows, bnul, binarray, anynull,
+     & status)
+      call ftgcvi(iunit, 2, 1, 1, nrows, inul, iinarray, anynull,
+     & status)
+      call ftgcve(iunit, 3, 1, 1, nrows, 99., einarray, anynull,
+     & status)
+      call ftgcvd(iunit, 4, 1, 1, nrows, dnul, dinarray, anynull,
+     & status)
+
+      write(*,'(1x,A)')' '
+      write(*,'(1x,A)') 'Data values after deleting column 3: '
+      do ii = 1,nrows
+        jj = ichar(binarray(ii))
+        write(*,1012) inskey(ii), jj,
+     &       iinarray(ii), einarray(ii), dinarray(ii)
+1012    format(1x,a15,2i3,1x,2f3.0)
+
+      end do
+
+      call fticol(iunit, 5, 'INSERT_COL', 'F14.6', status)
+
+      call ftgcvs(iunit, 1, 1, 1, nrows, 'UNDEFINED', inskey,
+     &   anynull, status)
+      call ftgcvb(iunit, 2, 1, 1, nrows, bnul, binarray, anynull,
+     & status)
+      call ftgcvi(iunit, 2, 1, 1, nrows, inul, iinarray, anynull,
+     & status)
+      call ftgcve(iunit, 3, 1, 1, nrows, 99., einarray, anynull,
+     & status)
+      call ftgcvd(iunit, 4, 1, 1, nrows, dnul, dinarray, anynull,
+     & status)
+      call ftgcvj(iunit, 5, 1, 1, nrows, 99, jinarray, anynull,
+     & status)
+
+      write(*,'(1x,A)')' '
+      write(*,'(1x,A)') ' Data values after inserting column 5: '
+      do ii = 1, nrows
+        jj = ichar(binarray(ii))
+        write(*,1013) inskey(ii), jj,
+     &       iinarray(ii), einarray(ii), dinarray(ii) , jinarray(ii)
+1013    format(1x,a15,2i3,1x,2f3.0,i2)
+
+      end do
+
+C        ################################
+C        #  read data from binary table #
+C        ################################
+      
+
+      call ftmrhd(iunit, 1, hdutype, status)
+      if (status .gt. 0)go to 999
+      call ftghdn(iunit, hdunum)
+      write(*,'(1x,A,I4)') 'HDU number = ', hdunum
+
+      call ftghsp(iunit, existkeys, morekeys, status)
+      write(*,'(1x,A)')' '
+      write(*,'(1x,A)')'Moved to binary table'
+      write(*,'(1x,A,I4,A,I4,A)') 'header contains ',existkeys,
+     & ' keywords with room for ',morekeys,' more '
+
+      call ftghbn(iunit, 99, nrows, tfields, ttype, 
+     &        tform, tunit, binname, pcount, status)
+
+      write(*,'(1x,A)')' '
+      write(*,'(1x,A,2I4,A,I4)') 
+     & 'Binary table: nrows, tfields, extname, pcount:',
+     &        nrows, tfields, binname, pcount
+
+      do ii = 1,tfields
+        write(*,'(1x,3A)') ttype(ii), tform(ii), tunit(ii)
+      end do
+
+      do ii = 1, 40
+          larray(ii) = .false.
+      end do
+
+      write(*,'(1x,A)')' '
+      write(*,'(1x,A)') 'Data values read from binary table: '
+      write(*,'(1x,A)') ' Bit column (X) data values:   '
+
+      call ftgcx(iunit, 3, 1, 1, 36, larray, status)
+      write(*,1014) (larray(ii), ii = 1,40)
+1014  format(1x,8l1,' ',8l1,' ',8l1,' ',8l1,' ',8l1)
+
+      nrows = 21
+      do ii = 1, nrows
+        larray(ii) = .false.
+        xinarray(ii) = ' '
+        binarray(ii) = ' '
+        iinarray(ii) = 0 
+        kinarray(ii) = 0
+        einarray(ii) = 0. 
+        dinarray(ii) = 0.
+        cinarray(ii * 2 -1) = 0. 
+        minarray(ii * 2 -1) = 0.
+        cinarray(ii * 2 ) = 0. 
+        minarray(ii * 2 ) = 0.
+      end do      
+
+      write(*,'(1x,A)') '  '
+      call ftgcvs(iunit, 1, 4, 1, 1, ' ',  inskey,   anynull,status)
+      if (ichar(inskey(1)(1:1)) .eq. 0)inskey(1)=' '
+      write(*,'(1x,2A)') 'null string column value (should be blank):',
+     &        inskey(1)
+
+      call ftgcvs(iunit, 1, 1, 1, nrows, 'NOT DEFINED',  inskey,
+     &   anynull, status)
+      call ftgcl( iunit, 2, 1, 1, nrows, larray, status)
+      bnul = char(98)
+      call ftgcvb(iunit, 3, 1, 1,nrows,bnul, xinarray,anynull,status)
+      call ftgcvb(iunit, 4, 1, 1,nrows,bnul, binarray,anynull,status)
+      inul = 98
+      call ftgcvi(iunit, 5, 1, 1,nrows,inul, iinarray,anynull,status)
+      call ftgcvj(iunit, 6, 1, 1, nrows, 98, kinarray,anynull,status)
+      call ftgcve(iunit, 7, 1, 1, nrows, 98.,einarray,anynull,status)
+      dnul = 98.
+      call ftgcvd(iunit, 8, 1, 1, nrows,dnul,dinarray,anynull,status)
+      call ftgcvc(iunit, 9, 1, 1, nrows, 98.,cinarray,anynull,status)
+      call ftgcvm(iunit,10, 1, 1, nrows,dnul,minarray,anynull,status)
+
+      write(*,'(1x,A)')' '
+      write(*,'(1x,A)') 'Read columns with ftgcv_: '
+      do ii = 1,nrows
+        jj = ichar(xinarray(ii))
+        jjj = ichar(binarray(ii))
+      write(*,1201)inskey(ii),larray(ii),jj,jjj,iinarray(ii),
+     & kinarray(ii), einarray(ii), dinarray(ii), cinarray(ii * 2 -1), 
+     &cinarray(ii * 2 ), minarray(ii * 2 -1), minarray(ii * 2 )
+      end do
+1201  format(1x,a14,l4,4i4,6f5.0)
+
+      do ii = 1, nrows
+        larray(ii) = .false.
+        xinarray(ii) = ' '
+        binarray(ii) = ' '
+        iinarray(ii) = 0 
+        kinarray(ii) = 0
+        einarray(ii) = 0. 
+        dinarray(ii) = 0.
+        cinarray(ii * 2 -1) = 0. 
+        minarray(ii * 2 -1) = 0.
+        cinarray(ii * 2 ) = 0. 
+        minarray(ii * 2 ) = 0.
+      end do      
+
+      call ftgcfs(iunit, 1, 1, 1, nrows, inskey,   larray2, anynull,
+     & status)
+C     put blanks in strings if they are undefined.  (contain nulls)
+      do ii = 1, nrows
+         if (larray2(ii))inskey(ii) = ' '
+      end do
+
+      call ftgcfl(iunit, 2, 1, 1, nrows, larray,   larray2, anynull,
+     & status)
+      call ftgcfb(iunit, 3, 1, 1, nrows, xinarray, larray2, anynull,
+     & status)
+      call ftgcfb(iunit, 4, 1, 1, nrows, binarray, larray2, anynull,
+     & status)
+      call ftgcfi(iunit, 5, 1, 1, nrows, iinarray, larray2, anynull,
+     & status)
+      call ftgcfj(iunit, 6, 1, 1, nrows, kinarray, larray2, anynull,
+     & status)
+      call ftgcfe(iunit, 7, 1, 1, nrows, einarray, larray2, anynull,
+     & status)
+      call ftgcfd(iunit, 8, 1, 1, nrows, dinarray, larray2, anynull,
+     & status)
+      call ftgcfc(iunit, 9, 1, 1, nrows, cinarray, larray2, anynull,
+     & status)
+      call ftgcfm(iunit, 10,1, 1, nrows, minarray, larray2, anynull,
+     & status)
+
+      write(*,'(1x,A)')' '
+      write(*,'(1x,A)') ' Read columns with ftgcf_: '
+      do ii = 1, 10
+        jj = ichar(xinarray(ii))
+        jjj = ichar(binarray(ii))
+      write(*,1201)
+     & inskey(ii),larray(ii),jj,jjj,iinarray(ii),
+     & kinarray(ii), einarray(ii), dinarray(ii), cinarray(ii * 2 -1), 
+     & cinarray(ii * 2 ), minarray(ii * 2 -1), minarray(ii * 2)
+      end do
+
+      do ii = 11, 21
+C don't try to print the NaN values 
+        jj = ichar(xinarray(ii))
+        jjj = ichar(binarray(ii))
+        write(*,1201) inskey(ii), larray(ii), jj,
+     &    jjj, iinarray(ii)
+      end do
+      
+      call ftprec(iunit,'key_prec= '// 
+     &'''This keyword was written by f_prec'' / comment here',
+     & status)
+
+C        ###############################################
+C        #  test the insert/delete row/column routines #
+C        ###############################################
+      
+      call ftirow(iunit, 2, 3, status)
+         if (status .gt. 0) go to 999
+
+      nrows = 14
+      call ftgcvs(iunit, 1, 1, 1, nrows, 'NOT DEFINED',  inskey,
+     & anynull, status)
+      call ftgcvb(iunit, 4, 1, 1, nrows,bnul,binarray,anynull,status)
+      call ftgcvi(iunit, 5, 1, 1, nrows,inul,iinarray,anynull,status)
+      call ftgcvj(iunit, 6, 1, 1, nrows, 98, jinarray,anynull,status)
+      call ftgcve(iunit, 7, 1, 1, nrows, 98.,einarray,anynull,status)
+      call ftgcvd(iunit, 8, 1, 1, nrows,dnul,dinarray,anynull,status)
+
+      write(*,'(1x,A)')' '
+      write(*,'(1x,A)')'Data values after inserting 3 rows after row 2:'
+      do ii = 1, nrows
+        jj = ichar(binarray(ii))
+        write(*,1202)  inskey(ii), jj,
+     &      iinarray(ii), jinarray(ii), einarray(ii), dinarray(ii)
+      end do      
+1202  format(1x,a14,3i4,2f5.0)
+
+      call ftdrow(iunit, 10, 2, status)
+          if (status .gt. 0)goto 999
+
+      nrows = 12
+      call ftgcvs(iunit, 1, 1, 1, nrows, 'NOT DEFINED',  inskey,
+     &   anynull, status)
+      call ftgcvb(iunit, 4, 1, 1, nrows,bnul,binarray,anynull,status)
+      call ftgcvi(iunit, 5, 1, 1, nrows,inul,iinarray,anynull,status)
+      call ftgcvj(iunit, 6, 1, 1, nrows, 98,jinarray,anynull,status)
+      call ftgcve(iunit, 7, 1, 1, nrows, 98.,einarray,anynull,status)
+      call ftgcvd(iunit, 8, 1, 1, nrows,dnul,dinarray,anynull,status)
+
+      write(*,'(1x,A)')' '
+      write(*,'(1x,A)') 'Data values after deleting 2 rows at row 10: '
+      do ii = 1, nrows
+        jj = ichar(binarray(ii))
+        write(*,1202) inskey(ii), jj,
+     &       iinarray(ii), jinarray(ii), einarray(ii), dinarray(ii)
+      end do
+
+      call ftdcol(iunit, 6, status)
+
+      call ftgcvs(iunit, 1, 1, 1, nrows, 'NOT DEFINED',  inskey,
+     &   anynull, status)
+      call ftgcvb(iunit, 4, 1, 1, nrows,bnul,binarray,anynull,status)
+      call ftgcvi(iunit, 5, 1, 1, nrows,inul,iinarray,anynull,status)
+      call ftgcve(iunit, 6, 1, 1, nrows, 98.,einarray,anynull,status)
+      call ftgcvd(iunit, 7, 1, 1, nrows,dnul,dinarray,anynull,status)
+
+      write(*,'(1x,A)')' '
+      write(*,'(1x,A)') 'Data values after deleting column 6: '
+      do ii = 1, nrows
+        jj = ichar(binarray(ii))      
+        write(*,1203) inskey(ii), jj,
+     &       iinarray(ii), einarray(ii), dinarray(ii)
+1203  format(1x,a14,2i4,2f5.0)
+
+      end do
+      call fticol(iunit, 8, 'INSERT_COL', '1E', status)
+
+      call ftgcvs(iunit, 1, 1, 1, nrows, 'NOT DEFINED',  inskey,
+     &   anynull, status)
+      call ftgcvb(iunit, 4, 1, 1, nrows,bnul,binarray,anynull,status)
+      call ftgcvi(iunit, 5, 1, 1, nrows,inul,iinarray,anynull,status)
+      call ftgcve(iunit, 6, 1, 1, nrows, 98.,einarray,anynull,status)
+      call ftgcvd(iunit, 7, 1, 1, nrows,dnul,dinarray,anynull,status)
+      call ftgcvj(iunit, 8, 1, 1, nrows, 98,jinarray,anynull,status)
+
+      write(*,'(1x,A)')' '
+      write(*,'(1x,A)') 'Data values after inserting column 8: '
+      do ii = 1, nrows
+        jj = ichar(binarray(ii))
+        write(*,1204) inskey(ii), jj,
+     &    iinarray(ii), einarray(ii), dinarray(ii) , jinarray(ii)
+1204  format(1x,a14,2i4,2f5.0,i3)
+      end do
+      call ftpclu(iunit, 8, 1, 1, 10, status)
+
+      call ftgcvs(iunit, 1, 1, 1, nrows, 'NOT DEFINED',  inskey,
+     &   anynull, status)
+      call ftgcvb(iunit, 4,1,1,nrows,bnul,binarray,anynull,status)
+      call ftgcvi(iunit, 5,1,1,nrows,inul,iinarray,anynull,status)
+      call ftgcve(iunit, 6,1,1,nrows,98., einarray,anynull,status)
+      call ftgcvd(iunit, 7,1,1,nrows,dnul, dinarray,anynull,status)
+      call ftgcvj(iunit, 8,1,1,nrows,98, jinarray,anynull, status)
+
+      write(*,'(1x,A)')' '
+      write(*,'(1x,A)') 
+     &  'Values after setting 1st 10 elements in column 8 = null: '
+      do ii = 1, nrows
+        jj = ichar(binarray(ii))
+        write(*,1204) inskey(ii), jj,
+     &      iinarray(ii), einarray(ii), dinarray(ii) , jinarray(ii)
+      end do      
+
+C        ####################################################
+C        #  insert binary table following the primary array #
+C        ####################################################
+   
+      call ftmahd(iunit,  1, hdutype, status)
+
+      tform(1) = '15A'
+      tform(2) = '1L'
+      tform(3) = '16X'
+      tform(4) = '1B'
+      tform(5) = '1I'
+      tform(6) = '1J'
+      tform(7) = '1E'
+      tform(8) = '1D'
+      tform(9) = '1C'
+      tform(10)= '1M'
+
+      ttype(1) = 'Avalue'
+      ttype(2) = 'Lvalue'
+      ttype(3) = 'Xvalue'
+      ttype(4) = 'Bvalue'
+      ttype(5) = 'Ivalue'
+      ttype(6) = 'Jvalue'
+      ttype(7) = 'Evalue'
+      ttype(8) = 'Dvalue'
+      ttype(9) = 'Cvalue'
+      ttype(10)= 'Mvalue'
+
+      tunit(1)= ' '
+      tunit(2)= 'm**2'
+      tunit(3)= 'cm'
+      tunit(4)= 'erg/s'
+      tunit(5)= 'km/s'
+      tunit(6)= ' '
+      tunit(7)= ' '
+      tunit(8)= ' '
+      tunit(9)= ' '
+      tunit(10)= ' '
+
+      nrows = 20
+      tfields = 10
+      pcount = 0
+
+      call ftibin(iunit, nrows, tfields, ttype, tform, tunit, 
+     & binname, pcount, status)
+      write(*,'(1x,A)')' '
+      write(*,'(1x,A,I4)') 'ftibin status = ', status
+      call ftghdn(iunit, hdunum)
+      write(*,'(1x,A,I4)') 'HDU number = ', hdunum
+
+      call ftpkyj(iunit, 'TNULL4', 77, 
+     & 'value for undefined pixels', status)
+      call ftpkyj(iunit, 'TNULL5', 77, 
+     & 'value for undefined pixels', status)
+      call ftpkyj(iunit, 'TNULL6', 77, 
+     & 'value for undefined pixels', status)
+
+      call ftpkyj(iunit, 'TSCAL4', 1000, 'scaling factor', status)
+      call ftpkyj(iunit, 'TSCAL5', 1, 'scaling factor', status)
+      call ftpkyj(iunit, 'TSCAL6', 100, 'scaling factor', status)
+
+      call ftpkyj(iunit, 'TZERO4', 0, 'scaling offset', status)
+      call ftpkyj(iunit, 'TZERO5', 32768, 'scaling offset', status)
+      call ftpkyj(iunit, 'TZERO6', 100, 'scaling offset', status)
+
+      call fttnul(iunit, 4, 77, status)   
+C define null value for int cols 
+      call fttnul(iunit, 5, 77, status)
+      call fttnul(iunit, 6, 77, status)
+      
+C set scaling 
+      scale=1000.
+      zero = 0.
+      call fttscl(iunit, 4, scale, zero, status)   
+      scale=1.
+      zero = 32768.
+      call fttscl(iunit, 5, scale, zero, status)
+      scale=100.
+      zero = 100.
+      call fttscl(iunit, 6, scale, zero, status)
+
+C  for some reason, it is still necessary to call ftrdef at this point
+      call ftrdef(iunit,status)
+
+C        ############################
+C        #  write data to columns   #
+C        ############################
+           
+C initialize arrays of values to write to table 
+ 
+      joutarray(1) = 0
+      joutarray(2) = 1000
+      joutarray(3) = 10000
+      joutarray(4) = 32768
+      joutarray(5) = 65535
+
+
+      do ii = 4,6
+      
+          call ftpclj(iunit, ii, 1, 1, 5, joutarray, status) 
+          if (status .eq. 412)then
+              write(*,'(1x,A,I4)') 'Overflow writing to column  ', ii
+              status = 0
+          end if
+
+          call ftpclu(iunit, ii, 6, 1, 1, status)  
+C write null value 
+      end do
+
+      do jj = 4,6  
+        call ftgcvj(iunit, jj, 1,1,6, -999,jinarray,anynull,status)
+        write(*,'(1x,6I6)') (jinarray(ii), ii=1,6)
+      end do
+
+      write(*,'(1x,A)') ' '
+      
+C turn off scaling, and read the unscaled values 
+      scale = 1.
+      zero = 0.
+      call fttscl(iunit, 4, scale, zero, status)   
+      call fttscl(iunit, 5, scale, zero, status)
+      call fttscl(iunit, 6, scale, zero, status)
+
+      do jj = 4,6
+        call ftgcvj(iunit, jj,1,1,6,-999,jinarray,anynull,status)       
+            write(*,'(1x,6I6)') (jinarray(ii), ii = 1,6)
+      end do
+
+      if (status .gt. 0)go to 999
+
+C        ######################################################
+C        #  insert image extension following the binary table #
+C        ######################################################
+      
+      bitpix = -32
+      naxis = 2
+      naxes(1) = 15
+      naxes(2) = 25
+      call ftiimg(iunit, bitpix, naxis, naxes, status)
+      write(*,'(1x,A)')' '
+      write(*,'(1x,A,I4)') 
+     & ' Create image extension: ftiimg status = ', status
+      call ftghdn(iunit, hdunum)
+      write(*,'(1x,A,I4)') 'HDU number = ', hdunum
+
+      do jj = 0,29
+        do ii = 0,18
+          imgarray(ii+1,jj+1) = (jj * 10) + ii
+        end do
+      end do
+
+      call ftp2di(iunit, 1, 19, naxes(1),naxes(2),imgarray,status)
+      write(*,'(1x,A)') ' '
+      write(*,'(1x,A,I4)')'Wrote whole 2D array: ftp2di status =',
+     &      status
+
+      do jj =1, 30
+        do ii = 1, 19
+          imgarray(ii,jj) = 0
+        end do        
+      end do
+      
+      call ftg2di(iunit,1,0,19,naxes(1),naxes(2),imgarray,anynull,
+     &       status)
+      write(*,'(1x,A)')' '
+      write(*,'(1x,A,I4)')'Read whole 2D array: ftg2di status =',status
+
+      do jj =1, 30
+        write (*,1301)(imgarray(ii,jj),ii=1,19)
+1301    format(1x,19I4)
+      end do
+
+        write(*,'(1x,A)') ' '
+      
+
+      do jj =1, 30
+        do ii = 1, 19
+          imgarray(ii,jj) = 0
+        end do        
+      end do
+      
+      do jj =0, 19
+        do ii = 0, 9
+          imgarray2(ii+1,jj+1) = (jj * (-10)) - ii
+        end do        
+      end do
+
+      fpixels(1) = 5
+      fpixels(2) = 5
+      lpixels(1) = 14
+      lpixels(2) = 14
+      call ftpssi(iunit, 1, naxis, naxes, fpixels, lpixels, 
+     &     imgarray2, status)
+      write(*,'(1x,A)')' '
+      write(*,'(1x,A,I4)')'Wrote subset 2D array: ftpssi status =',
+     & status
+
+      call ftg2di(iunit,1,0,19,naxes(1), naxes(2),imgarray,anynull,
+     &        status)
+      write(*,'(1x,A)')' '
+      write(*,'(1x,A,I4)')'Read whole 2D array: ftg2di status =',status
+
+      do jj =1, 30
+        write (*,1301)(imgarray(ii,jj),ii=1,19)
+      end do
+      write(*,'(1x,A)') ' '
+
+
+      fpixels(1) = 2
+      fpixels(2) = 5
+      lpixels(1) = 10
+      lpixels(2) = 8
+      inc(1) = 2
+      inc(2) = 3
+
+      do jj = 1,30    
+        do ii = 1, 19
+          imgarray(ii,jj) = 0
+        end do
+      end do
+      
+      call ftgsvi(iunit, 1, naxis, naxes, fpixels, lpixels, inc, 0,
+     &       imgarray, anynull, status)
+      write(*,'(1x,A)')' '
+      write(*,'(1x,A,I4)')
+     & 'Read subset of 2D array: ftgsvi status = ',status
+
+      write(*,'(1x,10I5)')(imgarray(ii,1),ii = 1,10)
+
+      
+C        ###########################################################
+C        #  insert another image extension                         #
+C        #  copy the image extension to primary array of tmp file. #
+C        #  then delete the tmp file, and the image extension      #
+C        ###########################################################
+      
+      bitpix = 16
+      naxis = 2
+      naxes(1) = 15
+      naxes(2) = 25
+      call ftiimg(iunit, bitpix, naxis, naxes, status)
+      write(*,'(1x,A)') ' '
+      write(*,'(1x,A,I4)')'Create image extension: ftiimg status =',
+     &   status
+      call ftrdef(iunit, status)
+      call ftghdn(iunit, hdunum)
+      write(*,'(1x,A,I4)') 'HDU number = ', hdunum
+
+
+      filename = 't1q2s3v4.tmp'
+      call ftinit(tmpunit, filename, 1, status)
+      write(*,'(1x,A,I4)')'Create temporary file: ftinit status = ',
+     & status
+
+      call ftcopy(iunit, tmpunit, 0, status)
+      write(*,'(1x,A)') 
+     &  'Copy image extension to primary array of tmp file.'
+      write(*,'(1x,A,I4)')'ftcopy status = ',status
+
+
+      call ftgrec(tmpunit, 1, card, status)
+      write(*,'(1x,A)')  card
+      call ftgrec(tmpunit, 2, card, status)
+      write(*,'(1x,A)')  card
+      call ftgrec(tmpunit, 3, card, status)
+      write(*,'(1x,A)')  card
+      call ftgrec(tmpunit, 4, card, status)
+      write(*,'(1x,A)')  card
+      call ftgrec(tmpunit, 5, card, status)
+      write(*,'(1x,A)')  card
+      call ftgrec(tmpunit, 6, card, status)
+      write(*,'(1x,A)')  card
+
+      call ftdelt(tmpunit, status)
+      write(*,'(1x,A,I4)')'Delete the tmp file: ftdelt status =',status
+      call ftdhdu(iunit, hdutype, status)
+      write(*,'(1x,A,2I4)')
+     &  'Delete the image extension hdutype, status =',
+     &         hdutype, status
+      call ftghdn(iunit, hdunum)
+      write(*,'(1x,A,I4)') 'HDU number = ', hdunum
+
+      
+C        ###########################################################
+C        #  append bintable extension with variable length columns #
+C        ###########################################################
+      
+      call ftcrhd(iunit, status)
+      write(*,'(1x,A,I4)') 'ftcrhd status = ', status
+
+      tform(1)= '1PA'
+      tform(2)= '1PL'
+      tform(3)= '1PB' 
+C Fortran FITSIO doesn't support  1PX 
+      tform(4)= '1PB'
+      tform(5)= '1PI'
+      tform(6)= '1PJ'
+      tform(7)= '1PE'
+      tform(8)= '1PD'
+      tform(9)= '1PC'
+      tform(10)= '1PM'
+
+      ttype(1)= 'Avalue'
+      ttype(2)= 'Lvalue'
+      ttype(3)= 'Xvalue'
+      ttype(4)= 'Bvalue'
+      ttype(5)= 'Ivalue'
+      ttype(6)= 'Jvalue'
+      ttype(7)= 'Evalue'
+      ttype(8)= 'Dvalue'
+      ttype(9)= 'Cvalue'
+      ttype(10)= 'Mvalue'
+
+      tunit(1)= ' '
+      tunit(2)= 'm**2'
+      tunit(3)= 'cm'
+      tunit(4)= 'erg/s'
+      tunit(5)= 'km/s'
+      tunit(6)= ' '
+      tunit(7)= ' '
+      tunit(8)= ' '
+      tunit(9)= ' '
+      tunit(10)= ' '
+
+      nrows = 20
+      tfields = 10
+      pcount = 0
+
+      call ftphbn(iunit, nrows, tfields, ttype, tform, 
+     & tunit, binname, pcount, status)
+      write(*,'(1x,A,I4)')'Variable length arrays: ftphbn status =',
+     & status
+      call ftpkyj(iunit, 'TNULL4', 88, 'value for undefined pixels',
+     & status)
+      call ftpkyj(iunit, 'TNULL5', 88, 'value for undefined pixels',
+     & status)
+      call ftpkyj(iunit, 'TNULL6', 88, 'value for undefined pixels',
+     & status)
+
+C        ############################
+C        #  write data to columns   #
+C        ############################
+            
+C initialize arrays of values to write to table 
+      iskey='abcdefghijklmnopqrst'
+
+      do ii = 1, 20
+      
+          boutarray(ii) = char(ii)
+          ioutarray(ii) = ii
+          joutarray(ii) = ii
+          eoutarray(ii) = ii
+          doutarray(ii) = ii
+      end do
+
+      larray(1) = .false.
+      larray(2) = .true.
+      larray(3) = .false.
+      larray(4) = .false.
+      larray(5) = .true.
+      larray(6) = .true.
+      larray(7) = .false.
+      larray(8) = .false.
+      larray(9) = .false.
+      larray(10) = .true.
+      larray(11) = .true.
+      larray(12) = .true.
+      larray(13) = .false.
+      larray(14) = .false.
+      larray(15) = .false.
+      larray(16) = .false.
+      larray(17) = .true.
+      larray(18) = .true.
+      larray(19) = .true.
+      larray(20) = .true.
+
+C      inskey(1) = iskey(1:1)
+      inskey(1) = ' '
+
+        call ftpcls(iunit, 1, 1, 1, 1, inskey, status)  
+C write string values 
+        call ftpcll(iunit, 2, 1, 1, 1, larray, status)  
+C write logicals 
+        call ftpclx(iunit, 3, 1, 1, 1, larray, status)  
+C write bits 
+        call ftpclb(iunit, 4, 1, 1, 1, boutarray, status)
+        call ftpcli(iunit, 5, 1, 1, 1, ioutarray, status) 
+        call ftpclj(iunit, 6, 1, 1, 1, joutarray, status) 
+        call ftpcle(iunit, 7, 1, 1, 1, eoutarray, status)
+        call ftpcld(iunit, 8, 1, 1, 1, doutarray, status)
+
+      do ii = 2, 20   
+C loop over rows 1 - 20 
+      
+        inskey(1) =  iskey(1:ii)
+        call ftpcls(iunit, 1, ii, 1, ii, inskey, status)  
+C write string values 
+
+        call ftpcll(iunit, 2, ii, 1, ii, larray, status)  
+C write logicals 
+        call ftpclu(iunit, 2, ii, ii-1, 1, status)
+
+        call ftpclx(iunit, 3, ii, 1, ii, larray, status)  
+C write bits 
+
+        call ftpclb(iunit, 4, ii, 1, ii, boutarray, status)
+        call ftpclu(iunit, 4, ii, ii-1, 1, status)
+
+        call ftpcli(iunit, 5, ii, 1, ii, ioutarray, status) 
+        call ftpclu(iunit, 5, ii, ii-1, 1, status)
+
+        call ftpclj(iunit, 6, ii, 1, ii, joutarray, status) 
+        call ftpclu(iunit, 6, ii, ii-1, 1, status)
+
+        call ftpcle(iunit, 7, ii, 1, ii, eoutarray, status)
+        call ftpclu(iunit, 7, ii, ii-1, 1, status)
+
+        call ftpcld(iunit, 8, ii, 1, ii, doutarray, status)
+        call ftpclu(iunit, 8, ii, ii-1, 1, status)
+      end do
+
+C     it is no longer necessary to update the PCOUNT keyword;
+C     FITSIO now does this automatically when the HDU is closed.
+C     call ftmkyj(iunit,'PCOUNT',4446, '&',status)
+      write(*,'(1x,A,I4)') 'ftpcl_ status = ', status
+
+C        #################################
+C        #  close then reopen this HDU   #
+C        #################################
+
+       call ftmrhd(iunit, -1, hdutype, status)
+       call ftmrhd(iunit,  1, hdutype, status)
+
+C        #############################
+C        #  read data from columns   #
+C        #############################
+      
+
+      call ftgkyj(iunit, 'PCOUNT', pcount, comm, status)
+      write(*,'(1x,A,I4)') 'PCOUNT = ', pcount
+      
+C initialize the variables to be read 
+      inskey(1) =' '
+      iskey = ' '
+
+      do jj = 1, ii
+          larray(jj) = .false.
+          boutarray(jj) = char(0)
+          ioutarray(jj) = 0
+          joutarray(jj) = 0
+          eoutarray(jj) = 0
+          doutarray(jj) = 0
+      end do      
+
+      call ftghdn(iunit, hdunum)
+      write(*,'(1x,A,I4)') 'HDU number = ', hdunum
+
+      do ii = 1, 20   
+C loop over rows 1 - 20 
+      
+        do jj = 1, ii
+          larray(jj) = .false.
+          boutarray(jj) = char(0)
+          ioutarray(jj) = 0
+          joutarray(jj) = 0
+          eoutarray(jj) = 0
+          doutarray(jj) = 0
+        end do      
+
+        call ftgcvs(iunit, 1, ii, 1,1,iskey,inskey,anynull,status)
+        write(*,'(1x,2A,I4)') 'A  ', inskey(1), status
+
+        call ftgcl( iunit, 2, ii, 1, ii, larray, status) 
+        write(*,1400)'L',status,(larray(jj),jj=1,ii)
+1400    format(1x,a1,i3,20l3)
+1401    format(1x,a1,21i3)
+
+        call ftgcx(iunit, 3, ii, 1, ii, larray, status)
+        write(*,1400)'X',status,(larray(jj),jj=1,ii)
+
+        bnul = char(99)
+        call ftgcvb(iunit, 4, ii, 1,ii,bnul,boutarray,anynull,status)
+        do jj = 1,ii
+          jinarray(jj) = ichar(boutarray(jj))
+        end do
+        write(*,1401)'B',(jinarray(jj),jj=1,ii),status
+
+        inul = 99
+        call ftgcvi(iunit, 5, ii, 1,ii,inul,ioutarray,anynull,status)
+        write(*,1401)'I',(ioutarray(jj),jj=1,ii),status
+
+        call ftgcvj(iunit, 6, ii, 1, ii,99,joutarray,anynull,status)
+        write(*,1401)'J',(joutarray(jj),jj=1,ii),status
+
+        call ftgcve(iunit, 7, ii, 1,ii,99.,eoutarray,anynull,status)
+        estatus=status
+        write(*,1402)'E',(eoutarray(jj),jj=1,ii),estatus
+1402    format(1x,a1,1x,21f3.0)
+
+        dnul = 99.
+        call ftgcvd(iunit, 8, ii,1,ii,dnul,doutarray,anynull,status)
+        estatus=status
+        write(*,1402)'D',(doutarray(jj),jj=1,ii),estatus
+
+        call ftgdes(iunit, 8, ii, repeat, offset, status)
+        write(*,'(1x,A,2I5)')'Column 8 repeat and offset =',
+     &       repeat,offset
+      end do
+
+C        #####################################
+C        #  create another image extension   #
+C        #####################################
+      
+
+      bitpix = 32
+      naxis = 2
+      naxes(1) = 10
+      naxes(2) = 2
+      npixels = 20
+ 
+      call ftiimg(iunit, bitpix, naxis, naxes, status)
+      write(*,'(1x,A)')' '
+      write(*,'(1x,A,I4)')'Create image extension: ftiimg status =',
+     &       status
+      
+C initialize arrays of values to write to primary array 
+      do ii = 1, npixels
+          boutarray(ii) = char(ii * 2 -2)
+          ioutarray(ii) = ii * 2 -2
+          joutarray(ii) = ii * 2 -2
+          koutarray(ii) = ii * 2 -2
+          eoutarray(ii) = ii * 2 -2
+          doutarray(ii) = ii * 2 -2
+      end do      
+
+C write a few pixels with each datatype 
+      call ftpprb(iunit, 1, 1,  2, boutarray(1),  status)
+      call ftppri(iunit, 1, 3,  2, ioutarray(3),  status)
+      call ftpprj(iunit, 1, 5,  2, koutarray(5),  status)
+      call ftppri(iunit, 1, 7,  2, ioutarray(7),  status)
+      call ftpprj(iunit, 1, 9,  2, joutarray(9),  status)
+      call ftppre(iunit, 1, 11, 2, eoutarray(11), status)
+      call ftpprd(iunit, 1, 13, 2, doutarray(13), status)
+      write(*,'(1x,A,I4)') 'ftppr status = ', status
+
+      
+C read back the pixels with each datatype 
+      bnul = char(0)
+      inul = 0
+      knul = 0
+      jnul = 0
+      enul = 0.
+      dnul = 0.
+
+      call ftgpvb(iunit, 1,  1,  14, bnul, binarray, anynull, status)
+      call ftgpvi(iunit, 1,  1,  14, inul, iinarray, anynull, status)
+      call ftgpvj(iunit, 1,  1,  14, knul, kinarray, anynull, status)
+      call ftgpvj(iunit, 1,  1,  14, jnul, jinarray, anynull, status)
+      call ftgpve(iunit, 1,  1,  14, enul, einarray, anynull, status)
+      call ftgpvd(iunit, 1,  1,  14, dnul, dinarray, anynull, status)
+
+      write(*,'(1x,A)')' '
+      write(*,'(1x,A)')
+     &   'Image values written with ftppr and read with ftgpv:'
+      npixels = 14
+      do jj = 1,ii
+          joutarray(jj) = ichar(binarray(jj))
+      end do
+
+      write(*,1501)(joutarray(ii),ii=1,npixels),anynull,'(byte)'
+1501  format(1x,14i3,l3,1x,a)
+      write(*,1501)(iinarray(ii),ii=1,npixels),anynull,'(short)'
+      write(*,1501)(kinarray(ii),ii=1,npixels),anynull,'(int)'
+      write(*,1501)(jinarray(ii),ii=1,npixels),anynull,'(long)'
+      write(*,1502)(einarray(ii),ii=1,npixels),anynull,'(float)'
+      write(*,1502)(dinarray(ii),ii=1,npixels),anynull,'(double)'
+1502  format(2x,14f3.0,l2,1x,a)
+
+C      ##########################################
+C      #  test world coordinate system routines #
+C      ##########################################
+
+      xrval = 45.83D+00
+      yrval =  63.57D+00
+      xrpix =  256.D+00
+      yrpix =  257.D+00
+      xinc =   -.00277777D+00
+      yinc =   .00277777D+00
+
+C     write the WCS keywords 
+C     use example values from the latest WCS document 
+      call ftpkyd(iunit, 'CRVAL1', xrval, 10, 'comment', status)
+      call ftpkyd(iunit, 'CRVAL2', yrval, 10, 'comment', status)
+      call ftpkyd(iunit, 'CRPIX1', xrpix, 10, 'comment', status)
+      call ftpkyd(iunit, 'CRPIX2', yrpix, 10, 'comment', status)
+      call ftpkyd(iunit, 'CDELT1', xinc, 10, 'comment', status)
+      call ftpkyd(iunit, 'CDELT2', yinc, 10, 'comment', status)
+C     call ftpkyd(iunit, 'CROTA2', rot, 10, 'comment', status) 
+      call ftpkys(iunit, 'CTYPE1', xctype, 'comment', status)
+      call ftpkys(iunit, 'CTYPE2', yctype, 'comment', status)
+      write(*,'(1x,A)')' '
+      write(*,'(1x,A,I4)')'Wrote WCS keywords status =', status
+
+C     reset value, to make sure they are reread correctly
+      xrval =  0.D+00
+      yrval =  0.D+00
+      xrpix =  0.D+00
+      yrpix =  0.D+00
+      xinc =   0.D+00
+      yinc =   0.D+00
+      rot =    67.D+00
+
+      call ftgics(iunit, xrval, yrval, xrpix,
+     &      yrpix, xinc, yinc, rot, ctype, status)
+      write(*,'(1x,A,I4)')'Read WCS keywords with ftgics status =',
+     &  status
+
+      xpix = 0.5D+00
+      ypix = 0.5D+00
+
+      call ftwldp(xpix,ypix,xrval,yrval,xrpix,yrpix,xinc,yinc,
+     &     rot,ctype, xpos, ypos,status)
+
+      write(*,'(1x,A,2f8.3)')'  CRVAL1, CRVAL2 =', xrval,yrval
+      write(*,'(1x,A,2f8.3)')'  CRPIX1, CRPIX2 =', xrpix,yrpix
+      write(*,'(1x,A,2f12.8)')'  CDELT1, CDELT2 =', xinc,yinc
+      write(*,'(1x,A,f8.3,2A)')'  Rotation =',rot,' CTYPE =',ctype
+      write(*,'(1x,A,I4)')'Calculated sky coord. with ftwldp status =',
+     &   status
+      write(*,6501)xpix,ypix,xpos,ypos
+6501  format('  Pixels (',f10.6,f10.6,') --> (',f10.6,f10.6,') Sky')
+
+      call ftxypx(xpos,ypos,xrval,yrval,xrpix,yrpix,xinc,yinc,
+     &     rot,ctype, xpix, ypix,status)
+      write(*,'(1x,A,I4)')
+     & 'Calculated pixel coord. with ftxypx status =', status
+      write(*,6502)xpos,ypos,xpix,ypix
+6502  format('  Sky (',f10.6,f10.6,') --> (',f10.6,f10.6,') Pixels')
+
+     
+C        ######################################
+C        #  append another ASCII table        #
+C        ######################################
+      
+
+      tform(1)= 'A15'
+      tform(2)= 'I11'
+      tform(3)= 'F15.6'
+      tform(4)= 'E13.5'
+      tform(5)= 'D22.14'
+
+      tbcol(1)= 1
+      tbcol(2)= 17
+      tbcol(3)= 29
+      tbcol(4)= 45
+      tbcol(5)= 59
+      rowlen = 80
+
+      ttype(1)= 'Name'
+      ttype(2)= 'Ivalue'
+      ttype(3)= 'Fvalue'
+      ttype(4)= 'Evalue'
+      ttype(5)= 'Dvalue'
+
+      tunit(1)= ' '
+      tunit(2)= 'm**2'
+      tunit(3)= 'cm'
+      tunit(4)= 'erg/s'
+      tunit(5)= 'km/s'
+
+      nrows = 11
+      tfields = 5
+      tblname = 'new_table'
+
+      call ftitab(iunit, rowlen, nrows, tfields, ttype, tbcol, 
+     & tform, tunit, tblname, status)
+      write(*,'(1x,A)') ' '
+      write(*,'(1x,A,I4)') 'ftitab status = ', status
+
+      call ftpcls(iunit, 1, 1, 1, 3, onskey, status)  
+C write string values 
+
+C initialize arrays of values to write to primary array 
+      
+      do ii = 1,npixels
+          boutarray(ii) = char(ii * 3 -3)
+          ioutarray(ii) = ii * 3 -3
+          joutarray(ii) = ii * 3 -3
+          koutarray(ii) = ii * 3 -3
+          eoutarray(ii) = ii * 3 -3
+          doutarray(ii) = ii * 3 -3
+      end do
+
+      do ii = 2,5 
+C loop over cols 2 - 5 
+      
+          call ftpclb(iunit,  ii, 1, 1, 2, boutarray,  status) 
+          call ftpcli(iunit,  ii, 3, 1, 2,ioutarray(3),status)
+          call ftpclj(iunit,  ii, 5, 1, 2,joutarray(5),status)
+          call ftpcle(iunit,  ii, 7, 1, 2,eoutarray(7),status)
+          call ftpcld(iunit,  ii, 9, 1, 2,doutarray(9),status)
+      end do
+      write(*,'(1x,A,I4)') 'ftpcl status = ', status
+      
+C read back the pixels with each datatype 
+      call ftgcvb(iunit,   2, 1, 1, 10, bnul, binarray,anynull,
+     & status)
+      call ftgcvi(iunit,  2, 1, 1, 10, inul, iinarray,anynull,
+     & status)
+      call ftgcvj(iunit,    3, 1, 1, 10, knul, kinarray,anynull,
+     & status)
+      call ftgcvj(iunit,    3, 1, 1, 10, jnul, jinarray,anynull,
+     & status)
+      call ftgcve(iunit,   4, 1, 1, 10, enul, einarray,anynull,
+     & status)
+      call ftgcvd(iunit, 5, 1, 1, 10, dnul, dinarray,anynull,
+     & status)
+
+      write(*,'(1x,A)') 
+     &'Column values written with ftpcl and read with ftgcl: '
+      npixels = 10
+      do ii = 1,npixels
+         joutarray(ii) = ichar(binarray(ii))
+      end do
+      write(*,1601)(joutarray(ii),ii = 1, npixels),anynull,'(byte) '
+      write(*,1601)(iinarray(ii),ii = 1, npixels),anynull,'(short) '
+      write(*,1601)(kinarray(ii),ii = 1, npixels),anynull,'(int) '
+      write(*,1601)(jinarray(ii),ii = 1, npixels),anynull,'(long) '
+      write(*,1602)(einarray(ii),ii = 1, npixels),anynull,'(float) '
+      write(*,1602)(dinarray(ii),ii = 1, npixels),anynull,'(double) '
+1601  format(1x,10i3,l3,1x,a)
+1602  format(2x,10f3.0,l2,1x,a)
+      
+C        ###########################################################
+C        #  perform stress test by cycling thru all the extensions #
+C        ###########################################################
+      write(*,'(1x,A)')' '
+      write(*,'(1x,A)')'Repeatedly move to the 1st 4 HDUs of the file: '
+
+      do ii = 1,10
+        call ftmahd(iunit,  1, hdutype, status)
+        call ftghdn(iunit, hdunum)
+        call ftmrhd(iunit,  1, hdutype, status)
+        call ftghdn(iunit, hdunum)
+        call ftmrhd(iunit,  1, hdutype, status)
+        call ftghdn(iunit, hdunum)
+        call ftmrhd(iunit,  1, hdutype, status)
+        call ftghdn(iunit, hdunum)
+        call ftmrhd(iunit, -1, hdutype, status)
+        call ftghdn(iunit, hdunum)
+        if (status .gt. 0) go to 999
+      end do
+      
+      write(*,'(1x,A)') ' '
+
+      checksum = 1234567890.D+00
+      call ftesum(checksum, .false., asciisum)
+      write(*,'(1x,A,F13.1,2A)')'Encode checksum: ',checksum,' -> ',
+     &  asciisum
+      checksum = 0
+      call ftdsum(asciisum, 0, checksum)
+      write(*,'(1x,3A,F13.1)') 'Decode checksum: ',asciisum,' -> ',
+     & checksum
+
+      call ftpcks(iunit, status)
+
+C         don't print the CHECKSUM value because it is different every day
+C         because the current date is in the comment field.
+
+         call ftgcrd(iunit, 'CHECKSUM', card, status)
+C         write(*,'(1x,A)') card
+
+      call ftgcrd(iunit, 'DATASUM', card, status)
+      write(*,'(1x,A)') card(1:22)
+
+      call ftgcks(iunit, datsum, checksum, status)
+      write(*,'(1x,A,F13.1,I4)') 'ftgcks data checksum, status = ',
+     &         datsum, status
+
+      call ftvcks(iunit, datastatus, hdustatus, status) 
+      write(*,'(1x,A,3I4)')'ftvcks datastatus, hdustatus, status =  ',
+     &          datastatus, hdustatus, status
+ 
+      call ftprec(iunit,
+     & 'new_key = ''written by fxprec'' / to change checksum',status)
+      call ftucks(iunit, status)
+      write(*,'(1x,A,I4)') 'ftupck status = ', status
+
+      call ftgcrd(iunit, 'DATASUM', card, status)
+      write(*,'(1x,A)') card(1:22)
+      call ftvcks(iunit, datastatus, hdustatus, status) 
+      write(*,'(1x,A,3I4)') 'ftvcks datastatus, hdustatus, status =  ',
+     &          datastatus, hdustatus, status
+ 
+C        delete the checksum keywords, so that the FITS file is always
+C        the same, regardless of the date of when testprog is run.
+      
+      call ftdkey(iunit, 'CHECKSUM', status)
+      call ftdkey(iunit, 'DATASUM',  status)
+
+
+C        ############################
+C        #  close file and quit     #
+C        ############################
+      
+
+999   continue  
+C jump here on error 
+
+      call ftclos(iunit, status) 
+      write(*,'(1x,A,I4)') 'ftclos status = ', status
+      write(*,'(1x,A)')' '
+
+      write(*,'(1x,A)')
+     &  'Normally, there should be 8 error messages on the'
+      write(*,'(1x,A)') 'stack all regarding ''numerical overflows'':'
+
+      call ftgmsg(errmsg)
+      nmsg = 0
+
+998   continue
+      if (errmsg .ne. ' ')then      
+          write(*,'(1x,A)') errmsg
+          nmsg = nmsg + 1
+          call ftgmsg(errmsg)
+          go to 998
+      end if
+
+      if (nmsg .ne. 8)write(*,'(1x,A)')
+     & ' WARNING: Did not find the expected 8 error messages!'
+
+      call ftgerr(status, errmsg)
+      write(*,'(1x,A)')' '
+      write(*,'(1x,A,I4,2A)') 'Status =', status,': ', errmsg(1:50)
+      end
diff --git a/external/cfitsio/testf77.out b/external/cfitsio/testf77.out
new file mode 100644
index 0000000..14a0e7e
--- /dev/null
+++ b/external/cfitsio/testf77.out
@@ -0,0 +1,746 @@
+ FITSIO TESTPROG, v  3.310
+  
+ Try opening then closing a nonexistent file: 
+   ftopen iunit, status (expect an error) =  15 104
+   ftclos status =  104
+  
+ ftinit create new file status =    0
+  
+ test writing of long string keywords: 
+ 123456789012345678901234567890123456789012345678901234567890123456789012345   
+ '12345678901234567890123456789012345678901234567890123456789012345678'        
+ 1234567890123456789012345678901234567890123456789012345678901234'6789012345   
+ '1234567890123456789012345678901234567890123456789012345678901234''67'        
+ 1234567890123456789012345678901234567890123456789012345678901234''789012345   
+ '1234567890123456789012345678901234567890123456789012345678901234'''''        
+ 1234567890123456789012345678901234567890123456789012345678901234567'9012345   
+ '1234567890123456789012345678901234567890123456789012345678901234567'         
+ Wrote all Keywords successfully 
+ ftflus status =    0
+  
+ HDU number =    1
+ Values read back from primary array (99 = null pixel)
+ The 1st, and every 4th pixel should be undefined: 
+  99  2  3 99  5  6  7 99  9 10 11 99 13 14 15 99 17 18 19 99  T (ftgpvb) 
+  99  2  3 99  5  6  7 99  9 10 11 99 13 14 15 99 17 18 19 99  T (ftgpvi) 
+  99  2  3 99  5  6  7 99  9 10 11 99 13 14 15 99 17 18 19 99  T (ftgpvj) 
+  99. 2. 3.99. 5. 6. 7.99. 9.10.11.99.13.14.15.99.17.18.19.99. T (ftgpve) 
+  99. 2. 3.99. 5. 6. 7.99. 9.10.11.99.13.14.15.99.17.18.19.99. T (ftgpvd) 
+   0  2  3  0  5  6  7  0  9 10 11  0 13 14 15  0 17 18 19  0  T (ftgpfb)
+   0  2  3  0  5  6  7  0  9 10 11  0 13 14 15  0 17 18 19  0  T (ftgpfi)
+   0  2  3  0  5  6  7  0  9 10 11  0 13 14 15  0 17 18 19  0  T (ftgpfj)
+   0. 2. 3. 0. 5. 6. 7. 0. 9.10.11. 0.13.14.15. 0.17.18.19. 0. T (ftgpfe)
+   0. 2. 3. 0. 5. 6. 7. 0. 9.10.11. 0.13.14.15. 0.17.18.19. 0. T (ftgpfd)
+  
+ Closed then reopened the FITS file 10 times.
+  
+ HDU number =    1
+ Read back keywords: 
+ simple, bitpix, naxis, naxes =    T  32   2  10   2
+   pcount, gcount, extend =    0   1   T
+ KEY_PREC= 'This keyword was written by fxprec' / comment goes here            
+ KEY_PREC 'This keyword was written by fxprec comment goes here   
+ KEY_PREC= 'This keyword was written by fxprec' / comment goes here            
+ KY_PKNS1 :'first string' :fxpkns comment  
+ KEY_PKYS :value_string         :fxpkys comment     0
+ KEY_PKYL :   T:fxpkyl comment     0
+ KEY_PKYJ :  11:fxpkyj comment     0
+ KEY_PKYE :    11.00000:fxpkyj comment     0
+ KEY_PKYD :    11.00000:fxpkyj comment     0
+ KEY_PKYS :value_string         :fxpkys comment     0
+ KEY_PKYL :   T:fxpkyl comment     0
+ KEY_PKYJ :  11:fxpkyj comment     0
+ KEY_PKYE :    13.13131:fxpkye comment     0
+ KEY_PKYD :    15.15152:fxpkyd comment     0
+ KEY_PKYF :    12.12121:fxpkyf comment     0
+ KEY_PKYE :    13.13131:fxpkye comment     0
+ KEY_PKYG : 14.141414141414:fxpkyg comment     0
+ KEY_PKYD : 15.151515151515:fxpkyd comment     0
+ KEY_PKYT  :  12345678:0.12345678901235fxpkyt comment     0
+ KEY_PKYJ :  11:[km/s/Mpc] fxpkyj comment                0
+ keyword unit=km/s/Mpc          
+ KEY_PKYJ :  11:fxpkyj comment                           0
+ keyword unit=                  
+ KEY_PKYJ :  11:[feet/second/second] fxpkyj comment      0
+ keyword unit=feet/second/second
+ KEY_PKLS long string value = This is a very long string value that is continued
+  over more than one keyword.                                          
+ header contains   61 keywords; located at keyword   23
+ ftgkns: first string  second string               
+ ftgknl:    T   F   T
+ ftgknj:   11  12  13
+ ftgkne:   13.13131  14.14141  15.15152
+ ftgknd:   15.15152  16.16162  17.17172
+  
+ Before deleting the HISTORY and DATE keywords...
+ COMMENT 
+ HISTORY 
+ DATE    
+ KY_PKNS1
+  
+ After deleting the keywords... 
+ COMMENT This keyword was written by fxpcom.                                   
+ KY_PKNS1= 'first string'       / fxpkns comment                               
+  
+ After inserting the keywords... 
+ COMMENT   continued over multiple keywords.  The HEASARC convention uses the &
+ KY_IREC = 'This keyword inserted by fxirec'                                   
+ KY_IKYS = 'insert_value_string' / ikys comment                                
+ KY_IKYJ =                   49 / ikyj comment                                 
+ KY_IKYL =                    T / ikyl comment                                 
+ KY_IKYE =           1.2346E+01 / ikye comment                                 
+ KY_IKYD = 1.23456789012346E+01 / ikyd comment                                 
+ KY_IKYF =              12.3456 / ikyf comment                                 
+ KY_IKYG =     12.3456789012346 / ikyg comment                                 
+ COMMENT   character at the end of each substring which is then continued      
+  
+ After modifying the keywords... 
+ COMMENT   This keyword was modified by fxmrec                                 
+ KY_MREC = 'This keyword was modified by fxmcrd'                               
+ NEWIKYS = 'modified_string'    / ikys comment                                 
+ KY_IKYJ =                   50 / This is a modified comment                   
+ KY_IKYL =                    F / ikyl comment                                 
+ KY_IKYE =          -1.2346E+01 / ikye comment                                 
+ KY_IKYD = -1.23456789012346E+01 / modified comment                            
+ KY_IKYF =             -12.3456 / ikyf comment                                 
+ KY_IKYG =    -12.3456789012346 / ikyg comment                                 
+ COMMENT   character at the end of each substring which is then continued      
+  
+ After updating the keywords... 
+ COMMENT   This keyword was modified by fxmrec                                 
+ KY_UCRD = 'This keyword was updated by fxucrd'                                
+ NEWIKYS = 'updated_string'     / ikys comment                                 
+ KY_IKYJ =                   51 / This is a modified comment                   
+ KY_IKYL =                    T / ikyl comment                                 
+ KY_IKYE =          -1.3346E+01 / ikye comment                                 
+ KY_IKYD = -1.33456789012346E+01 / modified comment                            
+ KY_IKYF =             -13.3456 / ikyf comment                                 
+ KY_IKYG =    -13.3456789012346 / ikyg comment                                 
+ COMMENT   character at the end of each substring which is then continued      
+  
+ Keywords found using wildcard search (should be 9)...
+ KEY_PKYS= 'value_string'       / fxpkys comment                               
+ KEY_PKYL=                    T / fxpkyl comment                               
+ KEY_PKYJ=                   11 / [feet/second/second] fxpkyj comment          
+ KEY_PKYF=             12.12121 / fxpkyf comment                               
+ KEY_PKYE=         1.313131E+01 / fxpkye comment                               
+ KEY_PKYG=    14.14141414141414 / fxpkyg comment                               
+ KEY_PKYD= 1.51515151515152E+01 / fxpkyd comment                               
+ NEWIKYS = 'updated_string'     / ikys comment                                 
+ KEY_PKYT= 12345678.1234567890123456 / fxpkyt comment                          
+  
+ ftibin status =    0
+ HDU number =    2
+ header contains   33 keywords located at keyword    1
+ header contains   33 keywords with room for   74 more
+ TDIM3 = (1,2,8)                 3   1   2   8
+ ftpcl_ status =    0
+  
+ Find the column numbers a returned status value of 237 is
+ expected and indicates that more than one column name matches
+ the input column name template.  Status = 219 indicates that
+ there was no matching column name.
+ Column Xvalue is number   3 status =   0
+ Column Avalue is number   1 status =  237
+ Column Lvalue is number   2 status =  237
+ Column Xvalue is number   3 status =  237
+ Column Bvalue is number   4 status =  237
+ Column Ivalue is number   5 status =  237
+ Column Jvalue is number   6 status =  237
+ Column Evalue is number   7 status =  237
+ Column Dvalue is number   8 status =  237
+ Column Cvalue is number   9 status =  237
+ Column Mvalue is number  10 status =  237
+ Column        is number   0 status =  219
+  
+ Information about each column: 
+ 15A  16  15  15 Avalue       A      1.00    0.00  1234554321        
+ 1L   14   1   1 Lvalue m**2  L      1.00    0.00  1234554321        
+ 16X   1  16   1 Xvalue cm    X      1.00    0.00  1234554321        
+ 1B   11   1   1 Bvalue erg/s B      1.00    0.00          99        
+ 1I   21   1   2 Ivalue km/s  I      1.00    0.00          99        
+ 1J   41   1   4 Jvalue       J      1.00    0.00          99        
+ 1E   42   1   4 Evalue       E      1.00    0.00  1234554321        
+ 1D   82   1   8 Dvalue       D      1.00    0.00  1234554321        
+ 1C   83   1   8 Cvalue       C      1.00    0.00  1234554321        
+ 1M  163   1  16 Mvalue       M      1.00    0.00  1234554321        
+  
+ ftitab status =    0
+ HDU number =    2
+ ftpcl_ status =    0
+  
+ ASCII table: rowlen, nrows, tfields, extname: 76 11  5 Test-ASCII     
+ Name      1 A15           
+ Ivalue   17 I10    m**2   
+ Fvalue   28 F14.6  cm     
+ Evalue   43 E12.5  erg/s  
+ Dvalue   56 D21.14 km/s   
+  
+ Data values read from ASCII table: 
+ first string     1  1  1  1. 1.
+ second string    2  2  2  2. 2.
+                  3  3  3  3. 3.
+ UNDEFINED        4  4  4  4. 4.
+                  5  5  5  5. 5.
+                  6  6  6  6. 6.
+                  7  7  7  7. 7.
+                  8  8  8  8. 8.
+                  9  9  9  9. 9.
+                 10 10 10 10.10.
+                 99 99 99 99.99.
+  
+       1       1.000000  1.00000E+00  1.00000000000000E+00second string        
+  
+ Column name is number   1 status =    0
+ Column Ivalue is number   2 status =  237
+ Column Fvalue is number   3 status =  237
+ Column Evalue is number   4 status =  237
+ Column Dvalue is number   5 status =  237
+ Column        is number   0 status =  219
+ A15      16   1  15 Name     1            1.00      0.00 null1   
+ I10      41   1  10 Ivalue  17 m**2       1.00      0.00 null2   
+ F14.6    82   1  14 Fvalue  28 cm         1.00      0.00 null3   
+ E12.5    42   1  12 Evalue  43 erg/s      1.00      0.00 null4   
+ D21.14   82   1  21 Dvalue  56 km/s       1.00      0.00 null5   
+  
+  
+ Data values after inserting 3 rows after row 2:
+ first string     1  1  1  1. 1.
+ second string    2  2  2  2. 2.
+                  0  0  0  0. 0.
+                  0  0  0  0. 0.
+                  0  0  0  0. 0.
+                  3  3  3  3. 3.
+ UNDEFINED        4  4  4  4. 4.
+                  5  5  5  5. 5.
+                  6  6  6  6. 6.
+                  7  7  7  7. 7.
+                  8  8  8  8. 8.
+                  9  9  9  9. 9.
+                 10 10 10 10.10.
+                 99 99 99 99.99.
+  
+ Data values after deleting 2 rows at row 10: 
+ first string     1  1  1  1. 1.
+ second string    2  2  2  2. 2.
+                  0  0  0  0. 0.
+                  0  0  0  0. 0.
+                  0  0  0  0. 0.
+                  3  3  3  3. 3.
+ UNDEFINED        4  4  4  4. 4.
+                  5  5  5  5. 5.
+                  6  6  6  6. 6.
+                  9  9  9  9. 9.
+                 10 10 10 10.10.
+                 99 99 99 99.99.
+  
+ Data values after deleting column 3: 
+ first string     1  1  1. 1.
+ second string    2  2  2. 2.
+                  0  0  0. 0.
+                  0  0  0. 0.
+                  0  0  0. 0.
+                  3  3  3. 3.
+ UNDEFINED        4  4  4. 4.
+                  5  5  5. 5.
+                  6  6  6. 6.
+                  9  9  9. 9.
+                 10 10 10.10.
+                 99 99 99.99.
+  
+  Data values after inserting column 5: 
+ first string     1  1  1. 1. 0
+ second string    2  2  2. 2. 0
+                  0  0  0. 0. 0
+                  0  0  0. 0. 0
+                  0  0  0. 0. 0
+                  3  3  3. 3. 0
+ UNDEFINED        4  4  4. 4. 0
+                  5  5  5. 5. 0
+                  6  6  6. 6. 0
+                  9  9  9. 9. 0
+                 10 10 10.10. 0
+                 99 99 99.99. 0
+ HDU number =    3
+  
+ Moved to binary table
+ header contains   37 keywords with room for   70 more 
+  
+ Binary table: nrows, tfields, extname, pcount:  21  10Test-BINTABLE     0
+ Avalue         15A                           
+ Lvalue         1L             m**2           
+ Xvalue         16X            cm             
+ Bvalue         1B             erg/s          
+ Ivalue         1I             km/s           
+ Jvalue         1J                            
+ Evalue         1E                            
+ Dvalue         1D                            
+ Cvalue         1C                            
+ Mvalue         1M                            
+  
+ Data values read from binary table: 
+  Bit column (X) data values:   
+ FTFFTTFF FTTTFFFF TTTTFFFF FTTTTTFF FFFFFFFF
+   
+ null string column value (should be blank):                              
+  
+ Read columns with ftgcv_: 
+ first string     F  76   1   1   1   1.   1.   1.  -2.   1.  -2.
+ second string    T 112   2   2   2   2.   2.   3.  -4.   3.  -4.
+                  F 240   3   3   3   3.   3.   5.  -6.   5.  -6.
+ NOT DEFINED      F 124   0  -4  -4  -4.  -4.   7.  -8.   7.  -8.
+ NOT DEFINED      T   0   5   5   5   5.   5.   9. -10.   9. -10.
+ NOT DEFINED      T   0   0  -6  -6  -6.  -6.  11. -12.  11. -12.
+ NOT DEFINED      F   0   7   7   7   7.   7.  13. -14.  13. -14.
+ NOT DEFINED      F   0   0  -8  -8  -8.  -8.  15. -16.  15. -16.
+ NOT DEFINED      F   0   9   9   9   9.   9.  17. -18.  17. -18.
+ NOT DEFINED      T   0   0 -10 -10 -10. -10.  19. -20.  19. -20.
+ NOT DEFINED      F   0  98  98  98  98.  98.   0.   0.   0.   0.
+ NOT DEFINED      T   0  12  12  12  12.  12.   0.   0.   0.   0.
+ NOT DEFINED      F   0  98  98  98  98.  98.   0.   0.   0.   0.
+ NOT DEFINED      F   0   0 -14 -14 -14. -14.   0.   0.   0.   0.
+ NOT DEFINED      F   0  98  98  98  98.  98.   0.   0.   0.   0.
+ NOT DEFINED      F   0   0 -16 -16 -16. -16.   0.   0.   0.   0.
+ NOT DEFINED      T   0  98  98  98  98.  98.   0.   0.   0.   0.
+ NOT DEFINED      T   0   0 -18 -18 -18. -18.   0.   0.   0.   0.
+ NOT DEFINED      T   0  98  98  98  98.  98.   0.   0.   0.   0.
+ NOT DEFINED      T   0   0 -20 -20 -20. -20.   0.   0.   0.   0.
+ NOT DEFINED      F   0  98  98  98  98.  98.   0.   0.   0.   0.
+  
+  Read columns with ftgcf_: 
+ first string     F  76   1   1   1   1.   1.   1.  -2.   1.  -2.
+ second string    T 112   2   2   2   2.   2.   3.  -4.   3.  -4.
+                  F 240   3   3   3   3.   3.   5.  -6.   5.  -6.
+                  F 124   0  -4  -4  -4.  -4.   7.  -8.   7.  -8.
+                  T   0   5   5   5   5.   5.   9. -10.   9. -10.
+                  T   0   0  -6  -6  -6.  -6.  11. -12.  11. -12.
+                  F   0   7   7   7   7.   7.  13. -14.  13. -14.
+                  F   0   0  -8  -8  -8.  -8.  15. -16.  15. -16.
+                  F   0   9   9   9   9.   9.  17. -18.  17. -18.
+                  T   0   0 -10 -10 -10. -10.  19. -20.  19. -20.
+                  F   0  99  99
+                  T   0  12  12
+                  F   0  99  99
+                  F   0   0 -14
+                  F   0  99  99
+                  F   0   0 -16
+                  T   0  99  99
+                  T   0   0 -18
+                  T   0  99  99
+                  T   0   0 -20
+                  F   0  99  99
+  
+ Data values after inserting 3 rows after row 2:
+ first string     1   1   1   1.   1.
+ second string    2   2   2   2.   2.
+ NOT DEFINED      0   0   0   0.   0.
+ NOT DEFINED      0   0   0   0.   0.
+ NOT DEFINED      0   0   0   0.   0.
+                  3   3   3   3.   3.
+ NOT DEFINED      0  -4  -4  -4.  -4.
+ NOT DEFINED      5   5   5   5.   5.
+ NOT DEFINED      0  -6  -6  -6.  -6.
+ NOT DEFINED      7   7   7   7.   7.
+ NOT DEFINED      0  -8  -8  -8.  -8.
+ NOT DEFINED      9   9   9   9.   9.
+ NOT DEFINED      0 -10 -10 -10. -10.
+ NOT DEFINED     98  98  98  98.  98.
+  
+ Data values after deleting 2 rows at row 10: 
+ first string     1   1   1   1.   1.
+ second string    2   2   2   2.   2.
+ NOT DEFINED      0   0   0   0.   0.
+ NOT DEFINED      0   0   0   0.   0.
+ NOT DEFINED      0   0   0   0.   0.
+                  3   3   3   3.   3.
+ NOT DEFINED      0  -4  -4  -4.  -4.
+ NOT DEFINED      5   5   5   5.   5.
+ NOT DEFINED      0  -6  -6  -6.  -6.
+ NOT DEFINED      9   9   9   9.   9.
+ NOT DEFINED      0 -10 -10 -10. -10.
+ NOT DEFINED     98  98  98  98.  98.
+  
+ Data values after deleting column 6: 
+ first string     1   1   1.   1.
+ second string    2   2   2.   2.
+ NOT DEFINED      0   0   0.   0.
+ NOT DEFINED      0   0   0.   0.
+ NOT DEFINED      0   0   0.   0.
+                  3   3   3.   3.
+ NOT DEFINED      0  -4  -4.  -4.
+ NOT DEFINED      5   5   5.   5.
+ NOT DEFINED      0  -6  -6.  -6.
+ NOT DEFINED      9   9   9.   9.
+ NOT DEFINED      0 -10 -10. -10.
+ NOT DEFINED     98  98  98.  98.
+  
+ Data values after inserting column 8: 
+ first string     1   1   1.   1.  0
+ second string    2   2   2.   2.  0
+ NOT DEFINED      0   0   0.   0.  0
+ NOT DEFINED      0   0   0.   0.  0
+ NOT DEFINED      0   0   0.   0.  0
+                  3   3   3.   3.  0
+ NOT DEFINED      0  -4  -4.  -4.  0
+ NOT DEFINED      5   5   5.   5.  0
+ NOT DEFINED      0  -6  -6.  -6.  0
+ NOT DEFINED      9   9   9.   9.  0
+ NOT DEFINED      0 -10 -10. -10.  0
+ NOT DEFINED     98  98  98.  98.  0
+  
+ Values after setting 1st 10 elements in column 8 = null: 
+ first string     1   1   1.   1. 98
+ second string    2   2   2.   2. 98
+ NOT DEFINED      0   0   0.   0. 98
+ NOT DEFINED      0   0   0.   0. 98
+ NOT DEFINED      0   0   0.   0. 98
+                  3   3   3.   3. 98
+ NOT DEFINED      0  -4  -4.  -4. 98
+ NOT DEFINED      5   5   5.   5. 98
+ NOT DEFINED      0  -6  -6.  -6. 98
+ NOT DEFINED      9   9   9.   9. 98
+ NOT DEFINED      0 -10 -10. -10.  0
+ NOT DEFINED     98  98  98.  98.  0
+  
+ ftibin status =    0
+ HDU number =    2
+      0  1000 10000 33000 66000  -999
+      0  1000 10000 32768 65535  -999
+      0  1000 10000 32800 65500  -999
+  
+      0     1    10    33    66  -999
+ -32768-31768-22768     0 32767  -999
+     -1     9    99   327   654  -999
+  
+  Create image extension: ftiimg status =    0
+ HDU number =    3
+  
+ Wrote whole 2D array: ftp2di status =   0
+  
+ Read whole 2D array: ftg2di status =   0
+    0   1   2   3   4   5   6   7   8   9  10  11  12  13  14   0   0   0   0
+   10  11  12  13  14  15  16  17  18  19  20  21  22  23  24   0   0   0   0
+   20  21  22  23  24  25  26  27  28  29  30  31  32  33  34   0   0   0   0
+   30  31  32  33  34  35  36  37  38  39  40  41  42  43  44   0   0   0   0
+   40  41  42  43  44  45  46  47  48  49  50  51  52  53  54   0   0   0   0
+   50  51  52  53  54  55  56  57  58  59  60  61  62  63  64   0   0   0   0
+   60  61  62  63  64  65  66  67  68  69  70  71  72  73  74   0   0   0   0
+   70  71  72  73  74  75  76  77  78  79  80  81  82  83  84   0   0   0   0
+   80  81  82  83  84  85  86  87  88  89  90  91  92  93  94   0   0   0   0
+   90  91  92  93  94  95  96  97  98  99 100 101 102 103 104   0   0   0   0
+  100 101 102 103 104 105 106 107 108 109 110 111 112 113 114   0   0   0   0
+  110 111 112 113 114 115 116 117 118 119 120 121 122 123 124   0   0   0   0
+  120 121 122 123 124 125 126 127 128 129 130 131 132 133 134   0   0   0   0
+  130 131 132 133 134 135 136 137 138 139 140 141 142 143 144   0   0   0   0
+  140 141 142 143 144 145 146 147 148 149 150 151 152 153 154   0   0   0   0
+  150 151 152 153 154 155 156 157 158 159 160 161 162 163 164   0   0   0   0
+  160 161 162 163 164 165 166 167 168 169 170 171 172 173 174   0   0   0   0
+  170 171 172 173 174 175 176 177 178 179 180 181 182 183 184   0   0   0   0
+  180 181 182 183 184 185 186 187 188 189 190 191 192 193 194   0   0   0   0
+  190 191 192 193 194 195 196 197 198 199 200 201 202 203 204   0   0   0   0
+  200 201 202 203 204 205 206 207 208 209 210 211 212 213 214   0   0   0   0
+  210 211 212 213 214 215 216 217 218 219 220 221 222 223 224   0   0   0   0
+  220 221 222 223 224 225 226 227 228 229 230 231 232 233 234   0   0   0   0
+  230 231 232 233 234 235 236 237 238 239 240 241 242 243 244   0   0   0   0
+  240 241 242 243 244 245 246 247 248 249 250 251 252 253 254   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   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   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   0   0
+    0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
+  
+  
+ Wrote subset 2D array: ftpssi status =   0
+  
+ Read whole 2D array: ftg2di status =   0
+    0   1   2   3   4   5   6   7   8   9  10  11  12  13  14   0   0   0   0
+   10  11  12  13  14  15  16  17  18  19  20  21  22  23  24   0   0   0   0
+   20  21  22  23  24  25  26  27  28  29  30  31  32  33  34   0   0   0   0
+   30  31  32  33  34  35  36  37  38  39  40  41  42  43  44   0   0   0   0
+   40  41  42  43   0  -1  -2  -3  -4  -5  -6  -7  -8  -9  54   0   0   0   0
+   50  51  52  53 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19  64   0   0   0   0
+   60  61  62  63 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29  74   0   0   0   0
+   70  71  72  73 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39  84   0   0   0   0
+   80  81  82  83 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49  94   0   0   0   0
+   90  91  92  93 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 104   0   0   0   0
+  100 101 102 103 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 114   0   0   0   0
+  110 111 112 113 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 124   0   0   0   0
+  120 121 122 123 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 134   0   0   0   0
+  130 131 132 133 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 144   0   0   0   0
+  140 141 142 143 144 145 146 147 148 149 150 151 152 153 154   0   0   0   0
+  150 151 152 153 154 155 156 157 158 159 160 161 162 163 164   0   0   0   0
+  160 161 162 163 164 165 166 167 168 169 170 171 172 173 174   0   0   0   0
+  170 171 172 173 174 175 176 177 178 179 180 181 182 183 184   0   0   0   0
+  180 181 182 183 184 185 186 187 188 189 190 191 192 193 194   0   0   0   0
+  190 191 192 193 194 195 196 197 198 199 200 201 202 203 204   0   0   0   0
+  200 201 202 203 204 205 206 207 208 209 210 211 212 213 214   0   0   0   0
+  210 211 212 213 214 215 216 217 218 219 220 221 222 223 224   0   0   0   0
+  220 221 222 223 224 225 226 227 228 229 230 231 232 233 234   0   0   0   0
+  230 231 232 233 234 235 236 237 238 239 240 241 242 243 244   0   0   0   0
+  240 241 242 243 244 245 246 247 248 249 250 251 252 253 254   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   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   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   0   0
+    0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
+  
+  
+ Read subset of 2D array: ftgsvi status =    0
+    41   43   -1   -3   -5   71   73  -31  -33  -35
+  
+ Create image extension: ftiimg status =   0
+ HDU number =    4
+ Create temporary file: ftinit status =    0
+ Copy image extension to primary array of tmp file.
+ ftcopy status =    0
+ SIMPLE  =                    T / file does conform to FITS standard           
+ BITPIX  =                   16 / number of bits per data pixel                
+ NAXIS   =                    2 / number of data axes                          
+ NAXIS1  =                   15 / length of data axis 1                        
+ NAXIS2  =                   25 / length of data axis 2                        
+ EXTEND  =                    T / FITS dataset may contain extensions          
+ Delete the tmp file: ftdelt status =   0
+ Delete the image extension hdutype, status =   1   0
+ HDU number =    4
+ ftcrhd status =    0
+ Variable length arrays: ftphbn status =   0
+ ftpcl_ status =    0
+ PCOUNT = 4446
+ HDU number =    6
+ A                                   0
+ L  0  F
+ X  0  F
+ B  1  0
+ I  1  0
+ J  1  0
+ E  1. 0.
+ D  1. 0.
+ Column 8 repeat and offset =    1   14
+ A  ab                               0
+ L  0  F  T
+ X  0  F  T
+ B 99  2  0
+ I 99  2  0
+ J 99  2  0
+ E 99. 2. 0.
+ D 99. 2. 0.
+ Column 8 repeat and offset =    2   49
+ A  abc                              0
+ L  0  F  F  F
+ X  0  F  T  F
+ B  1 99  3  0
+ I  1 99  3  0
+ J  1 99  3  0
+ E  1.99. 3. 0.
+ D  1.99. 3. 0.
+ Column 8 repeat and offset =    3  105
+ A  abcd                             0
+ L  0  F  T  F  F
+ X  0  F  T  F  F
+ B  1  2 99  4  0
+ I  1  2 99  4  0
+ J  1  2 99  4  0
+ E  1. 2.99. 4. 0.
+ D  1. 2.99. 4. 0.
+ Column 8 repeat and offset =    4  182
+ A  abcde                            0
+ L  0  F  T  F  F  T
+ X  0  F  T  F  F  T
+ B  1  2  3 99  5  0
+ I  1  2  3 99  5  0
+ J  1  2  3 99  5  0
+ E  1. 2. 3.99. 5. 0.
+ D  1. 2. 3.99. 5. 0.
+ Column 8 repeat and offset =    5  280
+ A  abcdef                           0
+ L  0  F  T  F  F  F  T
+ X  0  F  T  F  F  T  T
+ B  1  2  3  4 99  6  0
+ I  1  2  3  4 99  6  0
+ J  1  2  3  4 99  6  0
+ E  1. 2. 3. 4.99. 6. 0.
+ D  1. 2. 3. 4.99. 6. 0.
+ Column 8 repeat and offset =    6  399
+ A  abcdefg                          0
+ L  0  F  T  F  F  T  F  F
+ X  0  F  T  F  F  T  T  F
+ B  1  2  3  4  5 99  7  0
+ I  1  2  3  4  5 99  7  0
+ J  1  2  3  4  5 99  7  0
+ E  1. 2. 3. 4. 5.99. 7. 0.
+ D  1. 2. 3. 4. 5.99. 7. 0.
+ Column 8 repeat and offset =    7  539
+ A  abcdefgh                         0
+ L  0  F  T  F  F  T  T  F  F
+ X  0  F  T  F  F  T  T  F  F
+ B  1  2  3  4  5  6 99  8  0
+ I  1  2  3  4  5  6 99  8  0
+ J  1  2  3  4  5  6 99  8  0
+ E  1. 2. 3. 4. 5. 6.99. 8. 0.
+ D  1. 2. 3. 4. 5. 6.99. 8. 0.
+ Column 8 repeat and offset =    8  700
+ A  abcdefghi                        0
+ L  0  F  T  F  F  T  T  F  F  F
+ X  0  F  T  F  F  T  T  F  F  F
+ B  1  2  3  4  5  6  7 99  9  0
+ I  1  2  3  4  5  6  7 99  9  0
+ J  1  2  3  4  5  6  7 99  9  0
+ E  1. 2. 3. 4. 5. 6. 7.99. 9. 0.
+ D  1. 2. 3. 4. 5. 6. 7.99. 9. 0.
+ Column 8 repeat and offset =    9  883
+ A  abcdefghij                       0
+ L  0  F  T  F  F  T  T  F  F  F  T
+ X  0  F  T  F  F  T  T  F  F  F  T
+ B  1  2  3  4  5  6  7  8 99 10  0
+ I  1  2  3  4  5  6  7  8 99 10  0
+ J  1  2  3  4  5  6  7  8 99 10  0
+ E  1. 2. 3. 4. 5. 6. 7. 8.99.10. 0.
+ D  1. 2. 3. 4. 5. 6. 7. 8.99.10. 0.
+ Column 8 repeat and offset =   10 1087
+ A  abcdefghijk                      0
+ L  0  F  T  F  F  T  T  F  F  F  F  T
+ X  0  F  T  F  F  T  T  F  F  F  T  T
+ B  1  2  3  4  5  6  7  8  9 99 11  0
+ I  1  2  3  4  5  6  7  8  9 99 11  0
+ J  1  2  3  4  5  6  7  8  9 99 11  0
+ E  1. 2. 3. 4. 5. 6. 7. 8. 9.99.11. 0.
+ D  1. 2. 3. 4. 5. 6. 7. 8. 9.99.11. 0.
+ Column 8 repeat and offset =   11 1312
+ A  abcdefghijkl                     0
+ L  0  F  T  F  F  T  T  F  F  F  T  F  T
+ X  0  F  T  F  F  T  T  F  F  F  T  T  T
+ B  1  2  3  4  5  6  7  8  9 10 99 12  0
+ I  1  2  3  4  5  6  7  8  9 10 99 12  0
+ J  1  2  3  4  5  6  7  8  9 10 99 12  0
+ E  1. 2. 3. 4. 5. 6. 7. 8. 9.10.99.12. 0.
+ D  1. 2. 3. 4. 5. 6. 7. 8. 9.10.99.12. 0.
+ Column 8 repeat and offset =   12 1558
+ A  abcdefghijklm                    0
+ L  0  F  T  F  F  T  T  F  F  F  T  T  F  F
+ X  0  F  T  F  F  T  T  F  F  F  T  T  T  F
+ B  1  2  3  4  5  6  7  8  9 10 11 99 13  0
+ I  1  2  3  4  5  6  7  8  9 10 11 99 13  0
+ J  1  2  3  4  5  6  7  8  9 10 11 99 13  0
+ E  1. 2. 3. 4. 5. 6. 7. 8. 9.10.11.99.13. 0.
+ D  1. 2. 3. 4. 5. 6. 7. 8. 9.10.11.99.13. 0.
+ Column 8 repeat and offset =   13 1825
+ A  abcdefghijklmn                   0
+ L  0  F  T  F  F  T  T  F  F  F  T  T  T  F  F
+ X  0  F  T  F  F  T  T  F  F  F  T  T  T  F  F
+ B  1  2  3  4  5  6  7  8  9 10 11 12 99 14  0
+ I  1  2  3  4  5  6  7  8  9 10 11 12 99 14  0
+ J  1  2  3  4  5  6  7  8  9 10 11 12 99 14  0
+ E  1. 2. 3. 4. 5. 6. 7. 8. 9.10.11.12.99.14. 0.
+ D  1. 2. 3. 4. 5. 6. 7. 8. 9.10.11.12.99.14. 0.
+ Column 8 repeat and offset =   14 2113
+ A  abcdefghijklmno                  0
+ L  0  F  T  F  F  T  T  F  F  F  T  T  T  F  F  F
+ X  0  F  T  F  F  T  T  F  F  F  T  T  T  F  F  F
+ B  1  2  3  4  5  6  7  8  9 10 11 12 13 99 15  0
+ I  1  2  3  4  5  6  7  8  9 10 11 12 13 99 15  0
+ J  1  2  3  4  5  6  7  8  9 10 11 12 13 99 15  0
+ E  1. 2. 3. 4. 5. 6. 7. 8. 9.10.11.12.13.99.15. 0.
+ D  1. 2. 3. 4. 5. 6. 7. 8. 9.10.11.12.13.99.15. 0.
+ Column 8 repeat and offset =   15 2422
+ A  abcdefghijklmnop                 0
+ L  0  F  T  F  F  T  T  F  F  F  T  T  T  F  F  F  F
+ X  0  F  T  F  F  T  T  F  F  F  T  T  T  F  F  F  F
+ B  1  2  3  4  5  6  7  8  9 10 11 12 13 14 99 16  0
+ I  1  2  3  4  5  6  7  8  9 10 11 12 13 14 99 16  0
+ J  1  2  3  4  5  6  7  8  9 10 11 12 13 14 99 16  0
+ E  1. 2. 3. 4. 5. 6. 7. 8. 9.10.11.12.13.14.99.16. 0.
+ D  1. 2. 3. 4. 5. 6. 7. 8. 9.10.11.12.13.14.99.16. 0.
+ Column 8 repeat and offset =   16 2752
+ A  abcdefghijklmnopq                0
+ L  0  F  T  F  F  T  T  F  F  F  T  T  T  F  F  F  F  T
+ X  0  F  T  F  F  T  T  F  F  F  T  T  T  F  F  F  F  T
+ B  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 99 17  0
+ I  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 99 17  0
+ J  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 99 17  0
+ E  1. 2. 3. 4. 5. 6. 7. 8. 9.10.11.12.13.14.15.99.17. 0.
+ D  1. 2. 3. 4. 5. 6. 7. 8. 9.10.11.12.13.14.15.99.17. 0.
+ Column 8 repeat and offset =   17 3104
+ A  abcdefghijklmnopqr               0
+ L  0  F  T  F  F  T  T  F  F  F  T  T  T  F  F  F  F  F  T
+ X  0  F  T  F  F  T  T  F  F  F  T  T  T  F  F  F  F  T  T
+ B  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 99 18  0
+ I  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 99 18  0
+ J  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 99 18  0
+ E  1. 2. 3. 4. 5. 6. 7. 8. 9.10.11.12.13.14.15.16.99.18. 0.
+ D  1. 2. 3. 4. 5. 6. 7. 8. 9.10.11.12.13.14.15.16.99.18. 0.
+ Column 8 repeat and offset =   18 3477
+ A  abcdefghijklmnopqrs              0
+ L  0  F  T  F  F  T  T  F  F  F  T  T  T  F  F  F  F  T  F  T
+ X  0  F  T  F  F  T  T  F  F  F  T  T  T  F  F  F  F  T  T  T
+ B  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 99 19  0
+ I  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 99 19  0
+ J  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 99 19  0
+ E  1. 2. 3. 4. 5. 6. 7. 8. 9.10.11.12.13.14.15.16.17.99.19. 0.
+ D  1. 2. 3. 4. 5. 6. 7. 8. 9.10.11.12.13.14.15.16.17.99.19. 0.
+ Column 8 repeat and offset =   19 3871
+ A  abcdefghijklmnopqrst             0
+ L  0  F  T  F  F  T  T  F  F  F  T  T  T  F  F  F  F  T  T  F  T
+ X  0  F  T  F  F  T  T  F  F  F  T  T  T  F  F  F  F  T  T  T  T
+ B  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 99 20  0
+ I  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 99 20  0
+ J  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 99 20  0
+ E  1. 2. 3. 4. 5. 6. 7. 8. 9.10.11.12.13.14.15.16.17.18.99.20. 0.
+ D  1. 2. 3. 4. 5. 6. 7. 8. 9.10.11.12.13.14.15.16.17.18.99.20. 0.
+ Column 8 repeat and offset =   20 4286
+  
+ Create image extension: ftiimg status =   0
+ ftppr status =    0
+  
+ Image values written with ftppr and read with ftgpv:
+   0  2  4  6  8 10 12 14 16 18 20 22 24 26  F (byte)
+   0  2  4  6  8 10 12 14 16 18 20 22 24 26  F (short)
+   0  2  4  6  8 10 12 14 16 18 20 22 24 26  F (int)
+   0  2  4  6  8 10 12 14 16 18 20 22 24 26  F (long)
+   0. 2. 4. 6. 8.10.12.14.16.18.20.22.24.26. F (float)
+   0. 2. 4. 6. 8.10.12.14.16.18.20.22.24.26. F (double)
+  
+ Wrote WCS keywords status =   0
+ Read WCS keywords with ftgics status =   0
+   CRVAL1, CRVAL2 =  45.830  63.570
+   CRPIX1, CRPIX2 = 256.000 257.000
+   CDELT1, CDELT2 = -0.00277777  0.00277777
+   Rotation =   0.000 CTYPE =-TAN    
+ Calculated sky coord. with ftwldp status =   0
+  Pixels (  0.500000  0.500000) --> ( 47.385204 62.848968) Sky
+ Calculated pixel coord. with ftxypx status =   0
+  Sky ( 47.385204 62.848968) --> (  0.500000  0.500000) Pixels
+  
+ ftitab status =    0
+ ftpcl status =    0
+ Column values written with ftpcl and read with ftgcl: 
+   0  3  6  9 12 15 18 21 24 27  F (byte) 
+   0  3  6  9 12 15 18 21 24 27  F (short) 
+   0  3  6  9 12 15 18 21 24 27  F (int) 
+   0  3  6  9 12 15 18 21 24 27  F (long) 
+   0. 3. 6. 9.12.15.18.21.24.27. F (float) 
+   0. 3. 6. 9.12.15.18.21.24.27. F (double) 
+  
+ Repeatedly move to the 1st 4 HDUs of the file: 
+  
+ Encode checksum:  1234567890.0 -> dCW2fBU0dBU0dBU0 
+ Decode checksum: dCW2fBU0dBU0dBU0  ->  1234567890.0
+ DATASUM = '2338390162'
+ ftgcks data checksum, status =  2338390162.0   0
+ ftvcks datastatus, hdustatus, status =     1   1   0
+ ftupck status =    0
+ DATASUM = '2338390162'
+ ftvcks datastatus, hdustatus, status =     1   1   0
+ ftclos status =    0
+  
+ Normally, there should be 8 error messages on the
+ stack all regarding 'numerical overflows':
+ Numerical overflow during type conversion while writing FITS data.         
+ Numerical overflow during type conversion while writing FITS data.         
+ Numerical overflow during type conversion while writing FITS data.         
+ Numerical overflow during type conversion while writing FITS data.         
+ Numerical overflow during type conversion while writing FITS data.         
+ Numerical overflow during type conversion while writing FITS data.         
+ Numerical overflow during type conversion while writing FITS data.         
+ Numerical overflow during type conversion while writing FITS data.         
+  
+ Status =   0: OK - no error                                     
diff --git a/external/cfitsio/testf77.std b/external/cfitsio/testf77.std
new file mode 100644
index 0000000..24975a4
Binary files /dev/null and b/external/cfitsio/testf77.std differ
diff --git a/external/cfitsio/testprog.c b/external/cfitsio/testprog.c
new file mode 100644
index 0000000..5596f25
--- /dev/null
+++ b/external/cfitsio/testprog.c
@@ -0,0 +1,2588 @@
+#include 
+#include 
+#include "fitsio.h"
+int main(void);
+
+int main()
+{
+/*  
+    This is a big and complicated program that tests most of
+    the cfitsio routines.  This code does not represent
+    the most efficient method of reading or writing FITS files 
+    because this code is primarily designed to stress the cfitsio
+    library routines.
+*/
+    char asciisum[17];
+    unsigned long checksum, datsum;
+    int datastatus, hdustatus, filemode;
+    int status, simple, bitpix, naxis, extend, hdutype, hdunum, tfields;
+    long ii, jj, extvers;
+    int nkeys, nfound, colnum, typecode, signval,nmsg;
+    char cval, cvalstr[2];
+    long repeat, offset, width, jnulval;
+    int anynull;
+    float vers;
+    unsigned char xinarray[21], binarray[21], boutarray[21], bnul;
+    short         iinarray[21], ioutarray[21], inul;
+    int           kinarray[21], koutarray[21], knul;
+    long          jinarray[21], joutarray[21], jnul;
+    float         einarray[21], eoutarray[21], enul, cinarray[42];
+    double        dinarray[21], doutarray[21], dnul, minarray[42];
+    double scale, zero;
+    long naxes[3], pcount, gcount, npixels, nrows, rowlen, firstpix[3];
+    int existkeys, morekeys, keynum;
+
+    char larray[42], larray2[42], colname[70], tdisp[40], nulstr[40];
+    char oskey[] = "value_string";
+    char iskey[21];
+    int olkey = 1;
+    int ilkey;
+    short oshtkey, ishtkey;
+    long ojkey = 11, ijkey;
+    long otint = 12345678;
+    float ofkey = 12.121212;
+    float oekey = 13.131313, iekey;
+    double ogkey = 14.1414141414141414;
+    double odkey = 15.1515151515151515, idkey;
+    double otfrac = .1234567890123456;
+
+    double xrval,yrval,xrpix,yrpix,xinc,yinc,rot,xpos,ypos,xpix,ypix;
+    char xcoordtype[] = "RA---TAN";
+    char ycoordtype[] = "DEC--TAN";
+    char ctype[5];
+
+    char *lsptr;    /* pointer to long string value */
+    char  comm[73];
+    char *comms[3];
+    char *inskey[21];
+    char *onskey[3] = {"first string", "second string", "        "};
+    char *inclist[2] = {"key*", "newikys"};
+    char *exclist[2] = {"key_pr*", "key_pkls"};
+
+    int   onlkey[3] = {1, 0, 1}, inlkey[3];
+    long  onjkey[3] = {11, 12, 13}, injkey[3];
+    float onfkey[3] = {12.121212, 13.131313, 14.141414};
+    float onekey[3] = {13.131313, 14.141414, 15.151515}, inekey[3];
+    double ongkey[3] = {14.1414141414141414, 15.1515151515151515,
+           16.1616161616161616};
+    double ondkey[3] = {15.1515151515151515, 16.1616161616161616,
+           17.1717171717171717}, indkey[3];
+
+    long tbcol[5] = {1, 17, 28, 43, 56};
+
+    char filename[40], card[FLEN_CARD], card2[FLEN_CARD];
+    char keyword[FLEN_KEYWORD];
+    char value[FLEN_VALUE], comment[FLEN_COMMENT];
+    unsigned char uchars[80];
+
+    fitsfile *fptr, *tmpfptr;
+    char *ttype[10], *tform[10], *tunit[10];
+    char tblname[40];
+    char binname[] = "Test-BINTABLE";
+    char templt[] = "testprog.tpt";
+    char errmsg[FLEN_ERRMSG];
+    short imgarray[30][19], imgarray2[20][10];
+    long fpixels[2], lpixels[2], inc[2];
+
+    status = 0;
+    strcpy(tblname, "Test-ASCII");
+
+    ffvers(&vers);
+    printf("CFITSIO TESTPROG, v%.3f\n\n",vers);
+
+    printf("Try opening then closing a nonexistent file:\n");
+    fits_open_file(&fptr, "tq123x.kjl", READWRITE, &status);
+    printf("  ffopen fptr, status  = %lu %d (expect an error)\n", 
+           (unsigned long) fptr, status);
+    ffclos(fptr, &status);
+    printf("  ffclos status = %d\n\n", status);
+    ffcmsg();
+    status = 0;
+
+    for (ii = 0; ii < 21; ii++)  /* allocate space for string column value */
+        inskey[ii] = (char *) malloc(21);   
+
+    for (ii = 0; ii < 10; ii++)
+    {
+      ttype[ii] = (char *) malloc(20);
+      tform[ii] = (char *) malloc(20);
+      tunit[ii] = (char *) malloc(20);
+    }
+
+    comms[0] = comm;
+
+    /* delete previous version of the file, if it exists (with ! prefix) */
+    strcpy(filename, "!testprog.fit");
+
+    status = 0;
+
+    /*
+      #####################
+      #  create FITS file #
+      #####################
+    */
+
+    ffinit(&fptr, filename, &status);
+    printf("ffinit create new file status = %d\n", status);
+    if (status)
+        goto errstatus;
+
+    filename[0] = '\0';
+    ffflnm(fptr, filename, &status);
+
+    ffflmd(fptr, &filemode, &status);
+    printf("Name of file = %s, I/O mode = %d\n", filename, filemode);
+    simple = 1;
+    bitpix = 32;
+    naxis = 2;
+    naxes[0] = 10;
+    naxes[1] = 2;
+    npixels = 20;
+    pcount = 0;
+    gcount = 1;
+    extend = 1;
+    /*
+      ############################
+      #  write single keywords   #
+      ############################
+    */
+
+    if (ffphps(fptr, bitpix, naxis, naxes, &status) > 0)
+        printf("ffphps status = %d\n", status);
+
+    if (ffprec(fptr, 
+    "key_prec= 'This keyword was written by fxprec' / comment goes here", 
+     &status) > 0 )
+        printf("ffprec status = %d\n", status);
+
+    printf("\ntest writing of long string keywords:\n");
+    strcpy(card, "1234567890123456789012345678901234567890");
+    strcat(card, "12345678901234567890123456789012345");
+    ffpkys(fptr, "card1", card, "", &status);
+    ffgkey(fptr, "card1", card2, comment, &status);
+    printf(" %s\n%s\n", card, card2);
+    
+    strcpy(card, "1234567890123456789012345678901234567890");
+    strcat(card, "123456789012345678901234'6789012345");
+    ffpkys(fptr, "card2", card, "", &status);
+    ffgkey(fptr, "card2", card2, comment, &status);
+    printf(" %s\n%s\n", card, card2);
+    
+    strcpy(card, "1234567890123456789012345678901234567890");
+    strcat(card, "123456789012345678901234''789012345");
+    ffpkys(fptr, "card3", card, "", &status);
+    ffgkey(fptr, "card3", card2, comment, &status);
+    printf(" %s\n%s\n", card, card2);
+    
+    strcpy(card, "1234567890123456789012345678901234567890");
+    strcat(card, "123456789012345678901234567'9012345");
+    ffpkys(fptr, "card4", card, "", &status);
+    ffgkey(fptr, "card4", card2, comment, &status);
+    printf(" %s\n%s\n", card, card2);
+    
+    if (ffpkys(fptr, "key_pkys", oskey, "fxpkys comment", &status) > 0)
+        printf("ffpkys status = %d\n", status);
+
+    if (ffpkyl(fptr, "key_pkyl", olkey, "fxpkyl comment", &status) > 0)
+        printf("ffpkyl status = %d\n", status);
+
+    if (ffpkyj(fptr, "key_pkyj", ojkey, "fxpkyj comment", &status) > 0)
+        printf("ffpkyj status = %d\n", status);
+
+    if (ffpkyf(fptr, "key_pkyf", ofkey, 5, "fxpkyf comment", &status) > 0)
+        printf("ffpkyf status = %d\n", status);
+
+    if (ffpkye(fptr, "key_pkye", oekey, 6, "fxpkye comment", &status) > 0)
+        printf("ffpkye status = %d\n", status);
+
+    if (ffpkyg(fptr, "key_pkyg", ogkey, 14, "fxpkyg comment", &status) > 0)
+        printf("ffpkyg status = %d\n", status);
+
+    if (ffpkyd(fptr, "key_pkyd", odkey, 14, "fxpkyd comment", &status) > 0)
+        printf("ffpkyd status = %d\n", status);
+
+    if (ffpkyc(fptr, "key_pkyc", onekey, 6, "fxpkyc comment", &status) > 0)
+        printf("ffpkyc status = %d\n", status);
+
+    if (ffpkym(fptr, "key_pkym", ondkey, 14, "fxpkym comment", &status) > 0)
+        printf("ffpkym status = %d\n", status);
+
+    if (ffpkfc(fptr, "key_pkfc", onekey, 6, "fxpkfc comment", &status) > 0)
+        printf("ffpkfc status = %d\n", status);
+
+    if (ffpkfm(fptr, "key_pkfm", ondkey, 14, "fxpkfm comment", &status) > 0)
+        printf("ffpkfm status = %d\n", status);
+
+    if (ffpkls(fptr, "key_pkls", 
+"This is a very long string value that is continued over more than one keyword.",
+     "fxpkls comment", &status) > 0)
+        printf("ffpkls status = %d\n", status);
+
+    if (ffplsw(fptr, &status) > 0 )
+        printf("ffplsw status = %d\n", status);
+
+    if (ffpkyt(fptr, "key_pkyt", otint, otfrac, "fxpkyt comment", &status) > 0)
+        printf("ffpkyt status = %d\n", status);
+
+    if (ffpcom(fptr, "  This keyword was written by fxpcom.", &status) > 0)
+        printf("ffpcom status = %d\n", status);
+
+    if (ffphis(fptr, "    This keyword written by fxphis (w/ 2 leading spaces).",
+        &status) > 0)
+        printf("ffphis status = %d\n", status);
+
+    if (ffpdat(fptr, &status) > 0)
+    {
+        printf("ffpdat status = %d\n", status);
+        goto errstatus;
+    }
+
+    /*
+      ###############################
+      #  write arrays of keywords   #
+      ###############################
+    */
+    nkeys = 3;
+
+    comms[0] = comm;  /* use the inskey array of pointers for the comments */
+
+    strcpy(comm, "fxpkns comment&");
+    if (ffpkns(fptr, "ky_pkns", 1, nkeys, onskey, comms, &status) > 0)
+        printf("ffpkns status = %d\n", status);
+
+    strcpy(comm, "fxpknl comment&");
+    if (ffpknl(fptr, "ky_pknl", 1, nkeys, onlkey, comms, &status) > 0)
+        printf("ffpknl status = %d\n", status);
+
+    strcpy(comm, "fxpknj comment&");
+    if (ffpknj(fptr, "ky_pknj", 1, nkeys, onjkey, comms, &status) > 0)
+        printf("ffpknj status = %d\n", status);
+
+    strcpy(comm, "fxpknf comment&");
+    if (ffpknf(fptr, "ky_pknf", 1, nkeys, onfkey, 5, comms, &status) > 0)
+        printf("ffpknf status = %d\n", status);
+
+    strcpy(comm, "fxpkne comment&");
+    if (ffpkne(fptr, "ky_pkne", 1, nkeys, onekey, 6, comms, &status) > 0)
+        printf("ffpkne status = %d\n", status);
+
+    strcpy(comm, "fxpkng comment&");
+    if (ffpkng(fptr, "ky_pkng", 1, nkeys, ongkey, 13, comms, &status) > 0)
+        printf("ffpkng status = %d\n", status);
+
+    strcpy(comm, "fxpknd comment&");
+    if (ffpknd(fptr, "ky_pknd", 1, nkeys, ondkey, 14, comms, &status) > 0)
+    {
+        printf("ffpknd status = %d\n", status);
+        goto errstatus;
+    }
+    /*
+      ############################
+      #  write generic keywords  #
+      ############################
+    */
+
+    strcpy(oskey, "1");
+    if (ffpky(fptr, TSTRING, "tstring", oskey, "tstring comment", &status) > 0)
+        printf("ffpky status = %d\n", status);
+
+    olkey = TLOGICAL;
+    if (ffpky(fptr, TLOGICAL, "tlogical", &olkey, "tlogical comment",
+        &status) > 0)
+        printf("ffpky status = %d\n", status);
+
+    cval = TBYTE;
+    if (ffpky(fptr, TBYTE, "tbyte", &cval, "tbyte comment", &status) > 0)
+        printf("ffpky status = %d\n", status);
+
+    oshtkey = TSHORT;
+    if (ffpky(fptr, TSHORT, "tshort", &oshtkey, "tshort comment", &status) > 0)
+        printf("ffpky status = %d\n", status);
+
+    olkey = TINT;
+    if (ffpky(fptr, TINT, "tint", &olkey, "tint comment", &status) > 0)
+        printf("ffpky status = %d\n", status);
+
+    ojkey = TLONG;
+    if (ffpky(fptr, TLONG, "tlong", &ojkey, "tlong comment", &status) > 0)
+        printf("ffpky status = %d\n", status);
+
+    oekey = TFLOAT;
+    if (ffpky(fptr, TFLOAT, "tfloat", &oekey, "tfloat comment", &status) > 0)
+        printf("ffpky status = %d\n", status);
+
+    odkey = TDOUBLE;
+    if (ffpky(fptr, TDOUBLE, "tdouble", &odkey, "tdouble comment",
+              &status) > 0)
+        printf("ffpky status = %d\n", status);
+
+    /*
+      ############################
+      #  write data              #
+      ############################
+    */
+    /* define the null value (must do this before writing any data) */
+    if (ffpkyj(fptr, "BLANK", -99, "value to use for undefined pixels",
+       &status) > 0)
+        printf("BLANK keyword status = %d\n", status);
+
+    /* initialize arrays of values to write to primary array */
+    for (ii = 0; ii < npixels; ii++)
+    {
+        boutarray[ii] = ii + 1;
+        ioutarray[ii] = ii + 1;
+        joutarray[ii] = ii + 1;
+        eoutarray[ii] = ii + 1;
+        doutarray[ii] = ii + 1;
+    }
+
+    /* write a few pixels with each datatype */
+    /* set the last value in each group of 4 as undefined */
+
+/*
+    ffpprb(fptr, 1,  1, 2, &boutarray[0],  &status);
+    ffppri(fptr, 1,  5, 2, &ioutarray[4],  &status);
+    ffpprj(fptr, 1,  9, 2, &joutarray[8],  &status);
+    ffppre(fptr, 1, 13, 2, &eoutarray[12], &status);
+    ffpprd(fptr, 1, 17, 2, &doutarray[16], &status);
+*/
+
+/*  test the newer ffpx routine, instead of the older ffppr_ routines */
+    firstpix[0]=1;
+    firstpix[1]=1;
+    ffppx(fptr, TBYTE, firstpix, 2, &boutarray[0],  &status);
+    firstpix[0]=5;
+    ffppx(fptr, TSHORT, firstpix, 2, &ioutarray[4],  &status);
+    firstpix[0]=9;
+    ffppx(fptr, TLONG, firstpix, 2, &joutarray[8],  &status);
+    firstpix[0]=3;
+    firstpix[1]=2;
+    ffppx(fptr, TFLOAT, firstpix, 2, &eoutarray[12],  &status);
+    firstpix[0]=7;
+    ffppx(fptr, TDOUBLE, firstpix, 2, &doutarray[16],  &status);
+
+/*
+    ffppnb(fptr, 1,  3, 2, &boutarray[2],   4, &status);
+    ffppni(fptr, 1,  7, 2, &ioutarray[6],   8, &status);
+    ffppnj(fptr, 1, 11, 2, &joutarray[10],  12, &status);
+    ffppne(fptr, 1, 15, 2, &eoutarray[14], 16., &status);
+    ffppnd(fptr, 1, 19, 2, &doutarray[18], 20., &status);
+*/
+    firstpix[0]=3;
+    firstpix[1]=1;
+    bnul = 4;
+    ffppxn(fptr, TBYTE, firstpix, 2, &boutarray[2], &bnul,  &status);
+    firstpix[0]=7;
+    inul = 8;
+    ffppxn(fptr, TSHORT, firstpix, 2, &ioutarray[6], &inul, &status);
+    firstpix[0]=1;
+    firstpix[1]=2;
+    jnul = 12;
+    ffppxn(fptr, TLONG, firstpix, 2, &joutarray[10], &jnul, &status);
+    firstpix[0]=5;
+    enul = 16.;
+    ffppxn(fptr, TFLOAT, firstpix, 2, &eoutarray[14], &enul,  &status);
+    firstpix[0]=9;
+    dnul = 20.;
+    ffppxn(fptr, TDOUBLE, firstpix, 2, &doutarray[18], &dnul, &status);
+
+    ffppru(fptr, 1, 1, 1, &status);
+
+
+    if (status > 0)
+    {
+        printf("ffppnx status = %d\n", status);
+        goto errstatus;
+    }
+
+    ffflus(fptr, &status);   /* flush all data to the disk file */ 
+    printf("ffflus status = %d\n", status);
+    printf("HDU number = %d\n", ffghdn(fptr, &hdunum));
+
+    /*
+      ############################
+      #  read data               #
+      ############################
+    */
+    /* read back the data, setting null values = 99 */
+    printf("\nValues read back from primary array (99 = null pixel)\n");
+    printf("The 1st, and every 4th pixel should be undefined:\n");
+
+    anynull = 0;
+    ffgpvb(fptr, 1,  1, 10, 99, binarray, &anynull, &status); 
+
+    ffgpvb(fptr, 1, 11, 10, 99, &binarray[10], &anynull, &status);
+
+    for (ii = 0; ii < npixels; ii++)
+        printf(" %2d", binarray[ii]);
+    printf("  %d (ffgpvb)\n", anynull);  
+
+    ffgpvi(fptr, 1, 1, npixels, 99,   iinarray, &anynull, &status);
+
+    for (ii = 0; ii < npixels; ii++)
+        printf(" %2d", iinarray[ii]);
+    printf("  %d (ffgpvi)\n", anynull);  
+
+    ffgpvj(fptr, 1, 1, npixels, 99,  jinarray, &anynull, &status);
+
+    for (ii = 0; ii < npixels; ii++)
+        printf(" %2ld", jinarray[ii]);
+    printf("  %d (ffgpvj)\n", anynull);  
+
+    ffgpve(fptr, 1, 1, npixels, 99., einarray, &anynull, &status);
+
+    for (ii = 0; ii < npixels; ii++)
+        printf(" %2.0f", einarray[ii]);
+    printf("  %d (ffgpve)\n", anynull);  
+
+    ffgpvd(fptr, 1,  1, 10, 99.,  dinarray, &anynull, &status);
+    ffgpvd(fptr, 1, 11, 10, 99.,  &dinarray[10], &anynull, &status);
+
+    for (ii = 0; ii < npixels; ii++)
+        printf(" %2.0f", dinarray[ii]);
+    printf("  %d (ffgpvd)\n", anynull);  
+
+    if (status > 0)
+    {
+        printf("ERROR: ffgpv_ status = %d\n", status);
+        goto errstatus;
+    }
+    if (anynull == 0)
+       printf("ERROR: ffgpv_ did not detect null values\n");
+
+    /* reset the output null value to the expected input value */
+    for (ii = 3; ii < npixels; ii += 4)
+    {
+        boutarray[ii] = 99;
+        ioutarray[ii] = 99;
+        joutarray[ii] = 99;
+        eoutarray[ii] = 99.;
+        doutarray[ii] = 99.;
+    }
+        ii = 0;
+        boutarray[ii] = 99;
+        ioutarray[ii] = 99;
+        joutarray[ii] = 99;
+        eoutarray[ii] = 99.;
+        doutarray[ii] = 99.;
+
+    /* compare the output with the input; flag any differences */
+    for (ii = 0; ii < npixels; ii++)
+    {
+       if (boutarray[ii] != binarray[ii])
+           printf("bout != bin = %u %u \n", boutarray[ii], binarray[ii]);
+
+       if (ioutarray[ii] != iinarray[ii])
+           printf("iout != iin = %d %d \n", ioutarray[ii], iinarray[ii]);
+
+       if (joutarray[ii] != jinarray[ii])
+           printf("jout != jin = %ld %ld \n", joutarray[ii], jinarray[ii]);
+
+       if (eoutarray[ii] != einarray[ii])
+           printf("eout != ein = %f %f \n", eoutarray[ii], einarray[ii]);
+
+       if (doutarray[ii] != dinarray[ii])
+           printf("dout != din = %f %f \n", doutarray[ii], dinarray[ii]);
+    }
+
+    for (ii = 0; ii < npixels; ii++)
+    {
+      binarray[ii] = 0;
+      iinarray[ii] = 0;
+      jinarray[ii] = 0;
+      einarray[ii] = 0.;
+      dinarray[ii] = 0.;
+    }
+
+    anynull = 0;
+    ffgpfb(fptr, 1,  1, 10, binarray, larray, &anynull, &status);
+    ffgpfb(fptr, 1, 11, 10, &binarray[10], &larray[10], &anynull, &status);
+
+    for (ii = 0; ii < npixels; ii++)
+      if (larray[ii])
+        printf("  *");
+      else
+        printf(" %2d", binarray[ii]);
+    printf("  %d (ffgpfb)\n", anynull);  
+
+    ffgpfi(fptr, 1, 1, npixels, iinarray, larray, &anynull, &status);
+
+    for (ii = 0; ii < npixels; ii++)
+      if (larray[ii])
+        printf("  *");
+      else
+        printf(" %2d", iinarray[ii]);
+    printf("  %d (ffgpfi)\n", anynull);  
+
+    ffgpfj(fptr, 1, 1, npixels, jinarray, larray, &anynull, &status);
+
+    for (ii = 0; ii < npixels; ii++)
+      if (larray[ii])
+        printf("  *");
+      else
+        printf(" %2ld", jinarray[ii]);
+    printf("  %d (ffgpfj)\n", anynull);  
+
+    ffgpfe(fptr, 1, 1, npixels, einarray, larray, &anynull, &status);
+
+    for (ii = 0; ii < npixels; ii++)
+      if (larray[ii])
+        printf("  *");
+      else
+        printf(" %2.0f", einarray[ii]);
+    printf("  %d (ffgpfe)\n", anynull);  
+
+    ffgpfd(fptr, 1,  1, 10, dinarray, larray, &anynull, &status);
+    ffgpfd(fptr, 1, 11, 10, &dinarray[10], &larray[10], &anynull, &status);
+
+    for (ii = 0; ii < npixels; ii++)
+      if (larray[ii])
+        printf("  *");
+      else
+        printf(" %2.0f", dinarray[ii]);
+    printf("  %d (ffgpfd)\n", anynull);  
+
+    if (status > 0)
+    {
+        printf("ERROR: ffgpf_ status = %d\n", status);
+        goto errstatus;
+    }
+    if (anynull == 0)
+       printf("ERROR: ffgpf_ did not detect null values\n");
+
+
+    /*
+      ##########################################
+      #  close and reopen file multiple times  #
+      ##########################################
+    */
+
+    for (ii = 0; ii < 10; ii++)
+    {
+      if (ffclos(fptr, &status) > 0)
+      {
+        printf("ERROR in ftclos (1) = %d", status);
+        goto errstatus;
+      }
+
+      if (ffopen(&fptr, filename, READWRITE, &status) > 0)
+      {
+        printf("ERROR: ffopen open file status = %d\n", status);
+        goto errstatus;
+      }
+    }
+    printf("\nClosed then reopened the FITS file 10 times.\n");
+    printf("HDU number = %d\n", ffghdn(fptr, &hdunum));
+
+    filename[0] = '\0';
+    ffflnm(fptr, filename, &status);
+
+    ffflmd(fptr, &filemode, &status);
+    printf("Name of file = %s, I/O mode = %d\n", filename, filemode);
+
+    /*
+      ############################
+      #  read single keywords    #
+      ############################
+    */
+
+    simple = 0;
+    bitpix = 0;
+    naxis = 0;
+    naxes[0] = 0;
+    naxes[1] = 0;
+    pcount = -99;
+    gcount =  -99;
+    extend = -99;
+    printf("\nRead back keywords:\n");
+    ffghpr(fptr, 99, &simple, &bitpix, &naxis, naxes, &pcount,
+           &gcount, &extend, &status);
+    printf("simple = %d, bitpix = %d, naxis = %d, naxes = (%ld, %ld)\n",
+           simple, bitpix, naxis, naxes[0], naxes[1]);
+    printf("  pcount = %ld, gcount = %ld, extend = %d\n",
+               pcount, gcount, extend);
+
+    ffgrec(fptr, 9, card, &status);
+    printf("%s\n", card);
+    if (strncmp(card, "KEY_PREC= 'This", 15) )
+       printf("ERROR in ffgrec\n");
+
+    ffgkyn(fptr, 9, keyword, value, comment, &status);
+    printf("%s : %s : %s :\n",keyword, value, comment);
+    if (strncmp(keyword, "KEY_PREC", 8) )
+       printf("ERROR in ffgkyn: %s\n", keyword);
+
+    ffgcrd(fptr, keyword, card, &status);
+    printf("%s\n", card);
+
+    if (strncmp(keyword, card, 8) )
+       printf("ERROR in ffgcrd: %s\n", keyword);
+
+    ffgkey(fptr, "KY_PKNS1", value, comment, &status);
+    printf("KY_PKNS1 : %s : %s :\n", value, comment);
+
+    if (strncmp(value, "'first string'", 14) )
+       printf("ERROR in ffgkey: %s\n", value);
+
+    ffgkys(fptr, "key_pkys", iskey, comment, &status);
+    printf("KEY_PKYS %s %s %d\n", iskey, comment, status);
+
+    ffgkyl(fptr, "key_pkyl", &ilkey, comment, &status);
+    printf("KEY_PKYL %d %s %d\n", ilkey, comment, status);
+
+    ffgkyj(fptr, "KEY_PKYJ", &ijkey, comment, &status);
+    printf("KEY_PKYJ %ld %s %d\n",ijkey, comment, status);
+
+    ffgkye(fptr, "KEY_PKYJ", &iekey, comment, &status);
+    printf("KEY_PKYJ %f %s %d\n",iekey, comment, status);
+
+    ffgkyd(fptr, "KEY_PKYJ", &idkey, comment, &status);
+    printf("KEY_PKYJ %f %s %d\n",idkey, comment, status);
+
+    if (ijkey != 11 || iekey != 11. || idkey != 11.)
+       printf("ERROR in ffgky[jed]: %ld, %f, %f\n",ijkey, iekey, idkey);
+
+    iskey[0] = '\0';
+    ffgky(fptr, TSTRING, "key_pkys", iskey, comment, &status);
+    printf("KEY_PKY S %s %s %d\n", iskey, comment, status);
+
+    ilkey = 0;
+    ffgky(fptr, TLOGICAL, "key_pkyl", &ilkey, comment, &status);
+    printf("KEY_PKY L %d %s %d\n", ilkey, comment, status);
+
+    ffgky(fptr, TBYTE, "KEY_PKYJ", &cval, comment, &status);
+    printf("KEY_PKY BYTE %d %s %d\n",cval, comment, status);
+
+    ffgky(fptr, TSHORT, "KEY_PKYJ", &ishtkey, comment, &status);
+    printf("KEY_PKY SHORT %d %s %d\n",ishtkey, comment, status);
+
+    ffgky(fptr, TINT, "KEY_PKYJ", &ilkey, comment, &status);
+    printf("KEY_PKY INT %d %s %d\n",ilkey, comment, status);
+
+    ijkey = 0;
+    ffgky(fptr, TLONG, "KEY_PKYJ", &ijkey, comment, &status);
+    printf("KEY_PKY J %ld %s %d\n",ijkey, comment, status);
+
+    iekey = 0;
+    ffgky(fptr, TFLOAT, "KEY_PKYE", &iekey, comment, &status);
+    printf("KEY_PKY E %f %s %d\n",iekey, comment, status);
+
+    idkey = 0;
+    ffgky(fptr, TDOUBLE, "KEY_PKYD", &idkey, comment, &status);
+    printf("KEY_PKY D %f %s %d\n",idkey, comment, status);
+
+    ffgkyd(fptr, "KEY_PKYF", &idkey, comment, &status);
+    printf("KEY_PKYF %f %s %d\n",idkey, comment, status);
+
+    ffgkyd(fptr, "KEY_PKYE", &idkey, comment, &status);
+    printf("KEY_PKYE %f %s %d\n",idkey, comment, status);
+
+    ffgkyd(fptr, "KEY_PKYG", &idkey, comment, &status);
+    printf("KEY_PKYG %.14f %s %d\n",idkey, comment, status);
+
+    ffgkyd(fptr, "KEY_PKYD", &idkey, comment, &status);
+    printf("KEY_PKYD %.14f %s %d\n",idkey, comment, status);
+
+    ffgkyc(fptr, "KEY_PKYC", inekey, comment, &status);
+    printf("KEY_PKYC %f %f %s %d\n",inekey[0], inekey[1], comment, status);
+
+    ffgkyc(fptr, "KEY_PKFC", inekey, comment, &status);
+    printf("KEY_PKFC %f %f %s %d\n",inekey[0], inekey[1], comment, status);
+
+    ffgkym(fptr, "KEY_PKYM", indkey, comment, &status);
+    printf("KEY_PKYM %f %f %s %d\n",indkey[0], indkey[1], comment, status);
+
+    ffgkym(fptr, "KEY_PKFM", indkey, comment, &status);
+    printf("KEY_PKFM %f %f %s %d\n",indkey[0], indkey[1], comment, status);
+
+    ffgkyt(fptr, "KEY_PKYT", &ijkey, &idkey, comment, &status);
+    printf("KEY_PKYT %ld %.14f %s %d\n",ijkey, idkey, comment, status);
+
+    ffpunt(fptr, "KEY_PKYJ", "km/s/Mpc", &status);
+    ijkey = 0;
+    ffgky(fptr, TLONG, "KEY_PKYJ", &ijkey, comment, &status);
+    printf("KEY_PKY J %ld %s %d\n",ijkey, comment, status);
+    ffgunt(fptr,"KEY_PKYJ", comment, &status);
+    printf("KEY_PKY units = %s\n",comment);
+
+    ffpunt(fptr, "KEY_PKYJ", "", &status);
+    ijkey = 0;
+    ffgky(fptr, TLONG, "KEY_PKYJ", &ijkey, comment, &status);
+    printf("KEY_PKY J %ld %s %d\n",ijkey, comment, status);
+    ffgunt(fptr,"KEY_PKYJ", comment, &status);
+    printf("KEY_PKY units = %s\n",comment);
+
+    ffpunt(fptr, "KEY_PKYJ", "feet/second/second", &status);
+    ijkey = 0;
+    ffgky(fptr, TLONG, "KEY_PKYJ", &ijkey, comment, &status);
+    printf("KEY_PKY J %ld %s %d\n",ijkey, comment, status);
+    ffgunt(fptr,"KEY_PKYJ", comment, &status);
+    printf("KEY_PKY units = %s\n",comment);
+
+    ffgkls(fptr, "key_pkls", &lsptr, comment, &status);
+    printf("KEY_PKLS long string value = \n%s\n", lsptr);
+
+    /* free the memory for the long string value */
+    fits_free_memory(lsptr, &status);
+
+    /* get size and position in header */
+    ffghps(fptr, &existkeys, &keynum, &status);
+    printf("header contains %d keywords; located at keyword %d \n",existkeys,
+            keynum);
+
+    /*
+      ############################
+      #  read array keywords     #
+      ############################
+    */
+    ffgkns(fptr, "ky_pkns", 1, 3, inskey, &nfound, &status);
+    printf("ffgkns:  %s, %s, %s\n", inskey[0], inskey[1], inskey[2]);
+    if (nfound != 3 || status > 0)
+       printf("\nERROR in ffgkns %d, %d\n", nfound, status);
+
+    ffgknl(fptr, "ky_pknl", 1, 3, inlkey, &nfound, &status);
+    printf("ffgknl:  %d, %d, %d\n", inlkey[0], inlkey[1], inlkey[2]);
+    if (nfound != 3 || status > 0)
+       printf("\nERROR in ffgknl %d, %d\n", nfound, status);
+
+    ffgknj(fptr, "ky_pknj", 1, 3, injkey, &nfound, &status);
+    printf("ffgknj:  %ld, %ld, %ld\n", injkey[0], injkey[1], injkey[2]);
+    if (nfound != 3 || status > 0)
+       printf("\nERROR in ffgknj %d, %d\n", nfound, status);
+
+    ffgkne(fptr, "ky_pkne", 1, 3, inekey, &nfound, &status);
+    printf("ffgkne:  %f, %f, %f\n", inekey[0], inekey[1], inekey[2]);
+    if (nfound != 3 || status > 0)
+       printf("\nERROR in ffgkne %d, %d\n", nfound, status);
+
+    ffgknd(fptr, "ky_pknd", 1, 3, indkey, &nfound, &status);
+    printf("ffgknd:  %f, %f, %f\n", indkey[0], indkey[1], indkey[2]);
+    if (nfound != 3 || status > 0)
+       printf("\nERROR in ffgknd %d, %d\n", nfound, status);
+
+    /* get position of HISTORY keyword for subsequent deletes and inserts */
+    ffgcrd(fptr, "HISTORY", card, &status);
+    ffghps(fptr, &existkeys, &keynum, &status);
+    keynum -= 2;
+
+    printf("\nBefore deleting the HISTORY and DATE keywords...\n");
+    for (ii = keynum; ii <= keynum + 3; ii++)
+    {
+        ffgrec(fptr, ii, card, &status);
+        printf("%.8s\n", card);  /* don't print date value, so that */
+    }                            /* the output will always be the same */
+    /*
+      ############################
+      #  delete keywords         #
+      ############################
+    */
+
+    ffdrec(fptr, keynum + 1, &status);
+    ffdkey(fptr, "DATE", &status);
+
+    printf("\nAfter deleting the keywords...\n");
+    for (ii = keynum; ii <= keynum + 1; ii++)
+    {
+        ffgrec(fptr, ii, card, &status);
+        printf("%s\n", card);
+    }
+
+    if (status > 0)
+       printf("\nERROR deleting keywords\n");
+    /*
+      ############################
+      #  insert keywords         #
+      ############################
+    */
+    keynum += 4;
+    ffirec(fptr, keynum - 3, "KY_IREC = 'This keyword inserted by fxirec'",
+           &status);
+    ffikys(fptr, "KY_IKYS", "insert_value_string", "ikys comment", &status);
+    ffikyj(fptr, "KY_IKYJ", 49, "ikyj comment", &status);
+    ffikyl(fptr, "KY_IKYL", 1, "ikyl comment", &status);
+    ffikye(fptr, "KY_IKYE", 12.3456, 4, "ikye comment", &status);
+    ffikyd(fptr, "KY_IKYD", 12.345678901234567, 14, "ikyd comment", &status);
+    ffikyf(fptr, "KY_IKYF", 12.3456, 4, "ikyf comment", &status);
+    ffikyg(fptr, "KY_IKYG", 12.345678901234567, 13, "ikyg comment", &status);
+
+    printf("\nAfter inserting the keywords...\n");
+    for (ii = keynum - 4; ii <= keynum + 5; ii++)
+    {
+        ffgrec(fptr, ii, card, &status);
+        printf("%s\n", card);
+    }
+
+    if (status > 0)
+       printf("\nERROR inserting keywords\n");
+    /*
+      ############################
+      #  modify keywords         #
+      ############################
+    */
+    ffmrec(fptr, keynum - 4, "COMMENT   This keyword was modified by fxmrec", &status);
+    ffmcrd(fptr, "KY_IREC", "KY_MREC = 'This keyword was modified by fxmcrd'",
+            &status);
+    ffmnam(fptr, "KY_IKYS", "NEWIKYS", &status);
+
+    ffmcom(fptr, "KY_IKYJ","This is a modified comment", &status);
+    ffmkyj(fptr, "KY_IKYJ", 50, "&", &status);
+    ffmkyl(fptr, "KY_IKYL", 0, "&", &status);
+    ffmkys(fptr, "NEWIKYS", "modified_string", "&", &status);
+    ffmkye(fptr, "KY_IKYE", -12.3456, 4, "&", &status);
+    ffmkyd(fptr, "KY_IKYD", -12.345678901234567, 14, "modified comment",
+            &status);
+    ffmkyf(fptr, "KY_IKYF", -12.3456, 4, "&", &status);
+    ffmkyg(fptr, "KY_IKYG", -12.345678901234567, 13, "&", &status);
+
+    printf("\nAfter modifying the keywords...\n");
+    for (ii = keynum - 4; ii <= keynum + 5; ii++)
+    {
+        ffgrec(fptr, ii, card, &status);
+        printf("%s\n", card);
+    }
+    if (status > 0)
+       printf("\nERROR modifying keywords\n");
+
+    /*
+      ############################
+      #  update keywords         #
+      ############################
+    */
+    ffucrd(fptr, "KY_MREC", "KY_UCRD = 'This keyword was updated by fxucrd'",
+            &status);
+
+    ffukyj(fptr, "KY_IKYJ", 51, "&", &status);
+    ffukyl(fptr, "KY_IKYL", 1, "&", &status);
+    ffukys(fptr, "NEWIKYS", "updated_string", "&", &status);
+    ffukye(fptr, "KY_IKYE", -13.3456, 4, "&", &status);
+    ffukyd(fptr, "KY_IKYD", -13.345678901234567, 14, "modified comment",
+            &status);
+    ffukyf(fptr, "KY_IKYF", -13.3456, 4, "&", &status);
+    ffukyg(fptr, "KY_IKYG", -13.345678901234567, 13, "&", &status);
+
+    printf("\nAfter updating the keywords...\n");
+    for (ii = keynum - 4; ii <= keynum + 5; ii++)
+    {
+        ffgrec(fptr, ii, card, &status);
+        printf("%s\n", card);
+    }
+    if (status > 0)
+       printf("\nERROR modifying keywords\n");
+
+    /* move to top of header and find keywords using wild cards */
+    ffgrec(fptr, 0, card, &status);
+
+    printf("\nKeywords found using wildcard search (should be 13)...\n");
+    nfound = 0;
+    while (!ffgnxk(fptr,inclist, 2, exclist, 2, card, &status))
+    {
+        nfound++;
+        printf("%s\n", card);
+    }
+    if (nfound != 13)
+    {
+       printf("\nERROR reading keywords using wildcards (ffgnxk)\n");
+       goto errstatus;
+    }
+    status = 0;
+
+    /*
+      ############################
+      #  copy index keyword      #
+      ############################
+    */
+    ffcpky(fptr, fptr, 1, 4, "KY_PKNE", &status);
+    ffgkne(fptr, "ky_pkne", 2, 4, inekey, &nfound, &status);
+    printf("\nCopied keyword: ffgkne:  %f, %f, %f\n", inekey[0], inekey[1],
+           inekey[2]);
+
+    if (status > 0)
+    {
+       printf("\nERROR in ffgkne %d, %d\n", nfound, status);
+       goto errstatus;
+    }
+
+    /*
+      ######################################
+      #  modify header using template file #
+      ######################################
+    */
+    if (ffpktp(fptr, templt, &status))
+    {
+       printf("\nERROR returned by ffpktp:\n");
+       printf("Could not open or process the file 'testprog.tpt'.\n");
+       printf("  This file is included with the CFITSIO distribution\n");
+       printf("  and should be copied into the current directory\n");
+       printf("  before running the testprog program.\n");
+       status = 0;
+    }
+    printf("Updated header using template file (ffpktp)\n");
+    /*
+      ############################
+      #  create binary table     #
+      ############################
+    */
+
+    strcpy(tform[0], "15A");
+    strcpy(tform[1], "1L");
+    strcpy(tform[2], "16X");
+    strcpy(tform[3], "1B");
+    strcpy(tform[4], "1I");
+    strcpy(tform[5], "1J");
+    strcpy(tform[6], "1E");
+    strcpy(tform[7], "1D");
+    strcpy(tform[8], "1C");
+    strcpy(tform[9], "1M");
+
+    strcpy(ttype[0], "Avalue");
+    strcpy(ttype[1], "Lvalue");
+    strcpy(ttype[2], "Xvalue");
+    strcpy(ttype[3], "Bvalue");
+    strcpy(ttype[4], "Ivalue");
+    strcpy(ttype[5], "Jvalue");
+    strcpy(ttype[6], "Evalue");
+    strcpy(ttype[7], "Dvalue");
+    strcpy(ttype[8], "Cvalue");
+    strcpy(ttype[9], "Mvalue");
+
+    strcpy(tunit[0], "");
+    strcpy(tunit[1], "m**2");
+    strcpy(tunit[2], "cm");
+    strcpy(tunit[3], "erg/s");
+    strcpy(tunit[4], "km/s");
+    strcpy(tunit[5], "");
+    strcpy(tunit[6], "");
+    strcpy(tunit[7], "");
+    strcpy(tunit[8], "");
+    strcpy(tunit[9], "");
+
+    nrows = 21;
+    tfields = 10;
+    pcount = 0;
+
+/*
+    ffcrtb(fptr, BINARY_TBL, nrows, tfields, ttype, tform, tunit, binname,
+            &status);
+*/
+    ffibin(fptr, nrows, tfields, ttype, tform, tunit, binname, 0L,
+            &status);
+
+    printf("\nffibin status = %d\n", status);
+    printf("HDU number = %d\n", ffghdn(fptr, &hdunum));
+
+    /* get size and position in header, and reserve space for more keywords */
+    ffghps(fptr, &existkeys, &keynum, &status);
+    printf("header contains %d keywords; located at keyword %d \n",existkeys,
+            keynum);
+
+    morekeys = 40;
+    ffhdef(fptr, morekeys, &status);
+    ffghsp(fptr, &existkeys, &morekeys, &status);
+    printf("header contains %d keywords with room for %d more\n",existkeys,
+            morekeys);
+
+    fftnul(fptr, 4, 99, &status);   /* define null value for int cols */
+    fftnul(fptr, 5, 99, &status);
+    fftnul(fptr, 6, 99, &status);
+
+    extvers = 1;
+    ffpkyj(fptr, "EXTVER", extvers, "extension version number", &status);
+    ffpkyj(fptr, "TNULL4", 99, "value for undefined pixels", &status);
+    ffpkyj(fptr, "TNULL5", 99, "value for undefined pixels", &status);
+    ffpkyj(fptr, "TNULL6", 99, "value for undefined pixels", &status);
+
+    naxis = 3;
+    naxes[0] = 1;
+    naxes[1] = 2;
+    naxes[2] = 8;
+    ffptdm(fptr, 3, naxis, naxes, &status);
+
+    naxis = 0;
+    naxes[0] = 0;
+    naxes[1] = 0;
+    naxes[2] = 0;
+    ffgtdm(fptr, 3, 3, &naxis, naxes, &status);
+    ffgkys(fptr, "TDIM3", iskey, comment, &status);
+    printf("TDIM3 = %s, %d, %ld, %ld, %ld\n", iskey, naxis, naxes[0],
+         naxes[1], naxes[2]);
+
+    ffrdef(fptr, &status);  /* force header to be scanned (not required) */
+
+    /*
+      ############################
+      #  write data to columns   #
+      ############################
+    */
+
+    /* initialize arrays of values to write to table */
+    signval = -1;
+    for (ii = 0; ii < 21; ii++)
+    {
+        signval *= -1;
+        boutarray[ii] = (ii + 1);
+        ioutarray[ii] = (ii + 1) * signval;
+        joutarray[ii] = (ii + 1) * signval;
+        koutarray[ii] = (ii + 1) * signval;
+        eoutarray[ii] = (ii + 1) * signval;
+        doutarray[ii] = (ii + 1) * signval;
+    }
+
+    ffpcls(fptr, 1, 1, 1, 3, onskey, &status);  /* write string values */
+    ffpclu(fptr, 1, 4, 1, 1, &status);  /* write null value */
+
+    larray[0] = 0;
+    larray[1] = 1;
+    larray[2] = 0;
+    larray[3] = 0;
+    larray[4] = 1;
+    larray[5] = 1;
+    larray[6] = 0;
+    larray[7] = 0;
+    larray[8] = 0;
+    larray[9] = 1;
+    larray[10] = 1;
+    larray[11] = 1;
+    larray[12] = 0;
+    larray[13] = 0;
+    larray[14] = 0;
+    larray[15] = 0;
+    larray[16] = 1;
+    larray[17] = 1;
+    larray[18] = 1;
+    larray[19] = 1;
+    larray[20] = 0;
+    larray[21] = 0;
+    larray[22] = 0;
+    larray[23] = 0;
+    larray[24] = 0;
+    larray[25] = 1;
+    larray[26] = 1;
+    larray[27] = 1;
+    larray[28] = 1;
+    larray[29] = 1;
+    larray[30] = 0;
+    larray[31] = 0;
+    larray[32] = 0;
+    larray[33] = 0;
+    larray[34] = 0;
+    larray[35] = 0;
+
+
+    ffpclx(fptr, 3, 1, 1, 36, larray, &status); /*write bits*/
+
+    for (ii = 4; ii < 9; ii++)   /* loop over cols 4 - 8 */
+    {
+        ffpclb(fptr, ii, 1, 1, 2, boutarray, &status);
+        if (status == NUM_OVERFLOW)
+            status = 0;
+        ffpcli(fptr, ii, 3, 1, 2, &ioutarray[2], &status); 
+        if (status == NUM_OVERFLOW)
+            status = 0;
+        ffpclk(fptr, ii, 5, 1, 2, &koutarray[4], &status); 
+        if (status == NUM_OVERFLOW)
+            status = 0;
+        ffpcle(fptr, ii, 7, 1, 2, &eoutarray[6], &status);
+        if (status == NUM_OVERFLOW)
+            status = 0;
+        ffpcld(fptr, ii, 9, 1, 2, &doutarray[8], &status);
+        if (status == NUM_OVERFLOW)
+            status = 0;
+
+        ffpclu(fptr, ii, 11, 1, 1, &status);  /* write null value */
+    }
+
+    ffpclc(fptr, 9, 1, 1, 10, eoutarray, &status);
+    ffpclm(fptr, 10, 1, 1, 10, doutarray, &status);
+
+    for (ii = 4; ii < 9; ii++)   /* loop over cols 4 - 8 */
+    {
+        ffpcnb(fptr, ii, 12, 1, 2, &boutarray[11], 13, &status);
+        if (status == NUM_OVERFLOW)
+            status = 0;
+        ffpcni(fptr, ii, 14, 1, 2, &ioutarray[13], 15, &status); 
+        if (status == NUM_OVERFLOW)
+            status = 0;
+        ffpcnk(fptr, ii, 16, 1, 2, &koutarray[15], 17, &status); 
+        if (status == NUM_OVERFLOW)
+            status = 0;
+        ffpcne(fptr, ii, 18, 1, 2, &eoutarray[17], 19., &status);
+        if (status == NUM_OVERFLOW)
+            status = 0;
+        ffpcnd(fptr, ii, 20, 1, 2, &doutarray[19], 21., &status);
+        if (status == NUM_OVERFLOW)
+            status = 0;
+
+    }
+    ffpcll(fptr, 2, 1, 1, 21, larray, &status); /*write logicals*/
+    ffpclu(fptr, 2, 11, 1, 1, &status);  /* write null value */
+    printf("ffpcl_ status = %d\n", status);
+
+    /*
+      #########################################
+      #  get information about the columns    #
+      #########################################
+    */
+
+    printf("\nFind the column numbers; a returned status value of 237 is");
+    printf("\nexpected and indicates that more than one column name matches");
+    printf("\nthe input column name template.  Status = 219 indicates that");
+    printf("\nthere was no matching column name.");
+
+    ffgcno(fptr, 0, "Xvalue", &colnum, &status);
+    printf("\nColumn Xvalue is number %d; status = %d.\n", colnum, status);
+
+    while (status != COL_NOT_FOUND)
+    {
+      ffgcnn(fptr, 1, "*ue", colname, &colnum, &status);
+      printf("Column %s is number %d; status = %d.\n", 
+           colname, colnum, status);
+    }
+    status = 0;
+
+    printf("\nInformation about each column:\n");
+
+    for (ii = 0; ii < tfields; ii++)
+    {
+      ffgtcl(fptr, ii + 1, &typecode, &repeat, &width, &status);
+      printf("%4s %3d %2ld %2ld", tform[ii], typecode, repeat, width);
+      ffgbcl(fptr, ii + 1, ttype[0], tunit[0], cvalstr, &repeat, &scale,
+           &zero, &jnulval, tdisp, &status);
+      printf(" %s, %s, %c, %ld, %f, %f, %ld, %s.\n",
+         ttype[0], tunit[0], cvalstr[0], repeat, scale, zero, jnulval, tdisp);
+    }
+
+    printf("\n");
+
+    /*
+      ###############################################
+      #  insert ASCII table before the binary table #
+      ###############################################
+    */
+
+    if (ffmrhd(fptr, -1, &hdutype, &status) > 0)
+        goto errstatus;
+
+    strcpy(tform[0], "A15");
+    strcpy(tform[1], "I10");
+    strcpy(tform[2], "F14.6");
+    strcpy(tform[3], "E12.5");
+    strcpy(tform[4], "D21.14");
+
+    strcpy(ttype[0], "Name");
+    strcpy(ttype[1], "Ivalue");
+    strcpy(ttype[2], "Fvalue");
+    strcpy(ttype[3], "Evalue");
+    strcpy(ttype[4], "Dvalue");
+
+    strcpy(tunit[0], "");
+    strcpy(tunit[1], "m**2");
+    strcpy(tunit[2], "cm");
+    strcpy(tunit[3], "erg/s");
+    strcpy(tunit[4], "km/s");
+
+    rowlen = 76;
+    nrows = 11;
+    tfields = 5;
+
+    ffitab(fptr, rowlen, nrows, tfields, ttype, tbcol, tform, tunit, tblname,
+            &status);
+    printf("ffitab status = %d\n", status);
+    printf("HDU number = %d\n", ffghdn(fptr, &hdunum));
+
+    ffsnul(fptr, 1, "null1", &status);   /* define null value for int cols */
+    ffsnul(fptr, 2, "null2", &status);
+    ffsnul(fptr, 3, "null3", &status);
+    ffsnul(fptr, 4, "null4", &status);
+    ffsnul(fptr, 5, "null5", &status);
+ 
+    extvers = 2;
+    ffpkyj(fptr, "EXTVER", extvers, "extension version number", &status);
+
+    ffpkys(fptr, "TNULL1", "null1", "value for undefined pixels", &status);
+    ffpkys(fptr, "TNULL2", "null2", "value for undefined pixels", &status);
+    ffpkys(fptr, "TNULL3", "null3", "value for undefined pixels", &status);
+    ffpkys(fptr, "TNULL4", "null4", "value for undefined pixels", &status);
+    ffpkys(fptr, "TNULL5", "null5", "value for undefined pixels", &status);
+
+    if (status > 0)
+        goto errstatus;
+
+    /*
+      ############################
+      #  write data to columns   #
+      ############################
+    */
+
+    /* initialize arrays of values to write to table */
+    for (ii = 0; ii < 21; ii++)
+    {
+        boutarray[ii] = ii + 1;
+        ioutarray[ii] = ii + 1;
+        joutarray[ii] = ii + 1;
+        eoutarray[ii] = ii + 1;
+        doutarray[ii] = ii + 1;
+    }
+
+    ffpcls(fptr, 1, 1, 1, 3, onskey, &status);  /* write string values */
+    ffpclu(fptr, 1, 4, 1, 1, &status);  /* write null value */
+
+    for (ii = 2; ii < 6; ii++)   /* loop over cols 2 - 5 */
+    {
+        ffpclb(fptr, ii, 1, 1, 2, boutarray, &status);  /* char array */
+        if (status == NUM_OVERFLOW)
+            status = 0;
+        ffpcli(fptr, ii, 3, 1, 2, &ioutarray[2], &status);  /* short array */
+        if (status == NUM_OVERFLOW)
+            status = 0;
+        ffpclj(fptr, ii, 5, 1, 2, &joutarray[4], &status);  /* long array */
+        if (status == NUM_OVERFLOW)
+            status = 0;
+        ffpcle(fptr, ii, 7, 1, 2, &eoutarray[6], &status);  /* float array */
+        if (status == NUM_OVERFLOW)
+            status = 0;
+        ffpcld(fptr, ii, 9, 1, 2, &doutarray[8], &status);  /* double array */
+        if (status == NUM_OVERFLOW)
+            status = 0;
+
+        ffpclu(fptr, ii, 11, 1, 1, &status);  /* write null value */
+    }
+    printf("ffpcl_ status = %d\n", status);
+
+    /*
+      ################################
+      #  read data from ASCII table  #
+      ################################
+    */
+    ffghtb(fptr, 99, &rowlen, &nrows, &tfields, ttype, tbcol, 
+           tform, tunit, tblname, &status);
+
+    printf("\nASCII table: rowlen, nrows, tfields, extname: %ld %ld %d %s\n",
+           rowlen, nrows, tfields, tblname);
+
+    for (ii = 0; ii < tfields; ii++)
+      printf("%8s %3ld %8s %8s \n", ttype[ii], tbcol[ii], 
+                                   tform[ii], tunit[ii]);
+
+    nrows = 11;
+    ffgcvs(fptr, 1, 1, 1, nrows, "UNDEFINED", inskey,   &anynull, &status);
+    ffgcvb(fptr, 2, 1, 1, nrows, 99, binarray, &anynull, &status);
+    ffgcvi(fptr, 2, 1, 1, nrows, 99, iinarray, &anynull, &status);
+    ffgcvj(fptr, 3, 1, 1, nrows, 99, jinarray, &anynull, &status);
+    ffgcve(fptr, 4, 1, 1, nrows, 99., einarray, &anynull, &status);
+    ffgcvd(fptr, 5, 1, 1, nrows, 99., dinarray, &anynull, &status);
+
+    printf("\nData values read from ASCII table:\n");
+    for (ii = 0; ii < nrows; ii++)
+    {
+      printf("%15s %2d %2d %2ld %4.1f %4.1f\n", inskey[ii], binarray[ii],
+           iinarray[ii], jinarray[ii], einarray[ii], dinarray[ii]); 
+    }
+
+    ffgtbb(fptr, 1, 20, 78, uchars, &status);
+    uchars[78] = '\0';
+    printf("\n%s\n", uchars);
+    ffptbb(fptr, 1, 20, 78, uchars, &status);
+
+    /*
+      #########################################
+      #  get information about the columns    #
+      #########################################
+    */
+
+    ffgcno(fptr, 0, "name", &colnum, &status);
+    printf("\nColumn name is number %d; status = %d.\n", colnum, status);
+
+    while (status != COL_NOT_FOUND)
+    {
+      ffgcnn(fptr, 1, "*ue", colname, &colnum, &status);
+      printf("Column %s is number %d; status = %d.\n", 
+           colname, colnum, status);
+    }
+    status = 0;
+
+    for (ii = 0; ii < tfields; ii++)
+    {
+      ffgtcl(fptr, ii + 1, &typecode, &repeat, &width, &status);
+      printf("%4s %3d %2ld %2ld", tform[ii], typecode, repeat, width);
+      ffgacl(fptr, ii + 1, ttype[0], tbcol, tunit[0], tform[0], &scale,
+           &zero, nulstr, tdisp, &status);
+      printf(" %s, %ld, %s, %s, %f, %f, %s, %s.\n",
+         ttype[0], tbcol[0], tunit[0], tform[0], scale, zero,
+         nulstr, tdisp);
+    }
+
+    printf("\n");
+
+    /*
+      ###############################################
+      #  test the insert/delete row/column routines #
+      ###############################################
+    */
+
+    if (ffirow(fptr, 2, 3, &status) > 0)
+        goto errstatus;
+
+    nrows = 14;
+    ffgcvs(fptr, 1, 1, 1, nrows, "UNDEFINED", inskey,   &anynull, &status);
+    ffgcvb(fptr, 2, 1, 1, nrows, 99, binarray, &anynull, &status);
+    ffgcvi(fptr, 2, 1, 1, nrows, 99, iinarray, &anynull, &status);
+    ffgcvj(fptr, 3, 1, 1, nrows, 99, jinarray, &anynull, &status);
+    ffgcve(fptr, 4, 1, 1, nrows, 99., einarray, &anynull, &status);
+    ffgcvd(fptr, 5, 1, 1, nrows, 99., dinarray, &anynull, &status);
+
+
+    printf("\nData values after inserting 3 rows after row 2:\n");
+    for (ii = 0; ii < nrows; ii++)
+    {
+      printf("%15s %2d %2d %2ld %4.1f %4.1f\n",  inskey[ii], binarray[ii],
+          iinarray[ii], jinarray[ii], einarray[ii], dinarray[ii]);
+    }
+
+    if (ffdrow(fptr, 10, 2, &status) > 0)
+        goto errstatus;
+
+    nrows = 12;
+    ffgcvs(fptr, 1, 1, 1, nrows, "UNDEFINED", inskey,   &anynull, &status);
+    ffgcvb(fptr, 2, 1, 1, nrows, 99, binarray, &anynull, &status);
+    ffgcvi(fptr, 2, 1, 1, nrows, 99, iinarray, &anynull, &status);
+    ffgcvj(fptr, 3, 1, 1, nrows, 99, jinarray, &anynull, &status);
+    ffgcve(fptr, 4, 1, 1, nrows, 99., einarray, &anynull, &status);
+    ffgcvd(fptr, 5, 1, 1, nrows, 99., dinarray, &anynull, &status);
+
+    printf("\nData values after deleting 2 rows at row 10:\n");
+    for (ii = 0; ii < nrows; ii++)
+    {
+      printf("%15s %2d %2d %2ld %4.1f %4.1f\n",  inskey[ii], binarray[ii],
+          iinarray[ii], jinarray[ii], einarray[ii], dinarray[ii]);
+    }
+    if (ffdcol(fptr, 3, &status) > 0)
+        goto errstatus;
+
+    ffgcvs(fptr, 1, 1, 1, nrows, "UNDEFINED", inskey,   &anynull, &status);
+    ffgcvb(fptr, 2, 1, 1, nrows, 99, binarray, &anynull, &status);
+    ffgcvi(fptr, 2, 1, 1, nrows, 99, iinarray, &anynull, &status);
+    ffgcve(fptr, 3, 1, 1, nrows, 99., einarray, &anynull, &status);
+    ffgcvd(fptr, 4, 1, 1, nrows, 99., dinarray, &anynull, &status);
+
+    printf("\nData values after deleting column 3:\n");
+    for (ii = 0; ii < nrows; ii++)
+    {
+      printf("%15s %2d %2d %4.1f %4.1f\n", inskey[ii], binarray[ii],
+          iinarray[ii], einarray[ii], dinarray[ii]);
+    }
+
+    if (fficol(fptr, 5, "INSERT_COL", "F14.6", &status) > 0)
+        goto errstatus;
+
+    ffgcvs(fptr, 1, 1, 1, nrows, "UNDEFINED", inskey,   &anynull, &status);
+    ffgcvb(fptr, 2, 1, 1, nrows, 99, binarray, &anynull, &status);
+    ffgcvi(fptr, 2, 1, 1, nrows, 99, iinarray, &anynull, &status);
+    ffgcve(fptr, 3, 1, 1, nrows, 99., einarray, &anynull, &status);
+    ffgcvd(fptr, 4, 1, 1, nrows, 99., dinarray, &anynull, &status);
+    ffgcvj(fptr, 5, 1, 1, nrows, 99, jinarray, &anynull, &status);
+
+    printf("\nData values after inserting column 5:\n");
+    for (ii = 0; ii < nrows; ii++)
+    {
+      printf("%15s %2d %2d %4.1f %4.1f %ld\n", inskey[ii], binarray[ii],
+          iinarray[ii], einarray[ii], dinarray[ii] , jinarray[ii]);
+    }
+
+    /*
+      ############################################################
+      #  create a temporary file and copy the ASCII table to it, #
+      #  column by column.                                       #
+      ############################################################
+    */
+    bitpix = 16;
+    naxis = 0;
+
+    strcpy(filename, "!t1q2s3v6.tmp");
+    ffinit(&tmpfptr, filename, &status);
+    printf("Create temporary file: ffinit status = %d\n", status);
+
+    ffiimg(tmpfptr, bitpix, naxis, naxes, &status);
+    printf("\nCreate null primary array: ffiimg status = %d\n", status);
+
+    /* create an empty table with 12 rows and 0 columns */
+    nrows = 12;
+    tfields = 0;
+    rowlen = 0;
+    ffitab(tmpfptr, rowlen, nrows, tfields, ttype, tbcol, tform, tunit,
+           tblname, &status);
+    printf("\nCreate ASCII table with 0 columns: ffitab status = %d\n",
+           status);
+
+    /* copy columns from one table to the other */
+    ffcpcl(fptr, tmpfptr, 4, 1, TRUE, &status);
+    printf("copy column, ffcpcl status = %d\n", status);
+    ffcpcl(fptr, tmpfptr, 3, 1, TRUE, &status);
+    printf("copy column, ffcpcl status = %d\n", status);
+    ffcpcl(fptr, tmpfptr, 2, 1, TRUE, &status);
+    printf("copy column, ffcpcl status = %d\n", status);
+    ffcpcl(fptr, tmpfptr, 1, 1, TRUE, &status);
+    printf("copy column, ffcpcl status = %d\n", status);
+
+    /* now repeat by copying ASCII input to Binary output table */
+    ffibin(tmpfptr, nrows, tfields, ttype, tform, tunit,
+           tblname,  0L, &status);
+    printf("\nCreate Binary table with 0 columns: ffibin status = %d\n",
+           status);
+
+    /* copy columns from one table to the other */
+    ffcpcl(fptr, tmpfptr, 4, 1, TRUE, &status);
+    printf("copy column, ffcpcl status = %d\n", status);
+    ffcpcl(fptr, tmpfptr, 3, 1, TRUE, &status);
+    printf("copy column, ffcpcl status = %d\n", status);
+    ffcpcl(fptr, tmpfptr, 2, 1, TRUE, &status);
+    printf("copy column, ffcpcl status = %d\n", status);
+    ffcpcl(fptr, tmpfptr, 1, 1, TRUE, &status);
+    printf("copy column, ffcpcl status = %d\n", status);
+
+
+/*
+    ffclos(tmpfptr, &status);
+    printf("Close the tmp file: ffclos status = %d\n", status);
+*/
+
+    ffdelt(tmpfptr, &status);  
+    printf("Delete the tmp file: ffdelt status = %d\n", status);
+
+    if (status > 0)
+    {
+        goto errstatus;
+    }
+
+    /*
+      ################################
+      #  read data from binary table #
+      ################################
+    */
+
+    if (ffmrhd(fptr, 1, &hdutype, &status) > 0)
+        goto errstatus;
+
+    printf("HDU number = %d\n", ffghdn(fptr, &hdunum));
+
+    ffghsp(fptr, &existkeys, &morekeys, &status);
+    printf("header contains %d keywords with room for %d more\n",existkeys,
+            morekeys);
+
+    ffghbn(fptr, 99, &nrows, &tfields, ttype, 
+           tform, tunit, binname, &pcount, &status);
+
+    printf("\nBinary table: nrows, tfields, extname, pcount: %ld %d %s %ld\n",
+           nrows, tfields, binname, pcount);
+
+    for (ii = 0; ii < tfields; ii++)
+      printf("%8s %8s %8s \n", ttype[ii], tform[ii], tunit[ii]);
+
+    for (ii = 0; ii < 40; ii++)
+        larray[ii] = 0;
+
+    printf("\nData values read from binary table:\n");
+    printf("  Bit column (X) data values: \n\n");
+
+    ffgcx(fptr, 3, 1, 1, 36, larray, &status);
+    for (jj = 0; jj < 5; jj++)
+    {
+      for (ii = 0; ii < 8; ii++)
+        printf("%1d",larray[jj * 8 + ii]);
+      printf(" ");
+    }
+
+    for (ii = 0; ii < nrows; ii++)
+    {
+      larray[ii] = 0;
+      xinarray[ii] = 0;
+      binarray[ii] = 0;
+      iinarray[ii] = 0; 
+      kinarray[ii] = 0;
+      einarray[ii] = 0.; 
+      dinarray[ii] = 0.;
+      cinarray[ii * 2] = 0.; 
+      minarray[ii * 2] = 0.;
+      cinarray[ii * 2 + 1] = 0.; 
+      minarray[ii * 2 + 1] = 0.;
+    }
+
+    printf("\n\n");
+    ffgcvs(fptr, 1, 4, 1, 1, "",  inskey,   &anynull, &status);
+    printf("null string column value = -%s- (should be --)\n",inskey[0]);
+
+    nrows = 21;
+    ffgcvs(fptr, 1, 1, 1, nrows, "NOT DEFINED",  inskey,   &anynull, &status);
+    ffgcl( fptr, 2, 1, 1, nrows, larray, &status);
+    ffgcvb(fptr, 3, 1, 1, nrows, 98, xinarray, &anynull, &status);
+    ffgcvb(fptr, 4, 1, 1, nrows, 98, binarray, &anynull, &status);
+    ffgcvi(fptr, 5, 1, 1, nrows, 98, iinarray, &anynull, &status);
+    ffgcvk(fptr, 6, 1, 1, nrows, 98, kinarray, &anynull, &status);
+    ffgcve(fptr, 7, 1, 1, nrows, 98., einarray, &anynull, &status);
+    ffgcvd(fptr, 8, 1, 1, nrows, 98., dinarray, &anynull, &status);
+    ffgcvc(fptr, 9, 1, 1, nrows, 98., cinarray, &anynull, &status);
+    ffgcvm(fptr, 10, 1, 1, nrows, 98., minarray, &anynull, &status);
+
+    printf("\nRead columns with ffgcv_:\n");
+    for (ii = 0; ii < nrows; ii++)
+    {
+  printf("%15s %d %3d %2d %3d %3d %5.1f %5.1f (%5.1f,%5.1f) (%5.1f,%5.1f) \n",
+        inskey[ii], larray[ii], xinarray[ii], binarray[ii], iinarray[ii], 
+        kinarray[ii], einarray[ii], dinarray[ii], cinarray[ii * 2], 
+        cinarray[ii * 2 + 1], minarray[ii * 2], minarray[ii * 2 + 1]);
+    }
+
+    for (ii = 0; ii < nrows; ii++)
+    {
+      larray[ii] = 0;
+      xinarray[ii] = 0;
+      binarray[ii] = 0;
+      iinarray[ii] = 0; 
+      kinarray[ii] = 0;
+      einarray[ii] = 0.; 
+      dinarray[ii] = 0.;
+      cinarray[ii * 2] = 0.; 
+      minarray[ii * 2] = 0.;
+      cinarray[ii * 2 + 1] = 0.; 
+      minarray[ii * 2 + 1] = 0.;
+    }
+
+    ffgcfs(fptr, 1, 1, 1, nrows, inskey,   larray2, &anynull, &status);
+    ffgcfl(fptr, 2, 1, 1, nrows, larray,   larray2, &anynull, &status);
+    ffgcfb(fptr, 3, 1, 1, nrows, xinarray, larray2, &anynull, &status);
+    ffgcfb(fptr, 4, 1, 1, nrows, binarray, larray2, &anynull, &status);
+    ffgcfi(fptr, 5, 1, 1, nrows, iinarray, larray2, &anynull, &status);
+    ffgcfk(fptr, 6, 1, 1, nrows, kinarray, larray2, &anynull, &status);
+    ffgcfe(fptr, 7, 1, 1, nrows, einarray, larray2, &anynull, &status);
+    ffgcfd(fptr, 8, 1, 1, nrows, dinarray, larray2, &anynull, &status);
+    ffgcfc(fptr, 9, 1, 1, nrows, cinarray, larray2, &anynull, &status);
+    ffgcfm(fptr, 10, 1, 1, nrows, minarray, larray2, &anynull, &status);
+
+    printf("\nRead columns with ffgcf_:\n");
+    for (ii = 0; ii < 10; ii++)
+    {
+    
+    printf("%15s %d %3d %2d %3d %3d %5.1f %5.1f (%5.1f,%5.1f) (%5.1f,%5.1f)\n",
+        inskey[ii], larray[ii], xinarray[ii], binarray[ii], iinarray[ii], 
+        kinarray[ii], einarray[ii], dinarray[ii], cinarray[ii * 2], 
+        cinarray[ii * 2 + 1], minarray[ii * 2], minarray[ii * 2 + 1]);
+    }
+    for (ii = 10; ii < nrows; ii++)
+    {
+      /* don't try to print the NaN values */
+      printf("%15s %d %3d %2d %3d \n",
+        inskey[ii], larray[ii], xinarray[ii], binarray[ii], iinarray[ii]);
+    }
+    ffprec(fptr, 
+    "key_prec= 'This keyword was written by f_prec' / comment here", &status);
+
+    /*
+      ###############################################
+      #  test the insert/delete row/column routines #
+      ###############################################
+    */
+    if (ffirow(fptr, 2, 3, &status) > 0)
+        goto errstatus;
+
+    nrows = 14;
+    ffgcvs(fptr, 1, 1, 1, nrows, "NOT DEFINED",  inskey,   &anynull, &status);
+    ffgcvb(fptr, 4, 1, 1, nrows, 98, binarray, &anynull, &status);
+    ffgcvi(fptr, 5, 1, 1, nrows, 98, iinarray, &anynull, &status);
+    ffgcvj(fptr, 6, 1, 1, nrows, 98, jinarray, &anynull, &status);
+    ffgcve(fptr, 7, 1, 1, nrows, 98., einarray, &anynull, &status);
+    ffgcvd(fptr, 8, 1, 1, nrows, 98., dinarray, &anynull, &status);
+
+    printf("\nData values after inserting 3 rows after row 2:\n");
+    for (ii = 0; ii < nrows; ii++)
+    {
+      printf("%15s %2d %3d %3ld %5.1f %5.1f\n",  inskey[ii], binarray[ii],
+          iinarray[ii], jinarray[ii], einarray[ii], dinarray[ii]);
+    }
+
+    if (ffdrow(fptr, 10, 2, &status) > 0)
+        goto errstatus;
+
+    nrows = 12;
+    ffgcvs(fptr, 1, 1, 1, nrows, "NOT DEFINED",  inskey,   &anynull, &status);
+    ffgcvb(fptr, 4, 1, 1, nrows, 98, binarray, &anynull, &status);
+    ffgcvi(fptr, 5, 1, 1, nrows, 98, iinarray, &anynull, &status);
+    ffgcvj(fptr, 6, 1, 1, nrows, 98, jinarray, &anynull, &status);
+    ffgcve(fptr, 7, 1, 1, nrows, 98., einarray, &anynull, &status);
+    ffgcvd(fptr, 8, 1, 1, nrows, 98., dinarray, &anynull, &status);
+
+    printf("\nData values after deleting 2 rows at row 10:\n");
+    for (ii = 0; ii < nrows; ii++)
+    {
+      printf("%15s %2d %3d %3ld %5.1f %5.1f\n",  inskey[ii], binarray[ii],
+          iinarray[ii], jinarray[ii], einarray[ii], dinarray[ii]);
+    }
+
+    if (ffdcol(fptr, 6, &status) > 0)
+        goto errstatus;
+
+    ffgcvs(fptr, 1, 1, 1, nrows, "NOT DEFINED",  inskey,   &anynull, &status);
+    ffgcvb(fptr, 4, 1, 1, nrows, 98, binarray, &anynull, &status);
+    ffgcvi(fptr, 5, 1, 1, nrows, 98, iinarray, &anynull, &status);
+    ffgcve(fptr, 6, 1, 1, nrows, 98., einarray, &anynull, &status);
+    ffgcvd(fptr, 7, 1, 1, nrows, 98., dinarray, &anynull, &status);
+
+    printf("\nData values after deleting column 6:\n");
+    for (ii = 0; ii < nrows; ii++)
+    {
+      printf("%15s %2d %3d %5.1f %5.1f\n", inskey[ii], binarray[ii],
+          iinarray[ii], einarray[ii], dinarray[ii]);
+    }
+
+    if (fficol(fptr, 8, "INSERT_COL", "1E", &status) > 0)
+        goto errstatus;
+
+    ffgcvs(fptr, 1, 1, 1, nrows, "NOT DEFINED",  inskey,   &anynull, &status);
+    ffgcvb(fptr, 4, 1, 1, nrows, 98, binarray, &anynull, &status);
+    ffgcvi(fptr, 5, 1, 1, nrows, 98, iinarray, &anynull, &status);
+    ffgcve(fptr, 6, 1, 1, nrows, 98., einarray, &anynull, &status);
+    ffgcvd(fptr, 7, 1, 1, nrows, 98., dinarray, &anynull, &status);
+    ffgcvj(fptr, 8, 1, 1, nrows, 98, jinarray, &anynull, &status);
+
+    printf("\nData values after inserting column 8:\n");
+    for (ii = 0; ii < nrows; ii++)
+    {
+      printf("%15s %2d %3d %5.1f %5.1f %ld\n", inskey[ii], binarray[ii],
+          iinarray[ii], einarray[ii], dinarray[ii] , jinarray[ii]);
+    }
+
+    ffpclu(fptr, 8, 1, 1, 10, &status);
+
+    ffgcvs(fptr, 1, 1, 1, nrows, "NOT DEFINED",  inskey,   &anynull, &status);
+    ffgcvb(fptr, 4, 1, 1, nrows, 98, binarray, &anynull, &status);
+    ffgcvi(fptr, 5, 1, 1, nrows, 98, iinarray, &anynull, &status);
+    ffgcve(fptr, 6, 1, 1, nrows, 98., einarray, &anynull, &status);
+    ffgcvd(fptr, 7, 1, 1, nrows, 98., dinarray, &anynull, &status);
+    ffgcvj(fptr, 8, 1, 1, nrows, 98, jinarray, &anynull, &status);
+
+    printf("\nValues after setting 1st 10 elements in column 8 = null:\n");
+    for (ii = 0; ii < nrows; ii++)
+    {
+      printf("%15s %2d %3d %5.1f %5.1f %ld\n", inskey[ii], binarray[ii],
+          iinarray[ii], einarray[ii], dinarray[ii] , jinarray[ii]);
+    }
+
+    /*
+      ############################################################
+      #  create a temporary file and copy the binary table to it,#
+      #  column by column.                                       #
+      ############################################################
+    */
+    bitpix = 16;
+    naxis = 0;
+
+    strcpy(filename, "!t1q2s3v5.tmp");
+    ffinit(&tmpfptr, filename, &status);
+    printf("Create temporary file: ffinit status = %d\n", status);
+
+    ffiimg(tmpfptr, bitpix, naxis, naxes, &status);
+    printf("\nCreate null primary array: ffiimg status = %d\n", status);
+
+    /* create an empty table with 22 rows and 0 columns */
+    nrows = 22;
+    tfields = 0;
+    ffibin(tmpfptr, nrows, tfields, ttype, tform, tunit, binname, 0L,
+            &status);
+    printf("\nCreate binary table with 0 columns: ffibin status = %d\n",
+           status);
+
+    /* copy columns from one table to the other */
+    ffcpcl(fptr, tmpfptr, 7, 1, TRUE, &status);
+    printf("copy column, ffcpcl status = %d\n", status);
+    ffcpcl(fptr, tmpfptr, 6, 1, TRUE, &status);
+    printf("copy column, ffcpcl status = %d\n", status);
+    ffcpcl(fptr, tmpfptr, 5, 1, TRUE, &status);
+    printf("copy column, ffcpcl status = %d\n", status);
+    ffcpcl(fptr, tmpfptr, 4, 1, TRUE, &status);
+    printf("copy column, ffcpcl status = %d\n", status);
+    ffcpcl(fptr, tmpfptr, 3, 1, TRUE, &status);
+    printf("copy column, ffcpcl status = %d\n", status);
+    ffcpcl(fptr, tmpfptr, 2, 1, TRUE, &status);
+    printf("copy column, ffcpcl status = %d\n", status);
+    ffcpcl(fptr, tmpfptr, 1, 1, TRUE, &status);
+    printf("copy column, ffcpcl status = %d\n", status);
+
+/*
+    ffclos(tmpfptr, &status);
+    printf("Close the tmp file: ffclos status = %d\n", status);
+*/
+
+    ffdelt(tmpfptr, &status);
+    printf("Delete the tmp file: ffdelt status = %d\n", status);
+    if (status > 0)
+    {
+        goto errstatus;
+    }
+    /*
+      ####################################################
+      #  insert binary table following the primary array #
+      ####################################################
+    */
+
+    ffmahd(fptr,  1, &hdutype, &status);
+
+    strcpy(tform[0], "15A");
+    strcpy(tform[1], "1L");
+    strcpy(tform[2], "16X");
+    strcpy(tform[3], "1B");
+    strcpy(tform[4], "1I");
+    strcpy(tform[5], "1J");
+    strcpy(tform[6], "1E");
+    strcpy(tform[7], "1D");
+    strcpy(tform[8], "1C");
+    strcpy(tform[9], "1M");
+
+    strcpy(ttype[0], "Avalue");
+    strcpy(ttype[1], "Lvalue");
+    strcpy(ttype[2], "Xvalue");
+    strcpy(ttype[3], "Bvalue");
+    strcpy(ttype[4], "Ivalue");
+    strcpy(ttype[5], "Jvalue");
+    strcpy(ttype[6], "Evalue");
+    strcpy(ttype[7], "Dvalue");
+    strcpy(ttype[8], "Cvalue");
+    strcpy(ttype[9], "Mvalue");
+
+    strcpy(tunit[0], "");
+    strcpy(tunit[1], "m**2");
+    strcpy(tunit[2], "cm");
+    strcpy(tunit[3], "erg/s");
+    strcpy(tunit[4], "km/s");
+    strcpy(tunit[5], "");
+    strcpy(tunit[6], "");
+    strcpy(tunit[7], "");
+    strcpy(tunit[8], "");
+    strcpy(tunit[9], "");
+
+    nrows = 20;
+    tfields = 10;
+    pcount = 0;
+
+    ffibin(fptr, nrows, tfields, ttype, tform, tunit, binname, pcount,
+            &status);
+    printf("ffibin status = %d\n", status);
+    printf("HDU number = %d\n", ffghdn(fptr, &hdunum));
+
+    extvers = 3;
+    ffpkyj(fptr, "EXTVER", extvers, "extension version number", &status);
+
+
+    ffpkyj(fptr, "TNULL4", 77, "value for undefined pixels", &status);
+    ffpkyj(fptr, "TNULL5", 77, "value for undefined pixels", &status);
+    ffpkyj(fptr, "TNULL6", 77, "value for undefined pixels", &status);
+
+    ffpkyj(fptr, "TSCAL4", 1000, "scaling factor", &status);
+    ffpkyj(fptr, "TSCAL5", 1, "scaling factor", &status);
+    ffpkyj(fptr, "TSCAL6", 100, "scaling factor", &status);
+
+    ffpkyj(fptr, "TZERO4", 0, "scaling offset", &status);
+    ffpkyj(fptr, "TZERO5", 32768, "scaling offset", &status);
+    ffpkyj(fptr, "TZERO6", 100, "scaling offset", &status);
+
+    fftnul(fptr, 4, 77, &status);   /* define null value for int cols */
+    fftnul(fptr, 5, 77, &status);
+    fftnul(fptr, 6, 77, &status);
+    /* set scaling */
+    fftscl(fptr, 4, 1000., 0., &status);   
+    fftscl(fptr, 5, 1., 32768., &status);
+    fftscl(fptr, 6, 100., 100., &status);
+
+    /*
+      ############################
+      #  write data to columns   #
+      ############################
+    */
+
+    /* initialize arrays of values to write to table */
+ 
+    joutarray[0] = 0;
+    joutarray[1] = 1000;
+    joutarray[2] = 10000;
+    joutarray[3] = 32768;
+    joutarray[4] = 65535;
+
+
+    for (ii = 4; ii < 7; ii++)
+    {
+        ffpclj(fptr, ii, 1, 1, 5, joutarray, &status); 
+        if (status == NUM_OVERFLOW)
+        {
+            printf("Overflow writing to column %ld\n", ii);
+            status = 0;
+        }
+
+        ffpclu(fptr, ii, 6, 1, 1, &status);  /* write null value */
+    }
+
+    for (jj = 4; jj < 7; jj++)
+    {
+      ffgcvj(fptr, jj, 1, 1, 6, -999, jinarray, &anynull, &status);
+      for (ii = 0; ii < 6; ii++)
+      {
+        printf(" %6ld", jinarray[ii]);
+      }
+      printf("\n");
+    }
+
+    printf("\n");
+    /* turn off scaling, and read the unscaled values */
+    fftscl(fptr, 4, 1., 0., &status);   
+    fftscl(fptr, 5, 1., 0., &status);
+    fftscl(fptr, 6, 1., 0., &status);
+
+    for (jj = 4; jj < 7; jj++)
+    {
+      ffgcvj(fptr, jj, 1, 1, 6, -999, jinarray, &anynull, &status);
+      for (ii = 0; ii < 6; ii++)
+      {
+        printf(" %6ld", jinarray[ii]);
+      }
+      printf("\n");
+    }
+    /*
+      ######################################################
+      #  insert image extension following the binary table #
+      ######################################################
+    */
+
+    bitpix = -32;
+    naxis = 2;
+    naxes[0] = 15;
+    naxes[1] = 25;
+    ffiimg(fptr, bitpix, naxis, naxes, &status);
+    printf("\nCreate image extension: ffiimg status = %d\n", status);
+    printf("HDU number = %d\n", ffghdn(fptr, &hdunum));
+
+    for (jj = 0; jj < 30; jj++)
+    {
+      for (ii = 0; ii < 19; ii++)
+      {
+        imgarray[jj][ii] = (jj * 10) + ii;
+      }
+    }
+
+    ffp2di(fptr, 1, 19, naxes[0], naxes[1], imgarray[0], &status);
+    printf("\nWrote whole 2D array: ffp2di status = %d\n", status);
+
+    for (jj = 0; jj < 30; jj++)
+    {
+      for (ii = 0; ii < 19; ii++)
+      {
+        imgarray[jj][ii] = 0;
+      }
+    }
+    
+    ffg2di(fptr, 1, 0, 19, naxes[0], naxes[1], imgarray[0], &anynull,
+           &status);
+    printf("\nRead whole 2D array: ffg2di status = %d\n", status);
+
+    for (jj = 0; jj < 30; jj++)
+    {
+      for (ii = 0; ii < 19; ii++)
+      {
+        printf(" %3d", imgarray[jj][ii]);
+      }
+      printf("\n");
+    }
+
+    for (jj = 0; jj < 30; jj++)
+    {
+      for (ii = 0; ii < 19; ii++)
+      {
+        imgarray[jj][ii] = 0;
+      }
+    }
+    
+    for (jj = 0; jj < 20; jj++)
+    {
+      for (ii = 0; ii < 10; ii++)
+      {
+        imgarray2[jj][ii] = (jj * -10) - ii;
+      }
+    }
+
+    fpixels[0] = 5;
+    fpixels[1] = 5;
+    lpixels[0] = 14;
+    lpixels[1] = 14;
+    ffpssi(fptr, 1, naxis, naxes, fpixels, lpixels, 
+         imgarray2[0], &status);
+    printf("\nWrote subset 2D array: ffpssi status = %d\n", status);
+
+    ffg2di(fptr, 1, 0, 19, naxes[0], naxes[1], imgarray[0], &anynull,
+           &status);
+    printf("\nRead whole 2D array: ffg2di status = %d\n", status);
+
+    for (jj = 0; jj < 30; jj++)
+    {
+      for (ii = 0; ii < 19; ii++)
+      {
+        printf(" %3d", imgarray[jj][ii]);
+      }
+      printf("\n");
+    }
+
+    fpixels[0] = 2;
+    fpixels[1] = 5;
+    lpixels[0] = 10;
+    lpixels[1] = 8;
+    inc[0] = 2;
+    inc[1] = 3;
+
+    for (jj = 0; jj < 30; jj++)
+    {
+      for (ii = 0; ii < 19; ii++)
+      {
+        imgarray[jj][ii] = 0;
+      }
+    }
+    
+    ffgsvi(fptr, 1, naxis, naxes, fpixels, lpixels, inc, 0,
+          imgarray[0], &anynull, &status);
+    printf("\nRead subset of 2D array: ffgsvi status = %d\n", status);
+
+    for (ii = 0; ii < 10; ii++)
+    {
+        printf(" %3d", imgarray[0][ii]);
+    }
+    printf("\n");
+
+    /*
+      ###########################################################
+      #  insert another image extension                         #
+      #  copy the image extension to primary array of tmp file. #
+      #  then delete the tmp file, and the image extension      #
+      ###########################################################
+    */
+    bitpix = 16;
+    naxis = 2;
+    naxes[0] = 15;
+    naxes[1] = 25;
+    ffiimg(fptr, bitpix, naxis, naxes, &status);
+    printf("\nCreate image extension: ffiimg status = %d\n", status);
+    printf("HDU number = %d\n", ffghdn(fptr, &hdunum));
+
+    strcpy(filename, "t1q2s3v4.tmp");
+    ffinit(&tmpfptr, filename, &status);
+    printf("Create temporary file: ffinit status = %d\n", status);
+
+    ffcopy(fptr, tmpfptr, 0, &status);
+    printf("Copy image extension to primary array of tmp file.\n");
+    printf("ffcopy status = %d\n", status);
+
+    ffgrec(tmpfptr, 1, card, &status);
+    printf("%s\n", card);
+    ffgrec(tmpfptr, 2, card, &status);
+    printf("%s\n", card);
+    ffgrec(tmpfptr, 3, card, &status);
+    printf("%s\n", card);
+    ffgrec(tmpfptr, 4, card, &status);
+    printf("%s\n", card);
+    ffgrec(tmpfptr, 5, card, &status);
+    printf("%s\n", card);
+    ffgrec(tmpfptr, 6, card, &status);
+    printf("%s\n", card);
+
+    ffdelt(tmpfptr, &status);
+    printf("Delete the tmp file: ffdelt status = %d\n", status);
+
+    ffdhdu(fptr, &hdutype, &status);
+    printf("Delete the image extension; hdutype, status = %d %d\n",
+             hdutype, status);
+    printf("HDU number = %d\n", ffghdn(fptr, &hdunum));
+
+    /*
+      ###########################################################
+      #  append bintable extension with variable length columns #
+      ###########################################################
+    */
+
+    ffcrhd(fptr, &status);
+    printf("ffcrhd status = %d\n", status);
+
+    strcpy(tform[0], "1PA");
+    strcpy(tform[1], "1PL");
+    strcpy(tform[2], "1PB"); /* Fortran FITSIO doesn't support  1PX */
+    strcpy(tform[3], "1PB");
+    strcpy(tform[4], "1PI");
+    strcpy(tform[5], "1PJ");
+    strcpy(tform[6], "1PE");
+    strcpy(tform[7], "1PD");
+    strcpy(tform[8], "1PC");
+    strcpy(tform[9], "1PM");
+
+    strcpy(ttype[0], "Avalue");
+    strcpy(ttype[1], "Lvalue");
+    strcpy(ttype[2], "Xvalue");
+    strcpy(ttype[3], "Bvalue");
+    strcpy(ttype[4], "Ivalue");
+    strcpy(ttype[5], "Jvalue");
+    strcpy(ttype[6], "Evalue");
+    strcpy(ttype[7], "Dvalue");
+    strcpy(ttype[8], "Cvalue");
+    strcpy(ttype[9], "Mvalue");
+
+    strcpy(tunit[0], "");
+    strcpy(tunit[1], "m**2");
+    strcpy(tunit[2], "cm");
+    strcpy(tunit[3], "erg/s");
+    strcpy(tunit[4], "km/s");
+    strcpy(tunit[5], "");
+    strcpy(tunit[6], "");
+    strcpy(tunit[7], "");
+    strcpy(tunit[8], "");
+    strcpy(tunit[9], "");
+
+    nrows = 20;
+    tfields = 10;
+    pcount = 0;
+
+    ffphbn(fptr, nrows, tfields, ttype, tform, tunit, binname, pcount,
+            &status);
+    printf("Variable length arrays: ffphbn status = %d\n", status);
+
+
+    extvers = 4;
+    ffpkyj(fptr, "EXTVER", extvers, "extension version number", &status);
+
+    ffpkyj(fptr, "TNULL4", 88, "value for undefined pixels", &status);
+    ffpkyj(fptr, "TNULL5", 88, "value for undefined pixels", &status);
+    ffpkyj(fptr, "TNULL6", 88, "value for undefined pixels", &status);
+
+    /*
+      ############################
+      #  write data to columns   #
+      ############################
+    */
+
+    /* initialize arrays of values to write to table */
+    strcpy(iskey,"abcdefghijklmnopqrst");
+
+    for (ii = 0; ii < 20; ii++)
+    {
+        boutarray[ii] = ii + 1;
+        ioutarray[ii] = ii + 1;
+        joutarray[ii] = ii + 1;
+        eoutarray[ii] = ii + 1;
+        doutarray[ii] = ii + 1;
+    }
+
+    larray[0] = 0;
+    larray[1] = 1;
+    larray[2] = 0;
+    larray[3] = 0;
+    larray[4] = 1;
+    larray[5] = 1;
+    larray[6] = 0;
+    larray[7] = 0;
+    larray[8] = 0;
+    larray[9] = 1;
+    larray[10] = 1;
+    larray[11] = 1;
+    larray[12] = 0;
+    larray[13] = 0;
+    larray[14] = 0;
+    larray[15] = 0;
+    larray[16] = 1;
+    larray[17] = 1;
+    larray[18] = 1;
+    larray[19] = 1;
+
+    /* write values in 1st row */
+    /*  strncpy(inskey[0], iskey, 1); */
+      inskey[0][0] = '\0';  /* write a null string (i.e., a blank) */
+      ffpcls(fptr, 1, 1, 1, 1, inskey, &status);  /* write string values */
+      ffpcll(fptr, 2, 1, 1, 1, larray, &status);  /* write logicals */
+      ffpclx(fptr, 3, 1, 1, 1, larray, &status);  /* write bits */
+      ffpclb(fptr, 4, 1, 1, 1, boutarray, &status);
+      ffpcli(fptr, 5, 1, 1, 1, ioutarray, &status); 
+      ffpclj(fptr, 6, 1, 1, 1, joutarray, &status); 
+      ffpcle(fptr, 7, 1, 1, 1, eoutarray, &status);
+      ffpcld(fptr, 8, 1, 1, 1, doutarray, &status);
+
+    for (ii = 2; ii <= 20; ii++)   /* loop over rows 1 - 20 */
+    {
+      strncpy(inskey[0], iskey, ii);
+      inskey[0][ii] = '\0';
+      ffpcls(fptr, 1, ii, 1, 1, inskey, &status);  /* write string values */
+
+      ffpcll(fptr, 2, ii, 1, ii, larray, &status);  /* write logicals */
+      ffpclu(fptr, 2, ii, ii-1, 1, &status);
+
+      ffpclx(fptr, 3, ii, 1, ii, larray, &status);  /* write bits */
+
+      ffpclb(fptr, 4, ii, 1, ii, boutarray, &status);
+      ffpclu(fptr, 4, ii, ii-1, 1, &status);
+
+      ffpcli(fptr, 5, ii, 1, ii, ioutarray, &status); 
+      ffpclu(fptr, 5, ii, ii-1, 1, &status);
+
+      ffpclj(fptr, 6, ii, 1, ii, joutarray, &status); 
+      ffpclu(fptr, 6, ii, ii-1, 1, &status);
+
+      ffpcle(fptr, 7, ii, 1, ii, eoutarray, &status);
+      ffpclu(fptr, 7, ii, ii-1, 1, &status);
+
+      ffpcld(fptr, 8, ii, 1, ii, doutarray, &status);
+      ffpclu(fptr, 8, ii, ii-1, 1, &status);
+    }
+    printf("ffpcl_ status = %d\n", status);
+
+    /*
+      #################################
+      #  close then reopen this HDU   #
+      #################################
+    */
+
+
+     ffmrhd(fptr, -1, &hdutype, &status);
+     ffmrhd(fptr,  1, &hdutype, &status);
+
+    /*
+      #############################
+      #  read data from columns   #
+      #############################
+    */
+
+    ffgkyj(fptr, "PCOUNT", &pcount, comm, &status);
+    printf("PCOUNT = %ld\n", pcount);
+
+    /* initialize the variables to be read */
+    strcpy(inskey[0]," ");
+    strcpy(iskey," ");
+
+
+    printf("HDU number = %d\n", ffghdn(fptr, &hdunum));
+    for (ii = 1; ii <= 20; ii++)   /* loop over rows 1 - 20 */
+    {
+      for (jj = 0; jj < ii; jj++)
+      {
+        larray[jj] = 0;
+        boutarray[jj] = 0;
+        ioutarray[jj] = 0;
+        joutarray[jj] = 0;
+        eoutarray[jj] = 0;
+        doutarray[jj] = 0;
+      }
+
+      ffgcvs(fptr, 1, ii, 1, 1, iskey, inskey, &anynull, &status);  
+      printf("A %s %d\nL", inskey[0], status);
+
+      ffgcl( fptr, 2, ii, 1, ii, larray, &status); 
+      for (jj = 0; jj < ii; jj++)
+        printf(" %2d", larray[jj]);
+      printf(" %d\nX", status);
+
+      ffgcx(fptr, 3, ii, 1, ii, larray, &status);
+      for (jj = 0; jj < ii; jj++)
+        printf(" %2d", larray[jj]);
+      printf(" %d\nB", status);
+
+      ffgcvb(fptr, 4, ii, 1, ii, 99, boutarray, &anynull, &status);
+      for (jj = 0; jj < ii; jj++)
+        printf(" %2d", boutarray[jj]);
+      printf(" %d\nI", status);
+
+      ffgcvi(fptr, 5, ii, 1, ii, 99, ioutarray, &anynull, &status); 
+      for (jj = 0; jj < ii; jj++)
+        printf(" %2d", ioutarray[jj]);
+      printf(" %d\nJ", status);
+
+      ffgcvj(fptr, 6, ii, 1, ii, 99, joutarray, &anynull, &status); 
+      for (jj = 0; jj < ii; jj++)
+        printf(" %2ld", joutarray[jj]);
+      printf(" %d\nE", status);
+
+      ffgcve(fptr, 7, ii, 1, ii, 99., eoutarray, &anynull, &status);
+      for (jj = 0; jj < ii; jj++)
+        printf(" %2.0f", eoutarray[jj]);
+      printf(" %d\nD", status);
+
+      ffgcvd(fptr, 8, ii, 1, ii, 99., doutarray, &anynull, &status);
+      for (jj = 0; jj < ii; jj++)
+        printf(" %2.0f", doutarray[jj]);
+      printf(" %d\n", status);
+
+      ffgdes(fptr, 8, ii, &repeat, &offset, &status);
+      printf("Column 8 repeat and offset = %ld %ld\n", repeat, offset);
+    }
+
+    /*
+      #####################################
+      #  create another image extension   #
+      #####################################
+    */
+
+    bitpix = 32;
+    naxis = 2;
+    naxes[0] = 10;
+    naxes[1] = 2;
+    npixels = 20;
+ 
+/*    ffcrim(fptr, bitpix, naxis, naxes, &status); */
+    ffiimg(fptr, bitpix, naxis, naxes, &status);
+    printf("\nffcrim status = %d\n", status);
+
+    /* initialize arrays of values to write to primary array */
+    for (ii = 0; ii < npixels; ii++)
+    {
+        boutarray[ii] = ii * 2;
+        ioutarray[ii] = ii * 2;
+        joutarray[ii] = ii * 2;
+        koutarray[ii] = ii * 2;
+        eoutarray[ii] = ii * 2;
+        doutarray[ii] = ii * 2;
+    }
+
+    /* write a few pixels with each datatype */
+    ffppr(fptr, TBYTE,   1,  2, &boutarray[0],  &status);
+    ffppr(fptr, TSHORT,  3,  2, &ioutarray[2],  &status);
+    ffppr(fptr, TINT,    5,  2, &koutarray[4],  &status);
+    ffppr(fptr, TSHORT,  7,  2, &ioutarray[6],  &status);
+    ffppr(fptr, TLONG,   9,  2, &joutarray[8],  &status);
+    ffppr(fptr, TFLOAT,  11, 2, &eoutarray[10], &status);
+    ffppr(fptr, TDOUBLE, 13, 2, &doutarray[12], &status);
+    printf("ffppr status = %d\n", status);
+
+    /* read back the pixels with each datatype */
+    bnul = 0;
+    inul = 0;
+    knul = 0;
+    jnul = 0;
+    enul = 0.;
+    dnul = 0.;
+
+    ffgpv(fptr, TBYTE,   1,  14, &bnul, binarray, &anynull, &status);
+    ffgpv(fptr, TSHORT,  1,  14, &inul, iinarray, &anynull, &status);
+    ffgpv(fptr, TINT,    1,  14, &knul, kinarray, &anynull, &status);
+    ffgpv(fptr, TLONG,   1,  14, &jnul, jinarray, &anynull, &status);
+    ffgpv(fptr, TFLOAT,  1,  14, &enul, einarray, &anynull, &status);
+    ffgpv(fptr, TDOUBLE, 1,  14, &dnul, dinarray, &anynull, &status);
+
+    printf("\nImage values written with ffppr and read with ffgpv:\n");
+    npixels = 14;
+    for (ii = 0; ii < npixels; ii++)
+        printf(" %2d", binarray[ii]);
+    printf("  %d (byte)\n", anynull);  
+    for (ii = 0; ii < npixels; ii++)
+        printf(" %2d", iinarray[ii]);
+    printf("  %d (short)\n", anynull);  
+    for (ii = 0; ii < npixels; ii++)
+        printf(" %2d", kinarray[ii]);
+    printf("  %d (int)\n", anynull); 
+    for (ii = 0; ii < npixels; ii++)
+        printf(" %2ld", jinarray[ii]);
+    printf("  %d (long)\n", anynull); 
+    for (ii = 0; ii < npixels; ii++)
+        printf(" %2.0f", einarray[ii]);
+    printf("  %d (float)\n", anynull);
+    for (ii = 0; ii < npixels; ii++)
+        printf(" %2.0f", dinarray[ii]);
+    printf("  %d (double)\n", anynull);
+
+    /*
+      ##########################################
+      #  test world coordinate system routines #
+      ##########################################
+    */
+
+    xrval = 45.83;
+    yrval =  63.57;
+    xrpix =  256.;
+    yrpix =  257.;
+    xinc =   -.00277777;
+    yinc =   .00277777;
+
+    /* write the WCS keywords */
+    /* use example values from the latest WCS document */
+    ffpkyd(fptr, "CRVAL1", xrval, 10, "comment", &status);
+    ffpkyd(fptr, "CRVAL2", yrval, 10, "comment", &status);
+    ffpkyd(fptr, "CRPIX1", xrpix, 10, "comment", &status);
+    ffpkyd(fptr, "CRPIX2", yrpix, 10, "comment", &status);
+    ffpkyd(fptr, "CDELT1", xinc, 10, "comment", &status);
+    ffpkyd(fptr, "CDELT2", yinc, 10, "comment", &status);
+ /*   ffpkyd(fptr, "CROTA2", rot, 10, "comment", &status); */
+    ffpkys(fptr, "CTYPE1", xcoordtype, "comment", &status);
+    ffpkys(fptr, "CTYPE2", ycoordtype, "comment", &status);
+    printf("\nWrote WCS keywords status = %d\n",status);
+
+    xrval =  0.;
+    yrval =  0.;
+    xrpix =  0.;
+    yrpix =  0.;
+    xinc =   0.;
+    yinc =   0.;
+    rot =    0.;
+
+    ffgics(fptr, &xrval, &yrval, &xrpix,
+           &yrpix, &xinc, &yinc, &rot, ctype, &status);
+    printf("Read WCS keywords with ffgics status = %d\n",status);
+
+    xpix = 0.5;
+    ypix = 0.5;
+
+    ffwldp(xpix,ypix,xrval,yrval,xrpix,yrpix,xinc,yinc,rot,ctype,
+           &xpos, &ypos,&status);
+
+    printf("  CRVAL1, CRVAL2 = %16.12f, %16.12f\n", xrval,yrval);
+    printf("  CRPIX1, CRPIX2 = %16.12f, %16.12f\n", xrpix,yrpix);
+    printf("  CDELT1, CDELT2 = %16.12f, %16.12f\n", xinc,yinc);
+    printf("  Rotation = %10.3f, CTYPE = %s\n", rot, ctype);
+    printf("Calculated sky coordinate with ffwldp status = %d\n",status);
+    printf("  Pixels (%8.4f,%8.4f) --> (%11.6f, %11.6f) Sky\n",
+            xpix,ypix,xpos,ypos);
+    ffxypx(xpos,ypos,xrval,yrval,xrpix,yrpix,xinc,yinc,rot,ctype,
+           &xpix, &ypix,&status);
+    printf("Calculated pixel coordinate with ffxypx status = %d\n",status);
+    printf("  Sky (%11.6f, %11.6f) --> (%8.4f,%8.4f) Pixels\n",
+            xpos,ypos,xpix,ypix);
+    /*
+      ######################################
+      #  append another ASCII table        #
+      ######################################
+    */
+
+    strcpy(tform[0], "A15");
+    strcpy(tform[1], "I11");
+    strcpy(tform[2], "F15.6");
+    strcpy(tform[3], "E13.5");
+    strcpy(tform[4], "D22.14");
+
+    strcpy(ttype[0], "Name");
+    strcpy(ttype[1], "Ivalue");
+    strcpy(ttype[2], "Fvalue");
+    strcpy(ttype[3], "Evalue");
+    strcpy(ttype[4], "Dvalue");
+
+    strcpy(tunit[0], "");
+    strcpy(tunit[1], "m**2");
+    strcpy(tunit[2], "cm");
+    strcpy(tunit[3], "erg/s");
+    strcpy(tunit[4], "km/s");
+
+    nrows = 11;
+    tfields = 5;
+    strcpy(tblname, "new_table");
+
+    ffcrtb(fptr, ASCII_TBL, nrows, tfields, ttype, tform, tunit, tblname,
+            &status);
+    printf("\nffcrtb status = %d\n", status);
+
+    extvers = 5;
+    ffpkyj(fptr, "EXTVER", extvers, "extension version number", &status);
+
+    ffpcl(fptr, TSTRING, 1, 1, 1, 3, onskey, &status);  /* write string values */
+
+    /* initialize arrays of values to write */
+    
+    for (ii = 0; ii < npixels; ii++)
+    {
+        boutarray[ii] = ii * 3;
+        ioutarray[ii] = ii * 3;
+        joutarray[ii] = ii * 3;
+        koutarray[ii] = ii * 3;
+        eoutarray[ii] = ii * 3;
+        doutarray[ii] = ii * 3;
+    }
+
+    for (ii = 2; ii < 6; ii++)   /* loop over cols 2 - 5 */
+    {
+        ffpcl(fptr, TBYTE,   ii, 1, 1, 2, boutarray,     &status); 
+        ffpcl(fptr, TSHORT,  ii, 3, 1, 2, &ioutarray[2], &status);  
+        ffpcl(fptr, TLONG,   ii, 5, 1, 2, &joutarray[4], &status);  
+        ffpcl(fptr, TFLOAT,  ii, 7, 1, 2, &eoutarray[6], &status);
+        ffpcl(fptr, TDOUBLE, ii, 9, 1, 2, &doutarray[8], &status); 
+    }
+    printf("ffpcl status = %d\n", status);
+
+    /* read back the pixels with each datatype */
+    ffgcv(fptr, TBYTE,   2, 1, 1, 10, &bnul, binarray, &anynull, &status);
+    ffgcv(fptr, TSHORT,  2, 1, 1, 10, &inul, iinarray, &anynull, &status);
+    ffgcv(fptr, TINT,    3, 1, 1, 10, &knul, kinarray, &anynull, &status);
+    ffgcv(fptr, TLONG,   3, 1, 1, 10, &jnul, jinarray, &anynull, &status);
+    ffgcv(fptr, TFLOAT,  4, 1, 1, 10, &enul, einarray, &anynull, &status);
+    ffgcv(fptr, TDOUBLE, 5, 1, 1, 10, &dnul, dinarray, &anynull, &status);
+
+    printf("\nColumn values written with ffpcl and read with ffgcl:\n");
+    npixels = 10;
+    for (ii = 0; ii < npixels; ii++)
+        printf(" %2d", binarray[ii]);
+    printf("  %d (byte)\n", anynull);  
+    for (ii = 0; ii < npixels; ii++)
+        printf(" %2d", iinarray[ii]);
+    printf("  %d (short)\n", anynull);
+
+    for (ii = 0; ii < npixels; ii++)
+        printf(" %2d", kinarray[ii]);
+    printf("  %d (int)\n", anynull); 
+
+    for (ii = 0; ii < npixels; ii++)
+        printf(" %2ld", jinarray[ii]);
+    printf("  %d (long)\n", anynull); 
+    for (ii = 0; ii < npixels; ii++)
+        printf(" %2.0f", einarray[ii]);
+    printf("  %d (float)\n", anynull);
+    for (ii = 0; ii < npixels; ii++)
+        printf(" %2.0f", dinarray[ii]);
+    printf("  %d (double)\n", anynull);
+
+    /*
+      ###########################################################
+      #  perform stress test by cycling thru all the extensions #
+      ###########################################################
+    */
+    printf("\nRepeatedly move to the 1st 4 HDUs of the file:\n");
+    for (ii = 0; ii < 10; ii++)
+    {
+      ffmahd(fptr,  1, &hdutype, &status);
+      printf("%d", ffghdn(fptr, &hdunum));
+      ffmrhd(fptr,  1, &hdutype, &status);
+      printf("%d", ffghdn(fptr, &hdunum));
+      ffmrhd(fptr,  1, &hdutype, &status);
+      printf("%d", ffghdn(fptr, &hdunum));
+      ffmrhd(fptr,  1, &hdutype, &status);
+      printf("%d", ffghdn(fptr, &hdunum));
+      ffmrhd(fptr, -1, &hdutype, &status);
+      printf("%d", ffghdn(fptr, &hdunum));
+      if (status > 0)
+         break;
+    }
+    printf("\n");
+
+    printf("Move to extensions by name and version number: (ffmnhd)\n");
+    extvers = 1;
+    ffmnhd(fptr, ANY_HDU, binname, (int) extvers, &status);
+    ffghdn(fptr, &hdunum);
+    printf(" %s, %ld = hdu %d, %d\n", binname, extvers, hdunum, status);
+    extvers = 3;
+    ffmnhd(fptr, ANY_HDU, binname, (int) extvers, &status);
+    ffghdn(fptr, &hdunum);
+    printf(" %s, %ld = hdu %d, %d\n", binname, extvers, hdunum, status);
+    extvers = 4;
+    ffmnhd(fptr, ANY_HDU, binname, (int) extvers, &status);
+    ffghdn(fptr, &hdunum);
+    printf(" %s, %ld = hdu %d, %d\n", binname, extvers, hdunum, status);
+
+
+    strcpy(tblname, "Test-ASCII");
+    extvers = 2;
+    ffmnhd(fptr, ANY_HDU, tblname, (int) extvers, &status);
+    ffghdn(fptr, &hdunum);
+    printf(" %s, %ld = hdu %d, %d\n", tblname, extvers, hdunum, status);
+
+    strcpy(tblname, "new_table");
+    extvers = 5;
+    ffmnhd(fptr, ANY_HDU, tblname, (int) extvers, &status);
+    ffghdn(fptr, &hdunum);
+    printf(" %s, %ld = hdu %d, %d\n", tblname, extvers, hdunum, status);
+    extvers = 0;
+    ffmnhd(fptr, ANY_HDU, binname, (int) extvers, &status);
+    ffghdn(fptr, &hdunum);
+    printf(" %s, %ld = hdu %d, %d\n", binname, extvers, hdunum, status);
+    extvers = 17;
+    ffmnhd(fptr, ANY_HDU, binname, (int) extvers, &status);
+    ffghdn(fptr, &hdunum);
+    printf(" %s, %ld = hdu %d, %d", binname, extvers, hdunum, status);
+    printf (" (expect a 301 error status here)\n");
+    status = 0;
+
+    ffthdu(fptr, &hdunum, &status);
+    printf("Total number of HDUs in the file = %d\n", hdunum);
+    /*
+      ########################
+      #  checksum tests      #
+      ########################
+    */
+    checksum = 1234567890;
+    ffesum(checksum, 0, asciisum);
+    printf("\nEncode checksum: %lu -> %s\n", checksum, asciisum);
+    checksum = 0;
+    ffdsum(asciisum, 0, &checksum);
+    printf("Decode checksum: %s -> %lu\n", asciisum, checksum);
+
+    ffpcks(fptr, &status);
+
+    /*
+       don't print the CHECKSUM value because it is different every day
+       because the current date is in the comment field.
+
+       ffgcrd(fptr, "CHECKSUM", card, &status);
+       printf("%s\n", card);
+    */
+
+    ffgcrd(fptr, "DATASUM", card, &status);
+    printf("%.30s\n", card);
+
+    ffgcks(fptr, &datsum, &checksum, &status);
+    printf("ffgcks data checksum, status = %lu, %d\n",
+            datsum, status);
+
+    ffvcks(fptr, &datastatus, &hdustatus, &status); 
+    printf("ffvcks datastatus, hdustatus, status = %d %d %d\n",
+              datastatus, hdustatus, status);
+ 
+    ffprec(fptr,
+    "new_key = 'written by fxprec' / to change checksum", &status);
+    ffupck(fptr, &status);
+    printf("ffupck status = %d\n", status);
+
+    ffgcrd(fptr, "DATASUM", card, &status);
+    printf("%.30s\n", card);
+    ffvcks(fptr, &datastatus, &hdustatus, &status); 
+    printf("ffvcks datastatus, hdustatus, status = %d %d %d\n",
+              datastatus, hdustatus, status);
+ 
+    /*
+      delete the checksum keywords, so that the FITS file is always
+      the same, regardless of the date of when testprog is run.
+    */
+
+    ffdkey(fptr, "CHECKSUM", &status);
+    ffdkey(fptr, "DATASUM",  &status);
+
+    /*
+      ############################
+      #  close file and quit     #
+      ############################
+    */
+
+ errstatus:  /* jump here on error */
+
+    ffclos(fptr, &status); 
+    printf("ffclos status = %d\n", status);
+
+    printf("\nNormally, there should be 8 error messages on the stack\n");
+    printf("all regarding 'numerical overflows':\n");
+
+    ffgmsg(errmsg);
+    nmsg = 0;
+
+    while (errmsg[0])
+    {
+        printf(" %s\n", errmsg);
+        nmsg++;
+        ffgmsg(errmsg);
+    }
+
+    if (nmsg != 8)
+        printf("\nWARNING: Did not find the expected 8 error messages!\n");
+
+    ffgerr(status, errmsg);
+    printf("\nStatus = %d: %s\n", status, errmsg);
+
+    /* free the allocated memory */
+    for (ii = 0; ii < 21; ii++) 
+        free(inskey[ii]);   
+    for (ii = 0; ii < 10; ii++)
+    {
+      free(ttype[ii]);
+      free(tform[ii]);
+      free(tunit[ii]);
+    }
+
+    return(0);
+}
+
diff --git a/external/cfitsio/testprog.out b/external/cfitsio/testprog.out
new file mode 100644
index 0000000..3d6093f
--- /dev/null
+++ b/external/cfitsio/testprog.out
@@ -0,0 +1,797 @@
+CFITSIO TESTPROG, v3.310
+
+Try opening then closing a nonexistent file:
+  ffopen fptr, status  = 0 104 (expect an error)
+  ffclos status = 115
+
+ffinit create new file status = 0
+Name of file = testprog.fit, I/O mode = 1
+
+test writing of long string keywords:
+ 123456789012345678901234567890123456789012345678901234567890123456789012345
+'12345678901234567890123456789012345678901234567890123456789012345678'
+ 1234567890123456789012345678901234567890123456789012345678901234'6789012345
+'1234567890123456789012345678901234567890123456789012345678901234''67'
+ 1234567890123456789012345678901234567890123456789012345678901234''789012345
+'1234567890123456789012345678901234567890123456789012345678901234'''''
+ 1234567890123456789012345678901234567890123456789012345678901234567'9012345
+'1234567890123456789012345678901234567890123456789012345678901234567'
+ffflus status = 0
+HDU number = 1
+
+Values read back from primary array (99 = null pixel)
+The 1st, and every 4th pixel should be undefined:
+ 99  2  3 99  5  6  7 99  9 10 11 99 13 14 15 99 17 18 19 99  1 (ffgpvb)
+ 99  2  3 99  5  6  7 99  9 10 11 99 13 14 15 99 17 18 19 99  1 (ffgpvi)
+ 99  2  3 99  5  6  7 99  9 10 11 99 13 14 15 99 17 18 19 99  1 (ffgpvj)
+ 99  2  3 99  5  6  7 99  9 10 11 99 13 14 15 99 17 18 19 99  1 (ffgpve)
+ 99  2  3 99  5  6  7 99  9 10 11 99 13 14 15 99 17 18 19 99  1 (ffgpvd)
+  *  2  3  *  5  6  7  *  9 10 11  * 13 14 15  * 17 18 19  *  1 (ffgpfb)
+  *  2  3  *  5  6  7  *  9 10 11  * 13 14 15  * 17 18 19  *  1 (ffgpfi)
+  *  2  3  *  5  6  7  *  9 10 11  * 13 14 15  * 17 18 19  *  1 (ffgpfj)
+  *  2  3  *  5  6  7  *  9 10 11  * 13 14 15  * 17 18 19  *  1 (ffgpfe)
+  *  2  3  *  5  6  7  *  9 10 11  * 13 14 15  * 17 18 19  *  1 (ffgpfd)
+
+Closed then reopened the FITS file 10 times.
+HDU number = 1
+Name of file = testprog.fit, I/O mode = 1
+
+Read back keywords:
+simple = 1, bitpix = 32, naxis = 2, naxes = (10, 2)
+  pcount = 0, gcount = 1, extend = 1
+KEY_PREC= 'This keyword was written by fxprec' / comment goes here
+KEY_PREC : 'This keyword was written by fxprec' : comment goes here :
+KEY_PREC= 'This keyword was written by fxprec' / comment goes here
+KY_PKNS1 : 'first string' : fxpkns comment :
+KEY_PKYS value_string fxpkys comment 0
+KEY_PKYL 1 fxpkyl comment 0
+KEY_PKYJ 11 fxpkyj comment 0
+KEY_PKYJ 11.000000 fxpkyj comment 0
+KEY_PKYJ 11.000000 fxpkyj comment 0
+KEY_PKY S value_string fxpkys comment 0
+KEY_PKY L 1 fxpkyl comment 0
+KEY_PKY BYTE 11 fxpkyj comment 0
+KEY_PKY SHORT 11 fxpkyj comment 0
+KEY_PKY INT 11 fxpkyj comment 0
+KEY_PKY J 11 fxpkyj comment 0
+KEY_PKY E 13.131310 fxpkye comment 0
+KEY_PKY D 15.151515 fxpkyd comment 0
+KEY_PKYF 12.121210 fxpkyf comment 0
+KEY_PKYE 13.131310 fxpkye comment 0
+KEY_PKYG 14.14141414141414 fxpkyg comment 0
+KEY_PKYD 15.15151515151520 fxpkyd comment 0
+KEY_PKYC 13.131310 14.141410 fxpkyc comment 0
+KEY_PKFC 13.131313 14.141414 fxpkfc comment 0
+KEY_PKYM 15.151515 16.161616 fxpkym comment 0
+KEY_PKFM 15.151515 16.161616 fxpkfm comment 0
+KEY_PKYT 12345678 0.12345678901235 fxpkyt comment 0
+KEY_PKY J 11 [km/s/Mpc] fxpkyj comment 0
+KEY_PKY units = km/s/Mpc
+KEY_PKY J 11 fxpkyj comment 0
+KEY_PKY units = 
+KEY_PKY J 11 [feet/second/second] fxpkyj comment 0
+KEY_PKY units = feet/second/second
+KEY_PKLS long string value = 
+This is a very long string value that is continued over more than one keyword.
+header contains 65 keywords; located at keyword 27 
+ffgkns:  first string, second string, 
+ffgknl:  1, 0, 1
+ffgknj:  11, 12, 13
+ffgkne:  13.131310, 14.141410, 15.151520
+ffgknd:  15.151515, 16.161616, 17.171717
+
+Before deleting the HISTORY and DATE keywords...
+COMMENT 
+HISTORY 
+DATE    
+KY_PKNS1
+
+After deleting the keywords...
+COMMENT   This keyword was written by fxpcom.
+KY_PKNS1= 'first string'       / fxpkns comment
+
+After inserting the keywords...
+COMMENT   This keyword was written by fxpcom.
+KY_IREC = 'This keyword inserted by fxirec'
+KY_IKYS = 'insert_value_string' / ikys comment
+KY_IKYJ =                   49 / ikyj comment
+KY_IKYL =                    T / ikyl comment
+KY_IKYE =           1.2346E+01 / ikye comment
+KY_IKYD = 1.23456789012346E+01 / ikyd comment
+KY_IKYF =              12.3456 / ikyf comment
+KY_IKYG =     12.3456789012346 / ikyg comment
+KY_PKNS1= 'first string'       / fxpkns comment
+
+After modifying the keywords...
+COMMENT   This keyword was modified by fxmrec
+KY_MREC = 'This keyword was modified by fxmcrd'
+NEWIKYS = 'modified_string'    / ikys comment
+KY_IKYJ =                   50 / This is a modified comment
+KY_IKYL =                    F / ikyl comment
+KY_IKYE =          -1.2346E+01 / ikye comment
+KY_IKYD = -1.23456789012346E+01 / modified comment
+KY_IKYF =             -12.3456 / ikyf comment
+KY_IKYG =    -12.3456789012346 / ikyg comment
+KY_PKNS1= 'first string'       / fxpkns comment
+
+After updating the keywords...
+COMMENT   This keyword was modified by fxmrec
+KY_UCRD = 'This keyword was updated by fxucrd'
+NEWIKYS = 'updated_string'     / ikys comment
+KY_IKYJ =                   51 / This is a modified comment
+KY_IKYL =                    T / ikyl comment
+KY_IKYE =          -1.3346E+01 / ikye comment
+KY_IKYD = -1.33456789012346E+01 / modified comment
+KY_IKYF =             -13.3456 / ikyf comment
+KY_IKYG =    -13.3456789012346 / ikyg comment
+KY_PKNS1= 'first string'       / fxpkns comment
+
+Keywords found using wildcard search (should be 13)...
+KEY_PKYS= 'value_string'       / fxpkys comment
+KEY_PKYL=                    T / fxpkyl comment
+KEY_PKYJ=                   11 / [feet/second/second] fxpkyj comment
+KEY_PKYF=             12.12121 / fxpkyf comment
+KEY_PKYE=         1.313131E+01 / fxpkye comment
+KEY_PKYG=    14.14141414141414 / fxpkyg comment
+KEY_PKYD= 1.51515151515152E+01 / fxpkyd comment
+KEY_PKYC= (1.313131E+01, 1.414141E+01) / fxpkyc comment
+KEY_PKYM= (1.51515151515152E+01, 1.61616161616162E+01) / fxpkym comment
+KEY_PKFC= (13.131313, 14.141414) / fxpkfc comment
+KEY_PKFM= (15.15151515151515, 16.16161616161616) / fxpkfm comment
+KEY_PKYT= 12345678.1234567890123456 / fxpkyt comment
+NEWIKYS = 'updated_string'     / ikys comment
+
+Copied keyword: ffgkne:  14.141410, 15.151520, 13.131310
+Updated header using template file (ffpktp)
+
+ffibin status = 0
+HDU number = 2
+header contains 33 keywords; located at keyword 1 
+header contains 33 keywords with room for 74 more
+TDIM3 = (1,2,8), 3, 1, 2, 8
+ffpcl_ status = 0
+
+Find the column numbers; a returned status value of 237 is
+expected and indicates that more than one column name matches
+the input column name template.  Status = 219 indicates that
+there was no matching column name.
+Column Xvalue is number 3; status = 0.
+Column Avalue is number 1; status = 237.
+Column Lvalue is number 2; status = 237.
+Column Xvalue is number 3; status = 237.
+Column Bvalue is number 4; status = 237.
+Column Ivalue is number 5; status = 237.
+Column Jvalue is number 6; status = 237.
+Column Evalue is number 7; status = 237.
+Column Dvalue is number 8; status = 237.
+Column Cvalue is number 9; status = 237.
+Column Mvalue is number 10; status = 237.
+Column  is number 0; status = 219.
+
+Information about each column:
+ 15A  16 15 15 Avalue, , A, 15, 1.000000, 0.000000, 1234554321, .
+  1L  14  1  1 Lvalue, m**2, L, 1, 1.000000, 0.000000, 1234554321, .
+ 16X   1 16  1 Xvalue, cm, X, 16, 1.000000, 0.000000, 1234554321, .
+  1B  11  1  1 Bvalue, erg/s, B, 1, 1.000000, 0.000000, 99, .
+  1I  21  1  2 Ivalue, km/s, I, 1, 1.000000, 0.000000, 99, .
+  1J  41  1  4 Jvalue, , J, 1, 1.000000, 0.000000, 99, .
+  1E  42  1  4 Evalue, , E, 1, 1.000000, 0.000000, 1234554321, .
+  1D  82  1  8 Dvalue, , D, 1, 1.000000, 0.000000, 1234554321, .
+  1C  83  1  8 Cvalue, , C, 1, 1.000000, 0.000000, 1234554321, .
+  1M 163  1 16 Mvalue, , M, 1, 1.000000, 0.000000, 1234554321, .
+
+ffitab status = 0
+HDU number = 2
+ffpcl_ status = 0
+
+ASCII table: rowlen, nrows, tfields, extname: 76 11 5 Test-ASCII
+    Name   1      A15          
+  Ivalue  17      I10     m**2 
+  Fvalue  28    F14.6       cm 
+  Evalue  43    E12.5    erg/s 
+  Dvalue  56   D21.14     km/s 
+
+Data values read from ASCII table:
+   first string  1  1  1  1.0  1.0
+  second string  2  2  2  2.0  2.0
+                 3  3  3  3.0  3.0
+      UNDEFINED  4  4  4  4.0  4.0
+                 5  5  5  5.0  5.0
+                 6  6  6  6.0  6.0
+                 7  7  7  7.0  7.0
+                 8  8  8  8.0  8.0
+                 9  9  9  9.0  9.0
+                10 10 10 10.0 10.0
+                99 99 99 99.0 99.0
+
+      1       1.000000  1.00000E+00  1.00000000000000E+00second string        
+
+Column name is number 1; status = 0.
+Column Ivalue is number 2; status = 237.
+Column Fvalue is number 3; status = 237.
+Column Evalue is number 4; status = 237.
+Column Dvalue is number 5; status = 237.
+Column  is number 0; status = 219.
+ A15  16  1 15 Name, 1, , A15, 1.000000, 0.000000, null1, .
+ I10  41  1 10 Ivalue, 17, m**2, I10, 1.000000, 0.000000, null2, .
+F14.6  82  1 14 Fvalue, 28, cm, F14.6, 1.000000, 0.000000, null3, .
+E12.5  42  1 12 Evalue, 43, erg/s, E12.5, 1.000000, 0.000000, null4, .
+D21.14  82  1 21 Dvalue, 56, km/s, D21.14, 1.000000, 0.000000, null5, .
+
+
+Data values after inserting 3 rows after row 2:
+   first string  1  1  1  1.0  1.0
+  second string  2  2  2  2.0  2.0
+                 0  0  0  0.0  0.0
+                 0  0  0  0.0  0.0
+                 0  0  0  0.0  0.0
+                 3  3  3  3.0  3.0
+      UNDEFINED  4  4  4  4.0  4.0
+                 5  5  5  5.0  5.0
+                 6  6  6  6.0  6.0
+                 7  7  7  7.0  7.0
+                 8  8  8  8.0  8.0
+                 9  9  9  9.0  9.0
+                10 10 10 10.0 10.0
+                99 99 99 99.0 99.0
+
+Data values after deleting 2 rows at row 10:
+   first string  1  1  1  1.0  1.0
+  second string  2  2  2  2.0  2.0
+                 0  0  0  0.0  0.0
+                 0  0  0  0.0  0.0
+                 0  0  0  0.0  0.0
+                 3  3  3  3.0  3.0
+      UNDEFINED  4  4  4  4.0  4.0
+                 5  5  5  5.0  5.0
+                 6  6  6  6.0  6.0
+                 9  9  9  9.0  9.0
+                10 10 10 10.0 10.0
+                99 99 99 99.0 99.0
+
+Data values after deleting column 3:
+   first string  1  1  1.0  1.0
+  second string  2  2  2.0  2.0
+                 0  0  0.0  0.0
+                 0  0  0.0  0.0
+                 0  0  0.0  0.0
+                 3  3  3.0  3.0
+      UNDEFINED  4  4  4.0  4.0
+                 5  5  5.0  5.0
+                 6  6  6.0  6.0
+                 9  9  9.0  9.0
+                10 10 10.0 10.0
+                99 99 99.0 99.0
+
+Data values after inserting column 5:
+   first string  1  1  1.0  1.0 0
+  second string  2  2  2.0  2.0 0
+                 0  0  0.0  0.0 0
+                 0  0  0.0  0.0 0
+                 0  0  0.0  0.0 0
+                 3  3  3.0  3.0 0
+      UNDEFINED  4  4  4.0  4.0 0
+                 5  5  5.0  5.0 0
+                 6  6  6.0  6.0 0
+                 9  9  9.0  9.0 0
+                10 10 10.0 10.0 0
+                99 99 99.0 99.0 0
+Create temporary file: ffinit status = 0
+
+Create null primary array: ffiimg status = 0
+
+Create ASCII table with 0 columns: ffitab status = 0
+copy column, ffcpcl status = 0
+copy column, ffcpcl status = 0
+copy column, ffcpcl status = 0
+copy column, ffcpcl status = 0
+
+Create Binary table with 0 columns: ffibin status = 0
+copy column, ffcpcl status = 0
+copy column, ffcpcl status = 0
+copy column, ffcpcl status = 0
+copy column, ffcpcl status = 0
+Delete the tmp file: ffdelt status = 0
+HDU number = 3
+header contains 38 keywords with room for 69 more
+
+Binary table: nrows, tfields, extname, pcount: 21 10 Test-BINTABLE 0
+  Avalue      15A          
+  Lvalue       1L     m**2 
+  Xvalue      16X       cm 
+  Bvalue       1B    erg/s 
+  Ivalue       1I     km/s 
+  Jvalue       1J          
+  Evalue       1E          
+  Dvalue       1D          
+  Cvalue       1C          
+  Mvalue       1M          
+
+Data values read from binary table:
+  Bit column (X) data values: 
+
+01001100 01110000 11110000 01111100 00000000 
+
+null string column value = -- (should be --)
+
+Read columns with ffgcv_:
+   first string 0  76  1   1   1   1.0   1.0 (  1.0, -2.0) (  1.0, -2.0) 
+  second string 1 112  2   2   2   2.0   2.0 (  3.0, -4.0) (  3.0, -4.0) 
+                0 240  3   3   3   3.0   3.0 (  5.0, -6.0) (  5.0, -6.0) 
+    NOT DEFINED 0 124  0  -4  -4  -4.0  -4.0 (  7.0, -8.0) (  7.0, -8.0) 
+    NOT DEFINED 1   0  5   5   5   5.0   5.0 (  9.0,-10.0) (  9.0,-10.0) 
+    NOT DEFINED 1   0  0  -6  -6  -6.0  -6.0 ( 11.0,-12.0) ( 11.0,-12.0) 
+    NOT DEFINED 0   0  7   7   7   7.0   7.0 ( 13.0,-14.0) ( 13.0,-14.0) 
+    NOT DEFINED 0   0  0  -8  -8  -8.0  -8.0 ( 15.0,-16.0) ( 15.0,-16.0) 
+    NOT DEFINED 0   0  9   9   9   9.0   9.0 ( 17.0,-18.0) ( 17.0,-18.0) 
+    NOT DEFINED 1   0  0 -10 -10 -10.0 -10.0 ( 19.0,-20.0) ( 19.0,-20.0) 
+    NOT DEFINED 0   0 98  98  98  98.0  98.0 (  0.0,  0.0) (  0.0,  0.0) 
+    NOT DEFINED 1   0 12  12  12  12.0  12.0 (  0.0,  0.0) (  0.0,  0.0) 
+    NOT DEFINED 0   0 98  98  98  98.0  98.0 (  0.0,  0.0) (  0.0,  0.0) 
+    NOT DEFINED 0   0  0 -14 -14 -14.0 -14.0 (  0.0,  0.0) (  0.0,  0.0) 
+    NOT DEFINED 0   0 98  98  98  98.0  98.0 (  0.0,  0.0) (  0.0,  0.0) 
+    NOT DEFINED 0   0  0 -16 -16 -16.0 -16.0 (  0.0,  0.0) (  0.0,  0.0) 
+    NOT DEFINED 1   0 98  98  98  98.0  98.0 (  0.0,  0.0) (  0.0,  0.0) 
+    NOT DEFINED 1   0  0 -18 -18 -18.0 -18.0 (  0.0,  0.0) (  0.0,  0.0) 
+    NOT DEFINED 1   0 98  98  98  98.0  98.0 (  0.0,  0.0) (  0.0,  0.0) 
+    NOT DEFINED 1   0  0 -20 -20 -20.0 -20.0 (  0.0,  0.0) (  0.0,  0.0) 
+    NOT DEFINED 0   0 98  98  98  98.0  98.0 (  0.0,  0.0) (  0.0,  0.0) 
+
+Read columns with ffgcf_:
+   first string 0  76  1   1   1   1.0   1.0 (  1.0, -2.0) (  1.0, -2.0)
+  second string 1 112  2   2   2   2.0   2.0 (  3.0, -4.0) (  3.0, -4.0)
+                0 240  3   3   3   3.0   3.0 (  5.0, -6.0) (  5.0, -6.0)
+                0 124  0  -4  -4  -4.0  -4.0 (  7.0, -8.0) (  7.0, -8.0)
+                1   0  5   5   5   5.0   5.0 (  9.0,-10.0) (  9.0,-10.0)
+                1   0  0  -6  -6  -6.0  -6.0 ( 11.0,-12.0) ( 11.0,-12.0)
+                0   0  7   7   7   7.0   7.0 ( 13.0,-14.0) ( 13.0,-14.0)
+                0   0  0  -8  -8  -8.0  -8.0 ( 15.0,-16.0) ( 15.0,-16.0)
+                0   0  9   9   9   9.0   9.0 ( 17.0,-18.0) ( 17.0,-18.0)
+                1   0  0 -10 -10 -10.0 -10.0 ( 19.0,-20.0) ( 19.0,-20.0)
+                0   0 99  99 
+                1   0 12  12 
+                0   0 99  99 
+                0   0  0 -14 
+                0   0 99  99 
+                0   0  0 -16 
+                1   0 99  99 
+                1   0  0 -18 
+                1   0 99  99 
+                1   0  0 -20 
+                0   0 99  99 
+
+Data values after inserting 3 rows after row 2:
+   first string  1   1   1   1.0   1.0
+  second string  2   2   2   2.0   2.0
+    NOT DEFINED  0   0   0   0.0   0.0
+    NOT DEFINED  0   0   0   0.0   0.0
+    NOT DEFINED  0   0   0   0.0   0.0
+                 3   3   3   3.0   3.0
+    NOT DEFINED  0  -4  -4  -4.0  -4.0
+    NOT DEFINED  5   5   5   5.0   5.0
+    NOT DEFINED  0  -6  -6  -6.0  -6.0
+    NOT DEFINED  7   7   7   7.0   7.0
+    NOT DEFINED  0  -8  -8  -8.0  -8.0
+    NOT DEFINED  9   9   9   9.0   9.0
+    NOT DEFINED  0 -10 -10 -10.0 -10.0
+    NOT DEFINED 98  98  98  98.0  98.0
+
+Data values after deleting 2 rows at row 10:
+   first string  1   1   1   1.0   1.0
+  second string  2   2   2   2.0   2.0
+    NOT DEFINED  0   0   0   0.0   0.0
+    NOT DEFINED  0   0   0   0.0   0.0
+    NOT DEFINED  0   0   0   0.0   0.0
+                 3   3   3   3.0   3.0
+    NOT DEFINED  0  -4  -4  -4.0  -4.0
+    NOT DEFINED  5   5   5   5.0   5.0
+    NOT DEFINED  0  -6  -6  -6.0  -6.0
+    NOT DEFINED  9   9   9   9.0   9.0
+    NOT DEFINED  0 -10 -10 -10.0 -10.0
+    NOT DEFINED 98  98  98  98.0  98.0
+
+Data values after deleting column 6:
+   first string  1   1   1.0   1.0
+  second string  2   2   2.0   2.0
+    NOT DEFINED  0   0   0.0   0.0
+    NOT DEFINED  0   0   0.0   0.0
+    NOT DEFINED  0   0   0.0   0.0
+                 3   3   3.0   3.0
+    NOT DEFINED  0  -4  -4.0  -4.0
+    NOT DEFINED  5   5   5.0   5.0
+    NOT DEFINED  0  -6  -6.0  -6.0
+    NOT DEFINED  9   9   9.0   9.0
+    NOT DEFINED  0 -10 -10.0 -10.0
+    NOT DEFINED 98  98  98.0  98.0
+
+Data values after inserting column 8:
+   first string  1   1   1.0   1.0 0
+  second string  2   2   2.0   2.0 0
+    NOT DEFINED  0   0   0.0   0.0 0
+    NOT DEFINED  0   0   0.0   0.0 0
+    NOT DEFINED  0   0   0.0   0.0 0
+                 3   3   3.0   3.0 0
+    NOT DEFINED  0  -4  -4.0  -4.0 0
+    NOT DEFINED  5   5   5.0   5.0 0
+    NOT DEFINED  0  -6  -6.0  -6.0 0
+    NOT DEFINED  9   9   9.0   9.0 0
+    NOT DEFINED  0 -10 -10.0 -10.0 0
+    NOT DEFINED 98  98  98.0  98.0 0
+
+Values after setting 1st 10 elements in column 8 = null:
+   first string  1   1   1.0   1.0 98
+  second string  2   2   2.0   2.0 98
+    NOT DEFINED  0   0   0.0   0.0 98
+    NOT DEFINED  0   0   0.0   0.0 98
+    NOT DEFINED  0   0   0.0   0.0 98
+                 3   3   3.0   3.0 98
+    NOT DEFINED  0  -4  -4.0  -4.0 98
+    NOT DEFINED  5   5   5.0   5.0 98
+    NOT DEFINED  0  -6  -6.0  -6.0 98
+    NOT DEFINED  9   9   9.0   9.0 98
+    NOT DEFINED  0 -10 -10.0 -10.0 0
+    NOT DEFINED 98  98  98.0  98.0 0
+Create temporary file: ffinit status = 0
+
+Create null primary array: ffiimg status = 0
+
+Create binary table with 0 columns: ffibin status = 0
+copy column, ffcpcl status = 0
+copy column, ffcpcl status = 0
+copy column, ffcpcl status = 0
+copy column, ffcpcl status = 0
+copy column, ffcpcl status = 0
+copy column, ffcpcl status = 0
+copy column, ffcpcl status = 0
+Delete the tmp file: ffdelt status = 0
+ffibin status = 0
+HDU number = 2
+      0   1000  10000  33000  66000   -999
+      0   1000  10000  32768  65535   -999
+      0   1000  10000  32800  65500   -999
+
+      0      1     10     33     66   -999
+ -32768 -31768 -22768      0  32767   -999
+     -1      9     99    327    654   -999
+
+Create image extension: ffiimg status = 0
+HDU number = 3
+
+Wrote whole 2D array: ffp2di status = 0
+
+Read whole 2D array: ffg2di status = 0
+   0   1   2   3   4   5   6   7   8   9  10  11  12  13  14   0   0   0   0
+  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24   0   0   0   0
+  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34   0   0   0   0
+  30  31  32  33  34  35  36  37  38  39  40  41  42  43  44   0   0   0   0
+  40  41  42  43  44  45  46  47  48  49  50  51  52  53  54   0   0   0   0
+  50  51  52  53  54  55  56  57  58  59  60  61  62  63  64   0   0   0   0
+  60  61  62  63  64  65  66  67  68  69  70  71  72  73  74   0   0   0   0
+  70  71  72  73  74  75  76  77  78  79  80  81  82  83  84   0   0   0   0
+  80  81  82  83  84  85  86  87  88  89  90  91  92  93  94   0   0   0   0
+  90  91  92  93  94  95  96  97  98  99 100 101 102 103 104   0   0   0   0
+ 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114   0   0   0   0
+ 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124   0   0   0   0
+ 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134   0   0   0   0
+ 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144   0   0   0   0
+ 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154   0   0   0   0
+ 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164   0   0   0   0
+ 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174   0   0   0   0
+ 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184   0   0   0   0
+ 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194   0   0   0   0
+ 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204   0   0   0   0
+ 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214   0   0   0   0
+ 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224   0   0   0   0
+ 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234   0   0   0   0
+ 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244   0   0   0   0
+ 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254   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   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   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   0   0
+   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
+
+Wrote subset 2D array: ffpssi status = 0
+
+Read whole 2D array: ffg2di status = 0
+   0   1   2   3   4   5   6   7   8   9  10  11  12  13  14   0   0   0   0
+  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24   0   0   0   0
+  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34   0   0   0   0
+  30  31  32  33  34  35  36  37  38  39  40  41  42  43  44   0   0   0   0
+  40  41  42  43   0  -1  -2  -3  -4  -5  -6  -7  -8  -9  54   0   0   0   0
+  50  51  52  53 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19  64   0   0   0   0
+  60  61  62  63 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29  74   0   0   0   0
+  70  71  72  73 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39  84   0   0   0   0
+  80  81  82  83 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49  94   0   0   0   0
+  90  91  92  93 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 104   0   0   0   0
+ 100 101 102 103 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 114   0   0   0   0
+ 110 111 112 113 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 124   0   0   0   0
+ 120 121 122 123 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 134   0   0   0   0
+ 130 131 132 133 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 144   0   0   0   0
+ 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154   0   0   0   0
+ 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164   0   0   0   0
+ 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174   0   0   0   0
+ 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184   0   0   0   0
+ 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194   0   0   0   0
+ 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204   0   0   0   0
+ 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214   0   0   0   0
+ 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224   0   0   0   0
+ 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234   0   0   0   0
+ 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244   0   0   0   0
+ 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254   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   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   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   0   0
+   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
+
+Read subset of 2D array: ffgsvi status = 0
+  41  43  -1  -3  -5  71  73 -31 -33 -35
+
+Create image extension: ffiimg status = 0
+HDU number = 4
+Create temporary file: ffinit status = 0
+Copy image extension to primary array of tmp file.
+ffcopy status = 0
+SIMPLE  =                    T / file does conform to FITS standard
+BITPIX  =                   16 / number of bits per data pixel
+NAXIS   =                    2 / number of data axes
+NAXIS1  =                   15 / length of data axis 1
+NAXIS2  =                   25 / length of data axis 2
+EXTEND  =                    T / FITS dataset may contain extensions
+Delete the tmp file: ffdelt status = 0
+Delete the image extension; hdutype, status = 1 0
+HDU number = 4
+ffcrhd status = 0
+Variable length arrays: ffphbn status = 0
+ffpcl_ status = 0
+PCOUNT = 4446
+HDU number = 6
+A   0
+L  0 0
+X  0 0
+B  1 0
+I  1 0
+J  1 0
+E  1 0
+D  1 0
+Column 8 repeat and offset = 1 14
+A ab 0
+L  0  1 0
+X  0  1 0
+B 99  2 0
+I 99  2 0
+J 99  2 0
+E 99  2 0
+D 99  2 0
+Column 8 repeat and offset = 2 49
+A abc 0
+L  0  0  0 0
+X  0  1  0 0
+B  1 99  3 0
+I  1 99  3 0
+J  1 99  3 0
+E  1 99  3 0
+D  1 99  3 0
+Column 8 repeat and offset = 3 105
+A abcd 0
+L  0  1  0  0 0
+X  0  1  0  0 0
+B  1  2 99  4 0
+I  1  2 99  4 0
+J  1  2 99  4 0
+E  1  2 99  4 0
+D  1  2 99  4 0
+Column 8 repeat and offset = 4 182
+A abcde 0
+L  0  1  0  0  1 0
+X  0  1  0  0  1 0
+B  1  2  3 99  5 0
+I  1  2  3 99  5 0
+J  1  2  3 99  5 0
+E  1  2  3 99  5 0
+D  1  2  3 99  5 0
+Column 8 repeat and offset = 5 280
+A abcdef 0
+L  0  1  0  0  0  1 0
+X  0  1  0  0  1  1 0
+B  1  2  3  4 99  6 0
+I  1  2  3  4 99  6 0
+J  1  2  3  4 99  6 0
+E  1  2  3  4 99  6 0
+D  1  2  3  4 99  6 0
+Column 8 repeat and offset = 6 399
+A abcdefg 0
+L  0  1  0  0  1  0  0 0
+X  0  1  0  0  1  1  0 0
+B  1  2  3  4  5 99  7 0
+I  1  2  3  4  5 99  7 0
+J  1  2  3  4  5 99  7 0
+E  1  2  3  4  5 99  7 0
+D  1  2  3  4  5 99  7 0
+Column 8 repeat and offset = 7 539
+A abcdefgh 0
+L  0  1  0  0  1  1  0  0 0
+X  0  1  0  0  1  1  0  0 0
+B  1  2  3  4  5  6 99  8 0
+I  1  2  3  4  5  6 99  8 0
+J  1  2  3  4  5  6 99  8 0
+E  1  2  3  4  5  6 99  8 0
+D  1  2  3  4  5  6 99  8 0
+Column 8 repeat and offset = 8 700
+A abcdefghi 0
+L  0  1  0  0  1  1  0  0  0 0
+X  0  1  0  0  1  1  0  0  0 0
+B  1  2  3  4  5  6  7 99  9 0
+I  1  2  3  4  5  6  7 99  9 0
+J  1  2  3  4  5  6  7 99  9 0
+E  1  2  3  4  5  6  7 99  9 0
+D  1  2  3  4  5  6  7 99  9 0
+Column 8 repeat and offset = 9 883
+A abcdefghij 0
+L  0  1  0  0  1  1  0  0  0  1 0
+X  0  1  0  0  1  1  0  0  0  1 0
+B  1  2  3  4  5  6  7  8 99 10 0
+I  1  2  3  4  5  6  7  8 99 10 0
+J  1  2  3  4  5  6  7  8 99 10 0
+E  1  2  3  4  5  6  7  8 99 10 0
+D  1  2  3  4  5  6  7  8 99 10 0
+Column 8 repeat and offset = 10 1087
+A abcdefghijk 0
+L  0  1  0  0  1  1  0  0  0  0  1 0
+X  0  1  0  0  1  1  0  0  0  1  1 0
+B  1  2  3  4  5  6  7  8  9 99 11 0
+I  1  2  3  4  5  6  7  8  9 99 11 0
+J  1  2  3  4  5  6  7  8  9 99 11 0
+E  1  2  3  4  5  6  7  8  9 99 11 0
+D  1  2  3  4  5  6  7  8  9 99 11 0
+Column 8 repeat and offset = 11 1312
+A abcdefghijkl 0
+L  0  1  0  0  1  1  0  0  0  1  0  1 0
+X  0  1  0  0  1  1  0  0  0  1  1  1 0
+B  1  2  3  4  5  6  7  8  9 10 99 12 0
+I  1  2  3  4  5  6  7  8  9 10 99 12 0
+J  1  2  3  4  5  6  7  8  9 10 99 12 0
+E  1  2  3  4  5  6  7  8  9 10 99 12 0
+D  1  2  3  4  5  6  7  8  9 10 99 12 0
+Column 8 repeat and offset = 12 1558
+A abcdefghijklm 0
+L  0  1  0  0  1  1  0  0  0  1  1  0  0 0
+X  0  1  0  0  1  1  0  0  0  1  1  1  0 0
+B  1  2  3  4  5  6  7  8  9 10 11 99 13 0
+I  1  2  3  4  5  6  7  8  9 10 11 99 13 0
+J  1  2  3  4  5  6  7  8  9 10 11 99 13 0
+E  1  2  3  4  5  6  7  8  9 10 11 99 13 0
+D  1  2  3  4  5  6  7  8  9 10 11 99 13 0
+Column 8 repeat and offset = 13 1825
+A abcdefghijklmn 0
+L  0  1  0  0  1  1  0  0  0  1  1  1  0  0 0
+X  0  1  0  0  1  1  0  0  0  1  1  1  0  0 0
+B  1  2  3  4  5  6  7  8  9 10 11 12 99 14 0
+I  1  2  3  4  5  6  7  8  9 10 11 12 99 14 0
+J  1  2  3  4  5  6  7  8  9 10 11 12 99 14 0
+E  1  2  3  4  5  6  7  8  9 10 11 12 99 14 0
+D  1  2  3  4  5  6  7  8  9 10 11 12 99 14 0
+Column 8 repeat and offset = 14 2113
+A abcdefghijklmno 0
+L  0  1  0  0  1  1  0  0  0  1  1  1  0  0  0 0
+X  0  1  0  0  1  1  0  0  0  1  1  1  0  0  0 0
+B  1  2  3  4  5  6  7  8  9 10 11 12 13 99 15 0
+I  1  2  3  4  5  6  7  8  9 10 11 12 13 99 15 0
+J  1  2  3  4  5  6  7  8  9 10 11 12 13 99 15 0
+E  1  2  3  4  5  6  7  8  9 10 11 12 13 99 15 0
+D  1  2  3  4  5  6  7  8  9 10 11 12 13 99 15 0
+Column 8 repeat and offset = 15 2422
+A abcdefghijklmnop 0
+L  0  1  0  0  1  1  0  0  0  1  1  1  0  0  0  0 0
+X  0  1  0  0  1  1  0  0  0  1  1  1  0  0  0  0 0
+B  1  2  3  4  5  6  7  8  9 10 11 12 13 14 99 16 0
+I  1  2  3  4  5  6  7  8  9 10 11 12 13 14 99 16 0
+J  1  2  3  4  5  6  7  8  9 10 11 12 13 14 99 16 0
+E  1  2  3  4  5  6  7  8  9 10 11 12 13 14 99 16 0
+D  1  2  3  4  5  6  7  8  9 10 11 12 13 14 99 16 0
+Column 8 repeat and offset = 16 2752
+A abcdefghijklmnopq 0
+L  0  1  0  0  1  1  0  0  0  1  1  1  0  0  0  0  1 0
+X  0  1  0  0  1  1  0  0  0  1  1  1  0  0  0  0  1 0
+B  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 99 17 0
+I  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 99 17 0
+J  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 99 17 0
+E  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 99 17 0
+D  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 99 17 0
+Column 8 repeat and offset = 17 3104
+A abcdefghijklmnopqr 0
+L  0  1  0  0  1  1  0  0  0  1  1  1  0  0  0  0  0  1 0
+X  0  1  0  0  1  1  0  0  0  1  1  1  0  0  0  0  1  1 0
+B  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 99 18 0
+I  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 99 18 0
+J  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 99 18 0
+E  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 99 18 0
+D  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 99 18 0
+Column 8 repeat and offset = 18 3477
+A abcdefghijklmnopqrs 0
+L  0  1  0  0  1  1  0  0  0  1  1  1  0  0  0  0  1  0  1 0
+X  0  1  0  0  1  1  0  0  0  1  1  1  0  0  0  0  1  1  1 0
+B  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 99 19 0
+I  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 99 19 0
+J  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 99 19 0
+E  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 99 19 0
+D  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 99 19 0
+Column 8 repeat and offset = 19 3871
+A abcdefghijklmnopqrst 0
+L  0  1  0  0  1  1  0  0  0  1  1  1  0  0  0  0  1  1  0  1 0
+X  0  1  0  0  1  1  0  0  0  1  1  1  0  0  0  0  1  1  1  1 0
+B  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 99 20 0
+I  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 99 20 0
+J  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 99 20 0
+E  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 99 20 0
+D  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 99 20 0
+Column 8 repeat and offset = 20 4286
+
+ffcrim status = 0
+ffppr status = 0
+
+Image values written with ffppr and read with ffgpv:
+  0  2  4  6  8 10 12 14 16 18 20 22 24 26  0 (byte)
+  0  2  4  6  8 10 12 14 16 18 20 22 24 26  0 (short)
+  0  2  4  6  8 10 12 14 16 18 20 22 24 26  0 (int)
+  0  2  4  6  8 10 12 14 16 18 20 22 24 26  0 (long)
+  0  2  4  6  8 10 12 14 16 18 20 22 24 26  0 (float)
+  0  2  4  6  8 10 12 14 16 18 20 22 24 26  0 (double)
+
+Wrote WCS keywords status = 0
+Read WCS keywords with ffgics status = 0
+  CRVAL1, CRVAL2 =  45.830000000000,  63.570000000000
+  CRPIX1, CRPIX2 = 256.000000000000, 257.000000000000
+  CDELT1, CDELT2 =  -0.002777770000,   0.002777770000
+  Rotation =      0.000, CTYPE = -TAN
+Calculated sky coordinate with ffwldp status = 0
+  Pixels (  0.5000,  0.5000) --> (  47.385204,   62.848968) Sky
+Calculated pixel coordinate with ffxypx status = 0
+  Sky (  47.385204,   62.848968) --> (  0.5000,  0.5000) Pixels
+
+ffcrtb status = 0
+ffpcl status = 0
+
+Column values written with ffpcl and read with ffgcl:
+  0  3  6  9 12 15 18 21 24 27  0 (byte)
+  0  3  6  9 12 15 18 21 24 27  0 (short)
+  0  3  6  9 12 15 18 21 24 27  0 (int)
+  0  3  6  9 12 15 18 21 24 27  0 (long)
+  0  3  6  9 12 15 18 21 24 27  0 (float)
+  0  3  6  9 12 15 18 21 24 27  0 (double)
+
+Repeatedly move to the 1st 4 HDUs of the file:
+12343123431234312343123431234312343123431234312343
+Move to extensions by name and version number: (ffmnhd)
+ Test-BINTABLE, 1 = hdu 5, 0
+ Test-BINTABLE, 3 = hdu 2, 0
+ Test-BINTABLE, 4 = hdu 6, 0
+ Test-ASCII, 2 = hdu 4, 0
+ new_table, 5 = hdu 8, 0
+ Test-BINTABLE, 0 = hdu 2, 0
+ Test-BINTABLE, 17 = hdu 2, 301 (expect a 301 error status here)
+Total number of HDUs in the file = 8
+
+Encode checksum: 1234567890 -> dCW2fBU0dBU0dBU0
+Decode checksum: dCW2fBU0dBU0dBU0 -> 1234567890
+DATASUM = '475248536'         
+ffgcks data checksum, status = 475248536, 0
+ffvcks datastatus, hdustatus, status = 1 1 0
+ffupck status = 0
+DATASUM = '475248536'         
+ffvcks datastatus, hdustatus, status = 1 1 0
+ffclos status = 0
+
+Normally, there should be 8 error messages on the stack
+all regarding 'numerical overflows':
+ Numerical overflow during type conversion while writing FITS data.
+ Numerical overflow during type conversion while writing FITS data.
+ Numerical overflow during type conversion while writing FITS data.
+ Numerical overflow during type conversion while writing FITS data.
+ Numerical overflow during type conversion while writing FITS data.
+ Numerical overflow during type conversion while writing FITS data.
+ Numerical overflow during type conversion while writing FITS data.
+ Numerical overflow during type conversion while writing FITS data.
+
+Status = 0: OK - no error
diff --git a/external/cfitsio/testprog.std b/external/cfitsio/testprog.std
new file mode 100644
index 0000000..c1a2db5
--- /dev/null
+++ b/external/cfitsio/testprog.std
@@ -0,0 +1,48 @@
+SIMPLE  =                    T / file does conform to FITS standard             BITPIX  =                   32 / number of bits per data pixel                  NAXIS   =                    2 / number of data axes                            NAXIS1  =                   10 / length of data axis 1                          NAXIS2  =                    2 / length of data axis 2                          EXTEND  =                    T / FITS dataset may contain extensions            COMMENT   FITS (Flexible Image Transport System) format is defined in 'AstronomyCOMMENT   and Astrophysics', volume 376, page 359; bibcode: 2001A&A...376..359H KEY_PREC= 'This keyword was written by fxprec' / comment goes here              CARD1   = '12345678901234567890123456789012345678901234567890123456789012345678'CARD2   = '1234567890123456789012345678901234567890123456789012345678901234''67'CARD3   = '1234567890123456789012345678901234567890123456789012345678901234'''''CARD4   = '1234567890123456789012345678901234567890123456789012345678901234567' KEY_PKYS= 'value_string'       / fxpkys comment                                 KEY_PKYL=                    T / fxpkyl comment                                 KEY_PKYJ=                   11 / [feet/second/second] fxpkyj comment            KEY_PKYF=             12.12121 / fxpkyf comment                                 KEY_PKYE=         1.313131E+01 / fxpkye comment                                 KEY_PKYG=    14.14141414141414 / fxpkyg comment                                 KEY_PKYD= 1.51515151515152E+01 / fxpkyd comment                                 KEY_PKYC= (1.313131E+01, 1.414141E+01) / fxpkyc comment                         KEY_PKYM= (1.51515151515152E+01, 1.61616161616162E+01) / fxpkym comment         KEY_PKFC= (13.131313, 14.141414) / fxpkfc comment                               KEY_PKFM= (15.15151515151515, 16.16161616161616) / fxpkfm comment               KEY_PKLS= 'This is a very long string value that is continued over more than o&'CONTINUE  'ne keyword.'        / fxpkls comment                                 LONGSTRN= 'OGIP 1.0'           / The HEASARC Long String Convention may be used.COMMENT   This FITS file may contain long string keyword values that are        COMMENT   continued over multiple keywords.  The HEASARC convention uses the &  COMMENT   character at the end of each substring which is then continued        COMMENT   on the next keyword which has the name CONTINUE.                      KEY_PKYT= 12345678.1234567890123456 / fxpkyt comment                            COMMENT   This keyword was modified by fxmrec                                   KY_UCRD = 'This keyword was updated by fxucrd'                                  NEWIKYS = 'updated_string'     / ikys comment                                   KY_IKYJ =                   51 / This is a modified comment                     KY_IKYL =                    T / ikyl comment                                   KY_IKYE =          -1.3346E+01 / ikye comment                                   KY_IKYD = -1.33456789012346E+01 / modified comment                              KY_IKYF =             -13.3456 / ikyf comment                                   KY_IKYG =    -13.3456789012346 / ikyg comment                                   KY_PKNS1= 'first string'       / fxpkns comment                                 KY_PKNS2= 'second string'      / fxpkns comment                                 KY_PKNS3= '        '           / fxpkns comment                                 KY_PKNL1=                    T / fxpknl comment                                 KY_PKNL2=                    F / fxpknl comment                                 KY_PKNL3=                    T / fxpknl comment                                 KY_PKNJ1=                   11 / fxpknj comment                                 KY_PKNJ2=                   12 / fxpknj comment                                 KY_PKNJ3=                   13 / fxpknj comment                                 KY_PKNF1=             12.12121 / fxpknf comment                                 KY_PKNF2=             13.13131 / fxpknf comment                                 KY_PKNF3=             14.14141 / fxpknf comment                                 KY_PKNE1=         1.313131E+01 / fxpkne comment                                 KY_PKNE2=         1.414141E+01 / fxpkne comment                                 KY_PKNE3=         1.515152E+01 / fxpkne comment                                 KY_PKNG1=     14.1414141414141 / fxpkng comment                                 KY_PKNG2=     15.1515151515152 / fxpkng comment                                 KY_PKNG3=     16.1616161616162 / fxpkng comment                                 KY_PKND1= 1.51515151515152E+01 / fxpknd comment                                 KY_PKND2= 1.61616161616162E+01 / fxpknd comment                                 KY_PKND3= 1.71717171717172E+01 / fxpknd comment                                 TSTRING = '1       '           / tstring comment                                TLOGICAL=                    T / tlogical comment                               TBYTE   =                   11 / tbyte comment                                  TSHORT  =                   21 / tshort comment                                 TINT    =                   31 / tint comment                                   TLONG   =                   41 / tlong comment                                  TFLOAT  =                  42. / tfloat comment                                 TDOUBLE =                  82. / tdouble comment                                BLANK   =                  -99 / value to use for undefined pixels              KY_PKNE4=         1.313131E+01 / fxpkne comment                                 TMPCARDA=                 1001 / this is the 1st template card                  TMPCARD2= 'ABCD    '           / this is the 2nd template card                  TMPCARD3=              1001.23 / this is the 3rd template card                  COMMENT this is the 5th template card                                           HISTORY this is the 6th template card                                           TMPCARD7=                      / comment for null keyword                       END                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             ÿÿÿÿÿÿÿÿÿ	
+ÿÿÿ
ÿÿÿÿÿÿXTENSION= 'BINTABLE'           / binary table extension                         BITPIX  =                    8 / 8-bit bytes                                    NAXIS   =                    2 / 2-dimensional binary table                     NAXIS1  =                   61 / width of table in bytes                        NAXIS2  =                   20 / number of rows in table                        PCOUNT  =                    0 / size of special data area                      GCOUNT  =                    1 / one data group (required keyword)              TFIELDS =                   10 / number of fields in each row                   TTYPE1  = 'Avalue  '           / label for field   1                            TFORM1  = '15A     '           / data format of field: ASCII Character          TTYPE2  = 'Lvalue  '           / label for field   2                            TFORM2  = '1L      '           / data format of field: 1-byte LOGICAL           TUNIT2  = 'm**2    '           / physical unit of field                         TTYPE3  = 'Xvalue  '           / label for field   3                            TFORM3  = '16X     '           / data format of field: BIT                      TUNIT3  = 'cm      '           / physical unit of field                         TTYPE4  = 'Bvalue  '           / label for field   4                            TFORM4  = '1B      '           / data format of field: BYTE                     TUNIT4  = 'erg/s   '           / physical unit of field                         TTYPE5  = 'Ivalue  '           / label for field   5                            TFORM5  = '1I      '           / data format of field: 2-byte INTEGER           TUNIT5  = 'km/s    '           / physical unit of field                         TTYPE6  = 'Jvalue  '           / label for field   6                            TFORM6  = '1J      '           / data format of field: 4-byte INTEGER           TTYPE7  = 'Evalue  '           / label for field   7                            TFORM7  = '1E      '           / data format of field: 4-byte REAL              TTYPE8  = 'Dvalue  '           / label for field   8                            TFORM8  = '1D      '           / data format of field: 8-byte DOUBLE            TTYPE9  = 'Cvalue  '           / label for field   9                            TFORM9  = '1C      '           / data format of field: COMPLEX                  TTYPE10 = 'Mvalue  '           / label for field  10                            TFORM10 = '1M      '           / data format of field: DOUBLE COMPLEX           EXTNAME = 'Test-BINTABLE'      / name of this binary table extension            EXTVER  =                    3 / extension version number                       TNULL4  =                   77 / value for undefined pixels                     TNULL5  =                   77 / value for undefined pixels                     TNULL6  =                   77 / value for undefined pixels                     TSCAL4  =                 1000 / scaling factor                                 TSCAL5  =                    1 / scaling factor                                 TSCAL6  =                  100 / scaling factor                                 TZERO4  =                    0 / scaling offset                                 TZERO5  =                32768 / scaling offset                                 TZERO6  =                  100 / scaling offset                                 NEW_KEY = 'written by fxprec' / to change checksum                              END                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             €ÿÿÿÿƒè	
+§c!GBÿŽMMMXTENSION= 'IMAGE   '           / IMAGE extension                                BITPIX  =                  -32 / number of bits per data pixel                  NAXIS   =                    2 / number of data axes                            NAXIS1  =                   15 / length of data axis 1                          NAXIS2  =                   25 / length of data axis 2                          PCOUNT  =                    0 / required keyword; must = 0                     GCOUNT  =                    1 / required keyword; must = 1                     END                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             ?€@@@@€@ @À@àAAA A0A@APA`A A0A@APA`ApA€AˆAA˜A A¨A°A¸AÀA A¨A°A¸AÀAÈAÐAØAàAèAðAøBBBAðAøBBBBBBBBB B$B(B,B0B B$B(B,¿€ÀÀ@À€À ÀÀÀàÁÁBXBHBLBPBTÁ Á0Á@ÁPÁ`ÁpÁ€ÁˆÁÁ˜B€BpBtBxB|Á Á¨Á°Á¸ÁÀÁÈÁÐÁØÁàÁèB”BŒBŽBB’ÁðÁøÂÂÂÂÂÂÂÂB¨B B¢B¤B¦Â Â$Â(Â,Â0Â4Â8Â<Â@ÂDB¼B´B¶B¸BºÂHÂLÂPÂTÂXÂ\Â`ÂdÂhÂlBÐBÈBÊBÌBÎÂpÂtÂxÂ|€‚„†ˆŠBäBÜBÞBàB⌎’”–˜šœžBøBðBòBôBö ¢¤¦¨ª¬®°²CCCCC´¶¸º¼¾ÂÀÂÂÂÄÂÆCCC
CCCCCCCCCCCCCCCCCCCCCCCC C!C"C#C$C C!C"C#C$C%C&C'C(C)C*C+C,C-C.C*C+C,C-C.C/C0C1C2C3C4C5C6C7C8C4C5C6C7C8C9C:C;C<C=C>C?C@CACBC>C?C@CACBCCCDCECFCGCHCICJCKCLCHCICJCKCLCMCNCOCPCQCRCSCTCUCVCRCSCTCUCVCWCXCYCZC[C\C]C^C_C`C\C]C^C_C`CaCbCcCdCeCfCgChCiCjCfCgChCiCjCkClCmCnCoCpCqCrCsCtCpCqCrCsCtCuCvCwCxCyCzC{C|C}C~XTENSION= 'TABLE   '           / ASCII table extension                          BITPIX  =                    8 / 8-bit ASCII characters                         NAXIS   =                    2 / 2-dimensional ASCII table                      NAXIS1  =                   76 / width of table in characters                   NAXIS2  =                   12 / number of rows in table                        PCOUNT  =                    0 / no group parameters (required keyword)         GCOUNT  =                    1 / one data group (required keyword)              TFIELDS =                    5 / number of fields in each row                   TTYPE1  = 'Name    '           / label for field   1                            TBCOL1  =                    1 / beginning column of field   1                  TFORM1  = 'A15     '           / Fortran-77 format of field                     TTYPE2  = 'Ivalue  '           / label for field   2                            TBCOL2  =                   17 / beginning column of field   2                  TFORM2  = 'I10     '           / Fortran-77 format of field                     TUNIT2  = 'm**2    '           / physical unit of field                         TTYPE3  = 'Evalue  '           / label for field   4                            TBCOL3  =                   28 / beginning column of field   4                  TFORM3  = 'E12.5   '           / Fortran-77 format of field                     TUNIT3  = 'erg/s   '           / physical unit of field                         TTYPE4  = 'Dvalue  '           / label for field   5                            TBCOL4  =                   41 / beginning column of field   5                  TFORM4  = 'D21.14  '           / Fortran-77 format of field                     TUNIT4  = 'km/s    '           / physical unit of field                         EXTNAME = 'Test-ASCII'         / name of this ASCII table extension             EXTVER  =                    2 / extension version number                       TNULL1  = 'null1   '           / value for undefined pixels                     TNULL2  = 'null2   '           / value for undefined pixels                     TNULL3  = 'null4   '           / value for undefined pixels                     TNULL4  = 'null5   '           / value for undefined pixels                     TTYPE5  = 'INSERT_COL'         / label for field                                TFORM5  = 'F14.6   '           / format of field                                TBCOL5  =                   63 / beginning column of field                      END                                                                                                                                                                                                                                                                                                                             first string             1  1.00000E+00  1.00000000000000E+00               second string            2  2.00000E+00  2.00000000000000E+00                                                                                                                                                                                                                                                                            3  3.00000E+00  3.00000000000000E+00               null1                    4  4.00000E+00  4.00000000000000E+00                                        5  5.00000E+00  5.00000000000000E+00                                        6  6.00000E+00  6.00000000000000E+00                                        9  9.00000E+00  9.00000000000000E+00                                       10  1.00000E+01  1.00000000000000E+01                               null2      null4        null5                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               XTENSION= 'BINTABLE'           / binary table extension                         BITPIX  =                    8 / 8-bit bytes                                    NAXIS   =                    2 / 2-dimensional binary table                     NAXIS1  =                   61 / width of table in bytes                        NAXIS2  =                   22 / number of rows in table                        PCOUNT  =                    0 / size of special data area                      GCOUNT  =                    1 / one data group (required keyword)              TFIELDS =                   10 / number of fields in each row                   TTYPE1  = 'Avalue  '           / label for field   1                            TFORM1  = '15A     '           / data format of field: ASCII Character          TTYPE2  = 'Lvalue  '           / label for field   2                            TFORM2  = '1L      '           / data format of field: 1-byte LOGICAL           TUNIT2  = 'm**2    '           / physical unit of field                         TTYPE3  = 'Xvalue  '           / label for field   3                            TFORM3  = '16X     '           / data format of field: BIT                      TUNIT3  = 'cm      '           / physical unit of field                         TTYPE4  = 'Bvalue  '           / label for field   4                            TFORM4  = '1B      '           / data format of field: BYTE                     TUNIT4  = 'erg/s   '           / physical unit of field                         TTYPE5  = 'Ivalue  '           / label for field   5                            TFORM5  = '1I      '           / data format of field: 2-byte INTEGER           TUNIT5  = 'km/s    '           / physical unit of field                         TTYPE6  = 'Evalue  '           / label for field   7                            TFORM6  = '1E      '           / data format of field: 4-byte REAL              TTYPE7  = 'Dvalue  '           / label for field   8                            TFORM7  = '1D      '           / data format of field: 8-byte DOUBLE            TTYPE9  = 'Cvalue  '           / label for field   9                            TFORM9  = '1C      '           / data format of field: COMPLEX                  TTYPE10 = 'Mvalue  '           / label for field  10                            TFORM10 = '1M      '           / data format of field: DOUBLE COMPLEX           EXTNAME = 'Test-BINTABLE'      / name of this binary table extension            EXTVER  =                    1 / extension version number                       TNULL4  =                   99 / value for undefined pixels                     TNULL5  =                   99 / value for undefined pixels                     TDIM3   = '(1,2,8) '           / size of the multidimensional array             KEY_PREC= 'This keyword was written by f_prec' / comment here                   TTYPE8  = 'INSERT_COL'         / label for field                                TFORM8  = '1E      '           / format of field                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                END                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             first string   FLp?€?ðÿÿÿÿ?€À?ðÀsecond string  Tð|@@ÿÿÿÿ@@À€@Àÿÿÿÿÿÿÿÿÿÿÿÿ               F@@@ÿÿÿÿ@ ÀÀ@À              FÿüÀ€Àÿÿÿÿ@àÁ@À T@ @ÿÿÿÿAÁ @"À$TÿúÀÀÀÿÿÿÿA0Á@@&À(F		A@"ÿÿÿÿAˆÁ@1À2TÿöÁ À$A˜Á @3À4ccÿÿÿÿÿÿÿÿÿÿÿÿTA@@(FccÿÿÿÿÿÿÿÿÿÿÿÿFÿòÁ`À,FccÿÿÿÿÿÿÿÿÿÿÿÿFÿðÁ€À0TccÿÿÿÿÿÿÿÿÿÿÿÿTÿîÁÀ2TccÿÿÿÿÿÿÿÿÿÿÿÿTÿìÁ À4FccÿÿÿÿÿÿÿÿÿÿÿÿXTENSION= 'BINTABLE'           / binary table extension                         BITPIX  =                    8 / 8-bit bytes                                    NAXIS   =                    2 / 2-dimensional binary table                     NAXIS1  =                   80 / width of table in bytes                        NAXIS2  =                   20 / number of rows in table                        PCOUNT  =                 4446 / size of special data area                      GCOUNT  =                    1 / one data group (required keyword)              TFIELDS =                   10 / number of fields in each row                   TTYPE1  = 'Avalue  '           / label for field   1                            TFORM1  = '1PA(20) '           / data format of field: variable length array    TTYPE2  = 'Lvalue  '           / label for field   2                            TFORM2  = '1PL(20) '           / data format of field: variable length array    TUNIT2  = 'm**2    '           / physical unit of field                         TTYPE3  = 'Xvalue  '           / label for field   3                            TFORM3  = '1PB(3)  '           / data format of field: variable length array    TUNIT3  = 'cm      '           / physical unit of field                         TTYPE4  = 'Bvalue  '           / label for field   4                            TFORM4  = '1PB(20) '           / data format of field: variable length array    TUNIT4  = 'erg/s   '           / physical unit of field                         TTYPE5  = 'Ivalue  '           / label for field   5                            TFORM5  = '1PI(20) '           / data format of field: variable length array    TUNIT5  = 'km/s    '           / physical unit of field                         TTYPE6  = 'Jvalue  '           / label for field   6                            TFORM6  = '1PJ(20) '           / data format of field: variable length array    TTYPE7  = 'Evalue  '           / label for field   7                            TFORM7  = '1PE(20) '           / data format of field: variable length array    TTYPE8  = 'Dvalue  '           / label for field   8                            TFORM8  = '1PD(20) '           / data format of field: variable length array    TTYPE9  = 'Cvalue  '           / label for field   9                            TFORM9  = '1PC(0)  '           / data format of field: variable length array    TTYPE10 = 'Mvalue  '           / label for field  10                            TFORM10 = '1PM(0)  '           / data format of field: variable length array    EXTNAME = 'Test-BINTABLE'      / name of this binary table extension            EXTVER  =                    4 / extension version number                       TNULL4  =                   88 / value for undefined pixels                     TNULL5  =                   88 / value for undefined pixels                     TNULL6  =                   88 / value for undefined pixels                     END                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
+!)1ADGHKQ]i…‰ŠŽ–¦¶ÖÛàáæð@FLMS_w¿ÆÍÎÕãÿS[cdl|œ¼	ü				+	O	s
+»
+ÅÏ
+Ñ
+Û
+ï
+
+?𥧲Èô x„’ž¶æ
v
ƒ
’
Ÿ
¹
í
!‰—¥§µÑ	A±ÀÏÑàþ	:	v	î	þ
+
+
+ 
+@
+€
+À@Qbev˜Ü ¨ºÌÏá

M
•%8KNa‡Ó·Ëßâön¾F?€?ðabT@XXXÿÿÿÿ@ÿÿÿÿÿÿÿÿ@abcFF@XXX?€ÿÿÿÿ@@?ðÿÿÿÿÿÿÿÿ@abcdFTF@XXX?€@ÿÿÿÿ@€?ð@ÿÿÿÿÿÿÿÿ@abcdeFTFTHXXX?€@@@ÿÿÿÿ@ ?ð@@ÿÿÿÿÿÿÿÿ@abcdefFTFFTLXXX?€@@@@€ÿÿÿÿ@À?ð@@@ÿÿÿÿÿÿÿÿ@abcdefgFTFFTFLXXX?€@@@@€@ ÿÿÿÿ@à?ð@@@@ÿÿÿÿÿÿÿÿ@abcdefghFTFFTTFLXXX?€@@@@€@ @ÀÿÿÿÿA?ð@@@@@ÿÿÿÿÿÿÿÿ@ abcdefghiFTFFTTFFLX	X	X	?€@@@@€@ @À@àÿÿÿÿA?ð@@@@@@ÿÿÿÿÿÿÿÿ@"abcdefghijFTFFTTFFTL@X
+X
+X
+?€@@@@€@ @À@àAÿÿÿÿA ?ð@@@@@@@ ÿÿÿÿÿÿÿÿ@$abcdefghijkFTFFTTFFFTL`	X	X	X?€@@@@€@ @À@àAAÿÿÿÿA0?ð@@@@@@@ @"ÿÿÿÿÿÿÿÿ@&abcdefghijklFTFFTTFFFTTLp	
+X	
+X	
+X?€@@@@€@ @À@àAAA ÿÿÿÿA@?ð@@@@@@@ @"@$ÿÿÿÿÿÿÿÿ@(abcdefghijklmFTFFTTFFFTTFLp	
+X
	
+X
	
+X
?€@@@@€@ @À@àAAA A0ÿÿÿÿAP?ð@@@@@@@ @"@$@&ÿÿÿÿÿÿÿÿ@*abcdefghijklmnFTFFTTFFFTTTFLp	
+X	
+X	
+X?€@@@@€@ @À@àAAA A0A@ÿÿÿÿA`?ð@@@@@@@ @"@$@&@(ÿÿÿÿÿÿÿÿ@,abcdefghijklmnoFTFFTTFFFTTTFFLp	
+
X	
+
X	
+
X?€@@@@€@ @À@àAAA A0A@APÿÿÿÿAp?ð@@@@@@@ @"@$@&@(@*ÿÿÿÿÿÿÿÿ@.abcdefghijklmnopFTFFTTFFFTTTFFFLp	
+
X	
+
X	
+
X?€@@@@€@ @À@àAAA A0A@APA`ÿÿÿÿA€?ð@@@@@@@ @"@$@&@(@*@,ÿÿÿÿÿÿÿÿ@0abcdefghijklmnopqFTFFTTFFFTTTFFFTLp€	
+
X	
+
X	
+
X?€@@@@€@ @À@àAAA A0A@APA`ApÿÿÿÿAˆ?ð@@@@@@@ @"@$@&@(@*@,@.ÿÿÿÿÿÿÿÿ@1abcdefghijklmnopqrFTFFTTFFFTTTFFFFTLpÀ	
+
X	
+
X	
+
X?€@@@@€@ @À@àAAA A0A@APA`ApA€ÿÿÿÿA?ð@@@@@@@ @"@$@&@(@*@,@.@0ÿÿÿÿÿÿÿÿ@2abcdefghijklmnopqrsFTFFTTFFFTTTFFFFTTLpà	
+
X	
+
X	
+
X?€@@@@€@ @À@àAAA A0A@APA`ApA€AˆÿÿÿÿA˜?ð@@@@@@@ @"@$@&@(@*@,@.@0@1ÿÿÿÿÿÿÿÿ@3abcdefghijklmnopqrstFTFFTTFFFTTTFFFFTTTLpð	
+
X	
+
X	
+
X?€@@@@€@ @À@àAAA A0A@APA`ApA€AˆAÿÿÿÿA ?ð@@@@@@@ @"@$@&@(@*@,@.@0@1@2ÿÿÿÿÿÿÿÿ@4XTENSION= 'IMAGE   '           / IMAGE extension                                BITPIX  =                   32 / number of bits per data pixel                  NAXIS   =                    2 / number of data axes                            NAXIS1  =                   10 / length of data axis 1                          NAXIS2  =                    2 / length of data axis 2                          PCOUNT  =                    0 / required keyword; must = 0                     GCOUNT  =                    1 / required keyword; must = 1                     CRVAL1  =     4.5830000000E+01 / comment                                        CRVAL2  =     6.3570000000E+01 / comment                                        CRPIX1  =     2.5600000000E+02 / comment                                        CRPIX2  =     2.5700000000E+02 / comment                                        CDELT1  =    -2.7777700000E-03 / comment                                        CDELT2  =     2.7777700000E-03 / comment                                        CTYPE1  = 'RA---TAN'           / comment                                        CTYPE2  = 'DEC--TAN'           / comment                                        END                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
+XTENSION= 'TABLE   '           / ASCII table extension                          BITPIX  =                    8 / 8-bit ASCII characters                         NAXIS   =                    2 / 2-dimensional ASCII table                      NAXIS1  =                   80 / width of table in characters                   NAXIS2  =                   11 / number of rows in table                        PCOUNT  =                    0 / no group parameters (required keyword)         GCOUNT  =                    1 / one data group (required keyword)              TFIELDS =                    5 / number of fields in each row                   TTYPE1  = 'Name    '           / label for field   1                            TBCOL1  =                    1 / beginning column of field   1                  TFORM1  = 'A15     '           / Fortran-77 format of field                     TTYPE2  = 'Ivalue  '           / label for field   2                            TBCOL2  =                   17 / beginning column of field   2                  TFORM2  = 'I11     '           / Fortran-77 format of field                     TUNIT2  = 'm**2    '           / physical unit of field                         TTYPE3  = 'Fvalue  '           / label for field   3                            TBCOL3  =                   29 / beginning column of field   3                  TFORM3  = 'F15.6   '           / Fortran-77 format of field                     TUNIT3  = 'cm      '           / physical unit of field                         TTYPE4  = 'Evalue  '           / label for field   4                            TBCOL4  =                   45 / beginning column of field   4                  TFORM4  = 'E13.5   '           / Fortran-77 format of field                     TUNIT4  = 'erg/s   '           / physical unit of field                         TTYPE5  = 'Dvalue  '           / label for field   5                            TBCOL5  =                   59 / beginning column of field   5                  TFORM5  = 'D22.14  '           / Fortran-77 format of field                     TUNIT5  = 'km/s    '           / physical unit of field                         EXTNAME = 'new_table'          / name of this ASCII table extension             EXTVER  =                    5 / extension version number                       END                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             first string              0        0.000000   0.00000E+00   0.00000000000000E+00second string             3        3.000000   3.00000E+00   3.00000000000000E+00                          6        6.000000   6.00000E+00   6.00000000000000E+00                          9        9.000000   9.00000E+00   9.00000000000000E+00                         12       12.000000   1.20000E+01   1.20000000000000E+01                         15       15.000000   1.50000E+01   1.50000000000000E+01                         18       18.000000   1.80000E+01   1.80000000000000E+01                         21       21.000000   2.10000E+01   2.10000000000000E+01                         24       24.000000   2.40000E+01   2.40000000000000E+01                         27       27.000000   2.70000E+01   2.70000000000000E+01                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                
\ No newline at end of file
diff --git a/external/cfitsio/testprog.tpt b/external/cfitsio/testprog.tpt
new file mode 100644
index 0000000..def9dcb
--- /dev/null
+++ b/external/cfitsio/testprog.tpt
@@ -0,0 +1,12 @@
+tmpcard1  1001 this is the 1st template card 
+tmpcard2  ABCD this is the 2nd template card
+tmpcard3  1001.23 this is the 3rd template card
+tmpcard4  1001.45 this is the 4rd template card
+comment this is the 5th template card
+history this is the 6th template card
+tmpcard7 =            / comment for null keyword
+-tmpcard1 tmpcarda  change the name of tmpcard1
+-tmpcard4
+end
+
+junk will be ignored
diff --git a/external/cfitsio/trees.c b/external/cfitsio/trees.c
new file mode 100644
index 0000000..8436126
--- /dev/null
+++ b/external/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 
+#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< dist code (0..29) */
+    dist = 0;
+    for (code = 0 ; code < 16; code++) {
+        base_dist[code] = dist;
+        for (n = 0; n < (1<>= 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 
+#  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<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/external/cfitsio/trees.h b/external/cfitsio/trees.h
new file mode 100644
index 0000000..d35639d
--- /dev/null
+++ b/external/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/external/cfitsio/uncompr.c b/external/cfitsio/uncompr.c
new file mode 100644
index 0000000..769f83e
--- /dev/null
+++ b/external/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/external/cfitsio/vmsieee.c b/external/cfitsio/vmsieee.c
new file mode 100644
index 0000000..5ddc748
--- /dev/null
+++ b/external/cfitsio/vmsieee.c
@@ -0,0 +1,130 @@
+#include 
+#include 
+
+unsigned long CVT$CONVERT_FLOAT();
+
+/* IEEVPAKR -- Pack a native floating point vector into an IEEE one.
+*/
+void ieevpr (unsigned int *native, unsigned int *ieee, int *nelem)
+{
+        unsigned long status;
+        unsigned long options;
+        unsigned int *unanval;
+        int nanval = -1;
+        int     i,n;
+
+        unanval = (unsigned int *) &nanval;
+        options = CVT$M_BIG_ENDIAN;
+        
+        n = *nelem;
+        status = CVT$_NORMAL;
+
+        for (i = 0; i < n ; i++) {
+
+            status = CVT$CONVERT_FLOAT (&native[i], CVT$K_VAX_F,
+                                        &ieee[i], CVT$K_IEEE_S, 
+                                        options);
+           if (status != CVT$_NORMAL) {
+                 ieee[i] = *unanval;
+           }
+        }       
+
+}
+/* IEEVPAKD -- Pack a native double floating point vector into an IEEE one.
+*/
+void ieevpd (unsigned long *native, unsigned long *ieee, int *nelem)
+{
+        unsigned long status;
+        unsigned long options;
+        unsigned long *unanval;
+        long nanval = -1;
+        int     i,n;
+
+        unanval = (unsigned long *) &nanval;
+        options = CVT$M_BIG_ENDIAN;
+        
+        n = *nelem * 2;
+        status = CVT$_NORMAL;
+
+        for (i = 0; i < n ; i=i+2) {
+
+            status = CVT$CONVERT_FLOAT (&native[i], CVT$K_VAX_D,
+                                        &ieee[i], CVT$K_IEEE_T, 
+                                        options);
+           if (status != CVT$_NORMAL) {
+                 ieee[i]   = *unanval;
+                 ieee[i+1] = *unanval;
+           }
+        }       
+
+}
+/* IEEVUPKR -- Unpack an ieee vector into native single floating point vector.
+*/
+void ieevur (unsigned int *ieee, unsigned int *native, int *nelem)
+{
+        unsigned long status;
+        unsigned long options;
+        unsigned int *unanval;
+        int nanval = -1;
+        int     j,n;
+
+        unanval = (unsigned int *) &nanval;
+        options = CVT$M_ERR_UNDERFLOW+CVT$M_BIG_ENDIAN;
+        
+        n = *nelem;
+
+        status = CVT$_NORMAL;
+
+        for (j = 0; j < n ; j++) {
+           status = CVT$CONVERT_FLOAT (&ieee[j], CVT$K_IEEE_S,
+                                        &native[j], CVT$K_VAX_F, 
+                                        options);
+           if (status != CVT$_NORMAL)
+              switch(status) {
+              case CVT$_INVVAL:
+              case CVT$_NEGINF:
+              case CVT$_OVERFLOW:
+              case CVT$_POSINF:
+                 native[j]   = *unanval;
+                 break;
+              default:
+                 native[j] = 0;             
+              }
+        }
+}
+/* IEEVUPKD -- Unpack an ieee vector into native double floating point vector.
+*/
+void ieevud (unsigned long *ieee, unsigned long *native, int *nelem)
+{
+        unsigned long status;
+        unsigned long options;
+        unsigned long *unanval;
+        long nanval = -1;
+        int     j,n;
+
+        unanval = (unsigned long *) &nanval;
+        options = CVT$M_BIG_ENDIAN + CVT$M_ERR_UNDERFLOW; 
+        
+        n = *nelem * 2;
+
+        status = CVT$_NORMAL;
+
+        for (j = 0; j < n ; j=j+2) {
+           status = CVT$CONVERT_FLOAT (&ieee[j], CVT$K_IEEE_T,
+                                        &native[j], CVT$K_VAX_D, 
+                                        options);
+           if (status != CVT$_NORMAL)
+              switch(status) {
+              case CVT$_INVVAL:
+              case CVT$_NEGINF:
+              case CVT$_OVERFLOW:
+              case CVT$_POSINF:
+                 native[j]   = *unanval;
+                 native[j+1] = *unanval;
+                 break;
+              default:
+                 native[j]   = 0;             
+                 native[j+1] = 0;             
+              }
+        }
+}
diff --git a/external/cfitsio/vmsieeed.mar b/external/cfitsio/vmsieeed.mar
new file mode 100644
index 0000000..f9928dd
--- /dev/null
+++ b/external/cfitsio/vmsieeed.mar
@@ -0,0 +1,137 @@
+	.TITLE	ieeed - ieee double to vax floating conversions
+	.ident	/v1.0/
+
+;# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+;#
+;# IEEED.S -- IEEE double to VAX double floating conversions.
+;#
+;#	ieepakd (x)				# scalar, vax->ieee
+;#	ieeupkd (x)				# scalar, ieee->vax
+;#	ieevpakd (native, ieee, nelem)		# vector, vax->ieee
+;#	ieevupkd (ieee, native, nelem)		# vector, ieee->vax
+;#	ieesnand (NaN)				# set VAX NaN value
+;#	ieegnand (NaN)				# get VAX NaN value
+;#
+;# These routines convert between the VAX and IEEE double floating formats,
+;# operating upon a single value or an array of values.  +/- zero is converted
+;# to zero.  When converting IEEE to VAX, underflow maps to zero, and exponent
+;# overflow and NaN input values map to the value set by IEESNAND (default 0).
+;# These routines are functionally equivalent to the semi-portable versions of
+;# the IRAF ieee/native floating conversion routines in osb$ieeed.x.
+;# TODO - Add a function callback option for processing NaN values.
+
+; Vax NaN *MUST* be 11111... or the fitsio code will break horribly.
+; It is explicitly tested for in a couple of places, so be warned.
+
+	.PSECT	IEEED$CODE, PIC,USR,CON,REL,LCL,SHR,EXE,RD,NOWRT,NOVEC
+
+	.ENTRY	IEEPAD ^M
+;_ieepad_:	;# IEEPAKD (X)
+	movl	4(ap), r4			;# data addr -> r4
+	movl	r4, r5				;# output clobbers input
+	jsb	cvt_vax_ieee			;# convert value
+	ret
+	.ENTRY	IEEVPD ^M
+;_ieevpd_:	;# IEEVPAKD (VAX, IEEE, NELEM)
+	movl	4(ap), r4			;# input vector -> r4
+	movl	8(ap), r5			;# output vector -> r5
+	movl	@12(ap), r6			;# loop counter
+L1:	jsb	cvt_vax_ieee			;# convert one value
+	sobgtr	r6, L1				;# loop
+	ret
+	.ENTRY	IEEUPD ^M
+;_ieeupd_:	;# IEEUPKD (X)
+	movl	4(ap), r4			;# data addr -> r4
+	movl	r4, r5				;# output clobbers input
+	jsb	cvt_ieee_vax			;# convert value
+	ret
+	.ENTRY	IEEVUD ^M
+;_ieevud_:	;# IEEVUPKD (IEEE, VAX, NELEM)
+	movl	4(ap), r4			;# input vector -> r4
+	movl	8(ap), r5			;# output vector -> r5
+	movl	@12(ap), r6			;# loop counter
+L2:	jsb	cvt_ieee_vax			;# convert one value
+	sobgtr	r6, L2				;# loop
+	ret
+	.ENTRY	IEESND ^M<>
+;_ieesnd_:	;# IEESNAND (VAXNAN)
+bugger::nop					; real no-op added to enable
+						; enbuging.	
+;	movq	@4(ap), vaxnan			; no-oped. See above.
+	ret					; This could be no-oped in
+						; the vector, but isn't.
+	.ENTRY	IEEGND ^M<>
+;_ieegnd_:	;# IEEGNAND (VAXNAN)
+	movq	#-1, @4(ap)			; See above
+	ret
+
+cvt_vax_ieee:					;# R4=in, R5=out
+	rotl	#16, (r4)+, r1			;# swap words -> r1
+	rotl	#16, (r4)+, r0			;# swap words -> r0
+
+	extzv	#23, #8, r1, r2			;# 8 bit exponent -> r2
+	beql	L6				;# branch if zero exponent 
+	extzv	#2, #1, r0, r3			;# get round bit -> r3
+	ashq	#-3, r0, r0			;# shift 64 data bits by 3
+	addw2	#<1024-130>, r2			;# adjust exponent bias
+	insv	r2, #20, #11, r1		;# insert new exponent
+	blbc	r3, L5				;# branch if round bit clear
+	incl	r0				;# round low longword
+	adwc	#0, r1				;# carry to high longword
+L5:
+	movl	sp, r3				;# r3 points to input byte
+	pushl	r1				;# push r1 on stack
+	pushl	r0				;# push r0 on stack
+	movb	-(r3), (r5)+			;# output quadword, swapped
+	movb	-(r3), (r5)+
+	movb	-(r3), (r5)+
+	movb	-(r3), (r5)+
+	movb	-(r3), (r5)+
+	movb	-(r3), (r5)+
+	movb	-(r3), (r5)+
+	movb	-(r3), (r5)+
+	addl2	#8, sp				;# pop stack
+	rsb					;# all done
+L6:
+	clrl	r0				;# return all 64 bits zero
+	clrl	r1
+	brb	L5
+
+cvt_ieee_vax:					;# R4=in, R5=out
+	movb	(r4)+, -(sp)			;# byte swap quadword onto stack
+	movb	(r4)+, -(sp)
+	movb	(r4)+, -(sp)
+	movb	(r4)+, -(sp)
+	movb	(r4)+, -(sp)
+	movb	(r4)+, -(sp)
+	movb	(r4)+, -(sp)
+	movb	(r4)+, -(sp)
+
+	movl	(sp)+, r0			;# pop low bits
+	movl	(sp)+, r1			;# pop high bits
+	extzv	#20, #11, r1, r2		;# exponent -> r2
+	beql	L10				;# zero exponent
+	extzv	#31, #1, r1, r3			;# save sign bit
+	ashq	#3, r0, r0			;# shift 64 bits left 3 bits
+	subw2	#<1024-130>, r2			;# adjust exponent bias
+	bleq	L10				;# return zero if underflow
+	cmpw	r2, #256			;# compare with max VAX exponent
+	bgeq	L11				;# return VAX-NaN if overflow
+	insv	r2, #23, #8, r1			;# insert VAX-D exponent
+	insv	r3, #31, #1, r1			;# restore sign bit
+
+	rotl	#16, r1, (r5)+			;# output VAX double
+	rotl	#16, r0, (r5)+			;# output VAX double
+	rsb
+L10:
+	clrl	(r5)+				;# return all 64 bits zero
+	clrl	(r5)+
+	rsb
+L11:
+	movl	#-1, r3			;# return VAX equiv. of NaN
+	movl	r3, (r5)+
+	movl	r3, (r5)+		; changed to only return -1
+	rsb
+
+	.END
+
diff --git a/external/cfitsio/vmsieeer.mar b/external/cfitsio/vmsieeer.mar
new file mode 100644
index 0000000..f310588
--- /dev/null
+++ b/external/cfitsio/vmsieeer.mar
@@ -0,0 +1,106 @@
+	.TITLE	ieeer - ieee real to vax floating conversions
+	.ident	/v1.0/
+
+;# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+;#
+;# IEEER.S -- IEEE real to VAX single precision floating conversions.
+;#
+;#	ieepakr (x)				# scalar, vax->ieee
+;#	ieeupkr (x)				# scalar, ieee->vax
+;#	ieevpakr (native, ieee, nelem)		# vector, vax->ieee
+;#	ieevupkr (ieee, native, nelem)		# vector, ieee->vax
+;#	ieesnanr (NaN)				# set VAX NaN value
+;#	ieegnanr (NaN)				# get VAX NaN value
+;#
+;# These routines convert between the VAX and IEEE real floating formats,
+;# operating upon a single value or an array of values.  +/- zero is converted
+;# to zero.  When converting IEEE to VAX, underflow maps to zero, and exponent
+;# overflow and NaN input values map to the value set by IEESNANR (default 0).
+;# These routines are functionally equivalent to the semi-portable versions of
+;# the IRAF ieee/native floating conversion routines in osb$ieeer.x.
+;# TODO - Add a function callback option for processing NaN values.
+
+; See IEEED for details about NaNs.
+
+	.PSECT	IEEER$CODE, PIC,USR,CON,REL,LCL,SHR,EXE,RD,NOWRT,NOVEC
+
+	.ENTRY	IEEPAR ^M
+;_ieepar_:	;# IEEPAKR (X)
+	movl	4(ap), r2			;# data addr -> r2
+	movl	r2, r3				;# output clobbers input
+	jsb	cvt_vax_ieee			;# convert value
+	ret
+	.ENTRY	IEEVPR ^M
+;_ieevpr_:	;# IEEVPAKR (VAX, IEEE, NELEM)
+	movl	4(ap), r2			;# input vector -> r2
+	movl	8(ap), r3			;# output vector -> r3
+	movl	@12(ap), r4			;# loop counter
+L1:	jsb	cvt_vax_ieee			;# convert one value
+	sobgtr	r4, L1				;# loop
+	ret
+	.ENTRY	IEEUPR ^M
+;_ieeupr_:	;# IEEUPKR (X)
+	movl	4(ap), r2			;# data addr -> r2
+	movl	r2, r3				;# output clobbers input
+	jsb	cvt_ieee_vax			;# convert value
+	ret
+	.ENTRY	IEEVUR ^M
+;_ieevur_:	;# IEEVUPKR (IEEE, VAX, NELEM)
+	movl	4(ap), r2			;# input vector -> r2
+	movl	8(ap), r3			;# output vector -> r3
+	movl	@12(ap), r4			;# loop counter
+L2:	jsb	cvt_ieee_vax			;# convert one value
+	sobgtr	r4, L2				;# loop
+	ret
+	.ENTRY	IEESNR ^M<>
+;_ieesnr_:	;# IEESNANR (VAXNAN)
+buger::	nop				; plug bpt here for crap catching.
+;	movl	@4(ap), vaxnan
+	ret
+	.ENTRY	IEEGNR ^M<>
+;_ieegnr_:	;# IEEGNANR (VAXNAN)
+	movl	#-1, @4(ap)
+	ret
+
+cvt_vax_ieee:					;# R2=in, R3=out
+	rotl	#16, (r2)+, r0			;# swap words -> r0
+	extzv	#23, #8, r0, r1			;# 8 bit exponent -> r1
+	beql	L6				;# branch if zero exponent 
+	subw2	#2, r1				;# adjust exponent bias
+	bleq	L6				;# return zero if underflow
+	insv	r1, #23, #8, r0			;# insert new exponent
+L5:
+	movl	sp, r1				;# r3 points to input byte
+	pushl	r0				;# push r0 on stack
+	movb	-(r1), (r3)+			;# output longword, swapped
+	movb	-(r1), (r3)+
+	movb	-(r1), (r3)+
+	movb	-(r1), (r3)+
+	tstl	(sp)+				;# pop stack
+	rsb					;# all done
+L6:
+	clrl	r0				;# return all 32 bits zero
+	brb	L5
+
+cvt_ieee_vax:					;# R2=in, R3=out
+	movb	(r2)+, -(sp)			;# byte swap longword onto stack
+	movb	(r2)+, -(sp)
+	movb	(r2)+, -(sp)
+	movb	(r2)+, -(sp)
+	movl	(sp)+, r0			;# pop swapped value -> r0
+	extzv	#23, #8, r0, r1			;# exponent -> r1
+	beql	L10				;# zero exponent
+	addw2	#2, r1				;# adjust exponent bias
+	cmpw	r1, #256			;# compare with max VAX exponent
+	bgeq	L11				;# return VAX-NaN if overflow
+	insv	r1, #23, #8, r0			;# insert VAX-D exponent
+	rotl	#16, r0, (r3)+			;# output VAX value
+	rsb
+L10:
+	clrl	(r3)+				;# return all 32 bits zero
+	rsb
+L11:
+	movl	#-1, (r3)+			; return fixed NaN value...
+	rsb
+
+	.END
diff --git a/external/cfitsio/wcssub.c b/external/cfitsio/wcssub.c
new file mode 100644
index 0000000..afb8e5c
--- /dev/null
+++ b/external/cfitsio/wcssub.c
@@ -0,0 +1,1043 @@
+#include 
+#include 
+#include 
+#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/external/cfitsio/wcsutil.c b/external/cfitsio/wcsutil.c
new file mode 100644
index 0000000..01d80f9
--- /dev/null
+++ b/external/cfitsio/wcsutil.c
@@ -0,0 +1,502 @@
+#include 
+#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)1.0)
+	    return(*status = 501);
+          rat = asin (rat);
+          mg = 1.0 + sin(dect) * sin0 + cos(dect) * cos0 * cos(rat);
+          if (fabs(mg)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))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)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)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
+#include 
+#include 
+#include 
+
+#ifdef _ALPHA_
+#define e_magic_number IMAGE_FILE_MACHINE_ALPHA
+#else
+#define e_magic_number IMAGE_FILE_MACHINE_I386
+#endif
+
+/*
+ *----------------------------------------------------------------------
+ * GetArgcArgv --
+ * 
+ *	Break up a line into argc argv
+ *----------------------------------------------------------------------
+ */
+int
+GetArgcArgv(char *s, char **argv)
+{
+    int quote = 0;
+    int argc = 0;
+    char *bp;
+
+    bp = s;
+    while (1) {
+	while (isspace(*bp)) {
+	    bp++;
+	}
+	if (*bp == '\n' || *bp == '\0') {
+	    *bp = '\0';
+	    return argc;
+	}
+	if (*bp == '\"') {
+	    quote = 1;
+	    bp++;
+	}
+	argv[argc++] = bp;
+
+	while (*bp != '\0') {
+	    if (quote) {
+		if (*bp == '\"') {
+		    quote = 0;
+		    *bp = '\0';
+		    bp++;
+		    break;
+		}
+		bp++;
+		continue;
+	    }
+	    if (isspace(*bp)) {
+		*bp = '\0';
+		bp++;
+		break;
+	    }
+	    bp++;
+	}
+    }
+}
+
+/*
+ *  The names of the first group of possible symbol table storage classes
+ */
+char * SzStorageClass1[] = {
+    "NULL","AUTOMATIC","EXTERNAL","STATIC","REGISTER","EXTERNAL_DEF","LABEL",
+    "UNDEFINED_LABEL","MEMBER_OF_STRUCT","ARGUMENT","STRUCT_TAG",
+    "MEMBER_OF_UNION","UNION_TAG","TYPE_DEFINITION","UNDEFINED_STATIC",
+    "ENUM_TAG","MEMBER_OF_ENUM","REGISTER_PARAM","BIT_FIELD"
+};
+
+/*
+ * The names of the second group of possible symbol table storage classes
+ */
+char * SzStorageClass2[] = {
+    "BLOCK","FUNCTION","END_OF_STRUCT","FILE","SECTION","WEAK_EXTERNAL"
+};
+
+/*
+ *----------------------------------------------------------------------
+ * GetSZStorageClass --
+ *
+ *	Given a symbol storage class value, return a descriptive
+ *	ASCII string
+ *----------------------------------------------------------------------
+ */
+PSTR
+GetSZStorageClass(BYTE storageClass)
+{
+	if ( storageClass <= IMAGE_SYM_CLASS_BIT_FIELD )
+		return SzStorageClass1[storageClass];
+	else if ( (storageClass >= IMAGE_SYM_CLASS_BLOCK)
+		      && (storageClass <= IMAGE_SYM_CLASS_WEAK_EXTERNAL) )
+		return SzStorageClass2[storageClass-IMAGE_SYM_CLASS_BLOCK];
+	else
+		return "???";
+}
+
+/*
+ *----------------------------------------------------------------------
+ * GetSectionName --
+ *
+ *	Used by DumpSymbolTable, it gives meaningful names to
+ *	the non-normal section number.
+ *
+ * Results:
+ *	A name is returned in buffer
+ *----------------------------------------------------------------------
+ */
+void
+GetSectionName(WORD section, PSTR buffer, unsigned cbBuffer)
+{
+    char tempbuffer[10];
+	
+    switch ( (SHORT)section )
+    {
+      case IMAGE_SYM_UNDEFINED: strcpy(tempbuffer, "UNDEF"); break;
+      case IMAGE_SYM_ABSOLUTE:  strcpy(tempbuffer, "ABS  "); break;
+      case IMAGE_SYM_DEBUG:	  strcpy(tempbuffer, "DEBUG"); break;
+      default: wsprintf(tempbuffer, "%-5X", section);
+    }
+	
+    strncpy(buffer, tempbuffer, cbBuffer-1);
+}
+
+/*
+ *----------------------------------------------------------------------
+ * DumpSymbolTable --
+ *
+ *	Dumps a COFF symbol table from an EXE or OBJ.  We only use
+ *	it to dump tables from OBJs.
+ *----------------------------------------------------------------------
+ */
+void
+DumpSymbolTable(PIMAGE_SYMBOL pSymbolTable, FILE *fout, unsigned cSymbols)
+{
+    unsigned i;
+    PSTR stringTable;
+    char sectionName[10];
+	
+    fprintf(fout, "Symbol Table - %X entries  (* = auxillary symbol)\n",
+	    cSymbols);
+
+    fprintf(fout, 
+     "Indx Name                 Value    Section    cAux  Type    Storage\n"
+     "---- -------------------- -------- ---------- ----- ------- --------\n");
+
+    /*
+     * The string table apparently starts right after the symbol table
+     */
+    stringTable = (PSTR)&pSymbolTable[cSymbols]; 
+		
+    for ( i=0; i < cSymbols; i++ ) {
+	fprintf(fout, "%04X ", i);
+	if ( pSymbolTable->N.Name.Short != 0 )
+	    fprintf(fout, "%-20.8s", pSymbolTable->N.ShortName);
+	else
+	    fprintf(fout, "%-20s", stringTable + pSymbolTable->N.Name.Long);
+
+	fprintf(fout, " %08X", pSymbolTable->Value);
+
+	GetSectionName(pSymbolTable->SectionNumber, sectionName,
+		       sizeof(sectionName));
+	fprintf(fout, " sect:%s aux:%X type:%02X st:%s\n",
+	       sectionName,
+	       pSymbolTable->NumberOfAuxSymbols,
+	       pSymbolTable->Type,
+	       GetSZStorageClass(pSymbolTable->StorageClass) );
+#if 0
+	if ( pSymbolTable->NumberOfAuxSymbols )
+	    DumpAuxSymbols(pSymbolTable);
+#endif
+
+	/*
+	 * Take into account any aux symbols
+	 */
+	i += pSymbolTable->NumberOfAuxSymbols;
+	pSymbolTable += pSymbolTable->NumberOfAuxSymbols;
+	pSymbolTable++;
+    }
+}
+
+/*
+ *----------------------------------------------------------------------
+ * DumpExternals --
+ *
+ *	Dumps a COFF symbol table from an EXE or OBJ.  We only use
+ *	it to dump tables from OBJs.
+ *----------------------------------------------------------------------
+ */
+void
+DumpExternals(PIMAGE_SYMBOL pSymbolTable, FILE *fout, unsigned cSymbols)
+{
+    unsigned i;
+    PSTR stringTable;
+    char *s, *f;
+    char symbol[1024];
+	
+    /*
+     * The string table apparently starts right after the symbol table
+     */
+    stringTable = (PSTR)&pSymbolTable[cSymbols]; 
+		
+    for ( i=0; i < cSymbols; i++ ) {
+	if (pSymbolTable->SectionNumber > 0 && pSymbolTable->Type == 0x20) {
+	    if (pSymbolTable->StorageClass == IMAGE_SYM_CLASS_EXTERNAL) {
+		if (pSymbolTable->N.Name.Short != 0) {
+		    strncpy(symbol, pSymbolTable->N.ShortName, 8);
+		    symbol[8] = 0;
+		} else {
+		    s = stringTable + pSymbolTable->N.Name.Long;
+		    strcpy(symbol, s);
+		}
+		s = symbol;
+		f = strchr(s, '@');
+		if (f) {
+		    *f = 0;
+		}
+#if defined(_MSC_VER) && defined(_X86_)
+		if (symbol[0] == '_') {
+		    s = &symbol[1];
+		}
+#endif
+		if ((stricmp(s, "DllEntryPoint") != 0) 
+			&& (stricmp(s, "DllMain") != 0)) {
+		    fprintf(fout, "\t%s\n", s);
+		}
+	    }
+	}
+
+	/*
+	 * Take into account any aux symbols
+	 */
+	i += pSymbolTable->NumberOfAuxSymbols;
+	pSymbolTable += pSymbolTable->NumberOfAuxSymbols;
+	pSymbolTable++;
+    }
+}
+
+/*
+ *----------------------------------------------------------------------
+ * DumpObjFile --
+ *
+ *	Dump an object file--either a full listing or just the exported
+ *	symbols.
+ *----------------------------------------------------------------------
+ */
+void
+DumpObjFile(PIMAGE_FILE_HEADER pImageFileHeader, FILE *fout, int full)
+{
+    PIMAGE_SYMBOL PCOFFSymbolTable;
+    DWORD COFFSymbolCount;
+    
+    PCOFFSymbolTable = (PIMAGE_SYMBOL)
+	((DWORD)pImageFileHeader + pImageFileHeader->PointerToSymbolTable);
+    COFFSymbolCount = pImageFileHeader->NumberOfSymbols;
+
+    if (full) {
+	DumpSymbolTable(PCOFFSymbolTable, fout, COFFSymbolCount);
+    } else {
+	DumpExternals(PCOFFSymbolTable, fout, COFFSymbolCount);
+    }
+}
+
+/*
+ *----------------------------------------------------------------------
+ * SkipToNextRecord --
+ *
+ *	Skip over the current ROMF record and return the type of the
+ *	next record.
+ *----------------------------------------------------------------------
+ */
+
+BYTE
+SkipToNextRecord(BYTE **ppBuffer)
+{
+    int length;
+    (*ppBuffer)++;		/* Skip over the type.*/
+    length = *((WORD*)(*ppBuffer))++; /* Retrieve the length. */
+    *ppBuffer += length;	/* Skip over the rest. */
+    return **ppBuffer;		/* Return the type. */
+}
+
+/*
+ *----------------------------------------------------------------------
+ * DumpROMFObjFile --
+ *
+ *	Dump a Relocatable Object Module Format file, displaying only
+ *	the exported symbols.
+ *----------------------------------------------------------------------
+ */
+void
+DumpROMFObjFile(LPVOID pBuffer, FILE *fout)
+{
+    BYTE type, length;
+    char symbol[1024], *s;
+
+    while (1) {
+	type = SkipToNextRecord(&(BYTE*)pBuffer);
+	if (type == 0x90) {	/* PUBDEF */
+	    if (((BYTE*)pBuffer)[4] != 0) {
+		length = ((BYTE*)pBuffer)[5];
+		strncpy(symbol, ((char*)pBuffer) + 6, length);
+		symbol[length] = '\0';
+		s = symbol;
+		if ((stricmp(s, "DllEntryPoint") != 0) 
+			&& (stricmp(s, "DllMain") != 0)) {
+		    if (s[0] == '_') {
+			s++;
+			fprintf(fout, "\t_%s\n\t%s=_%s\n", s, s, s);
+		    } else {
+			fprintf(fout, "\t%s\n", s);
+		    }
+		}
+	    }
+	} else if (type == 0x8B || type == 0x8A) { /* MODEND */
+	    break;
+	}
+    }
+}
+
+/*
+ *----------------------------------------------------------------------
+ * DumpFile --
+ *
+ *	Open up a file, memory map it, and call the appropriate
+ *	dumping routine
+ *----------------------------------------------------------------------
+ */
+void
+DumpFile(LPSTR filename, FILE *fout, int full)
+{
+    HANDLE hFile;
+    HANDLE hFileMapping;
+    LPVOID lpFileBase;
+    PIMAGE_DOS_HEADER dosHeader;
+	
+    hFile = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL,
+		       OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
+					
+    if (hFile == INVALID_HANDLE_VALUE) {
+	fprintf(stderr, "Couldn't open file with CreateFile()\n");
+	return;
+    }
+
+    hFileMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
+    if (hFileMapping == 0) {
+	CloseHandle(hFile);
+	fprintf(stderr, "Couldn't open file mapping with CreateFileMapping()\n");
+	return;
+    }
+
+    lpFileBase = MapViewOfFile(hFileMapping, FILE_MAP_READ, 0, 0, 0);
+    if (lpFileBase == 0) {
+	CloseHandle(hFileMapping);
+	CloseHandle(hFile);
+	fprintf(stderr, "Couldn't map view of file with MapViewOfFile()\n");
+	return;
+    }
+
+    dosHeader = (PIMAGE_DOS_HEADER)lpFileBase;
+    if (dosHeader->e_magic == IMAGE_DOS_SIGNATURE) {
+#if 0
+	DumpExeFile( dosHeader );
+#else
+	fprintf(stderr, "File is an executable.  I don't dump those.\n");
+	return;
+#endif
+    }
+    /* Does it look like a i386 COFF OBJ file??? */
+    else if ((dosHeader->e_magic == e_magic_number)
+	    && (dosHeader->e_sp == 0)) {
+	/*
+	 * The two tests above aren't what they look like.  They're
+	 * really checking for IMAGE_FILE_HEADER.Machine == i386 (0x14C)
+	 * and IMAGE_FILE_HEADER.SizeOfOptionalHeader == 0;
+	 */
+	DumpObjFile((PIMAGE_FILE_HEADER) lpFileBase, fout, full);
+    } else if (*((BYTE *)lpFileBase) == 0x80) {
+	/*
+	 * This file looks like it might be a ROMF file.
+	 */
+	DumpROMFObjFile(lpFileBase, fout);
+    } else {
+	printf("unrecognized file format\n");
+    }
+    UnmapViewOfFile(lpFileBase);
+    CloseHandle(hFileMapping);
+    CloseHandle(hFile);
+}
+
+void
+main(int argc, char **argv)
+{
+    char *fargv[1000];
+    char cmdline[10000];
+    int i, arg;
+    FILE *fout;
+    int pos;
+    int full = 0;
+    char *outfile = NULL;
+
+    if (argc < 3) {
+      Usage:
+	fprintf(stderr, "Usage: %s ?-o outfile? ?-f(ull)?   ..\n", argv[0]);
+	exit(1);
+    }
+
+    arg = 1;
+    while (argv[arg][0] == '-') {
+	if (strcmp(argv[arg], "--") == 0) {
+	    arg++;
+	    break;
+	} else if (strcmp(argv[arg], "-f") == 0) {
+	    full = 1;
+	} else if (strcmp(argv[arg], "-o") == 0) {
+	    arg++;
+	    if (arg == argc) {
+		goto Usage;
+	    }
+	    outfile = argv[arg];
+	}
+	arg++;
+    }
+    if (arg == argc) {
+	goto Usage;
+    }
+
+    if (outfile) {
+	fout = fopen(outfile, "w+");
+	if (fout == NULL) {
+	    fprintf(stderr, "Unable to open \'%s\' for writing:\n",
+		    argv[arg]);
+	    perror("");
+	    exit(1);
+	}
+    } else {
+	fout = stdout;
+    }
+    
+    if (! full) {
+	char *dllname = argv[arg];
+	arg++;
+	if (arg == argc) {
+	    goto Usage;
+	}
+	fprintf(fout, "LIBRARY    %s\n", dllname);
+	fprintf(fout, "EXETYPE WINDOWS\n");
+	fprintf(fout, "CODE PRELOAD MOVEABLE DISCARDABLE\n");
+	fprintf(fout, "DATA PRELOAD MOVEABLE MULTIPLE\n\n");
+	fprintf(fout, "EXPORTS\n");
+    }
+
+    for (; arg < argc; arg++) {
+	if (argv[arg][0] == '@') {
+	    FILE *fargs = fopen(&argv[arg][1], "r");
+	    if (fargs == NULL) {
+		fprintf(stderr, "Unable to open \'%s\' for reading:\n",
+			argv[arg]);
+		perror("");
+		exit(1);
+	    }
+	    pos = 0;
+	    for (i = 0; i < arg; i++) {
+		strcpy(&cmdline[pos], argv[i]);
+		pos += strlen(&cmdline[pos]) + 1;
+		fargv[i] = argv[i];
+	    }
+	    fgets(&cmdline[pos], sizeof(cmdline), fargs);
+	    fprintf(stderr, "%s\n", &cmdline[pos]);
+	    fclose(fargs);
+	    i += GetArgcArgv(&cmdline[pos], &fargv[i]);
+	    argc = i;
+	    argv = fargv;
+	}
+	DumpFile(argv[arg], fout, full);
+    }
+    exit(0);
+}
diff --git a/external/cfitsio/zcompress.c b/external/cfitsio/zcompress.c
new file mode 100644
index 0000000..b8d7e79
--- /dev/null
+++ b/external/cfitsio/zcompress.c
@@ -0,0 +1,504 @@
+#include 
+#include 
+#include 
+#include 
+#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/external/cfitsio/zconf.h b/external/cfitsio/zconf.h
new file mode 100644
index 0000000..142c330
--- /dev/null
+++ b/external/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 
+     /* 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     /* 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        /* for SEEK_* and off_t */
+#  ifdef VMS
+#    include      /* 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/external/cfitsio/zlib.h b/external/cfitsio/zlib.h
new file mode 100644
index 0000000..bfbba83
--- /dev/null
+++ b/external/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/external/cfitsio/zuncompress.c b/external/cfitsio/zuncompress.c
new file mode 100644
index 0000000..c73ee6d
--- /dev/null
+++ b/external/cfitsio/zuncompress.c
@@ -0,0 +1,603 @@
+/* gzcompress.h -- definitions for the .Z decompression routine used in CFITSIO */
+
+#include 
+#include 
+#include 
+#include 
+
+#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< 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<>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<= 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<= 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<= 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/external/cfitsio/zutil.c b/external/cfitsio/zutil.c
new file mode 100644
index 0000000..15645c5
--- /dev/null
+++ b/external/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/external/cfitsio/zutil.h b/external/cfitsio/zutil.h
new file mode 100644
index 0000000..1f5a6c0
--- /dev/null
+++ b/external/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 
+#  endif
+#  include 
+#  include 
+#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 
+#    endif
+#  else /* MSC or DJGPP */
+#    include 
+#  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 
+#  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  /* 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 
+   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/external/cosmotool/.gitignore b/external/cosmotool/.gitignore
new file mode 100644
index 0000000..c248456
--- /dev/null
+++ b/external/cosmotool/.gitignore
@@ -0,0 +1,3 @@
+*~
+*.o
+*.prog
diff --git a/external/cosmotool/GetGitRevisionDescription.cmake b/external/cosmotool/GetGitRevisionDescription.cmake
new file mode 100644
index 0000000..f6f07ca
--- /dev/null
+++ b/external/cosmotool/GetGitRevisionDescription.cmake
@@ -0,0 +1,104 @@
+# - Returns a version string from Git
+#
+# These functions force a re-configure on each git commit so that you can
+# trust the values of the variables in your build system.
+#
+#  get_git_head_revision(  [ ...])
+#
+# Returns the refspec and sha hash of the current head revision
+#
+#  git_describe( [ ...])
+#
+# Returns the results of git describe on the source tree, and adjusting
+# the output so that it tests false if an error occurs.
+#
+#  git_get_exact_tag( [ ...])
+#
+# Returns the results of git describe --exact-match on the source tree,
+# and adjusting the output so that it tests false if there was no exact
+# matching tag.
+#
+# Requires CMake 2.6 or newer (uses the 'function' command)
+#
+# Original Author:
+# 2009-2010 Ryan Pavlik  
+# http://academic.cleardefinition.com
+# Iowa State University HCI Graduate Program/VRAC
+#
+# Copyright Iowa State University 2009-2010.
+# Distributed under the Boost Software License, Version 1.0.
+# (See accompanying file LICENSE_1_0.txt or copy at
+# http://www.boost.org/LICENSE_1_0.txt)
+
+if(__get_git_revision_description)
+	return()
+endif()
+set(__get_git_revision_description YES)
+
+# We must run the following at "include" time, not at function call time,
+# to find the path to this module rather than the path to a calling list file
+get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH)
+
+function(get_git_head_revision _refspecvar _hashvar)
+	set(GIT_DIR "${CMAKE_SOURCE_DIR}/.git")
+	if(NOT EXISTS "${GIT_DIR}")
+		# not in git
+		set(${_refspecvar} "GITDIR-NOTFOUND" PARENT_SCOPE)
+		set(${_hashvar} "GITDIR-NOTFOUND" PARENT_SCOPE)
+		return()
+	endif()
+	set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data")
+	if(NOT EXISTS "${GIT_DATA}")
+		file(MAKE_DIRECTORY "${GIT_DATA}")
+	endif()
+	set(HEAD_FILE "${GIT_DATA}/HEAD")
+	configure_file("${GIT_DIR}/HEAD" "${HEAD_FILE}" COPYONLY)
+
+	configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in" "${GIT_DATA}/grabRef.cmake" @ONLY)
+	include("${GIT_DATA}/grabRef.cmake")
+
+	set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE)
+	set(${_hashvar} "${HEAD_HASH}" PARENT_SCOPE)
+endfunction()
+
+function(git_describe _var)
+	if(NOT GIT_FOUND)
+		find_package(Git QUIET)
+	endif()
+	get_git_head_revision(refspec hash)
+	if(NOT GIT_FOUND)
+		set(${_var} "GIT-NOTFOUND"  PARENT_SCOPE)
+		return()
+	endif()
+	if(NOT hash)
+		set(${_var} "HEAD-HASH-NOTFOUND"  PARENT_SCOPE)
+		return()
+	endif()
+
+	# TODO sanitize
+	#if((${ARGN}" MATCHES "&&") OR
+	#	(ARGN MATCHES "||") OR
+	#	(ARGN MATCHES "\\;"))
+	#	message("Please report the following error to the project!")
+	#	message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}")
+	#endif()
+
+	#message(STATUS "Arguments to execute_process: ${ARGN}")
+
+	execute_process(COMMAND "${GIT_EXECUTABLE}" describe ${hash} ${ARGN}
+		WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
+		RESULT_VARIABLE res
+		OUTPUT_VARIABLE out
+		ERROR_QUIET
+		OUTPUT_STRIP_TRAILING_WHITESPACE)
+	if(NOT res EQUAL 0)
+		set(out "${out}-${res}-NOTFOUND")
+	endif()
+
+	set(${_var} "${out}" PARENT_SCOPE)
+endfunction()
+
+function(git_get_exact_tag _var)
+	git_describe(out --exact-match ${ARGN})
+	set(${_var} "${out}" PARENT_SCOPE)
+endfunction()
diff --git a/external/cosmotool/GetGitRevisionDescription.cmake.in b/external/cosmotool/GetGitRevisionDescription.cmake.in
new file mode 100644
index 0000000..c04662c
--- /dev/null
+++ b/external/cosmotool/GetGitRevisionDescription.cmake.in
@@ -0,0 +1,30 @@
+# 
+# Internal file for GetGitRevisionDescription.cmake
+#
+# Requires CMake 2.6 or newer (uses the 'function' command)
+#
+# Original Author:
+# 2009-2010 Ryan Pavlik  
+# http://academic.cleardefinition.com
+# Iowa State University HCI Graduate Program/VRAC
+#
+# Copyright Iowa State University 2009-2010.
+# Distributed under the Boost Software License, Version 1.0.
+# (See accompanying file LICENSE_1_0.txt or copy at
+# http://www.boost.org/LICENSE_1_0.txt)
+
+file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024)
+
+string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS)
+if(HEAD_CONTENTS MATCHES "ref")
+	# named branch
+	string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}")
+
+	configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY)
+else()
+	# detached HEAD
+	configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY)
+endif()
+
+file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024)
+string(STRIP "${HEAD_HASH}" HEAD_HASH)
diff --git a/external/cosmotool/LICENCE_CeCILL_V2 b/external/cosmotool/LICENCE_CeCILL_V2
new file mode 100644
index 0000000..fcc8df2
--- /dev/null
+++ b/external/cosmotool/LICENCE_CeCILL_V2
@@ -0,0 +1,506 @@
+
+CeCILL FREE SOFTWARE LICENSE AGREEMENT
+
+
+    Notice
+
+This Agreement is a Free Software license agreement that is the result
+of discussions between its authors in order to ensure compliance with
+the two main principles guiding its drafting:
+
+    * firstly, compliance with the principles governing the distribution
+      of Free Software: access to source code, broad rights granted to
+      users,
+    * secondly, the election of a governing law, French law, with which
+      it is conformant, both as regards the law of torts and
+      intellectual property law, and the protection that it offers to
+      both authors and holders of the economic rights over software.
+
+The authors of the CeCILL (for Ce[a] C[nrs] I[nria] L[ogiciel] L[ibre])
+license are:
+
+Commissariat à l'Energie Atomique - CEA, a public scientific, technical
+and industrial research establishment, having its principal place of
+business at 25 rue Leblanc, immeuble Le Ponant D, 75015 Paris, France.
+
+Centre National de la Recherche Scientifique - CNRS, a public scientific
+and technological establishment, having its principal place of business
+at 3 rue Michel-Ange, 75794 Paris cedex 16, France.
+
+Institut National de Recherche en Informatique et en Automatique -
+INRIA, a public scientific and technological establishment, having its
+principal place of business at Domaine de Voluceau, Rocquencourt, BP
+105, 78153 Le Chesnay cedex, France.
+
+
+    Preamble
+
+The purpose of this Free Software license agreement is to grant users
+the right to modify and redistribute the software governed by this
+license within the framework of an open source distribution model.
+
+The exercising of these rights is conditional upon certain obligations
+for users so as to preserve this status for all subsequent redistributions.
+
+In consideration of access to the source code and the rights to copy,
+modify and redistribute granted by the license, users are provided only
+with a limited warranty and the software's author, the holder of the
+economic rights, and the successive licensors only have limited liability.
+
+In this respect, the risks associated with loading, using, modifying
+and/or developing or reproducing the software by the user are brought to
+the user's attention, given its Free Software status, which may make it
+complicated to use, with the result that its use is reserved for
+developers and experienced professionals having in-depth computer
+knowledge. Users are therefore encouraged to load and test the
+suitability of the software as regards their requirements in conditions
+enabling the security of their systems and/or data to be ensured and,
+more generally, to use and operate it in the same conditions of
+security. This Agreement may be freely reproduced and published,
+provided it is not altered, and that no provisions are either added or
+removed herefrom.
+
+This Agreement may apply to any or all software for which the holder of
+the economic rights decides to submit the use thereof to its provisions.
+
+
+    Article 1 - DEFINITIONS
+
+For the purpose of this Agreement, when the following expressions
+commence with a capital letter, they shall have the following meaning:
+
+Agreement: means this license agreement, and its possible subsequent
+versions and annexes.
+
+Software: means the software in its Object Code and/or Source Code form
+and, where applicable, its documentation, "as is" when the Licensee
+accepts the Agreement.
+
+Initial Software: means the Software in its Source Code and possibly its
+Object Code form and, where applicable, its documentation, "as is" when
+it is first distributed under the terms and conditions of the Agreement.
+
+Modified Software: means the Software modified by at least one
+Contribution.
+
+Source Code: means all the Software's instructions and program lines to
+which access is required so as to modify the Software.
+
+Object Code: means the binary files originating from the compilation of
+the Source Code.
+
+Holder: means the holder(s) of the economic rights over the Initial
+Software.
+
+Licensee: means the Software user(s) having accepted the Agreement.
+
+Contributor: means a Licensee having made at least one Contribution.
+
+Licensor: means the Holder, or any other individual or legal entity, who
+distributes the Software under the Agreement.
+
+Contribution: means any or all modifications, corrections, translations,
+adaptations and/or new functions integrated into the Software by any or
+all Contributors, as well as any or all Internal Modules.
+
+Module: means a set of sources files including their documentation that
+enables supplementary functions or services in addition to those offered
+by the Software.
+
+External Module: means any or all Modules, not derived from the
+Software, so that this Module and the Software run in separate address
+spaces, with one calling the other when they are run.
+
+Internal Module: means any or all Module, connected to the Software so
+that they both execute in the same address space.
+
+GNU GPL: means the GNU General Public License version 2 or any
+subsequent version, as published by the Free Software Foundation Inc.
+
+Parties: mean both the Licensee and the Licensor.
+
+These expressions may be used both in singular and plural form.
+
+
+    Article 2 - PURPOSE
+
+The purpose of the Agreement is the grant by the Licensor to the
+Licensee of a non-exclusive, transferable and worldwide license for the
+Software as set forth in Article 5 hereinafter for the whole term of the
+protection granted by the rights over said Software. 
+
+
+    Article 3 - ACCEPTANCE
+
+3.1 The Licensee shall be deemed as having accepted the terms and
+conditions of this Agreement upon the occurrence of the first of the
+following events:
+
+    * (i) loading the Software by any or all means, notably, by
+      downloading from a remote server, or by loading from a physical
+      medium;
+    * (ii) the first time the Licensee exercises any of the rights
+      granted hereunder.
+
+3.2 One copy of the Agreement, containing a notice relating to the
+characteristics of the Software, to the limited warranty, and to the
+fact that its use is restricted to experienced users has been provided
+to the Licensee prior to its acceptance as set forth in Article 3.1
+hereinabove, and the Licensee hereby acknowledges that it has read and
+understood it.
+
+
+    Article 4 - EFFECTIVE DATE AND TERM
+
+
+      4.1 EFFECTIVE DATE
+
+The Agreement shall become effective on the date when it is accepted by
+the Licensee as set forth in Article 3.1.
+
+
+      4.2 TERM
+
+The Agreement shall remain in force for the entire legal term of
+protection of the economic rights over the Software.
+
+
+    Article 5 - SCOPE OF RIGHTS GRANTED
+
+The Licensor hereby grants to the Licensee, who accepts, the following
+rights over the Software for any or all use, and for the term of the
+Agreement, on the basis of the terms and conditions set forth hereinafter.
+
+Besides, if the Licensor owns or comes to own one or more patents
+protecting all or part of the functions of the Software or of its
+components, the Licensor undertakes not to enforce the rights granted by
+these patents against successive Licensees using, exploiting or
+modifying the Software. If these patents are transferred, the Licensor
+undertakes to have the transferees subscribe to the obligations set
+forth in this paragraph.
+
+
+      5.1 RIGHT OF USE
+
+The Licensee is authorized to use the Software, without any limitation
+as to its fields of application, with it being hereinafter specified
+that this comprises:
+
+   1. permanent or temporary reproduction of all or part of the Software
+      by any or all means and in any or all form.
+
+   2. loading, displaying, running, or storing the Software on any or
+      all medium.
+
+   3. entitlement to observe, study or test its operation so as to
+      determine the ideas and principles behind any or all constituent
+      elements of said Software. This shall apply when the Licensee
+      carries out any or all loading, displaying, running, transmission
+      or storage operation as regards the Software, that it is entitled
+      to carry out hereunder.
+
+
+      5.2 ENTITLEMENT TO MAKE CONTRIBUTIONS
+
+The right to make Contributions includes the right to translate, adapt,
+arrange, or make any or all modifications to the Software, and the right
+to reproduce the resulting software.
+
+The Licensee is authorized to make any or all Contributions to the
+Software provided that it includes an explicit notice that it is the
+author of said Contribution and indicates the date of the creation thereof.
+
+
+      5.3 RIGHT OF DISTRIBUTION
+
+In particular, the right of distribution includes the right to publish,
+transmit and communicate the Software to the general public on any or
+all medium, and by any or all means, and the right to market, either in
+consideration of a fee, or free of charge, one or more copies of the
+Software by any means.
+
+The Licensee is further authorized to distribute copies of the modified
+or unmodified Software to third parties according to the terms and
+conditions set forth hereinafter.
+
+
+        5.3.1 DISTRIBUTION OF SOFTWARE WITHOUT MODIFICATION
+
+The Licensee is authorized to distribute true copies of the Software in
+Source Code or Object Code form, provided that said distribution
+complies with all the provisions of the Agreement and is accompanied by:
+
+   1. a copy of the Agreement,
+
+   2. a notice relating to the limitation of both the Licensor's
+      warranty and liability as set forth in Articles 8 and 9,
+
+and that, in the event that only the Object Code of the Software is
+redistributed, the Licensee allows future Licensees unhindered access to
+the full Source Code of the Software by indicating how to access it, it
+being understood that the additional cost of acquiring the Source Code
+shall not exceed the cost of transferring the data.
+
+
+        5.3.2 DISTRIBUTION OF MODIFIED SOFTWARE
+
+When the Licensee makes a Contribution to the Software, the terms and
+conditions for the distribution of the resulting Modified Software
+become subject to all the provisions of this Agreement.
+
+The Licensee is authorized to distribute the Modified Software, in
+source code or object code form, provided that said distribution
+complies with all the provisions of the Agreement and is accompanied by:
+
+   1. a copy of the Agreement,
+
+   2. a notice relating to the limitation of both the Licensor's
+      warranty and liability as set forth in Articles 8 and 9,
+
+and that, in the event that only the object code of the Modified
+Software is redistributed, the Licensee allows future Licensees
+unhindered access to the full source code of the Modified Software by
+indicating how to access it, it being understood that the additional
+cost of acquiring the source code shall not exceed the cost of
+transferring the data.
+
+
+        5.3.3 DISTRIBUTION OF EXTERNAL MODULES
+
+When the Licensee has developed an External Module, the terms and
+conditions of this Agreement do not apply to said External Module, that
+may be distributed under a separate license agreement.
+
+
+        5.3.4 COMPATIBILITY WITH THE GNU GPL
+
+The Licensee can include a code that is subject to the provisions of one
+of the versions of the GNU GPL in the Modified or unmodified Software,
+and distribute that entire code under the terms of the same version of
+the GNU GPL.
+
+The Licensee can include the Modified or unmodified Software in a code
+that is subject to the provisions of one of the versions of the GNU GPL,
+and distribute that entire code under the terms of the same version of
+the GNU GPL.
+
+
+    Article 6 - INTELLECTUAL PROPERTY
+
+
+      6.1 OVER THE INITIAL SOFTWARE
+
+The Holder owns the economic rights over the Initial Software. Any or
+all use of the Initial Software is subject to compliance with the terms
+and conditions under which the Holder has elected to distribute its work
+and no one shall be entitled to modify the terms and conditions for the
+distribution of said Initial Software.
+
+The Holder undertakes that the Initial Software will remain ruled at
+least by this Agreement, for the duration set forth in Article 4.2.
+
+
+      6.2 OVER THE CONTRIBUTIONS
+
+The Licensee who develops a Contribution is the owner of the
+intellectual property rights over this Contribution as defined by
+applicable law.
+
+
+      6.3 OVER THE EXTERNAL MODULES
+
+The Licensee who develops an External Module is the owner of the
+intellectual property rights over this External Module as defined by
+applicable law and is free to choose the type of agreement that shall
+govern its distribution.
+
+
+      6.4 JOINT PROVISIONS
+
+The Licensee expressly undertakes:
+
+   1. not to remove, or modify, in any manner, the intellectual property
+      notices attached to the Software;
+
+   2. to reproduce said notices, in an identical manner, in the copies
+      of the Software modified or not.
+
+The Licensee undertakes not to directly or indirectly infringe the
+intellectual property rights of the Holder and/or Contributors on the
+Software and to take, where applicable, vis-à-vis its staff, any and all
+measures required to ensure respect of said intellectual property rights
+of the Holder and/or Contributors.
+
+
+    Article 7 - RELATED SERVICES
+
+7.1 Under no circumstances shall the Agreement oblige the Licensor to
+provide technical assistance or maintenance services for the Software.
+
+However, the Licensor is entitled to offer this type of services. The
+terms and conditions of such technical assistance, and/or such
+maintenance, shall be set forth in a separate instrument. Only the
+Licensor offering said maintenance and/or technical assistance services
+shall incur liability therefor.
+
+7.2 Similarly, any Licensor is entitled to offer to its licensees, under
+its sole responsibility, a warranty, that shall only be binding upon
+itself, for the redistribution of the Software and/or the Modified
+Software, under terms and conditions that it is free to decide. Said
+warranty, and the financial terms and conditions of its application,
+shall be subject of a separate instrument executed between the Licensor
+and the Licensee.
+
+
+    Article 8 - LIABILITY
+
+8.1 Subject to the provisions of Article 8.2, the Licensee shall be
+entitled to claim compensation for any direct loss it may have suffered
+from the Software as a result of a fault on the part of the relevant
+Licensor, subject to providing evidence thereof.
+
+8.2 The Licensor's liability is limited to the commitments made under
+this Agreement and shall not be incurred as a result of in particular:
+(i) loss due the Licensee's total or partial failure to fulfill its
+obligations, (ii) direct or consequential loss that is suffered by the
+Licensee due to the use or performance of the Software, and (iii) more
+generally, any consequential loss. In particular the Parties expressly
+agree that any or all pecuniary or business loss (i.e. loss of data,
+loss of profits, operating loss, loss of customers or orders,
+opportunity cost, any disturbance to business activities) or any or all
+legal proceedings instituted against the Licensee by a third party,
+shall constitute consequential loss and shall not provide entitlement to
+any or all compensation from the Licensor.
+
+
+    Article 9 - WARRANTY
+
+9.1 The Licensee acknowledges that the scientific and technical
+state-of-the-art when the Software was distributed did not enable all
+possible uses to be tested and verified, nor for the presence of
+possible defects to be detected. In this respect, the Licensee's
+attention has been drawn to the risks associated with loading, using,
+modifying and/or developing and reproducing the Software which are
+reserved for experienced users.
+
+The Licensee shall be responsible for verifying, by any or all means,
+the suitability of the product for its requirements, its good working
+order, and for ensuring that it shall not cause damage to either persons
+or properties.
+
+9.2 The Licensor hereby represents, in good faith, that it is entitled
+to grant all the rights over the Software (including in particular the
+rights set forth in Article 5).
+
+9.3 The Licensee acknowledges that the Software is supplied "as is" by
+the Licensor without any other express or tacit warranty, other than
+that provided for in Article 9.2 and, in particular, without any warranty 
+as to its commercial value, its secured, safe, innovative or relevant
+nature.
+
+Specifically, the Licensor does not warrant that the Software is free
+from any error, that it will operate without interruption, that it will
+be compatible with the Licensee's own equipment and software
+configuration, nor that it will meet the Licensee's requirements.
+
+9.4 The Licensor does not either expressly or tacitly warrant that the
+Software does not infringe any third party intellectual property right
+relating to a patent, software or any other property right. Therefore,
+the Licensor disclaims any and all liability towards the Licensee
+arising out of any or all proceedings for infringement that may be
+instituted in respect of the use, modification and redistribution of the
+Software. Nevertheless, should such proceedings be instituted against
+the Licensee, the Licensor shall provide it with technical and legal
+assistance for its defense. Such technical and legal assistance shall be
+decided on a case-by-case basis between the relevant Licensor and the
+Licensee pursuant to a memorandum of understanding. The Licensor
+disclaims any and all liability as regards the Licensee's use of the
+name of the Software. No warranty is given as regards the existence of
+prior rights over the name of the Software or as regards the existence
+of a trademark.
+
+
+    Article 10 - TERMINATION
+
+10.1 In the event of a breach by the Licensee of its obligations
+hereunder, the Licensor may automatically terminate this Agreement
+thirty (30) days after notice has been sent to the Licensee and has
+remained ineffective.
+
+10.2 A Licensee whose Agreement is terminated shall no longer be
+authorized to use, modify or distribute the Software. However, any
+licenses that it may have granted prior to termination of the Agreement
+shall remain valid subject to their having been granted in compliance
+with the terms and conditions hereof.
+
+
+    Article 11 - MISCELLANEOUS
+
+
+      11.1 EXCUSABLE EVENTS
+
+Neither Party shall be liable for any or all delay, or failure to
+perform the Agreement, that may be attributable to an event of force
+majeure, an act of God or an outside cause, such as defective
+functioning or interruptions of the electricity or telecommunications
+networks, network paralysis following a virus attack, intervention by
+government authorities, natural disasters, water damage, earthquakes,
+fire, explosions, strikes and labor unrest, war, etc.
+
+11.2 Any failure by either Party, on one or more occasions, to invoke
+one or more of the provisions hereof, shall under no circumstances be
+interpreted as being a waiver by the interested Party of its right to
+invoke said provision(s) subsequently.
+
+11.3 The Agreement cancels and replaces any or all previous agreements,
+whether written or oral, between the Parties and having the same
+purpose, and constitutes the entirety of the agreement between said
+Parties concerning said purpose. No supplement or modification to the
+terms and conditions hereof shall be effective as between the Parties
+unless it is made in writing and signed by their duly authorized
+representatives.
+
+11.4 In the event that one or more of the provisions hereof were to
+conflict with a current or future applicable act or legislative text,
+said act or legislative text shall prevail, and the Parties shall make
+the necessary amendments so as to comply with said act or legislative
+text. All other provisions shall remain effective. Similarly, invalidity
+of a provision of the Agreement, for any reason whatsoever, shall not
+cause the Agreement as a whole to be invalid.
+
+
+      11.5 LANGUAGE
+
+The Agreement is drafted in both French and English and both versions
+are deemed authentic.
+
+
+    Article 12 - NEW VERSIONS OF THE AGREEMENT
+
+12.1 Any person is authorized to duplicate and distribute copies of this
+Agreement.
+
+12.2 So as to ensure coherence, the wording of this Agreement is
+protected and may only be modified by the authors of the License, who
+reserve the right to periodically publish updates or new versions of the
+Agreement, each with a separate number. These subsequent versions may
+address new issues encountered by Free Software.
+
+12.3 Any Software distributed under a given version of the Agreement may
+only be subsequently distributed under the same version of the Agreement
+or a subsequent version, subject to the provisions of Article 5.3.4.
+
+
+    Article 13 - GOVERNING LAW AND JURISDICTION
+
+13.1 The Agreement is governed by French law. The Parties agree to
+endeavor to seek an amicable solution to any disagreements or disputes
+that may arise during the performance of the Agreement.
+
+13.2 Failing an amicable solution within two (2) months as from their
+occurrence, and unless emergency proceedings are necessary, the
+disagreements or disputes shall be referred to the Paris Courts having
+jurisdiction, by the more diligent Party.
+
+
+Version 2.0 dated 2006-09-05.
diff --git a/external/cosmotool/sample/Hartmann_Matrix.txt b/external/cosmotool/sample/Hartmann_Matrix.txt
new file mode 100644
index 0000000..18df79d
--- /dev/null
+++ b/external/cosmotool/sample/Hartmann_Matrix.txt
@@ -0,0 +1,7 @@
+6
+ 14.8253   -6.4243   7.8746  -1.2498  10.2733  10.2733 
+ -6.4243   15.1024  -1.1155  -0.2761  -8.2117  -8.2117 
+  7.8746   -1.1155  51.8519 -23.3482  12.5902  12.5902 
+ -1.2498   -0.2761 -23.3482  22.7962  -9.8958  -9.8958 
+ 10.2733   -8.2117  12.5902  -9.8958  21.0656  21.0656 
+ 10.2733   -8.2117  12.5902  -9.8958  21.0656  21.0656 
diff --git a/external/cosmotool/sample/testAlgo.cpp b/external/cosmotool/sample/testAlgo.cpp
new file mode 100644
index 0000000..65614f5
--- /dev/null
+++ b/external/cosmotool/sample/testAlgo.cpp
@@ -0,0 +1,21 @@
+#include 
+#include 
+#include "algo.hpp"
+
+using namespace CosmoTool;
+using namespace std;
+
+int main(int argc, char **argv)
+{
+  cout << square(2) << endl;
+  cout << cube(2) << endl;
+  cout << square(2.1f) << endl;
+  cout << cube(2.1f) << endl;
+  
+  cout << spower<-2>(2.1f) << endl;
+  cout << spower<2>(2.1f) << endl;
+  cout << spower<3>(2.1f) << endl;
+  cout << spower<4>(2.1f) << endl;
+  cout << spower<5>(2.1f) << endl;
+  return 0;
+}
diff --git a/external/cosmotool/sample/testBQueue.cpp b/external/cosmotool/sample/testBQueue.cpp
new file mode 100644
index 0000000..822cd6b
--- /dev/null
+++ b/external/cosmotool/sample/testBQueue.cpp
@@ -0,0 +1,33 @@
+#include 
+#include "bqueue.hpp"
+
+using namespace std;
+
+int main(int argc, char **argv)
+{
+  CosmoTool::BoundedQueue bq(4, 100000.);
+  
+  for (int i = 10; i >= 0; i--)
+    {
+      bq.push(i, i);
+      
+      int *prio = bq.getPriorities();
+      for (int j = 0; j < 4; j++)
+	cout << prio[j] << " ";
+
+      cout << endl;
+    }
+
+  for (int i = 1; i >= -2; i--)
+    {
+      bq.push(i, i);
+      
+      int *prio = bq.getPriorities();
+      for (int j = 0; j < 4; j++)
+	cout << prio[j] << " ";
+
+      cout << endl;
+    }
+
+  return 0;
+}
diff --git a/external/cosmotool/sample/testBSP.cpp b/external/cosmotool/sample/testBSP.cpp
new file mode 100644
index 0000000..f142fc2
--- /dev/null
+++ b/external/cosmotool/sample/testBSP.cpp
@@ -0,0 +1,15 @@
+#if 0
+#include "bsp_simple.hpp"
+
+int main(int argc, char **argv)
+{
+  CosmoTool::simple_bsp::BSP bsp;
+  double p[5][2] = { { 0, 0}, {1, 0}, {1, 1}, {0, 1}, {0, 0} };
+
+  for (int q = 0; q < 4; q++)
+    bsp.insert(p+q, p+q+2, q);
+
+  return 0;
+}
+#endif
+int main() {}
diff --git a/external/cosmotool/sample/testDelaunay.cpp b/external/cosmotool/sample/testDelaunay.cpp
new file mode 100644
index 0000000..fe14553
--- /dev/null
+++ b/external/cosmotool/sample/testDelaunay.cpp
@@ -0,0 +1,30 @@
+#include 
+#include 
+#include "dinterpolate.hpp"
+
+#define NX 30
+#define NY 30
+
+using namespace std;
+using namespace CosmoTool;
+
+typedef DelaunayInterpolate myTriangle;
+
+int main()
+{
+  myTriangle::CoordType pos[] = { {0,0}, {1,0}, {0,1}, {1, 1}, {2, 0}, {2, 1} } ;
+  double vals[] = { 0, 1, 1, 0, -0.5, 2.0 };
+  uint32_t simplex[] = { 0, 1, 2, 3, 1, 2, 1, 3, 4, 4, 5, 3 };
+
+  myTriangle t(&pos[0], &vals[0], &simplex[0], 6, 4);
+  ofstream f("output.txt");
+
+  for (uint32_t iy = 0; iy <= NY; iy++) {
+    for (uint32_t ix = 0; ix <= NX; ix++) {
+      myTriangle::CoordType inter = { ix *2.0/ NX, iy *1.0/NY };
+      f << inter[1] << " " << inter[0] << " " << t.computeValue(inter) << endl;
+    }
+    f << endl;
+  }
+  return 0;
+}
diff --git a/external/cosmotool/sample/testEskow.cpp b/external/cosmotool/sample/testEskow.cpp
new file mode 100644
index 0000000..60c1821
--- /dev/null
+++ b/external/cosmotool/sample/testEskow.cpp
@@ -0,0 +1,67 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "eskow.hpp"
+
+using namespace std;
+
+double Hartmann_Matrix[6][6] = {
+  { 14.8253,   -6.4243,   7.8746,  -1.2498,  10.2733,  10.2733 },
+  { -6.4243,   15.1024,  -1.1155,  -0.2761,  -8.2117,  -8.2117 },
+  {  7.8746,   -1.1155,  51.8519, -23.3482,  12.5902,  12.5902 },
+  { -1.2498,   -0.2761, -23.3482,  22.7962,  -9.8958,  -9.8958 },
+  { 10.2733,   -8.2117,  12.5902,  -9.8958,  21.0656,  21.0656 },
+  { 10.2733,   -8.2117,  12.5902,  -9.8958,  21.0656,  21.0656 }
+};
+
+struct MatrixOp
+{
+  vector M;
+  int N;
+
+  double& operator()(int i, int j)
+  {
+    return M[i*N + j];
+  }
+};
+
+int main(int argc, char **argv)
+{
+  MatrixOp M;
+  double norm_E;
+  ifstream fi(argv[1]);
+  ofstream f("eskowed.txt");
+  CholeskyEskow chol;
+
+  fi >> M.N;
+  M.M.resize(M.N*M.N);
+
+  for (int i = 0; i < M.N; i++)
+    {
+      for (int j = 0; j < M.N; j++)
+	{
+	  fi >> M(i,j);
+	  if (j > i)
+	    M(i,j) =0;
+	}
+    }
+
+  chol.cholesky(M, M.N, norm_E);
+
+  cout << "norm_E = " << norm_E << endl;
+
+  for (int i = 0; i < M.N; i++)
+    {
+      for (int j = 0; j < M.N; j++)
+	{
+	  if (j > i) 
+            f << "0 ";
+          else
+ 	    f << setprecision(25) << M(i,j) << " ";
+	}
+      f << endl;
+    }
+  return 0;
+}
diff --git a/external/cosmotool/sample/testInterpolate.cpp b/external/cosmotool/sample/testInterpolate.cpp
new file mode 100644
index 0000000..df146b2
--- /dev/null
+++ b/external/cosmotool/sample/testInterpolate.cpp
@@ -0,0 +1,31 @@
+#include 
+#include "interpolate3d.hpp"
+
+using namespace std;
+using namespace CosmoTool;
+
+int main()
+{
+   VectorField *vectors = new VectorField[27];
+
+   for (int i = 0; i < 3; i++)
+     for (int j = 0; j < 3; j++)
+       for (int k = 0; k < 3; k++)
+         {
+            int idx = i + 3*(j + 3*k);
+            vectors[idx].vec[0] = i;
+            vectors[idx].vec[1] = j;
+	    vectors[idx].vec[2] = k;
+         }
+
+   GridSampler > sampler(vectors, 3, 3, 3, 1);
+   Interpolate3D > > inter(sampler);
+
+   VectorField v = inter.get(0.5,0.15,0.5);
+   VectorField v2 = inter.get(1.5,1.65,1.5);
+
+   cout << v.vec[0] << " " << v.vec[1] << " " << v.vec[2] << endl;
+   cout << v2.vec[0] << " " << v2.vec[1] << " " << v2.vec[2] << endl;
+
+   return 0;
+}
diff --git a/external/cosmotool/sample/testNewton.cpp b/external/cosmotool/sample/testNewton.cpp
new file mode 100644
index 0000000..27415b8
--- /dev/null
+++ b/external/cosmotool/sample/testNewton.cpp
@@ -0,0 +1,27 @@
+#include 
+#include 
+
+using namespace std;
+
+#include "newton.hpp"
+
+using namespace CosmoTool;
+
+struct SimplePolynom
+{
+  double eval(double x) { return x*x*x+2*x-1; }
+  double derivative(double x) { return 3*x*x+2; }
+};
+
+int main()
+{
+  SimplePolynom poly;
+  double r;
+
+  double solution = -2*pow(2./(3*(9+sqrt(177.))), 1./3) + (pow(0.5*(9+sqrt(177.))/9, 1./3));
+
+  r = newtonSolver(3.0, poly);
+
+  cout << "Result = " << r << " delta = " << r-solution << endl;
+  return 0;
+}
diff --git a/external/cosmotool/sample/testPool.cpp b/external/cosmotool/sample/testPool.cpp
new file mode 100644
index 0000000..ee01468
--- /dev/null
+++ b/external/cosmotool/sample/testPool.cpp
@@ -0,0 +1,19 @@
+#include "pool.hpp"
+
+using namespace CosmoTool;
+
+int main(int argc, char **argv)
+{
+  MemoryPool pool(1024);
+  int **j = new int *[3000];
+
+  for (int i = 0; i < 3000; i++)
+    {
+      j[i] = pool.alloc();
+      j[i][0] = i;
+    }
+
+  pool.free_all();
+
+  return 0;
+}
diff --git a/external/cosmotool/sample/testReadFlash.cpp b/external/cosmotool/sample/testReadFlash.cpp
new file mode 100644
index 0000000..61d038e
--- /dev/null
+++ b/external/cosmotool/sample/testReadFlash.cpp
@@ -0,0 +1,17 @@
+#include 
+#include 
+#include 
+#include 
+#include "loadFlash.hpp"
+
+using namespace CosmoTool;
+using namespace std;
+
+int main () {
+ 
+  const char* filename = "lss_read_hdf5_chk_0000";
+ 
+  SimuData* data = CosmoTool::loadFlashMulti(filename, 0, 0);
+
+  return 0;
+}
diff --git a/external/cosmotool/sample/testSmooth.cpp b/external/cosmotool/sample/testSmooth.cpp
new file mode 100644
index 0000000..d1c94a9
--- /dev/null
+++ b/external/cosmotool/sample/testSmooth.cpp
@@ -0,0 +1,68 @@
+#include 
+#include "sphSmooth.hpp"
+#include "yorick.hpp"
+#include "mykdtree.hpp"
+
+using namespace std;
+using namespace CosmoTool;
+
+#define NX 1024
+#define ND 2
+
+typedef SPHSmooth MySmooth;
+typedef MySmooth::SPHTree MyTree;
+typedef MySmooth::SPHNode MyNode;
+typedef MySmooth::SPHCell MyCell;
+
+double unit_fun(const char& c)
+{
+  return 1.0;
+}
+
+int main()
+{
+  uint32_t Ncells = 10000;
+  MyCell *cells = new MyCell[Ncells];
+  
+  for (int i = 0; i < Ncells; i++)
+    {
+      cells[i].active = true;
+      for (int l = 0; l < ND; l++)
+	cells[i].coord[l] = drand48();
+      cells[i].val.weight = 0;
+    }
+  
+  MyTree tree(cells, Ncells);
+  MySmooth smooth(&tree, 16);
+
+  for (uint32_t iy = 0; iy < NX; iy++)
+    {
+      cout << "iy=" << iy << endl;
+      for (uint32_t ix = 0; ix < NX; ix++)
+        {
+           MyTree::coords c = { 1.0*ix/NX, 1.0*iy/NX };
+           smooth.fetchNeighbours(c);
+           smooth.addGridSite(c);
+        }
+    }
+
+  
+  uint32_t dims[] = { NX, NX };
+  ProgressiveOutput out = 
+    ProgressiveOutput::saveArrayProgressive("out.nc", dims, 2);
+  for (uint32_t iy = 0; iy < NX; iy++)
+    {
+      cout << "iy=" << iy << endl;
+      for (uint32_t ix = 0; ix < NX; ix++)
+        {
+           MyTree::coords c = { 1.0*ix/NX, 1.0*iy/NX };
+           smooth.fetchNeighbours(c);
+           out.put(smooth.computeSmoothedValue(c, unit_fun));
+
+	  
+        }
+    }
+
+
+  return 0;
+}
diff --git a/external/cosmotool/sample/test_fft_calls.cpp b/external/cosmotool/sample/test_fft_calls.cpp
new file mode 100644
index 0000000..35eeb12
--- /dev/null
+++ b/external/cosmotool/sample/test_fft_calls.cpp
@@ -0,0 +1,12 @@
+#include "fourier/euclidian.hpp"
+
+using namespace CosmoTool;
+
+int main()
+{
+  EuclidianFourierTransform_2d dft(128,128,1.0,1.0);
+
+  dft.realSpace().eigen().setRandom();
+  dft.analysis();
+  return 0;
+}
diff --git a/external/cosmotool/sample/testkd.cpp b/external/cosmotool/sample/testkd.cpp
new file mode 100644
index 0000000..eabcfb2
--- /dev/null
+++ b/external/cosmotool/sample/testkd.cpp
@@ -0,0 +1,123 @@
+#define __KD_TREE_SAVE_ON_DISK
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "mykdtree.hpp"
+
+#define NTRY 50000
+#define ND 2
+
+using namespace std;
+using namespace CosmoTool;
+
+typedef KDTree MyTree;
+typedef MyTree::Cell MyCell;
+
+MyCell *findNearest(MyTree::coords& xc, MyCell *cells, uint32_t Ncells)
+{
+  MyCell *near2 = 0;
+  double R2 = INFINITY;
+  for (int i = 0; i < Ncells; i++)
+    {
+      double d2 = 0;
+      for (int j = 0; j < ND; j++)
+	{
+	  double delta = xc[j]-cells[i].coord[j];
+	  d2 += delta*delta;
+	}
+      if (d2 < R2)
+	{
+	  near2 = &cells[i];
+	  near2->val = i;
+	  R2 = d2;
+	}
+    }
+  return near2;
+}
+
+int main()
+{
+  uint32_t Ncells = 3000;
+  MyCell *cells = new MyCell[Ncells];
+  
+  for (int i = 0; i < Ncells; i++)
+    {
+      cells[i].active = true;
+      for (int l = 0; l < ND; l++)
+	cells[i].coord[l] = drand48();
+    }
+  
+  MyTree tree(cells, Ncells);
+  
+  MyTree::coords *xc = new MyTree::coords[NTRY];
+
+  cout << "Generating seeds..." << endl;
+  for (int k = 0; k < NTRY; k++)
+    {
+      for (int l = 0; l < ND; l++)
+	xc[k][l] = drand48();
+    }
+
+  // Check consistency
+  cout << "Check consistency..." << endl;
+#if 0
+  for (int k = 0; k < NTRY; k++) {
+    MyCell *near = tree.getNearestNeighbour(xc[k]);
+    MyCell *near2 = findNearest(xc[k], cells, Ncells);
+    assert(near == near2);
+  }
+#endif  
+  cout << "Check timing..." << endl;
+  // Check timing
+  clock_t startTimer = clock();
+  
+  for (int k = 0; k < NTRY; k++) {
+    MyCell *near = tree.getNearestNeighbour(xc[k]);
+    near->val = 0;
+  }
+
+  clock_t endTimer = clock();
+  clock_t delta = endTimer-startTimer;
+  double myTime = delta*1.0/CLOCKS_PER_SEC * 1000000.0;
+
+  cout << "KDTree search/sec = " << myTime/NTRY << " us" << endl;
+  
+  startTimer = clock();
+  for (int k = 0; k < NTRY; k++) {
+    MyCell *near = findNearest(xc[k], cells, Ncells);
+  }
+  endTimer = clock();
+  delta = endTimer-startTimer;
+  myTime = delta*1.0/CLOCKS_PER_SEC * 1000000.0;
+
+  cout << "Direct search/sec = " << myTime/NTRY << " us" << endl;
+
+  {
+    ofstream f("kd.dat");
+    tree.saveTree(f);
+  }
+
+  cout << "Trying to reload the tree" << endl;
+  {
+    ifstream f("kd.dat");
+    MyTree tree2(f, cells, Ncells);
+    cout << "Check timing..." << endl;
+    // Check timing
+    clock_t startTimer = clock();
+    
+    for (int k = 0; k < NTRY; k++) {
+      MyCell *near = tree.getNearestNeighbour(xc[k]);
+    }
+    
+    clock_t endTimer = clock();
+    clock_t delta = endTimer-startTimer;
+    double myTime = delta*1.0/CLOCKS_PER_SEC * 1000000.0;
+    
+    cout << "KDTree search/sec = " << myTime/NTRY << " us" << endl;
+  
+  }
+  
+  return 0;
+}
diff --git a/external/cosmotool/sample/testkd2.cpp b/external/cosmotool/sample/testkd2.cpp
new file mode 100644
index 0000000..bc65899
--- /dev/null
+++ b/external/cosmotool/sample/testkd2.cpp
@@ -0,0 +1,128 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#define __KD_TREE_NUMNODES
+#include "mykdtree.hpp"
+#include "kdtree_splitters.hpp"
+
+#define NTRY 10
+#define ND 3
+
+using namespace std;
+using namespace CosmoTool;
+
+typedef KDTree > MyTree;
+//typedef KDTree MyTree;
+typedef KDCell MyCell;
+
+MyCell *findNearest(MyTree::coords& xc, MyCell *cells, uint32_t Ncells)
+{
+    MyCell *near2 = 0;
+    double R2 = INFINITY;
+    for (int i = 0; i < Ncells; i++)
+      {
+	double d2 = 0;
+	for (int j = 0; j < ND; j++)
+	  {
+	    double delta = xc[j]-cells[i].coord[j];
+	    d2 += delta*delta;
+	  }
+	if (d2 < R2)
+	  {
+	    near2 = &cells[i];
+	    R2 = d2;
+	  }
+      }
+    return near2;
+}
+
+int main()
+{
+  uint32_t Ncells = 10000000;
+  MyCell *cells = new MyCell[Ncells];
+  
+  for (int i = 0; i < Ncells; i++)
+    {
+      cells[i].active = true;
+      for (int l = 0; l < ND; l++)
+	cells[i].coord[l] = drand48();
+    }
+  
+  // Check timing
+  clock_t startTimer = clock();
+  MyTree tree(cells, Ncells);
+  clock_t endTimer = clock();
+
+  clock_t delta = endTimer-startTimer;
+  double myTime = delta*1.0/CLOCKS_PER_SEC * 1.0;
+
+  cout << "KDTree build = " << myTime << " s" << endl;
+
+  MyTree::coords *xc = new MyTree::coords[NTRY];
+
+  cout << "Generating seeds..." << endl;
+  for (int k = 0; k < NTRY; k++)
+    {
+      for (int l = 0; l < ND; l++)
+	xc[k][l] = drand48();
+    }
+
+  // Check consistency
+  cout << "Check consistency..." << endl;
+  MyCell **ngb = new MyCell *[12];
+  double *distances = new double[12];
+  
+  ofstream fngb("nearest.txt");
+  for (int k = 0; k < NTRY; k++) {
+    cout << "Seed = " << xc[k][0] << " " << xc[k][1] << " " << xc[k][2] << endl;
+    tree.getNearestNeighbours(xc[k], 12, ngb, distances);
+    int last = -1;
+
+    for (uint32_t i = 0; i < 12; i++)
+      {
+	if (ngb[i] == 0)
+	  continue;
+
+	last = i;
+
+	double d2 = 0;
+	for (int l = 0; l < 3; l++)
+	  d2 += ({double delta = xc[k][l] - ngb[i]->coord[l]; delta*delta;});
+	fngb << ngb[i]->coord[0] << " " << ngb[i]->coord[1] << " " << ngb[i]->coord[2] << " " << sqrt(d2) << endl;
+      }
+    fngb << endl << endl;
+    double farther_dist = distances[last];
+    for (uint32_t i = 0; i < Ncells; i++)
+      {
+         bool found = false;
+         // If the points is not in the list, it means it is farther than the farthest  point
+	 for (int j =0; j < 12; j++)
+          {
+             if (&cells[i] == ngb[j]) {
+               found = true;
+               break;
+             }
+          } 
+	 double dist_to_seed = 0;
+	 for (int l = 0; l < 3; l++)
+	   {
+	     double delta = xc[k][l]-cells[i].coord[l];
+	     dist_to_seed += delta*delta; 
+	   }
+        if (!found) 
+          { 
+	    if (dist_to_seed <= farther_dist)
+	      abort();
+          }
+	else
+	  {
+	    if (dist_to_seed > farther_dist)
+		abort();
+	  }
+      }
+  }
+
+  return 0;
+}
diff --git a/external/cosmotool/src/algo.hpp b/external/cosmotool/src/algo.hpp
new file mode 100644
index 0000000..485db75
--- /dev/null
+++ b/external/cosmotool/src/algo.hpp
@@ -0,0 +1,63 @@
+#ifndef __COSMOTOOL_ALGO_HPP
+#define __COSMOTOOL_ALGO_HPP
+
+#include "config.hpp"
+
+namespace  CosmoTool
+{
+
+  template
+  T cube(T a)
+  {
+    return a*a*a;
+  }
+
+  template
+  T square(T a)
+  {
+    return a*a;
+  }
+
+  template
+  class SPowerBase
+  {
+  public:
+    template
+    static T spower(T a)
+    {
+      if (N<0)
+        {
+          return 1/SPowerBase<-N>::spower(a);
+        }
+      if ((N%2)==0)
+	{
+	  T b = SPowerBase::spower(a);
+	  return b*b;
+	}
+      T b = SPowerBase<(N-1)/2>::spower(a);
+      return a*b*b;
+    }
+  };
+
+  template<>
+  class SPowerBase<0>
+  {
+  public:
+    template
+    static T spower(T a)
+    {
+      return T(1);
+    }
+  };
+
+  template
+  T spower(T a)
+  {
+    return SPowerBase::spower(a);
+  }
+
+
+
+};
+
+#endif
diff --git a/external/cosmotool/src/bqueue.hpp b/external/cosmotool/src/bqueue.hpp
new file mode 100644
index 0000000..792d309
--- /dev/null
+++ b/external/cosmotool/src/bqueue.hpp
@@ -0,0 +1,30 @@
+#ifndef __COSMO_QUEUE_HPP
+#define __COSMO_QUEUE_HPP
+
+#include 
+
+namespace CosmoTool {
+
+  template
+  class BoundedQueue
+  {
+  public:
+    BoundedQueue(int maxSize, QType defaultMax);
+    BoundedQueue(T *pQueue, int maxSize, QType defaultMax);
+    ~BoundedQueue();
+
+    void push(T a, QType v);
+    T *getQueue() { return m_queue; }
+    QType *getPriorities() { return priority; }
+    QType getMaxPriority() { return priority[maxQueueSize-1]; }
+  private:
+    int maxQueueSize;
+    T *m_queue;
+    QType *priority;
+    bool autoFree;
+  };
+};
+
+#include "bqueue.tcc"
+
+#endif
diff --git a/external/cosmotool/src/bqueue.tcc b/external/cosmotool/src/bqueue.tcc
new file mode 100644
index 0000000..892317f
--- /dev/null
+++ b/external/cosmotool/src/bqueue.tcc
@@ -0,0 +1,67 @@
+namespace CosmoTool
+{
+  
+  template
+  BoundedQueue::BoundedQueue(int maxSize, QType defaultVal)
+  {
+    maxQueueSize = maxSize;
+    m_queue = new T[maxSize];
+    priority = new QType[maxSize];
+    autoFree = true;
+
+    for (int i = 0; i < maxSize; i++)
+      priority[i] = defaultVal;
+  }
+ 
+  template
+  BoundedQueue::BoundedQueue(T *pQueue, int maxSize, QType defaultVal)
+  {
+    maxQueueSize = maxSize;
+    m_queue = pQueue;
+    priority = new QType[maxSize];
+    autoFree = false;
+    
+    for (int i = 0; i < maxSize; i++)
+      priority[i] = defaultVal;
+
+    
+  }
+  
+  template
+  BoundedQueue::~BoundedQueue()
+  {
+    if (autoFree)
+      delete[] m_queue;
+    delete[] priority;
+  }
+
+
+  template
+  void BoundedQueue::push(T a, QType v)
+  {
+    if (v > priority[maxQueueSize-1])
+      return;
+
+    int i;
+    for (i = maxQueueSize-2; i >= 0; i--)
+      {
+	if (v > priority[i])
+	  {
+	    priority[i+1] = v;
+	    m_queue[i+1] = a;
+	    return;
+	  }
+	else
+	  {
+	    priority[i+1] = priority[i];
+	    m_queue[i+1] = m_queue[i];
+	  }
+      }
+    if (i < 0)
+      {
+	priority[0] = v;
+	m_queue[0] = a;
+      }
+  }
+   
+};
diff --git a/external/cosmotool/src/bsp_simple.hpp b/external/cosmotool/src/bsp_simple.hpp
new file mode 100644
index 0000000..b61bcf3
--- /dev/null
+++ b/external/cosmotool/src/bsp_simple.hpp
@@ -0,0 +1,167 @@
+#ifndef __COSMOTOOL_SIMPLE_BSP_HPP
+#define __COSMOTOOL_SIMPLE_BSP_HPP
+
+#include 
+#include 
+#include "algo.hpp"
+#include 
+#include 
+
+namespace CosmoTool
+{
+  
+  namespace simple_bsp
+  {
+
+    template
+    struct space
+    {
+      typedef T data_t;
+      typedef PType point_t;
+      typedef PType coord_t[N];
+      static const int dim = N;
+    };
+
+    template
+    struct Plane
+    { 
+      typename SType::coord_t n;
+      typename SType::point_t d;
+    };
+
+    template
+    typename SType::point_t dot_product(const typename SType::coord_t& c1,
+				      const typename SType::coord_t& c2)
+    {
+      typename SType::point_t A = 0;
+
+      for(int j = 0; j < SType::dim; j++)
+	A += c1[j]*c2[j];
+      return A;
+    }
+
+    template
+    struct Node {
+      Plane plane;
+      Node *minus, *plus;
+      typename SType::data_t data;
+    };
+
+    template
+    void normal2(typename SType::coord_t p[2], typename SType::coord_t& n)
+    {
+      typename SType::point_t d;
+      using CosmoTool::square;
+
+      n[0] = p[1][1]-p[0][1];
+      n[1] = -p[1][0]+p[0][0];
+      d = std::sqrt(square(n[0])+square(n[1]));
+      n[0] /= d;
+      n[1] /= d;
+    }
+    
+    template
+    void normal3(typename SType::coord_t p[3], typename SType::coord_t& n)
+    {
+      typename SType::point_t delta0[3] = { p[1][0] - p[0][0], p[1][1] - p[0][1], p[1][2] - p[0][2] };
+      typename SType::point_t delta1[3] = { p[2][0] - p[0][0], p[2][1] - p[0][1], p[2][2] - p[0][2] };
+      typename SType::point_t d;
+      using CosmoTool::square;
+
+      n[0] = delta0[1] * delta1[2] - delta0[2] * delta1[1];
+      n[1] = delta0[2] * delta1[0] - delta0[0] * delta1[2];
+      n[2] = delta0[0] * delta1[1] - delta0[1] * delta1[0];
+      d = std::sqrt(square(n[0]) + square(n[1]) + square(n[2]));
+      n[0] /= d;
+      n[1] /= d;
+      n[2] /= d;
+    }
+    
+
+
+    template
+    struct Facet
+    {
+      typename SType::coord_t p[SType::dim];
+      typename SType::data_t data;
+
+      void center(typename SType::coord_t& c)
+      {
+	for (int j = 0; j < SType::dim; j++)
+	  {
+	    c[j] = 0;
+	    for (int i = 0; i < SType::dim; i++)
+	      {
+		c[j] += p[i][j]; 
+	      }
+	    c[j] /= SType::dim+1;
+	  }
+      }
+      
+      void normal(typename SType::coord_t& n)
+      {
+	if (SType::dim==2)
+	  {
+	    normal2(p, n);
+	    return;
+	  }
+	if (SType::dim == 3)
+	  {
+	    normal3(p, n);
+	    return;
+	  }
+	abort();
+      }
+
+    };
+
+
+    class InvalidPoint: public std::exception
+    {
+    };
+
+    template
+    class BSP
+    {
+    public:
+      typedef space space_t;
+      typedef Plane plane_t;
+      typedef Node node_t;
+
+      node_t *root;
+      std::queue allocated;
+      
+      BSP() throw();
+      ~BSP();
+      void insert(Facet& facet);
+
+      template
+      void insert(PIterator b, PIterator e, T data)
+      {
+	Facet f;
+	int q = 0;
+
+	while (b != e && q < N+1)
+	  {
+	    for (int j = 0; j < N; j++)
+	      f.p[q][j] = (*b)[j];
+	    ++b;
+	    ++q;
+	  }
+	if (q != N)
+	  throw InvalidPoint();
+	
+	f.data = data;
+	insert(f);
+      }
+
+      bool inside(const typename space_t::coord_t& p) const;
+    };
+
+  };
+
+};
+
+#include "bsp_simple.tcc"
+
+#endif
diff --git a/external/cosmotool/src/bsp_simple.tcc b/external/cosmotool/src/bsp_simple.tcc
new file mode 100644
index 0000000..6b74db4
--- /dev/null
+++ b/external/cosmotool/src/bsp_simple.tcc
@@ -0,0 +1,117 @@
+#include 
+#include 
+
+namespace CosmoTool
+{
+
+  namespace simple_bsp
+  {
+    
+    template
+    BSP::BSP()
+     throw()
+    {
+      root = 0;
+    }
+
+    template
+    BSP::~BSP()
+    {
+      while (!allocated.empty())
+	{
+	  node_t *r = allocated.front();
+	      
+	  allocated.pop();
+	  delete r;
+	}
+    }
+
+    template
+    void BSP::insert(Facet& f)
+    {
+      std::list subtrees;
+      std::list leaf_insert;
+
+      if (root != 0)
+	subtrees.push_back(&root);
+      else
+	leaf_insert.push_back(&root);
+
+      // Find the point of insertion. Do not bother to split triangle for this
+      // implementation.
+      while (!subtrees.empty())
+	{
+	  std::list new_subtrees;
+	  typename std::list::iterator iter = subtrees.begin();
+
+	  while (iter != subtrees.end())
+	    {
+	      typename space_t::point_t dp;
+	      bool cond_plus = false, cond_minus = false;
+	      node_t *current = *(*iter);
+	      
+	      for (int j = 0; j < N; j++)
+		{
+		  dp = dot_product(f.p[j],current->plane.n) + current->plane.d;
+		  cond_plus = cond_plus || (dp > 0);
+		  cond_minus = cond_minus || (dp <= 0);
+		}
+	      
+	      bool joint = cond_plus && cond_minus;
+	      bool not_joint = (!cond_plus) && (!cond_minus);
+	      
+	      if (joint || not_joint)
+		{
+		  // Crawl and add another subtree
+		  *iter = &(current->minus);
+		  if (current->plus != 0)
+		    new_subtrees.push_back(¤t->plus);
+		  else
+		    leaf_insert.push_back(¤t->plus);
+		}
+	      else
+		{
+		  if (cond_plus)
+		    *iter = ¤t->plus;
+		  else
+		    *iter = ¤t->minus;
+		}
+	      if (*(*iter) == 0)
+		{
+		  leaf_insert.push_back(*iter);
+		  iter = subtrees.erase(iter);
+		}
+	      else
+		++iter;
+	    }
+	  if (!new_subtrees.empty())
+	    subtrees.splice(subtrees.end(), new_subtrees);
+	}
+      
+      node_t * current = new node_t;
+      f.normal(current->plane.n);
+      current->plane.d = -dot_product((f.p[0]),current->plane.n);
+      
+      for (typename std::list::iterator i = leaf_insert.begin();
+	   i != leaf_insert.end();
+	   ++i)
+	*(*i) = current;
+
+      allocated.push(current);
+    }
+
+    template
+    bool BSP::inside(const typename space_t::coord_t& p) const
+    {
+      node_t *current = root;
+
+      do
+	{
+	}
+      while();
+      current
+    }
+
+  };
+
+};
diff --git a/external/cosmotool/src/cic.cpp b/external/cosmotool/src/cic.cpp
new file mode 100644
index 0000000..71a106d
--- /dev/null
+++ b/external/cosmotool/src/cic.cpp
@@ -0,0 +1,205 @@
+#include 
+#include 
+#include 
+#include "cic.hpp"
+
+CICFilter::CICFilter(uint32_t N, double len)
+{
+  spatialLen = len;
+  szGrid = N;
+  totalSize = N*N*N;
+  densityGrid = new CICType[totalSize];
+  resetMesh();
+}
+
+CICFilter::~CICFilter()
+{
+  delete[] densityGrid;
+}
+  
+void CICFilter::resetMesh()
+{
+  for (uint32_t i = 0; i < totalSize; i++)
+    densityGrid[i] = 0;
+}
+
+void CICFilter::putParticles(CICParticles *particles, uint32_t N)
+{
+#if 0
+  uint32_t numCorners = 1 << NUMDIMS;
+
+  for (uint32_t i = 0; i < N; i++)
+    {
+      Coordinates xyz;
+      int32_t ixyz[NUMDIMS];
+      int32_t rxyz[NUMDIMS];
+      CICType alpha[NUMDIMS];
+      CICType beta[NUMDIMS];
+      for (int j = 0; j < NUMDIMS; j++)
+	{
+	  xyz[j] = (particles[i].coords[j] / spatialLen * szGrid);
+	  ixyz[j] = (int32_t)floor(xyz[j] - 0.5);
+	  beta[j] = xyz[j] - ixyz[j] - 0.5;
+	  alpha[j] = 1 - beta[j];
+	  if (ixyz[j] < 0)
+	    ixyz[j] = szGrid-1;
+	}
+
+      CICType tot_mass = 0;
+      for (int j = 0; j < numCorners; j++)
+	{
+	  CICType rel_mass = 1;
+	  uint32_t idx = 0;
+	  uint32_t mul = 1;
+	  uint32_t mul2 = 1;
+
+	  for (int k = 0; k < NUMDIMS; k++)
+	    {
+	      uint32_t ipos = ((j & mul2) != 0);
+
+	      if (ipos == 1)
+		{
+		  rel_mass *= beta[k];
+		}
+	      else
+		{
+		  rel_mass *= alpha[k];
+		}
+
+	      rxyz[k] = ixyz[k] + ipos;
+	      
+	      if (rxyz[k] >= szGrid)
+		idx += (rxyz[k] - szGrid) * mul;
+	      else
+		idx += rxyz[k] * mul;
+
+	      mul2 *= 2;
+	      mul *= szGrid;
+	    }
+
+	  assert(rel_mass > 0);
+	  assert(rel_mass < 1);
+	  assert(idx < totalSize);
+	  densityGrid[idx] += rel_mass * particles[i].mass;
+	  tot_mass += rel_mass;
+	}
+      assert(tot_mass < 1.1);
+      assert(tot_mass > 0.9);
+    }
+#endif
+#if 0  
+  for (uint32_t i = 0; i < N; i++)
+    {
+      Coordinates xyz;
+      int32_t ixyz[NUMDIMS];
+      for (int j = 0; j < NUMDIMS; j++)
+	{
+	  xyz[j] = (particles[i].coords[j] / spatialLen * szGrid);
+	  ixyz[j] = (int32_t)round(xyz[j] - 0.5);
+	  if (ixyz[j] < 0)
+	    ixyz[j] = szGrid-1;
+	  else if (ixyz[j] >= szGrid)
+	    ixyz[j] = 0;
+	}
+
+      uint32_t idx = ixyz[0] + ixyz[1] * szGrid + ixyz[2] * szGrid * szGrid;
+      densityGrid[idx] += particles[i].mass;      
+    }
+
+#endif
+
+  for (uint32_t i = 0; i < N; i++)
+    {
+      CICType x, y, z;
+      int32_t ix, iy, iz;
+      int32_t ix2, iy2, iz2;
+
+      x = particles[i].coords[0] / spatialLen * szGrid + 0.5;
+      y = particles[i].coords[1] / spatialLen * szGrid + 0.5;
+      z = particles[i].coords[2] / spatialLen * szGrid + 0.5;
+
+      if (x < 0)
+	x += szGrid;
+      if (y < 0)
+	y += szGrid;
+      if (z < 0)
+	z += szGrid;
+
+      ix = ((int32_t)floor(x));
+      iy = ((int32_t)floor(y));
+      iz = ((int32_t)floor(z));
+      
+      ix2 = (ix + 1) % szGrid;
+      iy2 = (iy + 1) % szGrid;
+      iz2 = (iz + 1) % szGrid;
+
+      CICType alpha_x = x - ix;
+      CICType alpha_y = y - iy;
+      CICType alpha_z = z - iz;
+
+      ix %= szGrid;
+      iy %= szGrid;
+      iz %= szGrid;
+      
+      assert(alpha_x >= 0);
+      assert(alpha_y >= 0);
+      assert(alpha_z >= 0);
+
+      CICType beta_x = 1 - alpha_x;
+      CICType beta_y = 1 - alpha_y;
+      CICType beta_z = 1 - alpha_z;
+
+      assert(beta_x >= 0);
+      assert(beta_y >= 0);
+      assert(beta_z >= 0);
+
+      CICType mass = particles[i].mass;
+      uint32_t idx;
+
+      // 000
+      idx = ix + (iy + iz * szGrid) * szGrid;
+      densityGrid[idx] +=
+	mass * beta_x * beta_y * beta_z;
+      
+      // 100
+      idx = ix2 + (iy + iz * szGrid) * szGrid;
+      densityGrid[idx] +=
+	mass * alpha_x * beta_y * beta_z;
+      
+      // 010
+      idx = ix + (iy2 + iz * szGrid) * szGrid;
+      densityGrid[idx] +=
+	mass * beta_x * alpha_y * beta_z;
+      
+      // 110
+      idx = ix2 + (iy2 + iz * szGrid) * szGrid;
+      densityGrid[idx] +=
+	mass * alpha_x * alpha_y * beta_z;
+
+      // 001
+      idx = ix + (iy + iz2 * szGrid) * szGrid;
+      densityGrid[idx] +=
+	mass * beta_x * beta_y * alpha_z;
+
+      // 101
+      idx = ix2 + (iy + iz2 * szGrid) * szGrid;
+      densityGrid[idx] +=
+	mass * alpha_x * beta_y * alpha_z;
+
+      // 011
+      idx = ix + (iy2 + iz2 * szGrid) * szGrid;
+      densityGrid[idx] +=
+	mass * beta_x * alpha_y * alpha_z;
+
+      // 111
+      idx = ix2 + (iy2 + iz2 * szGrid) * szGrid;
+      densityGrid[idx] +=
+	mass * alpha_x * alpha_y * alpha_z;
+    }
+}
+  
+void CICFilter::getDensityField(CICType*& field, uint32_t& res)
+{
+  field = densityGrid;
+  res = totalSize;
+}
diff --git a/external/cosmotool/src/cic.hpp b/external/cosmotool/src/cic.hpp
new file mode 100644
index 0000000..d522d00
--- /dev/null
+++ b/external/cosmotool/src/cic.hpp
@@ -0,0 +1,35 @@
+#ifndef __CICFILTER_HPP
+#define __CICFILTER_HPP
+
+#include "CosmoTool/config.hpp"
+#include 
+
+using namespace CosmoTool;
+
+typedef float CICType;
+
+  typedef struct
+  {
+    float mass;
+    Coordinates coords;
+  } CICParticles;
+
+  class CICFilter
+  {
+  public:
+    CICFilter(uint32_t resolution, double spatialLen);
+    ~CICFilter();
+
+    void resetMesh();
+    void putParticles(CICParticles *particles, uint32_t N);    
+
+    void getDensityField(CICType*& field, uint32_t& res);
+
+  protected:
+    CICType *densityGrid;
+    double spatialLen;
+    uint32_t totalSize;
+    uint32_t szGrid;
+  };
+
+#endif
diff --git a/external/cosmotool/src/config.hpp b/external/cosmotool/src/config.hpp
new file mode 100644
index 0000000..b046e2f
--- /dev/null
+++ b/external/cosmotool/src/config.hpp
@@ -0,0 +1,134 @@
+#ifndef __COSMOTOOL_CONFIG_HPP
+#define __COSMOTOOL_CONFIG_HPP
+
+#include 
+#include 
+#include 
+#include 
+
+namespace CosmoTool
+{
+
+#define NUMDIMS 3
+#define NUMCUBES 8
+
+  /**
+   * Base type to specity at what precision we
+   * must achieve computations.
+   */
+  typedef double ComputePrecision;
+  /**
+   * Coordinate type (should be a 3-array).
+   */
+  typedef double Coordinates[NUMDIMS];
+
+  /* 
+   * Single precision coordinates.
+   */
+  typedef float FCoordinates[NUMDIMS];
+  
+  /**
+   * This function is used whenever one needs a general
+   * conversion between mass and luminosity (or the opposite).
+   * It should take a "mass" (or luminosity) in input, a unit is
+   * given to convert this mass into solar units. The output should
+   * be the "luminosity" (or mass), in solar units.
+   */
+  typedef double (*BiasFunction)(double mass, double unit);
+  
+  /**
+   * Function to copy the coordinates "a" into "b".
+   */
+  inline void copyCoordinates(const Coordinates& a, Coordinates& b)
+  {
+    memcpy(b, a, sizeof(a));
+  }
+
+  /**
+   * Base exception class for all exceptions handled by
+   * this library.
+   */
+  class Exception : public std::exception
+  {
+  public:
+    Exception(const std::string& mess)
+      : msg(mess), msgok(true) {}
+    Exception()
+      : msgok(false) {}
+
+    virtual ~Exception() throw () {}
+
+    const char *getMessage() const { return msgok ? msg.c_str() : "No message"; };
+    virtual const char *what() const throw () { return msgok ? msg.c_str() : "What 'what' ?"; };
+
+  private:
+    std::string msg;
+    bool msgok;
+  };
+
+  /**
+   * Exception raised when an invalid argument has been
+   * passed to a function of the library.
+   */
+  class InvalidArgumentException : public Exception
+  {
+  public:
+    InvalidArgumentException(const std::string& mess)
+      : Exception(mess) {}
+    InvalidArgumentException()
+      : Exception() {}
+  };
+
+  /**
+   */
+  class InvalidRangeException : public Exception
+  {
+  public:
+    InvalidRangeException(const std::string& mess)
+      : Exception(mess) {}
+    InvalidRangeException()
+      : Exception() {}
+  };
+  
+  /**
+   */
+  class NoSuchFileException : public Exception
+  {
+  public:
+    NoSuchFileException(const std::string& mess)
+      : Exception(mess) {}
+    NoSuchFileException()
+      : Exception() {}
+  };
+
+  /**
+   */
+  class InvalidFileFormatException : public Exception
+  {
+  public:
+    InvalidFileFormatException(const std::string& mess)
+      : Exception(mess) {}
+    InvalidFileFormatException()
+      : Exception() {}
+  };
+
+  class EndOfFileException: public Exception
+  {
+  public:
+    EndOfFileException(const std::string& mess)
+      : Exception(mess) {}
+    EndOfFileException()
+      : Exception() {}
+  };
+
+  class FilesystemFullException: public Exception
+  {
+  public:
+    FilesystemFullException(const std::string& mess)
+      : Exception(mess) {}
+    FilesystemFullException()
+      : Exception() {}
+  };
+};
+
+#endif
diff --git a/external/cosmotool/src/dinterpolate.hpp b/external/cosmotool/src/dinterpolate.hpp
new file mode 100644
index 0000000..acbe3f7
--- /dev/null
+++ b/external/cosmotool/src/dinterpolate.hpp
@@ -0,0 +1,94 @@
+#ifndef __COSMO_DINTERPOLATE_HPP
+#define __COSMO_DINTERPOLATE_HPP
+
+#include "config.hpp"
+#include "mykdtree.hpp"
+#include "kdtree_splitters.hpp"
+#include 
+
+namespace CosmoTool {
+ 
+  template
+  class DelaunayInterpolate {
+  public:
+    struct SimplexAccess {
+      int32_t *simplex_list;      
+    };
+ 
+    typedef KDTree > QuickTree;
+    typedef typename QuickTree::Cell QuickCell;
+    typedef PType CoordType[N];
+    
+    QuickTree *quickAccess;
+    QuickCell *cells;
+    PType *all_preweight;
+    int32_t *point_to_simplex_list_base;    
+    IType *values;
+    CoordType *positions;    
+    uint32_t numPoints;
+    uint32_t numSimplex;
+    uint32_t *simplex_list;
+    gsl_eigen_symmv_workspace *eigen_work;
+    bool *disable_simplex;
+    
+    /**
+     * This construct the interpolator. The construction is time consuming so
+     * please do it the less possible number of times, especially if you have
+     * a large number of points.
+     *
+     * @param positions list of the positions
+     * @param values list of the values taken at each position
+     * @param simplex_list list of points for each simplex. The packing
+     * is the following:
+     * [t(0,1),t(0,2),...,t(0,n+1),t(1,0),t(1,1),...,t(1,n+1),..],
+     * with t(i,j) the i-th simplex and j-th point of the simplex. The indexes
+     * refer to the previous list of points.
+     * @param numPoints the number of points
+     */
+    DelaunayInterpolate(CoordType *positions, IType *values, uint32_t *simplex_list,
+			uint32_t numPoints, uint32_t numSimplex)
+	throw (InvalidArgumentException)
+    {
+      this->positions = positions;
+      this->values = values;
+      this->simplex_list = simplex_list;
+      this->numPoints = numPoints;
+      this->numSimplex = numSimplex;
+      this->disable_simplex = new bool[numSimplex];
+      
+      buildPreweight();
+      buildQuickAccess();
+
+      eigen_work = gsl_eigen_symmv_alloc(N);
+
+    }
+    
+    ~DelaunayInterpolate()
+    {
+      delete[] cells;
+      delete quickAccess;
+      delete[] point_to_simplex_list_base;      
+      delete[] all_preweight;
+      delete[] disable_simplex;
+
+      gsl_eigen_symmv_free(eigen_work);
+    }
+
+    void buildPreweight()
+	throw (InvalidArgumentException);
+    void buildQuickAccess();
+    void buildHyperplane(const PType *v, CoordType& hyper);
+
+    bool checkPointInSimplex(const CoordType& pos, uint32_t simplex);
+    
+    uint32_t findSimplex(const CoordType& pos)
+      throw (InvalidArgumentException);
+
+    IType computeValue(const CoordType& pos)
+      throw (InvalidArgumentException);
+  };
+};
+
+#include "dinterpolate.tcc"
+
+#endif
diff --git a/external/cosmotool/src/dinterpolate.tcc b/external/cosmotool/src/dinterpolate.tcc
new file mode 100644
index 0000000..e0cf763
--- /dev/null
+++ b/external/cosmotool/src/dinterpolate.tcc
@@ -0,0 +1,339 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace CosmoTool {
+
+
+  template
+  void DelaunayInterpolate::buildQuickAccess()
+  {
+    cells = new QuickCell[numPoints];
+
+    uint32_t point_to_simplex_size = 0;
+    uint32_t *numSimplex_by_point = new uint32_t[numPoints];
+    uint32_t *index_by_point = new uint32_t[numPoints];
+
+    // First count the number of simplex for each point
+    for (uint32_t i = 0; i < numPoints; i++)
+      index_by_point[i] = numSimplex_by_point[i] = 0;
+    for (uint32_t i = 0; i < (N+1)*numSimplex; i++)
+      {
+	assert(simplex_list[i] < numPoints);
+	if (!disable_simplex[i/(N+1)])
+	  numSimplex_by_point[simplex_list[i]]++;
+      }
+
+    // Compute the total number and the index for accessing lists.
+    for (uint32_t i = 0; i < numPoints; i++)
+      {
+	index_by_point[i] = point_to_simplex_size;
+	point_to_simplex_size += numSimplex_by_point[i]+1;
+      }
+
+    // Now compute the real list.
+    point_to_simplex_list_base = new int32_t[point_to_simplex_size];
+    for (uint32_t i = 0; i < numSimplex; i++)
+      {
+	for (int j = 0; j <= N; j++)
+	  {
+	    uint32_t s = (N+1)*i+j;
+	    if (disable_simplex[i])
+	      continue;
+
+	    uint32_t p = simplex_list[s];
+	    assert(index_by_point[p] < point_to_simplex_size);
+	    point_to_simplex_list_base[index_by_point[p]] = i;
+	    ++index_by_point[p];
+	  }
+      }
+
+    // Finish the lists
+    for (uint32_t i = 0; i < numPoints; i++)
+      {
+	// check assertion
+	assert((i==0 && index_by_point[0]==numSimplex_by_point[0]) 
+	       || 
+	       ((index_by_point[i]-index_by_point[i-1]) == (numSimplex_by_point[i]+1)));
+	assert(index_by_point[i] < point_to_simplex_size);
+	point_to_simplex_list_base[index_by_point[i]] = -1;
+      }
+    
+    uint32_t idx = 0;
+    for (uint32_t i = 0; i < numPoints; i++)
+      {
+	cells[i].active = true;
+	cells[i].val.simplex_list = &point_to_simplex_list_base[idx];
+	// We may have to cast here.
+	for (int j = 0; j < N; j++)
+	  cells[i].coord[j] = positions[i][j];
+
+	idx += numSimplex_by_point[i]+1;
+      }
+
+    // Free the memory allocated for temporary arrays.
+    delete[] numSimplex_by_point;
+    delete[] index_by_point;
+
+    // Build the kd tree now.
+    quickAccess = new QuickTree(cells, numPoints);
+  }
+
+  template
+  void DelaunayInterpolate::buildPreweight()
+	throw(InvalidArgumentException)
+  {
+    double preweight[N*N];
+    double preweight_inverse[N*N];
+    gsl_permutation *p = gsl_permutation_alloc(N);
+    uint32_t numDisabled = 0;
+
+    all_preweight = new PType[N*N*numSimplex];
+
+    for (uint32_t i = 0; i < numSimplex; i++)     
+      {
+	uint32_t base = i*(N+1);
+	uint32_t pref = simplex_list[base];
+	// Compute the forward matrix first.
+	for (int j = 0; j < N; j++)
+	  {
+	    PType xref = positions[pref][j];
+
+	    for (int k = 0; k < N; k++)
+	      {
+		preweight[j*N + k] = positions[simplex_list[k+base+1]][j] - xref;
+	      }
+	  }
+
+	gsl_matrix_view M = gsl_matrix_view_array(preweight, N, N);
+	gsl_matrix_view iM = gsl_matrix_view_array(preweight_inverse, N, N);
+	int signum;
+
+	gsl_linalg_LU_decomp(&M.matrix, p, &signum);
+        double a = fabs(gsl_linalg_LU_det(&M.matrix, signum));
+	if (a < 1e-10)
+         {
+#ifdef DEBUG
+           for (int j = 0; j < N; j++)
+            {
+              PType xref = positions[pref][j];
+
+              for (int k = 0; k < N; k++)
+                {
+                  preweight[j*N + k] = positions[simplex_list[k+base+1]][j] - xref;
+                }
+            }
+           std::ofstream f("matrix.txt");
+           for (int j = 0; j < N*N; j++)
+             f << std::setprecision(12) << preweight[j] << std::endl;
+	   throw InvalidArgumentException("Invalid tesselation. One simplex is coplanar.");
+#else
+           gsl_matrix_set_zero(&iM.matrix);
+	   disable_simplex[i] = true;
+	   numDisabled++;
+#endif
+         }
+        else {
+	  gsl_linalg_LU_invert(&M.matrix, p, &iM.matrix);
+	  disable_simplex[i] = false;
+        }
+ 
+	for (int j = 0; j < N*N; j++)
+	  all_preweight[N*N*i + j] = preweight_inverse[j];
+      }
+
+    std::cout << "Number of disabled simplices: " << numDisabled << std::endl;
+
+    gsl_permutation_free(p);
+  }
+
+  template
+  void DelaunayInterpolate::buildHyperplane(const PType *v, CoordType& hyper)
+  {
+    double M[N][N], eVal[N], eVec[N][N];
+    gsl_matrix_view mM, evec;
+    gsl_vector_view eval;
+
+    // Construct the symmetric matrix
+    for (int k = 0; k < N; k++)
+      for (int l = k; l < N; l++)
+	{
+	  double val = 0;
+
+	  for (int i = 0; i < (N-1); i++)
+	    {
+	      val += v[i*N+l] * v[i*N+k];
+	    }
+	  M[l][k] = M[k][l] = val;
+	}
+
+    mM = gsl_matrix_view_array(&M[0][0], N, N);
+    evec = gsl_matrix_view_array(&eVec[0][0], N, N);
+    eval = gsl_vector_view_array(&eVal[0], N);
+
+    // Solve the eigensystem
+    gsl_eigen_symmv (&mM.matrix, &eval.vector, &evec.matrix, eigen_work);
+    
+    double minLambda = INFINITY;
+    uint32_t idx = N+1;
+
+    // Look for the smallest eigenvalue
+    for (int k = 0; k < N; k++)
+      {
+	if (minLambda > eVal[k])
+	  {
+	    minLambda = eVal[k];
+	    idx = k;
+	  }
+      }
+    assert(idx != (N+1));
+
+    // Copy the corresponding vector
+    for (int k = 0; k < N; k++)
+      {
+	hyper[k] = eVec[k][idx];
+      }
+  }
+
+  template
+  bool DelaunayInterpolate::checkPointInSimplex(const CoordType& pos, uint32_t simplex)
+  {
+    if (disable_simplex[simplex])
+      return false;
+
+    uint32_t *desc_simplex = &simplex_list[simplex*(N+1)];
+    CoordType *p[N+1], v[N], hyper;
+
+    for (int k = 0; k <= N; k++)
+      p[k] = &positions[desc_simplex[k]];
+
+
+    for (int i = 0; i <= N; i++)
+      {
+	// Build vectors 
+	for (int k = 1; k <= N; k++)
+	  for (int l = 0; l < N; l++)
+	    v[k-1][l] = (*p[k])[l] - (*p[0])[l];
+
+	// Build hyperplane.
+	buildHyperplane(&v[0][0], hyper);
+       
+	// Compute the appropriate sign using the last point.
+	PType sign = 0;
+	for (int k = 0; k < N; k++)
+	  sign += hyper[k] * v[N-1][k];
+
+	// Now check the point has the same sign;
+	PType pnt_sign = 0;
+	for (int k = 0; k < N; k++)
+	  pnt_sign += hyper[k] * (pos[k] - (*p[0])[k]);
+
+	if (pnt_sign*sign < 0)
+	  return false;
+
+
+	// Rotate the points.
+	for (int k = 1; k <= N; k++)
+	  {
+	    p[k-1] = p[k];
+	  }
+	p[N] = &positions[desc_simplex[i]];
+      }
+
+    // We checked all possibilities. Return now.
+    return true;
+  }
+
+
+  template
+  uint32_t DelaunayInterpolate::findSimplex(const CoordType& c)
+    throw (InvalidArgumentException)
+  {
+    uint32_t N_ngb = 1;
+    QuickCell **cell_Ngb = new QuickCell *[N_ngb];
+    typename QuickTree::coords kdc;
+
+    for (int i = 0; i < N; i++)
+       kdc[i] = c[i];
+
+    // It may happen that we are unlucky and have to iterate to farther
+    // neighbors. It is bound to happen, especially on the boundaries.
+    do
+      {
+	uint32_t i;
+
+	quickAccess->getNearestNeighbours(kdc, N_ngb, cell_Ngb);
+	
+	for (i = 0; i < N_ngb && cell_Ngb[i] != 0; i++)
+	  {
+	    int32_t *simplex_list = cell_Ngb[i]->val.simplex_list;
+	    uint32_t j = 0;
+
+	    while (simplex_list[j] >= 0)
+	      {
+		if (checkPointInSimplex(c, simplex_list[j]))
+		  {
+		    delete[] cell_Ngb;
+		    return simplex_list[j];
+		  }
+	        ++j;
+	      }
+	  }	
+	delete[] cell_Ngb;
+
+	// The point does not belong to any simplex.
+	if (i != N_ngb)
+	  throw InvalidArgumentException("the given point does not belong to any simplex");
+
+	N_ngb *= 2;
+	cell_Ngb = new QuickCell *[N_ngb];
+      }
+    while (1);
+
+    // Point not reached.
+    abort();
+    return 0;
+  }
+
+  template
+  IType DelaunayInterpolate::computeValue(const CoordType& c)
+    throw (InvalidArgumentException)
+  {
+    uint32_t simplex = findSimplex(c);
+    PType *preweight = &all_preweight[simplex*N*N];
+    PType weight[N+1];
+    PType p0[N];
+    PType sum_weight = 0;
+
+    for (int i = 0; i < N; i++)
+      p0[i] = positions[simplex_list[simplex*(N+1) + 0]][i];
+
+    // Now we use the preweight to compute the weight...
+    for (int i = 1; i <= N; i++)
+      {
+	weight[i] = 0;
+	for (int j = 0; j < N; j++)
+	  weight[i] += preweight[(i-1)*N+j]*(c[j]-p0[j]);
+
+	assert(weight[i] > -1e-7);
+	assert(weight[i] < 1+1e-7);
+	sum_weight += weight[i];
+      }   
+    weight[0] = 1-sum_weight;
+    assert(weight[0] > -1e-7);
+    assert(weight[0] < (1+1e-7));
+    
+    // We compute the final value by weighing the value at the N+1
+    // points by the proper weight.
+    IType final = 0;
+    for (int i = 0; i <= N; i++)
+      final += weight[i] * values[ simplex_list[simplex*(N+1) + i] ];
+
+    return final;
+  }
+  
+};
diff --git a/external/cosmotool/src/eskow.hpp b/external/cosmotool/src/eskow.hpp
new file mode 100644
index 0000000..ac9b292
--- /dev/null
+++ b/external/cosmotool/src/eskow.hpp
@@ -0,0 +1,271 @@
+#ifndef __ESKOW_CHOLESKY_HPP
+#define __ESKOW_CHOLESKY_HPP
+
+#include 
+#include 
+#include "mach.hpp"
+
+/* Implementation of Schnabel & Eskow, 1999, Vol. 9, No. 4, pp. 1135-148, SIAM J. OPTIM. */
+
+template
+class CholeskyEskow
+{
+private:
+  static const bool verbose_eskow = true;
+  T tau, tau_bar, mu;
+
+  void print_matrix(A& m, int N)
+  {
+    using std::cout;
+    using std::endl;
+    using std::setprecision;
+
+    if (verbose_eskow)
+      {
+
+	for (int i = 0; i < N; i++)
+	  {
+	    for (int j = 0; j < N; j++)
+	      {
+		cout.width(6);
+		cout << setprecision(5) << m(i,j) << " ";
+	      }
+	    cout << endl;
+	  }
+	cout << endl;
+      }
+  }
+
+  T max_diag(A& m, int j, int N)
+  {
+    T maxval = std::abs(m(j,j));
+
+    for (int k = j+1; k < N; k++)
+      {
+	maxval = std::max(maxval, std::abs(m(k,k)));
+      }
+    return maxval;
+  }
+
+  void minmax_diag(A& m, int j, int N, T& minval, T& maxval, int& i_min, int& i_max)
+  {
+    i_min = i_max = j;
+    minval = maxval = m(j,j);
+
+    for (int k = j+1; k < N; k++)
+      {
+	maxval = std::max(maxval, m(k,k));
+	minval = std::min(minval, m(k,k));
+      }
+  
+    for (int k = j; k < N; k++)
+      {
+	if (m(k,k) == minval && i_min < 0)
+	  i_min = k;
+	if (m(k,k) == maxval && i_max < 0)
+	  i_max = k;
+      }
+  }
+
+  void swap_rows(A& m, int N, int i0, int i1)
+  {
+    for (int r = 0; r < N; r++)
+      std::swap(m(r,i0), m(r,i1));
+  }
+
+  void swap_cols(A& m, int N, int i0, int i1)
+  {
+    for (int c = 0; c < N; c++)
+      std::swap(m(i0,c), m(i1,c));
+  }
+
+  T square(T x)
+  {
+    return x*x;
+  }
+
+  T min_row(A& m, int j, int N)
+  {
+    T a = 1/m(j,j);
+    T v = m(j+1,j+1) - square(m(j+1,j))*a;
+    
+    for (int i = j+2; i < N; i++)
+      {
+	v = std::min(v, m(i, i) - square(m(i,j))*a);
+      }
+
+    return v;
+  }
+
+  int g_max(const std::vector& g, int j, int N)
+  {
+    T a = g[j];
+    int k = j;
+
+    for (int i = j+1; i < N; i++)
+      {
+	if (a < g[i])
+	  {
+	    a = g[i];
+	    k = i;
+	  }
+      }
+    return k;
+  }
+
+public:
+  CholeskyEskow()
+  {
+    tau = std::pow(mach_epsilon(), 1./3);
+    tau_bar = std::pow(mach_epsilon(), 2./3);
+    mu=0.1;
+  }
+
+  void cholesky(A& m, int N, T& norm_E)
+  {
+    bool phaseone = true;
+    T gamma = max_diag(m, 0, N);
+    int j;
+
+    norm_E = 0;
+    
+    for (j = 0; j < N && phaseone; j++)
+      {
+	T minval, maxval;
+	int i_min, i_max;
+	
+	print_matrix(m, N);
+     
+	minmax_diag(m, j, N, minval, maxval, i_min, i_max);
+	if (maxval < tau_bar*gamma || minval < -mu*maxval)
+	  {
+	    phaseone = false;
+	    break;
+	  }
+      
+	if (i_max != j)	  
+	  {
+	    std::cout << "Have to swap i=" << i_max << " and j=" << j << std::endl;
+	    swap_cols(m, N, i_max, j);
+	    swap_rows(m, N, i_max, j);
+
+	  }
+      
+	if (min_row(m, j, N) < -mu*gamma)
+	  {
+	    phaseone = false;
+	    break;
+	  }
+      
+	T L_jj = std::sqrt(m(j,j));
+
+	m(j,j) = L_jj;
+	for (int i = j+1; i < N; i++)
+	  {
+	    m(i,j) /= L_jj;
+	    for (int k = j+1; k <= i; k++)
+	      m(i,k) -= m(i,j)*m(k,j);
+	  }
+      }
+
+
+    if (!phaseone && j == N-1)
+      {
+	T A_nn = m(N-1,N-1);
+	T delta = -A_nn + std::max(tau*(-A_nn)/(1-tau), tau_bar*gamma);
+      
+	m(N-1,N-1) = std::sqrt(m(N-1,N-1) + delta);	  	  
+      }
+
+    
+
+    if (!phaseone && j < (N-1))
+      {
+	std::cout << "Phase two ! (j=" << j << ")" << std::endl;
+
+	int k = j-1;
+	std::vector g(N);
+
+	for (int i = k+1; i < N; i++)
+	  {
+	    g[i] = m(i,i);
+	    for (int j = k+1; j < i; j++)
+	      g[i] -= std::abs(m(i,j));
+	    for (int j = i+1; j < N; j++)
+	      g[i] -= std::abs(m(j,i));
+	  }
+
+	T delta, delta_prev = 0;
+	
+	for (int j = k+1; j < N-2; j++)
+	  {
+	    int i = g_max(g, j, N);
+	    T norm_j;
+
+	    print_matrix(m, N);
+
+	    if (i != j)
+	      {
+		swap_cols(m, N, i, j);
+		swap_rows(m, N, i, j);		
+	      }
+
+
+	    for (int i = j+1; j < N; j++)
+	      {
+		norm_j += std::abs(m(i,j));
+	      }
+	    
+	    delta = std::max(delta_prev, std::max((T)0, -m(j,j) + std::max(norm_j,tau_bar*gamma)));
+	    if (delta > 0)
+	      {
+		m(j,j) += delta;
+		delta_prev = delta;
+	      }
+	    
+	    if (m(j,j) != norm_j)
+	      {
+		T temp = 1 - norm_j/m(j,j);
+		
+		for (int i = j+1; j < N; j++)
+		  {
+		    g[i] += std::abs(m(i,j))*temp;
+		  }
+	      }
+
+	    // Now we do the classic cholesky iteration
+	    T L_jj = std::sqrt(m(j,j));
+	    
+	    m(j,j) = L_jj;
+	    for (int i = j+1; i < N; i++)
+	      {
+		m(i,j) /= L_jj;
+		for (int k = j+1; k <= i; k++)
+		  m(i,k) -= m(i,j)*m(k,j);
+	      }
+	  }
+
+	// The final 2x2 submatrix is special
+	T A00 = m(N-2, N-2), A01 = m(N-2, N-1), A11 = m(N-1,N-1);
+	T sq_DELTA = std::sqrt(square(A00-A11) + square(A01));
+	T lambda_hi = 0.5*((A00+A11) + sq_DELTA);
+	T lambda_lo = 0.5*((A00+A11) - sq_DELTA);
+       
+	delta = std::max(std::max((T)0, -lambda_lo + std::max(tau*sq_DELTA/(1-tau), tau_bar*gamma)),delta_prev);
+	if (delta > 0)
+	  {
+	    m(N-1,N-1) += delta;
+	    m(N,N) += delta;
+	    delta_prev = delta;
+	  }
+	m(N-2,N-2) = A00 = std::sqrt(A00);
+	m(N-1,N-2) = (A01 /= A00);
+	m(N-1,N-1) = std::sqrt(A11-A01*A01);
+	norm_E = delta_prev;
+      }
+  }
+
+};
+
+
+#endif
diff --git a/external/cosmotool/src/field.hpp b/external/cosmotool/src/field.hpp
new file mode 100644
index 0000000..25ec447
--- /dev/null
+++ b/external/cosmotool/src/field.hpp
@@ -0,0 +1,83 @@
+#ifndef __COSMOTOOL_FIELD
+#define __COSMOTOOL_FIELD
+
+#include "config.hpp"
+#include 
+#include 
+
+namespace CosmoTool {
+
+  template
+  struct ScalarField
+  {
+    BaseType value;    
+  };
+
+  template
+  struct VectorField
+  {
+    BaseType vec[N];
+    
+    VectorField& operator=(const VectorField& a)
+    {
+      for (int i = 0; i < N; i++)
+	vec[i] = a.vec[i];
+      return *this;
+    }
+
+    VectorField()
+    {
+      for (int i = 0; i < N; i++)
+	vec[i] = 0;      
+    }
+
+    VectorField(double a)
+    {
+      assert(a == 0);
+      for (int i = 0; i < N; i++)
+	vec[i] = 0;      
+    }
+  };
+
+  template
+  VectorField operator*(BaseType s, const VectorField& a)
+  {
+    VectorField v;
+
+    for (int i = 0; i < N; i++)
+      v.vec[i] = a.vec[i]*s;
+    
+    return v;
+  }  
+  
+  template
+  VectorField operator+(const VectorField& a, const VectorField& b)
+  {
+    VectorField v;
+
+    for (int i = 0; i < N; i++)
+      v.vec[i] = a.vec[i]+b.vec[i];
+    
+    return v;
+  }  
+
+  template
+  VectorField& operator+=(VectorField& a, const VectorField& b)
+  {
+    for (int i = 0; i < N; i++)
+      a.vec[i]+=b.vec[i];
+    
+    return a;
+  }  
+
+};
+
+template
+std::ostream& operator<<(std::ostream& s, const CosmoTool::VectorField& a)
+{
+  for (int i = 0; i < N; i++)
+    s << a.vec[i] << " " ;
+  return s;
+}
+
+#endif
diff --git a/external/cosmotool/src/fixArray.hpp b/external/cosmotool/src/fixArray.hpp
new file mode 100644
index 0000000..d1516e4
--- /dev/null
+++ b/external/cosmotool/src/fixArray.hpp
@@ -0,0 +1,40 @@
+#ifndef __FIX_ARRAY_HPP
+#define __FIX_ARRAY_HPP
+
+namespace CosmoTool
+{
+
+  template  class fixArray
+  {
+  private:
+    T d[sz];
+    
+  public:
+    /*! Returns the size of the array. */
+    long size() const { return sz; }
+
+    /*! Returns a reference to element \a #n */
+    template T &operator[] (T2 n) {
+      return d[n];
+    }
+
+    /*! Returns a constant reference to element \a #n */
+    template const T &operator[] (T2 n) const {
+      return d[n];
+    }
+
+    template void importArray(T2 *indata) {
+      for (int i = 0; i < sz; i++)
+	d[i] = indata[i];
+    }
+
+    template void exportArray(T2 *outdata) {
+      for (int i = 0; i < sz; i++)
+	outdata[i] = d[i];
+    }
+
+  };
+
+};
+
+#endif
diff --git a/external/cosmotool/src/fortran.cpp b/external/cosmotool/src/fortran.cpp
new file mode 100644
index 0000000..727a6d0
--- /dev/null
+++ b/external/cosmotool/src/fortran.cpp
@@ -0,0 +1,354 @@
+#include "config.hpp"
+#include 
+#include 
+#include 
+#include "fortran.hpp"
+
+using namespace std;
+using namespace CosmoTool;
+
+UnformattedRead::UnformattedRead(const string& fname)
+  throw(NoSuchFileException)
+{
+  f = new ifstream(fname.c_str());
+  if (!*f)
+    throw NoSuchFileException();
+
+  swapOrdering = false;
+  cSize = Check_32bits;
+  checkPointRef = checkPointAccum = 0;
+}
+
+UnformattedRead::UnformattedRead(const char *fname)
+  throw(NoSuchFileException)
+{
+  f = new ifstream(fname);
+  if (!*f)
+    throw NoSuchFileException();
+  swapOrdering = false;
+  cSize = Check_32bits;
+  checkPointRef = checkPointAccum = 0;
+}
+
+
+UnformattedRead::~UnformattedRead()
+{
+  delete f;
+}
+
+// Todo implement primitive description
+void UnformattedRead::setOrdering(Ordering o)
+{
+  if (o == LittleEndian)
+    {
+    }
+}
+
+void UnformattedRead::setCheckpointSize(CheckpointSize cs)
+{
+  cSize = cs;
+}
+    
+void UnformattedRead::skip(int64_t off)
+  throw (InvalidUnformattedAccess)
+{
+  if (checkPointAccum == 0 && checkPointRef == 0)
+    {
+      // We are not in a checked block
+      f->seekg(off, ios::cur);
+      return;
+    }
+  if (off < 0)
+    throw InvalidUnformattedAccess();
+  
+  if ((checkPointAccum+off) > checkPointRef)
+       throw InvalidUnformattedAccess();
+
+  f->seekg(off, ios::cur);
+  checkPointAccum += off;
+}
+
+void UnformattedRead::beginCheckpoint()
+  throw (InvalidUnformattedAccess,EndOfFileException)
+{
+  if (checkPointAccum != 0)
+    throw InvalidUnformattedAccess();
+  
+  checkPointRef = (cSize == Check_32bits) ? 4 : 8;
+  checkPointAccum = 0;  
+
+  checkPointRef = (cSize == Check_32bits) ? readInt32() : readInt64();
+  checkPointAccum = 0;
+
+  if (f->eof())
+    throw EndOfFileException();
+}
+
+void UnformattedRead::endCheckpoint(bool autodrop)
+  throw (InvalidUnformattedAccess)
+{
+  if (checkPointRef != checkPointAccum)
+    {
+      if (!autodrop || checkPointAccum > checkPointRef)
+	throw InvalidUnformattedAccess();
+      f->seekg(checkPointRef-checkPointAccum, ios::cur);
+    }
+
+  int64_t oldCheckPoint = checkPointRef;
+
+  checkPointRef = (cSize == Check_32bits) ? 4 : 8;
+  checkPointAccum = 0;
+  checkPointRef = (cSize == Check_32bits) ? readInt32() : readInt64();
+  
+  if (oldCheckPoint != checkPointRef)
+    throw InvalidUnformattedAccess();
+
+  checkPointAccum = checkPointRef = 0;
+}
+
+void UnformattedRead::readOrderedBuffer(void *buffer, int size)
+      throw (InvalidUnformattedAccess)
+{
+  if ((checkPointAccum+size) > checkPointRef)
+       throw InvalidUnformattedAccess();
+
+  f->read((char *)buffer, size);
+  
+  if (swapOrdering)
+    {
+      char *cb = (char *)buffer;
+      for (int i = 0; i < size/2; i++)
+	swap(cb[i], cb[size-i-1]);
+    }
+  checkPointAccum += size;
+}
+  
+double UnformattedRead::readReal64()
+  throw (InvalidUnformattedAccess)
+{
+  union
+  {
+    char b[8];
+    double d;
+  } a;
+
+  readOrderedBuffer(&a, 8);
+
+  return a.d;
+}
+
+float UnformattedRead::readReal32()
+  throw (InvalidUnformattedAccess)
+{
+  union
+  {
+    char b[4];
+    float f;
+  } a;
+
+  readOrderedBuffer(&a, 4);
+
+  return a.f;
+}
+
+int32_t UnformattedRead::readInt32()
+  throw (InvalidUnformattedAccess)
+{
+  union
+  {
+    char b[4];
+    int32_t i;
+  } a;
+
+  readOrderedBuffer(&a, 4);
+
+  return a.i;
+}
+
+int64_t UnformattedRead::readInt64()
+  throw (InvalidUnformattedAccess)
+{
+  union
+  {
+    char b[8];
+    int64_t i;
+  } a;
+
+  readOrderedBuffer(&a, 8);
+
+  return a.i;
+}
+
+//// UnformattedWrite
+
+UnformattedWrite::UnformattedWrite(const string& fname)
+  throw(NoSuchFileException)
+{
+  f = new ofstream(fname.c_str());
+  if (!*f)
+    throw NoSuchFileException();
+
+  swapOrdering = false;
+  cSize = Check_32bits;
+  checkPointRef = checkPointAccum = 0;
+}
+
+UnformattedWrite::UnformattedWrite(const char *fname)
+  throw(NoSuchFileException)
+{
+  f = new ofstream(fname);
+  if (!*f)
+    throw NoSuchFileException();
+  swapOrdering = false;
+  cSize = Check_32bits;
+  checkPointRef = checkPointAccum = 0;
+}
+
+
+UnformattedWrite::~UnformattedWrite()
+{
+  delete f;
+}
+
+// Todo implement primitive description
+void UnformattedWrite::setOrdering(Ordering o)
+{
+  if (o == LittleEndian)
+    {
+    }
+}
+
+void UnformattedWrite::setCheckpointSize(CheckpointSize cs)
+{
+  cSize = cs;
+}
+    
+void UnformattedWrite::beginCheckpoint()
+  throw (InvalidUnformattedAccess,FilesystemFullException)
+{
+  if (checkPointAccum != 0)
+    throw InvalidUnformattedAccess();
+  
+  checkPointRef = f->tellp();
+  if (cSize == Check_32bits)
+    writeInt32(0);
+  else
+    writeInt64(0);
+
+  checkPointAccum = 0;
+
+  if (!*f)
+    throw FilesystemFullException();
+}
+
+void UnformattedWrite::endCheckpoint()
+  throw (InvalidUnformattedAccess,FilesystemFullException)
+{
+  if (checkPointAccum == 0)
+    throw InvalidUnformattedAccess();
+
+  streampos curPos = f->tellp();
+
+  int64_t deltaPos = curPos-checkPointRef;
+
+  deltaPos -= (cSize == Check_32bits) ? 4 : 8;
+  // This is a sanity check.
+  if (checkPointAccum != deltaPos)
+    throw InvalidUnformattedAccess();
+
+  uint64_t saveAccum = checkPointAccum;
+
+  f->seekp(checkPointRef);
+  if (cSize == Check_32bits)
+    writeInt32(saveAccum);
+  else
+    writeInt64(saveAccum);
+
+  f->seekp(curPos);
+  if (cSize == Check_32bits)
+    writeInt32(saveAccum);
+  else
+    writeInt64(saveAccum);
+
+  checkPointAccum = checkPointRef = 0;
+}
+
+void UnformattedWrite::writeOrderedBuffer(void *buffer, int size)
+  throw (FilesystemFullException)
+{
+  f->write((char *)buffer, size);
+  
+  if (swapOrdering)
+    {
+      char *cb = (char *)buffer;
+      for (int i = 0; i < size/2; i++)
+	swap(cb[i], cb[size-i-1]);
+    }
+  checkPointAccum += size;
+
+  if (!*f)
+    throw FilesystemFullException();
+}
+  
+void UnformattedWrite::writeReal64(double d)
+  throw (FilesystemFullException)
+{
+  union
+  {
+    char b[8];
+    double d;
+  } a;
+
+  a.d = d;
+
+  writeOrderedBuffer(&a, 8);
+}
+
+void UnformattedWrite::writeReal32(float f)
+  throw (FilesystemFullException)
+{
+  union
+  {
+    char b[4];
+    float f;
+  } a;
+  
+  a.f = f;
+
+  writeOrderedBuffer(&a, 4);
+}
+
+void UnformattedWrite::writeInt32(int32_t i)
+  throw (FilesystemFullException)
+{
+  union
+  {
+    char b[4];
+    int32_t i;
+  } a;
+
+  a.i = i;
+  writeOrderedBuffer(&a, 4);
+}
+
+void UnformattedWrite::writeInt64(int64_t i)
+  throw (FilesystemFullException)
+{
+  union
+  {
+    char b[8];
+    int64_t i;
+  } a;
+
+  a.i = i;
+  writeOrderedBuffer(&a, 8);
+}
+
+void UnformattedWrite::writeInt8(int8_t i)
+  throw (FilesystemFullException)
+{
+  union { char b; int8_t i; } a;
+
+  a.i = i;
+  writeOrderedBuffer(&a, 1);
+}
diff --git a/external/cosmotool/src/fortran.hpp b/external/cosmotool/src/fortran.hpp
new file mode 100644
index 0000000..651d0fd
--- /dev/null
+++ b/external/cosmotool/src/fortran.hpp
@@ -0,0 +1,113 @@
+#ifndef __COSMO_FORTRAN_HPP
+#define __COSMO_FORTRAN_HPP
+
+#include 
+#include 
+#include 
+#include 
+#include "config.hpp"
+
+namespace CosmoTool
+{
+  class InvalidUnformattedAccess : public Exception
+  {
+  };
+
+  class FortranTypes
+  {
+  public:
+    enum Ordering {
+      LittleEndian, BigEndian
+    };
+
+    enum CheckpointSize {
+      Check_32bits, Check_64bits
+    };
+  };
+
+  class UnformattedRead: public FortranTypes
+  {
+  public:
+
+    UnformattedRead(const std::string& fname)
+      throw (NoSuchFileException);
+    UnformattedRead(const char *fname)
+      throw (NoSuchFileException);
+    ~UnformattedRead();
+
+    // Todo implement primitive description
+    void setOrdering(Ordering o);
+    void setCheckpointSize(CheckpointSize cs);
+    
+    void beginCheckpoint()
+      throw (InvalidUnformattedAccess,EndOfFileException);
+    void endCheckpoint(bool autodrop = false)
+      throw (InvalidUnformattedAccess);
+    
+    double readReal64()
+      throw (InvalidUnformattedAccess);
+    float readReal32()
+      throw (InvalidUnformattedAccess);
+    int32_t readInt32()
+      throw (InvalidUnformattedAccess);
+    int64_t readInt64()
+      throw (InvalidUnformattedAccess);
+
+    void skip(int64_t off)
+      throw (InvalidUnformattedAccess);
+
+  protected:
+    bool swapOrdering;
+    CheckpointSize cSize;
+    uint64_t checkPointRef;
+    uint64_t checkPointAccum;
+    std::ifstream *f;
+
+    void readOrderedBuffer(void *buffer, int size)
+      throw (InvalidUnformattedAccess);
+  };
+
+ class UnformattedWrite: public FortranTypes
+  {
+  public:
+
+    UnformattedWrite(const std::string& fname)
+      throw (NoSuchFileException);
+    UnformattedWrite(const char *fname)
+      throw (NoSuchFileException);
+    ~UnformattedWrite();
+
+    // Todo implement primitive description
+    void setOrdering(Ordering o);
+    void setCheckpointSize(CheckpointSize cs);
+    
+    void beginCheckpoint()
+      throw (FilesystemFullException,InvalidUnformattedAccess);
+    void endCheckpoint()
+      throw (FilesystemFullException,InvalidUnformattedAccess);
+    
+    void writeReal64(double d)
+      throw (FilesystemFullException);
+    void writeReal32(float f)
+      throw (FilesystemFullException);
+    void writeInt32(int32_t i)
+      throw (FilesystemFullException);
+    void writeInt64(int64_t i)
+      throw (FilesystemFullException);
+    void writeInt8(int8_t c)
+      throw (FilesystemFullException);
+
+    void writeOrderedBuffer(void *buffer, int size)
+      throw(FilesystemFullException);
+  protected:
+    bool swapOrdering;
+    CheckpointSize cSize;
+    std::streamoff checkPointRef;
+    uint64_t checkPointAccum;
+    std::ofstream *f;
+
+  };
+
+};
+
+#endif
diff --git a/external/cosmotool/src/fourier/base_types.hpp b/external/cosmotool/src/fourier/base_types.hpp
new file mode 100644
index 0000000..9a02a7e
--- /dev/null
+++ b/external/cosmotool/src/fourier/base_types.hpp
@@ -0,0 +1,102 @@
+#ifndef __BASE_FOURIER_TYPES_HPP
+#define __BASE_FOURIER_TYPES_HPP
+
+#include 
+#include 
+#include 
+
+namespace CosmoTool
+{
+  template
+  class FourierMap
+  {
+  protected:
+    FourierMap() {}
+    
+  public:
+    typedef Eigen::Matrix VecType;
+    typedef Eigen::Map MapType;
+    typedef Eigen::Map ConstMapType;
+
+    virtual ~FourierMap() {}
+
+    virtual const T* data() const = 0;
+    virtual T* data()  = 0;
+
+    virtual long size() const = 0;
+
+    MapType eigen()
+    {
+      return MapType(data(), size());
+    }
+
+    ConstMapType eigen() const
+    {
+      return ConstMapType(data(), size());
+    }
+ 
+    void scale(const T& factor)
+    {
+      MapType m(data(), size());
+      m *= factor;
+    }
+
+    void scale(const FourierMap *map2)
+    {
+      assert(size() == map2->size());
+      MapType m(data(), size());
+      MapType m2(map2->data(), map2->size());
+      m *= m2;
+    }
+
+    void add(const T&  factor)
+    {
+      eigen() += factor;
+    }
+
+    void add(const FourierMap *map2)
+    {
+      assert(size() == map2->size());
+      MapType m(data(), size());
+      MapType m2(map2->data(), map2->size());
+
+      eigen() += map2->eigen();
+    }
+
+    virtual FourierMap *copy() const
+    {
+      FourierMap *m = this->mimick();
+      
+      m->eigen() = this->eigen();
+      return m;
+    }
+
+    virtual FourierMap *mimick() const = 0;
+  };
+
+  template
+  class FourierTransform
+  {
+  protected:
+    FourierTransform() {}
+  public:
+    virtual ~FourierTransform() { }
+    
+    virtual const FourierMap >& fourierSpace() const = 0;
+    virtual FourierMap >& fourierSpace() = 0;
+
+    virtual const FourierMap& realSpace() const = 0;
+    virtual FourierMap& realSpace() = 0;
+
+    virtual FourierTransform *mimick() const = 0;
+
+    virtual void analysis() = 0;
+    virtual void synthesis() = 0;
+    virtual void analysis_conjugate() = 0;
+    virtual void synthesis_conjugate() = 0;
+  };
+
+
+};
+
+#endif
diff --git a/external/cosmotool/src/fourier/euclidian.hpp b/external/cosmotool/src/fourier/euclidian.hpp
new file mode 100644
index 0000000..0f40e1a
--- /dev/null
+++ b/external/cosmotool/src/fourier/euclidian.hpp
@@ -0,0 +1,200 @@
+#ifndef __COSMOTOOL_FOURIER_EUCLIDIAN_HPP
+#define __COSMOTOOL_FOURIER_EUCLIDIAN_HPP
+
+#include 
+#include 
+#include "base_types.hpp"
+#include "fft/fftw_calls.hpp"
+
+namespace CosmoTool
+{
+  
+  template
+  class EuclidianFourierMap: public FourierMap
+  {
+  private:
+    boost::shared_ptr m_data;
+    std::vector m_dims;
+    long m_size;
+  public:
+    EuclidianFourierMap(boost::shared_ptr indata, std::vector indims)
+    {
+      m_data = indata;
+      m_dims = indims;
+      m_size = 1;
+      for (int i = 0; i < m_dims.size(); i++)
+	m_size *= m_dims[i];
+    }
+
+    virtual ~EuclidianFourierMap()
+    {
+    }
+
+    virtual const T *data() const { return m_data.get(); }
+    virtual T *data() { return m_data.get(); }
+    virtual long size() const  { return m_size; } 
+    
+    virtual FourierMap *copy() const
+    {
+      FourierMap *m = this->mimick();
+      m->eigen() = this->eigen();
+      return m;
+    }
+
+    virtual FourierMap *mimick() const
+    {
+      return new EuclidianFourierMap(
+		      boost::shared_ptr((T *)fftw_malloc(sizeof(T)*m_size), 
+					   std::ptr_fun(fftw_free)),
+		       m_dims);
+    }
+  };
+
+  
+  template
+  class EuclidianFourierTransform: public FourierTransform
+  {
+  private:
+    typedef FFTW_Calls calls;
+    EuclidianFourierMap *realMap;
+    EuclidianFourierMap > *fourierMap;
+    typename calls::plan_type m_analysis, m_synthesis;
+    double volume;
+    long N;
+    std::vector m_dims;
+    std::vector m_L;
+  public:
+    EuclidianFourierTransform(const std::vector& dims, const std::vector& L)
+    {
+      assert(L.size() == dims.size());
+
+      m_dims = dims;
+      m_L = L;
+
+      N = 1;
+      volume = 1;
+      for (int i = 0; i < dims.size(); i++)
+	{
+	  N *= dims[i];
+	  volume *= L[i];
+	}
+
+      realMap = new EuclidianFourierMap(
+		   boost::shared_ptr(calls::alloc_real(N),
+					std::ptr_fun(calls::free)),
+		   dims);
+      fourierMap = new EuclidianFourierMap >(
+		   boost::shared_ptr >((std::complex*)calls::alloc_complex(N),
+						       std::ptr_fun(calls::free)), 
+		   dims);
+      m_analysis = calls::plan_dft_r2c(dims.size(), &dims[0],
+				       realMap->data(), (typename calls::complex_type *)fourierMap->data(),
+				       FFTW_MEASURE);
+      m_synthesis = calls::plan_dft_c2r(dims.size(), &dims[0],
+					(typename calls::complex_type *)fourierMap->data(), realMap->data(),
+					FFTW_MEASURE);
+    }
+    
+    virtual ~EuclidianFourierTransform()
+    {
+      delete realMap;
+      delete fourierMap;
+      calls::destroy_plan(m_synthesis);
+      calls::destroy_plan(m_analysis);
+    }
+
+    void synthesis()
+    {
+      calls::execute(m_synthesis);
+      realMap->scale(1/volume);
+    }
+    
+    void analysis()
+    {
+      calls::execute(m_analysis);
+      fourierMap->scale(volume/N);
+    }
+    
+    void synthesis_conjugate()
+    {
+      calls::execute(m_analysis);
+      fourierMap->scale(1/volume);
+    }
+    
+    void analysis_conjugate()
+    {
+      calls::execute(m_synthesis);
+      realMap->scale(volume/N);
+    }
+
+    const FourierMap >& fourierSpace() const
+    {
+      return *fourierMap;
+    }
+
+    FourierMap >& fourierSpace()
+    {
+      return *fourierMap;
+    }
+
+    const FourierMap& realSpace() const
+    {
+      return *realMap;
+    }
+
+    FourierMap& realSpace()
+    {
+      return *realMap;
+    }
+
+    FourierTransform *mimick() const
+    {
+      return new EuclidianFourierTransform(m_dims, m_L);
+    }
+  };
+
+  template
+  class EuclidianFourierTransform_2d: public EuclidianFourierTransform
+  {
+  private:
+    template
+    static std::vector make_2d_vector(T2 a, T2 b)
+    {
+      T2 arr[2] = { a, b};
+      return std::vector(&arr[0], &arr[2]);
+    }
+  public:
+    EuclidianFourierTransform_2d(int Nx, int Ny, double Lx, double Ly)
+      : EuclidianFourierTransform(make_2d_vector(Nx, Ny), make_2d_vector(Lx, Ly))
+    {
+    }
+
+    virtual ~EuclidianFourierTransform_2d() {}
+
+  };
+
+  template
+  class EuclidianFourierTransform_3d: public EuclidianFourierTransform
+  {
+  private:
+    template
+    static std::vector make_3d_vector(T2 a, T2 b, T2 c)
+    {
+      T2 arr[2] = { a, b, c};
+      return std::vector(&arr[0], &arr[3]);
+    }
+
+  public:
+    EuclidianFourierTransform_3d(int Nx, int Ny, int Nz, double Lx, double Ly, double Lz)
+      : EuclidianFourierTransform(make_3d_vector(Nx, Ny, Nz), make_3d_vector(Lx, Ly, Lz))
+    {
+    }
+
+    virtual ~EuclidianFourierTransform_3d() {}
+  };
+
+
+
+};
+
+#endif
diff --git a/external/cosmotool/src/fourier/fft/fftw_calls.hpp b/external/cosmotool/src/fourier/fft/fftw_calls.hpp
new file mode 100644
index 0000000..0106055
--- /dev/null
+++ b/external/cosmotool/src/fourier/fft/fftw_calls.hpp
@@ -0,0 +1,75 @@
+#ifndef __FFTW_UNIFIED_CALLS_HPP
+#define __FFTW_UNIFIED_CALLS_HPP
+
+#include 
+
+namespace CosmoTool
+{
+
+static inline void init_fftw_wisdom()
+{
+  fftw_import_system_wisdom();
+  fftw_import_wisdom_from_filename("fft_wisdom");
+}
+
+static inline void save_fftw_wisdom()
+{
+  fftw_export_wisdom_to_filename("fft_wisdom");
+}
+
+template class FFTW_Calls {};
+
+
+#define FFTW_CALLS_BASE(rtype, prefix) \
+  template<>				\
+class FFTW_Calls {		\
+public: \
+  typedef rtype real_type; \
+  typedef prefix ## _complex complex_type; \
+  typedef prefix ## _plan plan_type; \
+  \
+  static complex_type *alloc_complex(int N) { return prefix ## _alloc_complex(N); } \
+  static real_type *alloc_real(int N) { return prefix ## _alloc_real(N); } \
+  static void free(void *p) { fftw_free(p); } \
+\
+  static void execute(plan_type p) { prefix ## _execute(p); } \
+  static plan_type plan_dft_r2c_2d(int Nx, int Ny,  \
+		       real_type *in, complex_type *out, \
+		       unsigned flags) \
+  { \
+    return prefix ## _plan_dft_r2c_2d(Nx, Ny, in, out, \
+				flags); \
+  } \
+  static plan_type plan_dft_c2r_2d(int Nx, int Ny,  \
+                       complex_type *in, real_type *out, \
+                       unsigned flags) \
+  { \
+    return prefix ## _plan_dft_c2r_2d(Nx, Ny, in, out, \
+                                flags); \
+  } \
+  static plan_type plan_dft_r2c_3d(int Nx, int Ny, int Nz, \
+                                   real_type *in, complex_type *out, \
+                                   unsigned flags) \
+  { \
+    return prefix ## _plan_dft_r2c_3d(Nx, Ny, Nz, in, out, flags); \
+  } \
+  static plan_type plan_dft_r2c(int rank, const int *n, real_type *in, \
+                                complex_type *out, unsigned flags) \
+  { \
+    return prefix ## _plan_dft_r2c(rank, n, in, out, flags); \
+  } \
+  static plan_type plan_dft_c2r(int rank, const int *n, complex_type *in, \
+                                real_type *out, unsigned flags) \
+  { \
+    return prefix ## _plan_dft_c2r(rank, n, in, out, flags); \
+  } \
+  static void destroy_plan(plan_type plan) { prefix ## _destroy_plan(plan); } \
+}
+
+
+FFTW_CALLS_BASE(double, fftw);
+FFTW_CALLS_BASE(float, fftwf);
+
+};
+
+#endif
diff --git a/external/cosmotool/src/fourier/healpix.hpp b/external/cosmotool/src/fourier/healpix.hpp
new file mode 100644
index 0000000..1759a78
--- /dev/null
+++ b/external/cosmotool/src/fourier/healpix.hpp
@@ -0,0 +1,135 @@
+#ifndef __COSMOTOOL_FOURIER_HEALPIX_HPP
+#define __COSMOTOOL_FOURIER_HEALPIX_HPP
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace CosmoTool
+{
+    
+  template
+  class HealpixFourierMap: public FourierMap, public Healpix_Base
+  {
+  private:
+    T *m_data;
+    Eigen::aligned_allocator alloc;
+  public:
+    HealpixFourierMap(long nSide)
+      : Healpix_Base(RING, nSide, SET_NSIDE)
+    {
+      m_data = alloc.allocate(Npix);
+    }
+
+    virtual ~HealpixFourierMap()
+    {
+      alloc.deallocate(m_data);
+    }
+
+    virtual const T* data() const { return m_data; }
+    virtual T *data() { return m_data; }
+    virtual long size() const { return Npix(); }
+    
+    virtual FourierMap *mimick() const
+    {
+      return new HealpixFourierMap(Nside());
+    }
+  };
+
+
+  template
+  class HealpixFourierALM: public FourierMap >, public Alm_Base
+  {
+  private:
+    std::complex *alms;
+    long size;
+    Eigen::aligned_allocator > alloc;
+  public:
+    HealpixFourierALM(long Lmax, long Mmax)
+      : Alm_Base(Lmax, Mmax)
+    {
+      size = Num_Alms(Lmax, Mmax);
+      alms = alloc.allocate(size);
+    }
+
+    virtual ~HealpixFourierALM()
+    {
+      alloc.deallocate(alms);
+    }
+
+    virtual const T* data() const { return alms; }
+    virtual T * data() { return alms;} 
+    virtual long size() const { return size; }
+
+    virtual FourierMap *mimick() const
+    {
+      return new HealpixFourierALM(Lmax(), Mmax());
+    }
+  };
+
+  template
+  class HealpixFourierTransform: public FourierTransform
+  {
+  private:
+    HealpixFourierMap realMap;
+    HealpixFourierALM fourierMap;
+    psht_joblist jobs;
+  public:
+    HealpixFourierTransform(long nSide, long Lmax, long Mmax)
+      : realMap(nSide), fourierMap(Lmax, Mmax)
+    {
+      jobs.set_Healpix_geometry(nSide);
+      jobs.set_triangular_alm_info(Lmax, Mmax);
+    }
+
+    virtual ~HealpixFourierTransform() {}
+
+    virtual const FourierMap >& fourierSpace() const { return fourierMap; }
+
+    virtual FourierMap >& fourierSpace() { return fourierMap; }
+
+    virtual const FourierMap& realSpace() const { return realMap; }
+
+    virtual FourierMap& realSpace() { return realMap; }
+    
+    virtual FourierTransform *mimick() const 
+    {
+      return new HealpixFourierTransform(realMap.Nside(), fourierMap.Lmax(), fourierMap.Mmax());
+    }
+
+    virtual void analysis()
+    {
+      jobs.add_map2alm(realMap.data(), 
+		       reinterpret_cast *>(fourierMap.data()), 
+		       false);
+      jobs.execute();
+      jobs.clear_jobs();
+    }
+
+    virtual void synthesis()
+    {
+      jobs.add_alm2map(reinterpret_cast *>(fourierMap.data()), 
+		       realMap.data(),
+		       false);
+      jobs.execute();
+      jobs.clear_jobs();
+    }
+
+    virtual void analysis_conjugate()
+    {
+      synthesis();
+      realMap.scale(4*M_PI/realMap.Npix());
+    }
+
+    virtual void synthesis_conjugate()
+    {
+      analysis();
+      fourierMap.scale(realMap.Npix()/(4*M_PI));
+    }
+    
+  };
+};
+
+#endif
diff --git a/external/cosmotool/src/growthFactor.cpp b/external/cosmotool/src/growthFactor.cpp
new file mode 100644
index 0000000..485d876
--- /dev/null
+++ b/external/cosmotool/src/growthFactor.cpp
@@ -0,0 +1,111 @@
+#include 
+#include 
+#include "interpolate.hpp"
+#include "growthFactor.hpp"
+
+using namespace CosmoTool;
+
+#define AMIN 1e-5
+#define AMAX 1.0
+#define NUM_WORK 5000
+#define TOLERANCE 1e-6
+
+typedef struct {
+  double OmegaLambda;
+  double OmegaMatter;
+  double Hubble;
+} Cosmology;
+
+static double computeOmegaMatter(Cosmology *cosmo, double a)
+{
+  return cosmo->OmegaMatter / (cosmo->OmegaMatter + a*a*a * cosmo->OmegaLambda);
+}
+
+static double computeHdotH(Cosmology *cosmo, double a)
+{
+  return -1.5 * cosmo->OmegaMatter / (a * (cosmo->OmegaMatter + a*a*a*cosmo->OmegaLambda));
+}
+
+static double computeE(double OmegaMatter, double OmegaLambda, double a)
+{
+  double H2;
+  double OmegaK = (1 - OmegaMatter - OmegaLambda);
+  
+  H2 = OmegaMatter/(a*a*a) + OmegaLambda + OmegaK/(a*a);
+
+  return sqrt(H2);
+}
+
+static double computeEprime(Cosmology *cosmo, double a)
+{
+  double H2;
+  double OmegaK = (1 - cosmo->OmegaMatter - cosmo->OmegaLambda);
+  
+  H2 = -3*cosmo->OmegaMatter/(a*a*a*a) - 2*OmegaK/(a*a*a);
+
+  return 0.5*H2/computeE(cosmo->OmegaMatter, cosmo->OmegaLambda, a);  
+}
+
+static inline double cube(double x)
+{
+  return x*x*x;
+}
+
+static double integrandGrowthFactor(double a, void *params)
+{
+  Cosmology *cosmo = (Cosmology *)params;
+
+  return 1/cube(computeE(cosmo->OmegaMatter, cosmo->OmegaLambda, a)*a);
+}
+
+Interpolate CosmoTool::buildLinearGrowth(double OmegaLambda, double OmegaMatter, double Hubble, int numPoints)
+{
+  Cosmology cosmology;
+  gsl_integration_workspace *work = gsl_integration_workspace_alloc(NUM_WORK);
+  gsl_function f;
+  double *a_input, *D_output;
+
+  cosmology.OmegaLambda = OmegaLambda;
+  cosmology.OmegaMatter = OmegaMatter;
+  cosmology.Hubble = Hubble;
+
+  a_input = new double[numPoints];
+  D_output = new double[numPoints];
+
+  f.params = &cosmology;
+  f.function = integrandGrowthFactor;
+  
+  a_input[0] = 0;
+  D_output[0] = 0;
+
+  for (int i = 1; i < numPoints; i++)
+    {
+      double a_dest = 0 + 1.0*i/(numPoints-1);
+      double result, abserr;
+      double E = computeE(cosmology.OmegaMatter, cosmology.OmegaLambda, a_dest);
+      double Eprime = computeEprime(&cosmology, a_dest);
+
+      gsl_integration_qag(&f, 0, a_dest, 0, TOLERANCE, NUM_WORK, 
+			  GSL_INTEG_GAUSS61, work, &result, &abserr);
+
+
+      result *= 2.5 * computeE(cosmology.OmegaMatter, cosmology.OmegaLambda, a_dest) * OmegaMatter;
+
+      D_output[i] = result;
+      a_input[i] = a_dest;
+
+    }
+  gsl_integration_workspace_free(work);
+
+  for (int i = 0; i < numPoints; i++)
+    {
+      D_output[i] /= D_output[numPoints-1];
+    }
+
+  Interpolate p(a_input, D_output, numPoints, true, false, true);
+
+  delete[] a_input;
+  delete[] D_output;
+
+  return p;
+}
diff --git a/external/cosmotool/src/growthFactor.hpp b/external/cosmotool/src/growthFactor.hpp
new file mode 100644
index 0000000..2643fd0
--- /dev/null
+++ b/external/cosmotool/src/growthFactor.hpp
@@ -0,0 +1,12 @@
+#ifndef COSMO_GROWTH_FACTOR_HPP
+#define COSMO_GROWTH_FACTOR_HPP
+
+#include "interpolate.hpp"
+
+namespace CosmoTool
+{
+  Interpolate buildLinearGrowth(double OmegaLambda, double OmegaMatter, double Hubble, int numPoints = 10000);
+
+};
+
+#endif
diff --git a/external/cosmotool/src/h5_readFlash.cpp b/external/cosmotool/src/h5_readFlash.cpp
new file mode 100644
index 0000000..0c78f58
--- /dev/null
+++ b/external/cosmotool/src/h5_readFlash.cpp
@@ -0,0 +1,474 @@
+/* This file contains the functions that read the data from the HDF5 file
+ * The functions accept the PARAMESH data through arguments.
+ */
+
+#include "h5_readFlash.hpp"
+
+using namespace H5;
+
+/* indices of attributes in fof_particle_type */
+int iptag_out = 0;
+int ipx_out   = 0;
+int ipy_out   = 1;
+int ipz_out   = 2;
+int ipvx_out   = 3;
+int ipvy_out   = 4;
+int ipvz_out   = 5;
+
+/* xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx */
+
+/*  n*_runtime_parameters should be set by the caller to 
+    the maximum number of runtime parameters to read. 
+*/
+
+void h5_read_runtime_parameters
+     (H5File* file,    /* file handle */
+      double* LBox,    // box size
+      int* numPart,
+      double *hubble,
+      double *omegam,
+      double *omegalambda)       // number of particles
+{
+
+ int MAX_PARM = 200;
+
+ int nreal_runtime_parameters, nint_runtime_parameters, nstr_runtime_parameters;
+
+ char real_runtime_parameter_names[MAX_PARM][RUNTIME_PARAMETER_STRING_SIZE];
+ char int_runtime_parameter_names[MAX_PARM][RUNTIME_PARAMETER_STRING_SIZE];
+ char str_runtime_parameter_names[MAX_PARM][RUNTIME_PARAMETER_STRING_SIZE];
+
+ double real_runtime_parameter_values[MAX_PARM];
+ int    int_runtime_parameter_values[MAX_PARM];
+ char   str_runtime_parameter_values[MAX_PARM][RUNTIME_PARAMETER_STRING_SIZE];
+
+  int rank;
+  hsize_t dimens_1d, maxdimens_1d;
+
+  real_runtime_params_t *real_rt_parms;
+  int_runtime_params_t *int_rt_parms;
+  str_runtime_params_t *str_rt_parms;
+  log_runtime_params_t *log_rt_parms;
+
+  double omegarad;
+
+  int i;
+
+  nint_runtime_parameters = MAX_PARM;
+  nreal_runtime_parameters = MAX_PARM;
+
+  /* integer runtime parameters */
+  int_rt_parms = (int_runtime_params_t *) malloc(nint_runtime_parameters * sizeof(int_runtime_params_t)); 
+
+  rank = 1;
+  DataSet dataset = file->openDataSet("integer runtime parameters");
+  
+  IntType int_rt_type = dataset.getIntType();  
+  //int_rt_type = H5Dget_type(dataset);
+
+  DataSpace dataspace = dataset.getSpace();
+  //dataspace = H5Dget_space(dataset);
+
+  int ndims = dataspace.getSimpleExtentDims(&dimens_1d, NULL);
+  //H5Sget_simple_extent_dims(dataspace, &dimens_1d, &maxdimens_1d);
+
+  /* don't read in more than we can handle */
+  if (nint_runtime_parameters < dimens_1d) {
+    dimens_1d = nint_runtime_parameters;
+  } else {
+    nint_runtime_parameters = dimens_1d;
+  }
+  DataSpace memspace(rank, &dimens_1d);
+  //memspace = H5Screate_simple(rank, &dimens_1d, NULL);
+
+  dataset.read(int_rt_parms, int_rt_type, memspace, dataspace,
+		    H5P_DEFAULT);
+  //status = H5Dread(dataset, int_rt_type, memspace, dataspace,
+	//	    H5P_DEFAULT, int_rt_parms);
+
+
+  for (i = 0; i < nint_runtime_parameters; i++) {
+    strncpy(int_runtime_parameter_names[i], int_rt_parms[i].name, RUNTIME_PARAMETER_STRING_SIZE);
+    int_runtime_parameter_values[i] = int_rt_parms[i].value;
+  }
+
+  free(int_rt_parms);
+  memspace.close();
+  dataspace.close();
+  dataset.close();
+  //H5Sclose(dataspace);
+  //H5Dclose(dataset);
+
+  /* done with int runtime parameters */
+
+  /* reals */
+
+  real_rt_parms = (real_runtime_params_t *) malloc(nreal_runtime_parameters * sizeof(real_runtime_params_t)); 
+  
+  rank = 1;
+  dataset = file->openDataSet("real runtime parameters");
+  //dataset = H5Dopen(*file_identifier, "real runtime parameters"); 
+  
+  dataspace = dataset.getSpace();
+  FloatType real_rt_type = dataset.getFloatType();  
+  ndims = dataspace.getSimpleExtentDims(&dimens_1d, NULL);
+  //dataspace = H5Dget_space(dataset);
+  //real_rt_type = H5Dget_type(dataset);
+  //H5Sget_simple_extent_dims(dataspace, &dimens_1d, &maxdimens_1d);
+
+  /* don't read in more than we can handle */
+  if (nreal_runtime_parameters < dimens_1d) {
+    dimens_1d = nreal_runtime_parameters;
+  } else {
+    nreal_runtime_parameters = dimens_1d;
+  }
+   memspace = DataSpace(rank, &dimens_1d);
+  //memspace = H5Screate_simple(rank, &dimens_1d, NULL);
+
+  dataset.read(real_rt_parms, real_rt_type, memspace, dataspace,
+		    H5P_DEFAULT);
+  //status = H5Dread(dataset, real_rt_type, memspace, dataspace,
+//		    H5P_DEFAULT, real_rt_parms);
+
+
+  for (i = 0; i < nreal_runtime_parameters; i++) {
+    strncpy(real_runtime_parameter_names[i], real_rt_parms[i].name, RUNTIME_PARAMETER_STRING_SIZE);
+    real_runtime_parameter_values[i] = real_rt_parms[i].value;
+  }
+
+  free(real_rt_parms);
+  memspace.close();
+  dataspace.close();
+  dataset.close();
+  //H5Sclose(dataspace);
+  //H5Dclose(dataset);
+
+  /* done with reals */
+
+  // grab the data we want
+  for (i = 0; i < nreal_runtime_parameters; i++) {
+    if (strncmp(real_runtime_parameter_names[i],"xmax",4) == 0 ) {
+      *LBox = real_runtime_parameter_values[i];
+    }
+    if (strncmp(real_runtime_parameter_names[i],"hubbleconstant", 14) == 0 ) {
+      *hubble = real_runtime_parameter_values[i];      
+    }
+    if (strncmp(real_runtime_parameter_names[i],"omegamatter", 11) == 0 ) {
+      *omegam = real_runtime_parameter_values[i];
+    }
+    if (strncmp(real_runtime_parameter_names[i],"omegaradiation", 11) == 0 ) {
+      omegarad = real_runtime_parameter_values[i];
+    }
+    if (strncmp(real_runtime_parameter_names[i],"cosmologicalconstant", 20) == 0 ) {
+      *omegalambda = real_runtime_parameter_values[i];
+    }
+  }
+  
+  for (i = 0; i < nint_runtime_parameters; i++) {
+    if (strncmp(int_runtime_parameter_names[i],"pt_numx",7) == 0 ) {
+      *numPart = int_runtime_parameter_values[i];
+      *numPart = *numPart * *numPart * *numPart;
+    }
+  }
+
+}
+
+     
+/* xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/
+void h5_read_flash3_particles (H5File* file,
+                        int* totalparticles,
+                        int* localnp,
+                        int* particle_offset,
+                        float *pos1,
+                        float *pos2,
+                        float *pos3,
+                        float *vel1,
+                        float *vel2,
+                        float *vel3,
+                        int    *id)
+{
+
+  herr_t   status;
+  hsize_t  dimens_1d, maxdims_1d;
+  hsize_t  start_1d, stride_1d, count_1d;
+  int rank;
+
+  int      numProps, i, p;
+  int      numPartBuffer = 5000, sizePartBuffer, sizePart;
+  int      pstack, poffset, pcount;
+  int      iptag, ipx, ipy, ipz, ipvx, ipvy, ipvz;
+  char     *propName;
+  double   *partBuffer;
+  char    part_names[50][OUTPUT_PROP_LENGTH];
+  int     string_size;  
+         
+//  char part_names[NPART_PROPS][OUTPUT_PROP_LENGTH];
+  hsize_t dimens_2d[2], maxdimens_2d[2];
+  hsize_t start_2d[2], count_2d[2], stride_2d[2];
+
+  /* skip this routine if no particles to read */
+  if ((*localnp) == 0) {
+    return;
+  }
+
+
+ /* first determine how many particle properties are
+     present in the input data file, and determine which of these
+     are the properties we are interested in */
+  DataSet dataset = file->openDataSet("particle names");
+  DataSpace dataspace = dataset.getSpace();
+  //dataset = H5Dopen(*file_identifier, "particle names");
+  //dataspace = H5Dget_space(dataset);
+
+  int ndims = dataspace.getSimpleExtentDims(dimens_2d, NULL);
+  //H5Sget_simple_extent_dims(dataspace, dimens_2d, maxdimens_2d);
+
+  //total number of particle properties
+  numProps = dimens_2d[0];
+  
+  string_size = OUTPUT_PROP_LENGTH;
+  StrType string_type = H5Tcopy(H5T_C_S1);
+  string_type.setSize(string_size);
+  //status = H5Tset_size(string_type, string_size);
+  
+  rank = 2;
+
+  start_2d[0] = 0;
+  start_2d[1] = 0;
+
+  stride_2d[0] = 1;
+  stride_2d[1] = 1;
+
+  count_2d[0] = dimens_2d[0];
+  count_2d[1] = dimens_2d[1];
+
+  dataspace.selectHyperslab(H5S_SELECT_SET, count_2d, start_2d); 
+  //status = H5Sselect_hyperslab(dataspace, H5S_SELECT_SET, start_2d,
+  //                      stride_2d, count_2d, NULL);
+
+  DataSpace memspace(rank, dimens_2d);
+  //memspace = H5Screate_simple(rank, dimens_2d, NULL);
+
+  dataset.read(part_names, string_type, H5S_ALL, H5S_ALL, H5P_DEFAULT);
+  //status = H5Dread(dataset, string_type, H5S_ALL, H5S_ALL,
+  //              H5P_DEFAULT, part_names);
+
+ 
+  string_type.close();
+  memspace.close();
+  dataspace.close();
+  dataset.close(); 
+  //H5Tclose(string_type);
+  //H5Sclose(memspace);
+  //H5Sclose(dataspace);
+  //H5Dclose(dataset);
+
+  for (i=0;iopenDataSet("tracer particles");
+  //dataset = H5Dopen(*file_identifier, "tracer particles");
+  
+  FloatType datatype = dataset.getFloatType();
+  //datatype = H5Dget_type(dataset);
+
+  /* establish read-in particle buffer */
+  sizePart = numProps*(sizeof(double));
+  sizePartBuffer = numPartBuffer * sizePart;
+  partBuffer = (double *)malloc(sizePartBuffer);
+
+  dataspace = dataset.getSpace();
+  ndims = dataspace.getSimpleExtentDims(dimens_2d, NULL);
+  //dataspace = H5Dget_space(dataset);
+  //H5Sget_simple_extent_dims(dataspace, dimens_2d, maxdimens_2d);
+ 
+  /*insert particle properties (numPartBuffer) particles at a time*/
+  pstack = (*localnp);
+  poffset = 0;
+  if (pstack > numPartBuffer) {
+   pcount = numPartBuffer;
+  }
+  else {
+   pcount = pstack;
+  }
+
+  while ( pstack > 0) {
+    rank       = 2;
+    maxdimens_2d[0] = (hsize_t) (*totalparticles);
+    maxdimens_2d[1] = (hsize_t) (numProps);
+
+    start_2d[0]  = (hsize_t) (*particle_offset + poffset);
+    start_2d[1]  = (hsize_t) 0;
+
+    stride_2d[0] = 1;
+    stride_2d[1] = 1;
+
+    count_2d[0]  = (hsize_t) (pcount);
+    count_2d[1]  = (hsize_t) (numProps);
+
+    dimens_2d[0] = (pcount);
+    dimens_2d[1] = (numProps);
+
+    dataspace.selectHyperslab(H5S_SELECT_SET, count_2d, start_2d); 
+    //status     = H5Sselect_hyperslab(dataspace, H5S_SELECT_SET, start_2d,
+    //                                 stride_2d, count_2d, NULL);
+
+    memspace = DataSpace(rank, dimens_2d);
+    //memspace   = H5Screate_simple(rank, dimens_2d, maxdimens_2d);
+
+    /* read data from the dataset */
+   dataset.read(partBuffer, datatype, memspace, dataspace, H5P_DEFAULT);
+   //status = H5Dread(dataset, datatype, memspace, dataspace, H5P_DEFAULT, partBuffer);
+
+    /* convert buffer into particle struct */
+
+    if (id) {
+    for(p=0; p < (pcount); p++) {
+      id[p+poffset] = (int)  *(partBuffer+iptag-1+p*numProps);
+    } }
+
+    if (pos1 && pos2 && pos3) {
+    for(p=0; p < (pcount); p++) {
+      pos1[p+poffset]  = (float) *(partBuffer+ipx-1+p*numProps);
+      pos2[p+poffset]  = (float) *(partBuffer+ipy-1+p*numProps);
+      pos3[p+poffset]  = (float) *(partBuffer+ipz-1+p*numProps);
+    }
+    }
+   
+    
+    if (vel1 && vel2 && vel3) {
+    for(p=0; p < (pcount); p++) {
+      vel1[p+poffset]  = (float) *(partBuffer+ipvx-1+p*numProps);
+      vel2[p+poffset]  = (float) *(partBuffer+ipvy-1+p*numProps);
+      vel3[p+poffset]  = (float) *(partBuffer+ipvz-1+p*numProps);
+    }
+    }
+
+    memspace.close();
+    //status = H5Sclose(memspace);
+   /* advance buffer */
+    pstack = pstack - pcount;
+    poffset = poffset + pcount;
+    if (pstack > numPartBuffer) {
+      pcount = numPartBuffer;
+    }
+    else {
+      pcount = pstack;
+    }
+
+  } /* end while */
+
+
+  datatype.close();
+  dataspace.close();
+  dataset.close();
+  //status = H5Tclose(datatype);
+  //status = H5Sclose(dataspace);
+  //status = H5Dclose(dataset);
+
+}
+ 
+
+/*xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/
+
+void h5_read_flash3_header_info(H5File* file,
+				double* time,                  /* simulation time */
+				double* redshift)              /* redshift of checkpoint */
+{
+
+  herr_t status;
+
+  int file_version;
+
+  hid_t sp_type, si_type;
+
+  hsize_t dimens_1d, maxdimens_1d;
+  hid_t string_type;
+  real_list_t *real_list;
+  int* num_real, num_int;
+  int MAX_SCALARS = 100;
+  char real_names[MAX_SCALARS][MAX_STRING_LENGTH];
+  double real_values[MAX_SCALARS];
+  char int_names[MAX_SCALARS][MAX_STRING_LENGTH];
+  int int_values[MAX_SCALARS];
+  int i;
+
+  H5std_string DATASET_NAME;
+
+  string_type = H5Tcopy(H5T_C_S1);  
+  H5Tset_size(string_type, MAX_STRING_LENGTH);
+
+  DataSet dataset = file->openDataSet("real scalars");
+  DataSpace dataspace = dataset.getSpace();
+
+  /* read extent of 'dataspace' (i.e. # of name/value pairs) into 'dimens_1d' */
+  int ndims = dataspace.getSimpleExtentDims(&dimens_1d, NULL);
+
+  if (dimens_1d > MAX_SCALARS) {
+    printf("Error: reading more than MAX_SCALARS runtime parameters in checkpoint file!\n");
+  }
+
+  /* malloc a pointer to a list of real_list_t's */
+  real_list = (real_list_t *) malloc(dimens_1d * sizeof(real_list_t));
+
+  // create a new simple dataspace of 1 dimension and size of 'dimens_1d' 
+  DataSpace memspace(1, &dimens_1d);
+
+  // create an empty vessel sized to hold one real_list_t's worth of data 
+  CompType real_list_type( sizeof(real_list_t) );
+
+  // subdivide the empty vessel into its component sections (name and value) 
+  real_list_type.insertMember(
+          "name",
+          HOFFSET(real_list_t, name),
+          string_type);
+
+  real_list_type.insertMember(
+          "value",
+          HOFFSET(real_list_t, value),
+          PredType::NATIVE_DOUBLE);
+
+  // read the data into 'real_list' 
+  dataset.read( real_list, real_list_type, memspace, dataspace,
+                H5P_DEFAULT);
+
+
+  if (status < 0) {
+    printf("Error readingruntime parameterss from data file\n");
+  }
+
+  for (i = 0; i < dimens_1d; i++) {
+    strncpy(real_names[i], real_list[i].name, MAX_STRING_LENGTH);
+    real_values[i] = real_list[i].value;
+
+    if (strncmp(real_names[i],"time",4) == 0 ) {
+      *time = real_values[i];
+    }
+    if (strncmp(real_names[i],"redshift",8) == 0 ) {
+      *redshift = real_values[i];
+    }
+  }
+
+  free(real_list);
+  real_list_type.close();
+  memspace.close();
+  dataspace.close();
+  dataset.close();
+}
diff --git a/external/cosmotool/src/h5_readFlash.hpp b/external/cosmotool/src/h5_readFlash.hpp
new file mode 100644
index 0000000..5187232
--- /dev/null
+++ b/external/cosmotool/src/h5_readFlash.hpp
@@ -0,0 +1,36 @@
+/* This file contains the functions that read the data from the HDF5 file
+ * The functions accept the PARAMESH data through arguments.
+ */
+
+#include 
+#include 
+#include 
+#include 
+#include "hdf5_flash.h"
+#include "H5Cpp.h"
+
+using namespace H5;
+
+void h5_read_runtime_parameters
+     (H5File* file,    /* file handle */
+      double* LBox,
+      int* numPart,
+      double* hubble,
+      double* omegam,
+      double* omegalambda);
+
+void h5_read_flash3_particles (H5File* file,
+                        int* totalparticles,
+                        int* localnp,
+                        int* particle_offset,
+                        float *pos1,
+                        float *pos2,
+                        float *pos3,
+                        float *vel1,
+                        float *vel2,
+                        float *vel3,
+                        int    *id);
+
+void h5_read_flash3_header_info(H5File* file,
+				double* time,                   /* simulation time */
+				double *redshift);              /* simulation redshift */
diff --git a/external/cosmotool/src/hdf5_flash.h b/external/cosmotool/src/hdf5_flash.h
new file mode 100644
index 0000000..aab54e6
--- /dev/null
+++ b/external/cosmotool/src/hdf5_flash.h
@@ -0,0 +1,192 @@
+/* general header file for the HDF 5 IO in FLASH */
+
+
+#ifndef _HDF5_FLASH_H
+#define _HDF5_FLASH_H
+
+/* pull in some basic FLASH information */
+
+//#include "flash_defines.fh"
+
+/* define an integer file format version number, that is stored
+   in the output files.  This way, people can check this number
+   before reading, and compare against a published format to know
+   what is stored in the file.  In theory, this number should be
+   incremented anytime a change is made to the file format */
+
+/* File format history:
+
+   1 -- original version
+
+   2 -- added build records:
+         "FLASH build date"
+         "FLASH build directory"
+         "FLASH build machine"
+         "FLASH setup call"
+
+        added the "file format version" record
+
+   3 -- added the "run comment record" (why was this
+        not done long ago?)
+
+   4 -- added extrema attributes to the variable records
+
+   5 -- redshift included
+
+   6 -- added the Module data to the attributes of "/"
+
+   7 -- make build info attributes on "/"
+*/
+
+#define FILE_FORMAT_VERSION 7
+
+#define RUNTIME_PARAMETER_STRING_SIZE 80
+
+#define TIMER_NAME_STRING_SIZE 30
+
+#define MAX_STRING_LENGTH 80
+
+#define LIST_STRING_SIZE 80
+
+#define OUTPUT_PROP_LENGTH 24
+
+typedef struct real_list_t {
+  char name[LIST_STRING_SIZE];
+  double value;
+} real_list_t;
+
+typedef struct int_runtime_params_t {
+  char name[RUNTIME_PARAMETER_STRING_SIZE];
+  int value;
+} int_runtime_params_t;
+
+typedef struct real_runtime_params_t {
+  char name[RUNTIME_PARAMETER_STRING_SIZE];
+  double value;
+} real_runtime_params_t;
+
+typedef struct str_runtime_params_t {
+  char value[RUNTIME_PARAMETER_STRING_SIZE];
+  char name[RUNTIME_PARAMETER_STRING_SIZE];
+} str_runtime_params_t;
+
+typedef struct log_runtime_params_t {
+  int value;
+  char name[RUNTIME_PARAMETER_STRING_SIZE];
+} log_runtime_params_t;
+
+
+#define MAX_TIMER_PARENTS 20
+#define MAX_TIMER_CALL_STACK_DEPTH 20
+typedef struct timer_data_t {
+  char name[TIMER_NAME_STRING_SIZE];
+  double t_value[MAX_TIMER_PARENTS];
+  int    t_counts[MAX_TIMER_PARENTS];
+  int    t_on[MAX_TIMER_PARENTS];
+  int    t_stacks[MAX_TIMER_PARENTS][MAX_TIMER_CALL_STACK_DEPTH];
+  int    t_num_parents;
+  int    t_stack_sizes[MAX_TIMER_PARENTS];
+} full_timer_data_t;
+
+
+typedef struct sim_params_t {
+  int total_blocks;
+  int nsteps;
+  int nxb;
+  int nyb;
+  int nzb;
+  double time; 
+  double timestep;
+  double redshift;
+    
+} sim_params_t;
+
+typedef struct sim_params_sp_t {
+  int total_blocks;
+  int nsteps;
+  int nxb;
+  int nyb;
+  int nzb;
+  float time; 
+  float timestep;
+  float redshift;
+    
+} sim_params_sp_t;
+
+typedef struct sim_info_t {
+  int file_format_version;
+  char setup_call[400];
+  char file_creation_time[MAX_STRING_LENGTH];
+  char flash_version[MAX_STRING_LENGTH];
+  char build_date[MAX_STRING_LENGTH];
+  char build_dir[MAX_STRING_LENGTH];
+  char build_machine[MAX_STRING_LENGTH];
+  char cflags[400];
+  char fflags[400];
+  char setup_time_stamp[MAX_STRING_LENGTH];
+  char build_time_stamp[MAX_STRING_LENGTH];
+} sim_info_t;
+
+/* define some particle property constants */
+
+#if FLASH_NUMBER_OF_INT_PARTICLE_PROPS > 0
+#define NUMINTPROPS  2*((FLASH_NUMBER_OF_INT_PARTICLE_PROPS+1)/2)
+#else
+#define NUMINTPROPS  2
+#endif
+
+#if FLASH_NUMBER_OF_REAL_PARTICLE_PROPS > 0
+#define NUMREALPROPS FLASH_NUMBER_OF_REAL_PARTICLE_PROPS
+#else
+#define NUMREALPROPS 1
+#endif
+
+#define NUMACTUALINTPROPS FLASH_NUMBER_OF_INT_PARTICLE_PROPS
+#define NUMACTUALREALPROPS FLASH_NUMBER_OF_REAL_PARTICLE_PROPS
+
+
+/* set the dimension and grid variables -- the variable N_DIM is set 
+   in the compile line */
+
+
+/* mdim is the maximum dimension -- this is set in tree.fh */
+#define MDIM 3
+#define MGID 15
+
+/* 3-d problem */
+#if N_DIM == 3 
+
+#define NDIM  3
+
+#define NGID 15
+
+#define k2d 1
+#define k3d 1
+
+
+/* 2-d problem */
+#elif N_DIM == 2
+
+#define NDIM  2
+
+#define NGID 9
+
+#define k2d 1
+#define k3d 0
+
+
+/* 1-d problem */
+#else
+
+#define NDIM 1
+
+#define NGID 5
+
+#define k2d 0
+#define k3d 0
+
+#endif
+
+
+#endif
+
diff --git a/external/cosmotool/src/interpolate.cpp b/external/cosmotool/src/interpolate.cpp
new file mode 100644
index 0000000..9a9fc70
--- /dev/null
+++ b/external/cosmotool/src/interpolate.cpp
@@ -0,0 +1,278 @@
+#include 
+#include 
+#include 
+#include "interpolate.hpp"
+#include 
+#include 
+#include 
+#include 
+
+using namespace std;
+using namespace CosmoTool;
+  
+Interpolate::Interpolate(double *xs, double *values, uint32_t N, bool autofree,
+			 bool logx, bool logy)
+{
+  spline = gsl_spline_alloc (gsl_interp_linear, N);
+  gsl_spline_init (spline, xs, values, N);
+  accel_interp = gsl_interp_accel_alloc();
+
+  this->logx = logx;
+  this->logy = logy;
+  this->autoFree = autofree;
+}
+
+Interpolate::~Interpolate()
+{
+  if (spline != 0)
+    gsl_spline_free (spline);
+  if (accel_interp != 0)
+    gsl_interp_accel_free (accel_interp);
+}
+
+double Interpolate::compute(double x)
+  throw (InvalidRangeException)
+{
+  double y;
+  
+  if (logx)
+    x = log(x);
+
+  int err = gsl_spline_eval_e(spline, x, accel_interp, &y);
+
+  if (err)
+    throw InvalidRangeException("Interpolate argument outside range");
+
+  if (logy)
+    return exp(y);
+  else
+    return y;
+}
+
+double Interpolate::compute(double x) const
+  throw (InvalidRangeException)
+{
+  double y;
+
+  if (logx)
+    x = log(x);
+
+  int err = gsl_spline_eval_e(spline, x, 0, &y);
+
+  if (err)
+    throw InvalidRangeException("Interpolate argument outside range");
+
+  if (logy)
+    return exp(y);
+  else
+    return y;
+}
+
+
+
+
+double Interpolate::derivative(double x)
+  throw (InvalidRangeException)
+{
+  double y, dy, x0 = x;
+  
+  if (logx)
+    x0 = log(x0);
+
+  int err = gsl_spline_eval_deriv_e(spline, x0, accel_interp, &dy);
+
+  if (err)
+    throw InvalidRangeException("Interpolate argument outside range");
+
+  if (logy)
+    {
+      int err = gsl_spline_eval_e(spline, x0, accel_interp, &y);
+      
+      assert(err == 0);
+      
+      return dy*exp(y)/x0;
+    }
+  else
+    return dy;
+}
+
+uint32_t Interpolate::getNumPoints() const
+{
+  return spline->size;
+}
+
+void Interpolate::fillWithXY(double *x, double *y) const
+{
+  if (x != 0)
+    memcpy(x, spline->x, sizeof(double)*spline->size);
+  if (y != 0)
+    memcpy(y, spline->y, sizeof(double)*spline->size);
+}
+
+const Interpolate& Interpolate::operator=(const Interpolate& a)
+{
+  double *x, *y;
+
+  if (spline != NULL)
+    {
+      gsl_spline_free (spline);
+      gsl_interp_accel_free (accel_interp);
+    }
+
+  autoFree = true;
+  spline = gsl_spline_alloc (gsl_interp_linear, a.spline->size);
+  accel_interp = gsl_interp_accel_alloc ();
+  gsl_spline_init(spline, a.spline->x, a.spline->y, a.spline->size);
+  logx = a.logx;
+  logy = a.logy;
+}
+
+double Interpolate::getMaxX() const
+{
+  if (logx)
+    return exp(spline->x[spline->size-1]);
+  else
+    return spline->x[spline->size-1];
+}
+
+typedef struct {
+  double a, b;
+} MyPair;
+
+bool operator<(const MyPair& a, const MyPair& b)
+{
+  return a.a < b.a;
+}
+
+Interpolate CosmoTool::buildFromVector(const InterpolatePairs& v)
+{
+  double *x = new double[v.size()];
+  double *y = new double[v.size()];
+  
+  for (uint32_t i = 0; i < v.size(); i++)
+    {
+      x[i] = v[i].first;
+      y[i] = v[i].second;
+    }
+
+  Interpolate inter = Interpolate(x, y, v.size(), true);
+
+  delete[] x;
+  delete[] y;
+
+  return inter;  
+}
+
+Interpolate CosmoTool::buildInterpolateFromFile(const char *fname)
+  throw (NoSuchFileException)
+{
+  vector allData;
+  ifstream f(fname);
+
+  if (!f)
+    throw NoSuchFileException(fname);
+
+  do 
+    {
+      MyPair m;
+
+      if (!(f >> m.a >> m.b))
+	break;
+
+      allData.push_back(m);
+    }
+  while (1);
+
+  sort(allData.begin(), allData.end());
+
+  double *x = new double[allData.size()];
+  double *y = new double[allData.size()];
+  
+  for (uint32_t i = 0; i < allData.size(); i++)
+    {
+      x[i] = allData[i].a;
+      y[i] = allData[i].b;
+    }
+
+  Interpolate inter = Interpolate(x, y, allData.size(), true);
+
+  delete[] x;
+  delete[] y;
+
+  return inter;
+}
+
+Interpolate CosmoTool::buildInterpolateFromColumns(const char *fname, uint32_t col1, uint32_t col2, bool logx,
+						   bool logy)
+  throw (NoSuchFileException,InvalidRangeException)
+{
+  vector allData;
+  ifstream f(fname);
+
+  if (!f)
+    throw NoSuchFileException(fname);
+  
+  bool swapped = (col1 > col2);
+  uint32_t colMin = min(col1, col2);
+  uint32_t colMax = max(col1, col2);
+
+  do 
+    {
+      MyPair m;
+      string line;
+
+      if (getline(f, line).eof())
+	break;
+
+      istringstream iss(line);
+      double dummy;
+      double val1, val2;
+
+      for (uint32_t i = 0; i < colMin; i++)
+	iss >> dummy;
+      if (!(iss >> val1))
+	throw InvalidRangeException("Invalid first column");
+
+      if (col2 != col1)
+	{
+	  for (uint32_t i = 0; i < (colMax-colMin-1); i++)
+	    iss >> dummy;
+	  if (!(iss >> val2))
+	    throw InvalidRangeException("Invalid second column");
+	}
+      else
+	val2 = val2;
+
+      if (!swapped)
+	{
+	  m.a = val1;
+	  m.b = val2;
+	}
+      else
+	{
+	  m.a = val2;
+	  m.b = val1;
+	}
+
+      allData.push_back(m);
+    }
+  while (1);
+
+  sort(allData.begin(), allData.end());
+
+  double *x = new double[allData.size()];
+  double *y = new double[allData.size()];
+  
+  for (uint32_t i = 0; i < allData.size(); i++)
+    {
+      x[i] = logx ? log(allData[i].a) : allData[i].a;
+      y[i] = logy ? log(allData[i].b) : allData[i].b;
+    }
+
+  Interpolate inter = Interpolate(x, y, allData.size(), true, logx, logy);
+
+  delete[] x;
+  delete[] y;
+  
+  return inter;
+}
diff --git a/external/cosmotool/src/interpolate.hpp b/external/cosmotool/src/interpolate.hpp
new file mode 100644
index 0000000..a0ffacc
--- /dev/null
+++ b/external/cosmotool/src/interpolate.hpp
@@ -0,0 +1,65 @@
+#ifndef __CTOOL_INTERPOLATE_HPP
+#define __CTOOL_INTERPOLATE_HPP
+
+#include "config.hpp"
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace CosmoTool
+{
+  
+  class Interpolate
+  {
+  public:
+    Interpolate() : spline(0), accel_interp(0) { }
+    Interpolate(double *xs, double *values, uint32_t N, bool autofree = false,
+		bool logx = false, bool logy = false);
+    ~Interpolate();
+
+    double compute(double x)
+      throw (InvalidRangeException);
+    double compute(double x) const
+      throw (InvalidRangeException);
+    double derivative(double x)
+      throw (InvalidRangeException);
+
+    const Interpolate& operator=(const Interpolate& a);
+
+    uint32_t getNumPoints() const;
+    void fillWithXY(double *x, double *y) const;
+    double getMaxX() const;
+    double getXi(int i) const { return spline->x[i]; }
+    double getYi(int i) const { return spline->y[i]; }
+  protected:
+    gsl_interp_accel *accel_interp;
+    gsl_spline *spline;
+    bool autoFree;
+    bool logx, logy;
+  };
+
+  typedef std::vector< std::pair > InterpolatePairs;
+
+  Interpolate buildInterpolateFromFile(const char *fname)
+    throw (NoSuchFileException);
+  Interpolate buildInterpolateFromColumns(const char *fname, uint32_t col1, uint32_t col2, bool logx = false, bool logy = false)
+    throw (NoSuchFileException,InvalidRangeException);
+  Interpolate buildFromVector(const InterpolatePairs& v);
+
+
+  class FunctorInterpolate
+  {
+  public:
+    FunctorInterpolate(Interpolate& i) : m_i(i) {}    
+
+    double eval(double x) { return m_i.compute(x); }
+    double derivative(double x) { return m_i.derivative(x); }
+  private:
+    Interpolate& m_i;
+  };
+  
+};
+
+#endif
diff --git a/external/cosmotool/src/interpolate3d.hpp b/external/cosmotool/src/interpolate3d.hpp
new file mode 100644
index 0000000..8c165ff
--- /dev/null
+++ b/external/cosmotool/src/interpolate3d.hpp
@@ -0,0 +1,105 @@
+#ifndef __COSMO_INTERPOLATE3D_HPP
+#define __COSMO_INTERPOLATE3D_HPP
+
+#include "config.hpp"
+#include "field.hpp"
+#include 
+
+namespace CosmoTool
+{
+
+  template
+  class GridSampler
+  {
+  public:
+    typedef IType result_type;
+
+    GridSampler(IType *array_, int Nx_, int Ny_, int Nz_, int stride_)
+      : array(array_), Nx(Nx_), Ny(Ny_), Nz(Nz_), stride(stride_)
+    {
+    }
+
+    ~GridSampler()
+    {
+    }
+
+    IType& operator()(int x, int y, int z)
+      throw ()
+    {
+      while (x < 0)
+	x += Nx;
+      x %= Nx;
+      while (y < 0)
+	y += Ny;
+      y %= Ny;
+      while (z < 0)
+	z += Nz;
+      z %= Nz;
+      
+      uint32_t idx = x + Nx * (y + Ny * z);
+
+      return array[idx*stride];
+    }
+
+  private:
+    IType *array;
+    int Nx, Ny, Nz, stride;
+  };
+
+
+  // IType is the quantity to interpolate, 
+  template
+  class Interpolate3D
+  {
+  public:
+    typedef typename SampledFunction::result_type IType;
+
+    Interpolate3D(SampledFunction& f)
+      : sampler(f)
+    {
+    };
+
+    ~Interpolate3D()
+    {
+    };
+    
+    IType get(PosType x, PosType y, PosType z)
+      throw (InvalidArgumentException)
+    {
+      int ix = (int)std::floor(x);
+      int iy = (int)std::floor(y);
+      int iz = (int)std::floor(z);
+
+      PosType rx = x-ix;
+      PosType ry = y-iy;
+      PosType rz = z-iz;
+
+      IType v000 = sampler(ix,iy,iz);
+      IType v001 = sampler(ix,iy,iz+1);
+      IType v010 = sampler(ix,iy+1,iz);
+      IType v011 = sampler(ix,iy+1,iz+1);
+
+      IType v100 = sampler(ix+1,iy,iz);
+      IType v101 = sampler(ix+1,iy,iz+1);
+      IType v110 = sampler(ix+1,iy+1,iz);
+      IType v111 = sampler(ix+1,iy+1,iz+1);
+
+      return
+	((1-rx) * (1-ry) * (1-rz)) * v000 +
+	((1-rx) * (1-ry) *    rz)  * v001 +
+	((1-rx) *    ry  * (1-rz)) * v010 +
+	((1-rx) *    ry  *    rz)  * v011 +
+	(   rx  * (1-ry) * (1-rz)) * v100 +
+	(   rx  * (1-ry) *    rz)  * v101 +
+	(   rx  *    ry  * (1-rz)) * v110 +
+	(   rx  *    ry  *    rz)  * v111;
+    }
+
+  private:
+    SampledFunction& sampler;    
+    int Nx, Ny, Nz;
+  };
+
+};
+
+#endif
diff --git a/external/cosmotool/src/kdtree_leaf.hpp b/external/cosmotool/src/kdtree_leaf.hpp
new file mode 100644
index 0000000..145fa57
--- /dev/null
+++ b/external/cosmotool/src/kdtree_leaf.hpp
@@ -0,0 +1,107 @@
+#ifndef __LEAF_KDTREE_HPP
+#define __LEAF_KDTREE_HPP
+
+#include 
+#include "config.hpp"
+#include "bqueue.hpp"
+
+namespace CosmoTool {
+
+  template 
+  struct KDLeafDef
+  {
+    typedef CType CoordType;
+    typedef float KDLeafCoordinates[N];
+  };
+  
+  template
+  struct KDLeafCell
+  {
+    bool active;
+    ValType val;
+    typename KDLeafDef::KDLeafCoordinates coord;
+  };
+
+  class NotEnoughCells: public Exception
+  {
+  public:
+    NotEnoughCells() : Exception() {}
+    ~NotEnoughCells() throw () {}
+  };
+
+  template
+  struct KDLeafTreeNode
+  {
+    bool leaf;
+    union {
+      KDLeafCell *value;
+      KDLeafTreeNode *children[2];
+    };
+    typename KDLeafDef::KDLeafCoordinates minBound, maxBound;
+#ifdef __KDLEAF_TREE_NUMNODES
+    uint32_t numNodes;
+#endif
+  };
+
+  template
+  class KDLeafTree
+  {
+  public:
+    typedef typename KDLeafDef::CoordType CoordType;
+    typedef typename KDLeafDef::KDLeafCoordinates coords;
+    typedef KDLeafCell Cell;
+    typedef KDLeafTreeNode Node;
+
+    KDLeafTree(Cell *cells, uint32_t Ncells);
+    ~KDLeafTree();
+
+    Node *getRoot() { return root; }
+
+    void optimize();
+
+    Node *getAllNodes() { return nodes; }
+    uint32_t getNumNodes() const { return lastNode; }
+
+    uint32_t countActives() const;
+
+    CoordType computeDistance(const Cell *cell, const coords& x) const;
+
+#ifdef __KDLEAF_TREE_NUMNODES
+    uint32_t getNumberInNode(const Node *n) const { return n->numNodes; }
+#else
+    uint32_t getNumberInNode(const Node *n) const {
+      if (n->leaf)
+        return 1;
+      if (n == 0) 
+        return 0;
+      return getNumberInNode(n->children[0])+getNumberInNode(n->children[1]);
+    }
+#endif
+
+    double countInRange(CType sLo, CType sHi, Node *root1 = 0, Node *root2 = 0) const;
+
+  protected:
+    Node *nodes;
+    uint32_t numNodes, numCells;
+    uint32_t lastNode;
+
+    Node *root;
+    Cell **sortingHelper;
+
+    Node *buildTree(Cell **cell0,
+		    uint32_t NumCells,
+		    uint32_t depth,
+		    coords minBound,
+		    coords maxBound);
+    
+    double recursiveCountInRange(Node *na, Node *nb, CType sLo, CType sHi) const;
+  };
+
+  template
+  uint32_t gatherActiveCells(KDLeafCell **cells, uint32_t numCells);
+
+};
+
+#include "kdtree_leaf.tcc"
+
+#endif
diff --git a/external/cosmotool/src/kdtree_leaf.tcc b/external/cosmotool/src/kdtree_leaf.tcc
new file mode 100644
index 0000000..704b488
--- /dev/null
+++ b/external/cosmotool/src/kdtree_leaf.tcc
@@ -0,0 +1,308 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace CosmoTool {
+
+  template
+  class CellCompare
+  {
+  public:
+    CellCompare(int k)
+    {
+      rank = k;
+    }
+
+    bool operator()(const KDLeafCell *a, const KDLeafCell *b) const
+    {
+      return (a->coord[rank] < b->coord[rank]);
+    }
+  protected:
+    int rank;
+  };
+
+  template
+  KDLeafTree::~KDLeafTree()
+  {
+  }
+
+  template
+  KDLeafTree::KDLeafTree(Cell *cells, uint32_t Ncells)
+  {
+    numNodes = Ncells*3;
+    numCells = Ncells;
+    nodes = new Node[numNodes];
+
+    sortingHelper = new Cell *[Ncells];
+    for (uint32_t i = 0; i < Ncells; i++)
+	sortingHelper[i] = &cells[i];    
+
+    optimize();
+  }
+
+  template
+  void KDLeafTree::optimize()
+  {
+    coords absoluteMin, absoluteMax;
+
+    std::cout << "Optimizing the tree..." << std::endl;
+    uint32_t activeCells = gatherActiveCells(sortingHelper, numCells);
+    std::cout << "  number of active cells = " << activeCells << std::endl;
+
+    lastNode = 0;
+    for (int i = 0; i < N; i++)
+      {
+	absoluteMin[i] = std::numeric_limits::max();
+	absoluteMax[i] = -std::numeric_limits::max();
+      }
+    // Find min and max corner
+    for (uint32_t i = 0; i < activeCells; i++)
+      {
+        KDLeafCell *cell = sortingHelper[i];
+
+        for (int k = 0; k < N; k++) {
+          if (cell->coord[k] < absoluteMin[k])
+            absoluteMin[k] = cell->coord[k];
+          if (cell->coord[k] > absoluteMax[k])
+            absoluteMax[k] = cell->coord[k];
+        }
+      }
+    
+    std::cout << "   rebuilding the tree..." << std::endl;
+    root = buildTree(sortingHelper, activeCells, 0, absoluteMin, absoluteMax);    
+    std::cout << "   done." << std::endl;
+  }
+
+  template
+  uint32_t gatherActiveCells(KDLeafCell **cells,
+			     uint32_t Ncells)
+  {
+    uint32_t swapId = Ncells-1;
+    uint32_t i = 0;
+
+    while (!cells[swapId]->active && swapId > 0)
+      swapId--;
+
+    while (i < swapId)
+      {
+	if (!cells[i]->active)
+	  {
+	    std::swap(cells[i], cells[swapId]);
+	    while (!cells[swapId]->active && swapId > i)
+	      {
+		swapId--;
+	      }
+	  }
+	i++;
+      }
+    return swapId+1;
+  }
+
+  template
+  KDLeafTreeNode *
+        KDLeafTree::buildTree(Cell **cell0,
+					       uint32_t Ncells,
+					       uint32_t depth,
+					       coords minBound,
+					       coords maxBound)
+  {
+    if (Ncells == 0)
+      return 0;
+
+    int axis = depth % N;
+    assert(lastNode != numNodes);
+    Node *node = &nodes[lastNode++];
+    uint32_t mid = Ncells/2;
+    coords tmpBound;
+    
+    // Isolate the environment
+    {
+      CellCompare compare(axis);
+      std::sort(cell0, cell0+Ncells, compare);
+    }
+
+    node->leaf = false;
+    memcpy(&node->minBound[0], &minBound[0], sizeof(coords));
+    memcpy(&node->maxBound[0], &maxBound[0], sizeof(coords));
+
+    if (Ncells == 1)
+      {
+	node->leaf = true;
+	node->value = *cell0;
+
+#ifdef __KDLEAF_TREE_NUMNODES
+	node->numNodes = 1;
+#endif
+	return node;
+      }
+
+    memcpy(tmpBound, maxBound, sizeof(coords));
+    tmpBound[axis] = (*(cell0+mid))->coord[axis];
+    depth++;
+    node->children[0] = buildTree(cell0, mid, depth, minBound, tmpBound);
+    
+    memcpy(tmpBound, minBound, sizeof(coords));
+    tmpBound[axis] = (*(cell0+mid))->coord[axis];
+    node->children[1] = buildTree(cell0+mid, Ncells-mid, depth, 
+				  tmpBound, maxBound);
+
+#ifdef __KDLEAF_TREE_NUMNODES
+    node->numNodes = (node->children[0] != 0) ? node->children[0]->numNodes : 0;
+    node->numNodes += (node->children[1] != 0) ? node->children[1]->numNodes : 0;
+#endif
+
+    return node;
+  }
+
+  template
+  uint32_t KDLeafTree::countActives() const
+  {
+    uint32_t numActive = 0;
+    for (uint32_t i = 0; i < lastNode; i++)
+      {
+	if (nodes[i].value->active)
+	  numActive++;
+      }
+    return numActive;
+  }
+
+  template
+  typename KDLeafDef::CoordType
+  KDLeafTree::computeDistance(const Cell *cell, const coords& x) const
+  {
+    CoordType d2 = 0;
+
+    for (int i = 0; i < N; i++)
+      {
+	CoordType delta = cell->coord[i] - x[i];
+	d2 += delta*delta;
+      }
+    return d2;
+  }
+
+ template
+ double KDLeafTree::countInRange(CType sLo, CType sHigh, Node *root1, Node *root2) const
+ {
+   double result = recursiveCountInRange((root1 == 0) ? root : root1,
+				(root2 == 0) ? root : root2, 
+				sLo*sLo, sHigh*sHigh); 
+   return result;
+ }
+
+ 
+ template
+ double KDLeafTree::recursiveCountInRange(Node *na, Node *nb,
+                                                         CType sLo, CType sHi) const
+ {
+   assert(nb != 0);
+   if (na == 0)
+     {
+       return 0;
+     }
+   
+   uint32_t numNa = getNumberInNode(na);
+   uint32_t numNb = getNumberInNode(nb);
+   double Cleft, Cright;
+   CType minDist, maxDist;
+
+   if (numNa == 1 && numNb == 1)
+     {
+       assert(na->leaf && nb->leaf);
+       CType ab_dist = computeDistance(na->value, nb->value->coord);
+       if (ab_dist >= sLo && ab_dist < sHi)
+	 return 1;
+       else
+	 return 0;
+     }
+   assert(numNa > 1 || numNb > 1);
+   
+   bool overlapping_a = true, overlapping_b = true;
+   for (int k = 0; k < N; k++) 
+     {
+       bool min_a_in_B = 
+	 ((na->minBound[k] >= nb->minBound[k] && 
+	   na->minBound[k] <= nb->maxBound[k]));
+       bool max_a_in_B =
+	 ((na->maxBound[k] >= nb->minBound[k] && 
+	   na->maxBound[k] <= nb->maxBound[k]));
+       bool min_b_in_A = 
+	 ((nb->minBound[k] >= na->minBound[k] && 
+	   nb->minBound[k] <= na->maxBound[k]));
+       bool max_b_in_A = 
+	 ((nb->maxBound[k] >= na->minBound[k] && 
+	   nb->maxBound[k] <= na->maxBound[k]));
+
+      if (!min_a_in_B && !max_a_in_B)
+         overlapping_a = false;
+      if (!min_b_in_A && !max_b_in_A)
+         overlapping_b = false;
+     }
+
+   if (overlapping_a || overlapping_b)
+     {
+        minDist = 0;
+        maxDist = 0;
+        for (int k = 0; k < N; k++)
+        {
+          CType delta = max(nb->maxBound[k]-na->minBound[k],na->maxBound[k]-nb->minBound[k]);
+          maxDist += delta*delta;
+        }
+     }
+   else
+     {
+       minDist = maxDist = 0;
+       for (int k = 0; k < N; k++)
+         {
+           CType delta2;
+           delta2 = max(nb->maxBound[k]-na->minBound[k],
+			na->maxBound[k]-nb->minBound[k]);
+           maxDist += delta2*delta2;
+         }
+       // mins and maxs
+       CType minmax[N][2];
+       for (int k = 0; k < N; k++)
+	 {
+	   if (na->minBound[k] < nb->minBound[k])
+	     {
+	       minmax[k][1] = na->maxBound[k];
+	       minmax[k][0] = nb->minBound[k];
+	     }
+	   else
+	     {
+	       minmax[k][1] = nb->maxBound[k];
+	       minmax[k][0] = na->minBound[k];
+	     }
+	 }
+       for (int k = 0; k < N; k++)
+	 {
+	   CType delta = max(minmax[k][0]-minmax[k][1], 0.);
+	   minDist += delta*delta;
+	 }
+     }
+   
+   if (minDist >= sHi)
+     return 0;
+   if (maxDist < sLo)
+     return 0;
+   
+   if (sLo <= minDist && maxDist < sHi)
+     return ((double)numNa)*numNb;
+   
+   if (numNa < numNb) 
+     {
+       assert(!nb->leaf);
+       Cleft = recursiveCountInRange(nb->children[0], na, sLo, sHi);
+       Cright = recursiveCountInRange(nb->children[1], na, sLo, sHi);
+     }
+   else
+     {
+       assert(!na->leaf);
+       Cleft = recursiveCountInRange(na->children[0], nb, sLo, sHi);
+       Cright = recursiveCountInRange(na->children[1], nb, sLo, sHi);
+     }
+   return Cleft+Cright;
+ }
+
+};
diff --git a/external/cosmotool/src/kdtree_splitters.hpp b/external/cosmotool/src/kdtree_splitters.hpp
new file mode 100644
index 0000000..4092cde
--- /dev/null
+++ b/external/cosmotool/src/kdtree_splitters.hpp
@@ -0,0 +1,134 @@
+#ifndef __KDTREE_SPLITTERS_HPP
+#define __KDTREE_SPLITTERS_HPP
+
+#include 
+
+namespace CosmoTool
+{
+
+  template 
+  struct KD_homogeneous_cell_splitter
+  {
+    typedef typename KDDef::KDCoordinates coords; 
+    typedef typename KDDef::CoordType ctype; 
+
+
+    void check_splitting(KDCell **cells, uint32_t Ncells, int axis, uint32_t split_index, ctype midCoord)
+    {
+      ctype delta = std::numeric_limits::max();
+      assert(split_index < Ncells);
+      assert(axis < N);
+      for (uint32_t i = 0; i < split_index; i++)
+	{
+	  assert(cells[i]->coord[axis] <= midCoord);
+	  delta = min(midCoord-cells[i]->coord[axis], delta);
+	}
+      for (uint32_t i = split_index+1; i < Ncells; i++)
+	{
+	  assert(cells[i]->coord[axis] > midCoord);
+	  delta = min(cells[i]->coord[axis]-midCoord, delta);
+	}
+      assert(delta >= 0);
+      assert (std::abs(cells[split_index]->coord[axis]-midCoord) <= delta);
+    } 
+    
+    void operator()(KDCell **cells, uint32_t Ncells, uint32_t& split_index, int axis, coords minBound, coords maxBound)
+    {
+      if (Ncells == 1)
+	{
+	  split_index = 0;
+	  return;
+	}
+
+      ctype midCoord = 0.5*(maxBound[axis]+minBound[axis]);
+      uint32_t below = 0, above = Ncells-1;
+      ctype delta_min = std::numeric_limits::max();
+      uint32_t idx_min = std::numeric_limits::max();     
+
+      while (below < above)
+	{
+          ctype delta = cells[below]->coord[axis]-midCoord;
+	  if (delta > 0)
+            {
+              if (delta < delta_min)
+                {
+                  delta_min = delta;
+                  idx_min = above;
+                }
+	      std::swap(cells[below], cells[above--]);
+            }
+	  else
+            {
+              if (-delta < delta_min)
+                {
+                  delta_min = -delta;
+                  idx_min = below;
+                }
+	      below++;
+            }
+	}
+      // Last iteration
+      {
+	ctype delta = cells[below]->coord[axis]-midCoord;	
+	if (delta > 0)
+	  {
+	    if (delta < delta_min)
+	      {
+		delta_min = delta;
+		idx_min = above;
+	      }
+	  }
+	else
+	  {
+	    if (-delta < delta_min)
+	      {
+		delta_min = -delta;
+		idx_min = above;
+	      }
+	  }
+      }
+
+      if (idx_min != above)
+        {
+          bool cond1 = cells[idx_min]->coord[axis] > midCoord;
+          bool cond2 = cells[above]->coord[axis] > midCoord;
+          if ((cond1 && cond2) || (!cond1 && !cond2))
+            {
+              split_index = above;
+              std::swap(cells[above], cells[idx_min]);
+            }
+          else if (cond2)
+            {
+              if (above >= 1)
+		{
+		  split_index = above-1;
+		  std::swap(cells[above-1], cells[idx_min]);
+		}
+	      else
+		split_index = 0;
+	      assert(split_index >= 0);
+            }
+          else
+            {
+	      if (above+1 < Ncells)
+		{
+		  split_index = above+1;
+		  std::swap(cells[above+1], cells[idx_min]);
+		}
+	      else
+		split_index = Ncells-1;
+
+	      assert(split_index < Ncells);
+            }
+         }
+       else split_index = above;
+
+
+      //      check_splitting(cells, Ncells, axis, split_index, midCoord);
+    }
+  };
+
+
+};
+
+#endif
diff --git a/external/cosmotool/src/loadFlash.cpp b/external/cosmotool/src/loadFlash.cpp
new file mode 100644
index 0000000..ada498d
--- /dev/null
+++ b/external/cosmotool/src/loadFlash.cpp
@@ -0,0 +1,104 @@
+/* Reads in FLASH v3 files in HDF5 format */
+
+#include 
+#include 
+#include 
+#include 
+#include "load_data.hpp"
+#include "loadFlash.hpp"
+#include "h5_readFlash.hpp"
+#include "H5Cpp.h"
+
+using namespace CosmoTool;
+using namespace std;
+using namespace H5;
+
+SimuData *CosmoTool::loadFlashMulti(const char *fname, int id, int loadflags)
+{
+  SimuData *data;
+  int p, n;
+  H5File *fileID;
+  H5std_string filename;
+  //char filename[81];
+  double lbox, time, hubble, omegam, omegalambda, redshift;
+  int npart;
+
+  const double kpc2cm = 3.08568025e21;
+  const double km2cm = 1.e5;
+  const double hubble2cm = 3.240779270005e-18;
+
+  if (id != 0)
+    throw NoSuchFileException();
+
+  data = new SimuData;
+  if (data == 0) {
+    return 0;
+  }
+
+  filename = fname;
+  try {
+    H5File file (filename, H5F_ACC_RDONLY);
+
+    // simulation info
+    h5_read_flash3_header_info(&file, &time, &redshift);
+    data->time    = 1/(1+redshift);    
+
+    h5_read_runtime_parameters(&file, &lbox, &npart, &hubble, &omegam, &omegalambda);
+    data->TotalNumPart = data->NumPart = npart;
+    data->Hubble = hubble/hubble2cm;
+    data->BoxSize = lbox/kpc2cm*data->Hubble;
+    data->Omega_M = omegam;
+    data->Omega_Lambda = omegalambda;
+
+    // particle data
+    if (loadflags& NEED_POSITION) {
+      for (int i = 0; i < 3; i++) {
+        data->Pos[i] = new float[data->NumPart];
+        if (data->Pos[i] == 0) {
+	  delete data;
+	return 0;
+      }
+    } }
+
+
+    if (loadflags &NEED_VELOCITY) {
+    for (int i = 0; i < 3; i++) {
+      data->Vel[i] = new float[data->NumPart];
+      if (data->Vel[i] == 0) {
+	delete data;
+	return 0;
+      }
+    } }
+ 
+    if (loadflags & NEED_GADGET_ID) {
+    data->Id = new int[data->NumPart];
+    if (data->Id == 0) {
+      delete data;
+      return 0;
+    }
+    }
+
+    int offset = 0;
+
+    if (loadflags &(NEED_GADGET_ID|NEED_POSITION|NEED_VELOCITY))
+    h5_read_flash3_particles(&file, &npart, &npart, &offset, 
+			     data->Pos[0], data->Pos[1], data->Pos[2], 
+			     data->Vel[0], data->Vel[1], data->Vel[2],
+			     data->Id); 
+
+    for (int i = 0; i < 3; i++) {
+      for (int n = 0; n < data->NumPart; n++) {
+	if (loadflags& NEED_POSITION) data->Pos[i][n] = data->Pos[i][n] * data->Hubble / kpc2cm;
+	if (loadflags&NEED_VELOCITY) data->Vel[i][n] = data->Vel[i][n] * data->Hubble / km2cm;
+      }
+    }
+
+    file.close();
+  } catch (const FileIException& e) {
+    throw NoSuchFileException();
+  }
+
+  return data;
+}
+
+
diff --git a/external/cosmotool/src/loadFlash.hpp b/external/cosmotool/src/loadFlash.hpp
new file mode 100644
index 0000000..3d7d8af
--- /dev/null
+++ b/external/cosmotool/src/loadFlash.hpp
@@ -0,0 +1,13 @@
+#ifndef __COSMO_LOAD_FLASH_HPP
+#define __COSMO_LOAD_FLASH_HPP
+
+#include "load_data.hpp"
+#include "loadSimu.hpp"
+
+namespace CosmoTool {
+  
+  SimuData *loadFlashMulti(const char *fname, int id, int flags);
+  
+};
+
+#endif
diff --git a/external/cosmotool/src/loadFlash_dummy.cpp b/external/cosmotool/src/loadFlash_dummy.cpp
new file mode 100644
index 0000000..2df6604
--- /dev/null
+++ b/external/cosmotool/src/loadFlash_dummy.cpp
@@ -0,0 +1,9 @@
+#include "load_data.hpp"
+#include "loadFlash.hpp"
+
+using namespace CosmoTool;
+
+SimuData *CosmoTool::loadFlashMulti(const char *fname, int id, int loadflags)
+{
+  return 0;
+}
diff --git a/external/cosmotool/src/loadGadget.cpp b/external/cosmotool/src/loadGadget.cpp
new file mode 100644
index 0000000..a824d28
--- /dev/null
+++ b/external/cosmotool/src/loadGadget.cpp
@@ -0,0 +1,349 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "load_data.hpp"
+#include "loadGadget.hpp"
+#include "fortran.hpp"
+
+using namespace CosmoTool;
+using namespace std;
+
+PurePositionData *CosmoTool::loadGadgetPosition(const char *fname)
+{
+  PurePositionData *data;
+  int p, n;
+  UnformattedRead f(fname);
+  GadgetHeader h;
+  
+  data = new PurePositionData;
+  f.beginCheckpoint();
+  for (int i = 0; i < 6; i++)
+    h.npart[i] = f.readInt32();
+  for (int i = 0; i < 6; i++)    
+    h.mass[i] = f.readReal64();
+  h.time = f.readReal64();
+  h.redshift = f.readReal64();
+  h.flag_sfr = f.readInt32();
+  h.flag_feedback = f.readInt32();
+  for (int i = 0; i < 6; i++)
+    h.npartTotal[i] = f.readInt32();
+  h.flag_cooling = f.readInt32();
+  h.num_files = f.readInt32();
+  data->BoxSize = h.BoxSize = f.readReal64();
+  h.Omega0 = f.readReal64();
+  h.OmegaLambda = f.readReal64();
+  h.HubbleParam = f.readReal64();
+  f.endCheckpoint(true);
+  
+  data->NumPart = 0;
+  for(int k=0; k<5; k++)
+    data->NumPart += h.npart[k];
+  
+  data->pos = new FCoordinates[data->NumPart];
+  
+  f.beginCheckpoint();
+  for(int k = 0, p = 0; k < 5; k++) {
+    for(int n = 0; n < h.npart[k]; n++) {
+      data->pos[p][0] = f.readReal32();
+      data->pos[p][1] = f.readReal32();
+      data->pos[p][2] = f.readReal32();
+      p++;
+    }
+  }
+  f.endCheckpoint();
+  
+  // Skip velocities
+  f.skip((long)data->NumPart*3+2*4);
+  // Skip ids
+
+  return data;
+}
+
+
+
+
+SimuData *CosmoTool::loadGadgetMulti(const char *fname, int id, int loadflags, int GadgetFormat)
+{
+  SimuData *data;
+  int p, n;
+  UnformattedRead *f;
+  GadgetHeader h;
+  float velmul;
+
+  if (id >= 0) {
+    int k = snprintf(0, 0, "%s.%d", fname, id)+1;
+    char *out_fname = new char[k];
+    snprintf(out_fname, k, "%s.%d", fname, id);
+
+    f = new UnformattedRead(out_fname);
+    if (f == 0)
+      return 0;
+
+    delete out_fname;
+
+  } else {
+
+    f = new UnformattedRead(fname);
+    if (f == 0)
+      return 0;
+
+  }
+
+  data = new SimuData;
+  if (data == 0) {
+    delete f;
+    return 0;
+  }
+
+  long NumPart = 0, NumPartTotal = 0;
+
+  try
+    {
+      f->beginCheckpoint();
+      for (int i = 0; i < 6; i++)
+	h.npart[i] = f->readInt32();
+      for (int i = 0; i < 6; i++)    
+	h.mass[i] = f->readReal64();
+      data->time = h.time = f->readReal64();
+      h.redshift = f->readReal64();
+      h.flag_sfr = f->readInt32();
+      h.flag_feedback = f->readInt32();
+      for (int i = 0; i < 6; i++)
+	h.npartTotal[i] = f->readInt32();
+      h.flag_cooling = f->readInt32();
+      h.num_files = f->readInt32();
+      data->BoxSize = h.BoxSize = f->readReal64();
+      data->Omega_M = h.Omega0 = f->readReal64();
+      data->Omega_Lambda = h.OmegaLambda = f->readReal64();
+      data->Hubble = h.HubbleParam = f->readReal64();
+      f->endCheckpoint(true);
+      
+      for(int k=0; k<6; k++)
+	{
+	  NumPart += h.npart[k];
+	  NumPartTotal += (id < 0) ? h.npart[k] : h.npartTotal[k];
+	}
+      data->NumPart = NumPart;
+      data->TotalNumPart = NumPartTotal;
+      if (GadgetFormat == 1)
+        velmul = sqrt(h.time);
+      else if (GadgetFormat == 2)
+        velmul = 1/(h.time);
+      else {
+        cerr << "unknown gadget format" << endl;
+	abort();
+      }
+    }
+  catch (const InvalidUnformattedAccess& e)
+    {
+      cerr << "Invalid format while reading header" << endl;
+      delete data;
+      delete f;
+      return 0;
+    }
+
+  if (loadflags & NEED_TYPE)
+    {
+      int p = 0;
+
+      data->type = new int[data->NumPart];
+      for (int k = 0; k < 6; k++)
+	for (int n = 0; n < h.npart[k]; n++,p++)
+	  data->type[p] = k;
+    }
+
+  if (loadflags & NEED_POSITION) {
+      
+    for (int i = 0; i < 3; i++) {
+      data->Pos[i] = new float[data->NumPart];
+      if (data->Pos[i] == 0) {
+	delete data;
+	return 0;
+      }
+    }
+    
+    try
+      {
+	f->beginCheckpoint();
+	for(int k = 0, p = 0; k < 6; k++) {
+	  for(int n = 0; n < h.npart[k]; n++) {
+	    data->Pos[0][p] = f->readReal32();
+	    data->Pos[1][p] = f->readReal32();
+	    data->Pos[2][p] = f->readReal32();
+	    p++;
+	  }
+	}
+	f->endCheckpoint();
+      }
+    catch (const InvalidUnformattedAccess& e)
+      {
+	cerr << "Invalid format while reading positions" << endl;
+	delete f;
+	delete data;
+	return 0;
+      }
+    
+  } else {
+    // Skip positions
+    f->skip(NumPart * 3 * sizeof(float) + 2*4);
+  }
+  
+  if (loadflags & NEED_VELOCITY) {
+    for (int i = 0; i < 3; i++)
+      {
+	data->Vel[i] = new float[data->NumPart];
+	if (data->Vel[i] == 0) 
+	  {
+	    delete f;
+	    delete data;
+	    return 0;
+	  }
+      }
+    
+    try
+      {
+	f->beginCheckpoint();
+	for(int k = 0, p = 0; k < 6; k++) {
+	  for(int n = 0; n < h.npart[k]; n++) {
+	    // THIS IS GADGET 1
+	    data->Vel[0][p] = f->readReal32()*velmul;
+	    data->Vel[1][p] = f->readReal32()*velmul;
+	    data->Vel[2][p] = f->readReal32()*velmul;
+	    p++;
+	  }
+	}
+	f->endCheckpoint();
+      }
+    catch (const InvalidUnformattedAccess& e)
+      {
+	cerr << "Invalid format while reading velocities" << endl;
+	delete f;
+	delete data;
+	return 0;
+      }
+    
+    // THE VELOCITIES ARE IN PHYSICAL COORDINATES
+///    // TODO: FIX THE UNITS OF THESE FUNKY VELOCITIES !!!
+  } else {
+    // Skip velocities
+    f->skip(NumPart*3*sizeof(float)+2*4);
+  }
+
+  // Skip ids
+  if (loadflags & NEED_GADGET_ID) {
+    try
+      {
+	f->beginCheckpoint();
+	data->Id = new int[data->NumPart];
+	if (data->Id == 0)
+	  {
+	    delete f;
+	    delete data;
+	    return 0;
+	  }
+	
+	for(int k = 0, p = 0; k < 6; k++)
+	  {
+	    for(int n = 0; n < h.npart[k]; n++)
+	      {
+		data->Id[p] = f->readInt32();
+		p++;
+	      }
+	  }
+	f->endCheckpoint();
+      }
+    catch (const InvalidUnformattedAccess& e)
+      {
+	cerr << "Invalid formatted while reading ID" << endl;
+	delete f;
+	delete data;
+	return 0;
+      }
+  } else {
+    f->skip(2*4);
+    for (int k = 0; k < 6; k++)
+      f->skip(h.npart[k]*4);
+  }
+
+  delete f;
+
+  return data;
+}
+
+
+
+void CosmoTool::writeGadget(const char *fname, SimuData *data, int GadgetFormat)
+{
+  UnformattedWrite *f;
+  int npart[6];
+  float mass[6];
+
+  if (data->Pos[0] == 0 || data->Vel[0] == 0 || data->Id == 0)
+    return;
+
+  f = new UnformattedWrite(fname);
+  if (f == 0)
+    return;
+
+  for (int i = 0; i < 6; i++)
+    {
+      npart[i] = 0;
+      mass[i] = 0;
+    }
+  mass[1] = 1.0;
+
+  npart[1] = data->NumPart;
+  
+  f->beginCheckpoint();
+  for (int i = 0; i < 6; i++)
+    f->writeInt32(npart[i]);
+  for (int i = 0; i < 6; i++)
+    f->writeReal64(mass[i]);
+
+  f->writeReal64(data->time);
+  f->writeReal64(1/data->time-1);
+  f->writeInt32(0);
+  f->writeInt32(0);
+
+  for (int i = 0; i < 6; i++)
+    f->writeInt32(npart[i]);
+  f->writeInt32(0);
+  f->writeInt32(1);
+  f->writeReal64(data->BoxSize);
+  f->writeReal64(data->Omega_M);
+  f->writeReal64(data->Omega_Lambda);
+  f->writeReal64(data->Hubble);
+  char buf[100] = { 0, };
+  f->writeOrderedBuffer(buf, 96);
+  f->endCheckpoint();
+ 
+  f->beginCheckpoint();
+  for(int n = 0; n < data->NumPart; n++) {
+    for (int k = 0; k < 3; k++)
+      f->writeReal32(data->Pos[k][n]);
+  }
+  f->endCheckpoint();
+
+  float velmul = 1.0;
+  if (GadgetFormat == 1)
+    velmul = sqrt(data->time);
+
+  f->beginCheckpoint();
+  for(int n = 0; n < data->NumPart; n++) {
+    for (int k = 0; k < 3; k++)
+      f->writeReal32(data->Vel[k][n]/velmul);
+  }
+  f->endCheckpoint();
+
+  f->beginCheckpoint();
+  for(int n = 0; n < data->NumPart; n++)
+    {
+      f->writeInt32(data->Id[n]);
+    }
+  f->endCheckpoint();
+  delete f;
+}
+
+  
diff --git a/external/cosmotool/src/loadGadget.hpp b/external/cosmotool/src/loadGadget.hpp
new file mode 100644
index 0000000..69f0ea6
--- /dev/null
+++ b/external/cosmotool/src/loadGadget.hpp
@@ -0,0 +1,18 @@
+#ifndef __COSMO_LOAD_GADGET_HPP
+#define __COSMO_LOAD_GADGET_HPP
+
+#include "load_data.hpp"
+#include "loadSimu.hpp"
+
+namespace CosmoTool {
+  
+  PurePositionData *loadGadgetPosition(const char *fname);
+
+  SimuData *loadGadgetMulti(const char *fname, int id, int flags, int GadgetFormat = 1);
+ 
+  // Only single snapshot supported
+  void writeGadget(const char *fname, SimuData *data, int GadgetFormat = 1);
+ 
+};
+
+#endif
diff --git a/external/cosmotool/src/loadRamses.cpp b/external/cosmotool/src/loadRamses.cpp
new file mode 100644
index 0000000..5cd2e1e
--- /dev/null
+++ b/external/cosmotool/src/loadRamses.cpp
@@ -0,0 +1,1002 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "loadRamses.hpp"
+#include "load_data.hpp"
+#include "fortran.hpp"
+#include 
+
+using namespace std;
+
+CosmoTool::GadgetData *CosmoTool::loadRamses(const char *name, bool quiet)
+{
+  GadgetData *gd = (GadgetData *)malloc(sizeof(GadgetData));
+  int id = 1;
+  uint32_t totPart = 0;
+  
+  if (!quiet)
+    cout << "Detecting number of files and particles..." << endl;
+  while (1)
+    {
+      ostringstream ss_fname;  
+      ss_fname << name << setfill('0') << setw(5) << id;
+      string fname = ss_fname.str();
+
+      if (!quiet)
+	cout <<  " ... " << fname << endl;
+
+      try
+	{
+	  UnformattedRead infile(fname);
+	  
+	  int nCpu, ndim, nPar;
+	  
+	  infile.beginCheckpoint();
+	  nCpu = max(1,infile.readInt32());
+	  infile.endCheckpoint();
+	  
+	  infile.beginCheckpoint();
+	  ndim = infile.readInt32();
+	  infile.endCheckpoint();
+	  
+	  infile.beginCheckpoint();
+	  nPar = infile.readInt32();
+	  infile.endCheckpoint();
+	  
+	  if (!quiet)
+	    cout << "    NUMCPU=" << nCpu <<  " NUMDIM=" << ndim << " NumPart=" << nPar << endl;
+	  
+	  totPart += nPar;
+	  id++;
+	}
+      catch (const NoSuchFileException& e)
+	{
+	  break;
+	}
+    }
+
+  assert (totPart <= ((~(size_t)0)/sizeof(ParticleState)));
+  size_t memSize = sizeof(ParticleState)*(size_t)totPart;
+  if (!quiet)
+    cout << " Needing " << memSize / 1024 << " kBytes" << endl;
+  gd->particles = (ParticleState *)malloc(memSize);
+  assert(gd->particles != 0);
+  gd->NumPart = totPart;
+  gd->ntot_withmasses = totPart;
+  gd->header.npart[0] = totPart;
+  for (int k = 1; k < 6; k++)
+    {
+      gd->header.npart[k] = 0;
+      gd->header.mass[k] = 0;
+    }
+  gd->header.num_files = id;
+  gd->header.BoxSize = 200.0 * 1000; // kPc
+  gd->header.Omega0 = 0.30; // ????
+  gd->header.OmegaLambda = 0.70; // ????
+  gd->header.HubbleParam = 0.70; // ????
+
+  if (!quiet)
+    cout << " Total number part=" << totPart << endl
+	 << "Loading particles ..." << endl;
+
+  uint32_t curPos = 0;
+  id = 1;
+  while (1)
+    {
+      ostringstream ss_fname;  
+      ss_fname << name << setfill('0') << setw(5) << id;
+      string fname = ss_fname.str();
+
+      if (!quiet)
+	cout <<  " ... " << id;
+      cout.flush();
+      
+      try {
+	UnformattedRead infile(fname);
+	
+	int nCpu, ndim, nPar;
+	
+	
+	infile.beginCheckpoint();
+	nCpu = infile.readInt32();
+	infile.endCheckpoint();
+	
+	infile.beginCheckpoint();
+	ndim = infile.readInt32();
+	infile.endCheckpoint();
+	
+	infile.beginCheckpoint();
+	nPar = infile.readInt32();
+	infile.endCheckpoint();
+	
+	ParticleState *s = &gd->particles[curPos];
+	
+	for (uint32_t i = nPar; i > 0; i--,s++)
+	  s->Mass = 1.0;
+	
+	s = &gd->particles[curPos];
+	infile.beginCheckpoint();
+	for (uint32_t i = nPar; i > 0; i--)
+	  {
+	    s->Pos[0] = infile.readReal32();
+	    s->Pos[0] *= gd->header.BoxSize;
+	    s++;
+	  }
+	infile.endCheckpoint();
+
+	s = &gd->particles[curPos];
+	infile.beginCheckpoint();
+	for (uint32_t i = nPar; i > 0; i--)
+	{
+	  s->Pos[1] = infile.readReal32();
+	  s->Pos[1] *= gd->header.BoxSize;
+	  s++;
+	}
+	infile.endCheckpoint();
+
+	s = &gd->particles[curPos];
+	infile.beginCheckpoint();
+	for (uint32_t i = nPar; i > 0; i--)
+	  {
+	    s->Pos[2] = infile.readReal32();
+	    s->Pos[2] *= gd->header.BoxSize;
+	    s++;
+	  }
+	infile.endCheckpoint();
+
+ 	infile.beginCheckpoint();
+	for (uint32_t i = 0; i < nPar; i++)
+	  {
+	    gd->particles[curPos+i].Vel[0] = infile.readReal32();
+	  }
+	infile.endCheckpoint();
+	
+	infile.beginCheckpoint();
+	for (uint32_t i = 0; i < nPar; i++)
+	  {
+	    gd->particles[curPos+i].Vel[1] = infile.readReal32();
+	  }
+ 	infile.endCheckpoint();
+
+ 	infile.beginCheckpoint();
+	for (uint32_t i = 0; i < nPar; i++)
+	  {
+	    gd->particles[curPos+i].Vel[2] = infile.readReal32();
+	  }
+	infile.endCheckpoint();
+
+	curPos += nPar;
+      }
+      catch (const NoSuchFileException& e)
+	{
+	  break;
+	}
+
+ 
+      id++;
+    }
+
+  if (!quiet)
+    cout << endl;
+
+  return gd;
+}
+
+typedef struct 
+{
+  double unitLength;
+  double aexp;
+  double boxSize;
+  double unit_t;
+  double omega_m;
+  double omega_lambda;
+} InfoData;
+
+int readInfoFile(const char *basename, int outputId, InfoData& info)
+{
+  ostringstream ss_fname;  
+  ss_fname << basename << "/info_" << setfill('0') << setw(5) << outputId << ".txt";
+
+//  cout << "Opening info file " << ss_fname.str() << endl;
+  ifstream infile(ss_fname.str().c_str());
+  if (!infile)
+    return 0;
+
+  int err;
+  regex_t unit_l_rx;
+
+  //  const char *pattern = "^unit_l[ ]*=[ ]*([0-9\\.E+\\-]+)";
+  const char *pattern = "^([A-Za-z_]+)[ ]*=[ ]*([0-9\\.E+\\-]+)";
+
+  err = regcomp (&unit_l_rx, pattern, REG_EXTENDED);
+  cout << unit_l_rx.re_nsub << endl;
+  if (err)
+    {
+      char errString[255];
+      regerror(err, &unit_l_rx, errString, sizeof(errString));
+      cout << errString << endl;
+      abort();
+    }
+
+  map infoMap;
+  string line;
+  while (getline(infile, line))
+    {
+      regmatch_t allMatch[4];
+      if (!regexec(&unit_l_rx, line.c_str(), 4, allMatch, 0))
+	{
+	  uint32_t start0 = allMatch[1].rm_so, end0 = allMatch[1].rm_eo;
+	  uint32_t start1 = allMatch[2].rm_so, end1 = allMatch[2].rm_eo;
+
+	  string keyword = line.substr(start0, end0-start0);
+	  istringstream iss(line.substr(start1, end1-start1));
+	  double unitLength;
+
+	  iss >> unitLength;
+
+	  infoMap[keyword] = unitLength;
+	}
+    }
+  
+  regfree(&unit_l_rx);
+
+  info.unitLength = infoMap["unit_l"];
+  info.aexp = infoMap["aexp"];
+  info.boxSize = infoMap["boxlen"];
+  info.unit_t = infoMap["unit_t"];
+  info.omega_m = infoMap["omega_m"];
+  info.omega_lambda = infoMap["omega_l"];
+
+  return 1;
+}
+
+CosmoTool::SimuData *CosmoTool::loadRamsesSimu(const char *basename, int outputId, int cpuid, bool dp, int flags)
+{
+  CosmoTool::SimuData *data = new CosmoTool::SimuData();
+
+  int id = 1;
+  uint32_t totPart = 0;
+  int nCpu = 0;
+  InfoData info;
+
+  static const double CM_IN_MPC = 3.08e24;
+
+  if (!readInfoFile(basename, outputId, info))
+    return 0;
+
+  double hubble = info.aexp*info.aexp/info.unit_t / (1e5/CM_IN_MPC);
+  double L0 = info.boxSize*info.unitLength*hubble/100/CM_IN_MPC/info.aexp;
+  double unit_vel = L0*hubble/info.aexp;
+  
+  while (1)
+    {
+      ostringstream ss_fname;
+      ss_fname << basename << "/part_" << setfill('0') << setw(5) << outputId << ".out" << setfill('0') << setw(5) << id;
+      string fname = ss_fname.str();
+
+      try
+        {
+          UnformattedRead infile(fname);
+
+          int ndim, nPar;
+
+          infile.beginCheckpoint();
+          nCpu = max(1,infile.readInt32());
+          infile.endCheckpoint();
+
+          infile.beginCheckpoint();
+          ndim = infile.readInt32();
+          infile.endCheckpoint();
+
+          infile.beginCheckpoint();
+          nPar = infile.readInt32();
+          infile.endCheckpoint();
+
+          totPart += nPar;
+          id++;
+        }
+      catch (const NoSuchFileException& e)
+        {
+          break;
+        }
+    }
+
+
+  data->BoxSize = L0;
+  data->time = info.aexp;
+  data->NumPart = 0;
+  data->TotalNumPart = totPart;
+  data->Hubble = hubble;
+  data->Omega_M = info.omega_m;
+  data->Omega_Lambda = info.omega_lambda;
+
+  if (flags == 0)
+    return data;
+
+  if (cpuid < 0)
+    cpuid = 1;
+  else cpuid++;
+
+  uint32_t curPos = 0;
+  
+  ostringstream ss_fname;
+  ss_fname << basename << "/part_" << setfill('0') << setw(5) << outputId << ".out" << setfill('0') << setw(5) << cpuid;
+  
+  string fname = ss_fname.str();
+    
+  try
+    {
+      UnformattedRead infile(fname);
+      
+      int nCpu, ndim, nPar;
+      
+      infile.beginCheckpoint();
+      nCpu = infile.readInt32();
+      infile.endCheckpoint();
+      
+      infile.beginCheckpoint();
+      ndim = infile.readInt32();
+      infile.endCheckpoint();
+      
+      infile.beginCheckpoint();
+      data->NumPart = nPar = infile.readInt32();
+      infile.endCheckpoint();
+
+      infile.beginCheckpoint();
+      for (int i = 0; i < 4; i++) infile.readInt32();
+      infile.endCheckpoint();
+      
+      infile.beginCheckpoint();
+      infile.readInt32();
+      infile.endCheckpoint();
+      
+      infile.beginCheckpoint();
+      infile.readReal64();
+      infile.endCheckpoint();
+
+      infile.beginCheckpoint();
+      infile.readReal64();
+      infile.endCheckpoint();
+      
+      infile.beginCheckpoint();
+      infile.readInt32();
+      infile.endCheckpoint();
+      
+      if (flags & NEED_POSITION)
+	{
+	  data->Pos[0] = new float[nPar];
+	  data->Pos[1] = new float[nPar];
+	  data->Pos[2] = new float[nPar];
+	}
+      if (flags & NEED_VELOCITY)
+	{
+	  data->Vel[0] = new float[nPar];
+	  data->Vel[1] = new float[nPar];
+	  data->Vel[2] = new float[nPar];
+	}
+      if (flags & NEED_GADGET_ID)
+	{
+	  data->Id = new int[nPar];
+	}
+
+      for (int k = 0; k < 3; k++)
+	{
+	  infile.beginCheckpoint();
+	  if (flags & NEED_POSITION)
+	    {
+	      for (uint32_t i = 0; i < nPar; i++)
+		{
+		  data->Pos[k][i] = dp ? infile.readReal64() : infile.readReal32();
+		  data->Pos[k][i] *= data->BoxSize;
+		}
+	      infile.endCheckpoint();
+	    }
+	  else
+	    infile.endCheckpoint(true);
+	}
+      
+      for (int k = 0; k < 3; k++) {
+	infile.beginCheckpoint();
+	if (flags & NEED_VELOCITY)
+	  {
+	    for (uint32_t i = 0; i < nPar; i++)
+	      {
+		data->Vel[k][i] = dp ? infile.readReal64() : infile.readReal32();
+		data->Vel[k][i] *= unit_vel;
+	      }
+	    infile.endCheckpoint();
+	  }
+	else
+	  infile.endCheckpoint(true);
+      }
+      
+      float minMass = INFINITY;
+      infile.beginCheckpoint();
+      for (uint32_t i = nPar; i > 0; i--)
+	{
+	  float dummyF = dp ? infile.readReal64() : infile.readReal32();
+	  if (dummyF < minMass) minMass = dummyF;
+	}
+      infile.endCheckpoint();
+      
+      infile.beginCheckpoint();
+      if (flags & NEED_GADGET_ID)
+	{
+	  for (uint32_t i = 0; i < nPar; i++)
+	    data->Id[i] = infile.readInt32();
+	  
+	  infile.endCheckpoint();
+	}
+      else
+	infile.endCheckpoint(true);
+      
+      curPos += nPar;
+    }
+  catch (const NoSuchFileException& e)  
+    {
+	cerr << "No such file " << fname << endl;
+      delete data;
+      return 0;
+    }
+  
+  return data;
+}
+
+
+
+
+
+
+
+CosmoTool::PurePositionData *CosmoTool::loadRamsesPosition(const char *basename, int outputId, bool quiet, bool dp)
+{
+  PurePositionData *gd = (PurePositionData *)malloc(sizeof(PurePositionData));
+  int id = 1;
+  uint32_t totPart = 0;
+  int nCpu = 0;
+  InfoData info;
+  
+  static const double CM_IN_MPC = 3.08e24;
+
+  if (!readInfoFile(basename, outputId, info))
+    return 0;
+
+  double hubble = info.aexp*info.aexp/info.unit_t / (1e5/CM_IN_MPC);
+  double L0 = info.boxSize*info.unitLength*hubble/100/CM_IN_MPC/info.aexp;
+  if (!quiet)
+    {
+      cout << "L0=" << L0 << " Mpc" << endl;
+      cout << "H=" << hubble << " km/s/Mpc" << endl;
+    }
+
+  if (!quiet)
+    cout << "Detecting number of files and particles..." << endl;
+  while (1)
+    {
+      ostringstream ss_fname;  
+      ss_fname << basename << "/part_" << setfill('0') << setw(5) << outputId << ".out" << setfill('0') << setw(5) << id;
+      string fname = ss_fname.str();
+      
+      if (!quiet)
+	cout <<  " ... " << fname << endl;
+      
+
+      try
+	{
+	  UnformattedRead infile(fname);
+	  
+	  int ndim, nPar;
+	  
+	  infile.beginCheckpoint();
+	  nCpu = max(1,infile.readInt32());
+	  infile.endCheckpoint();
+	  
+	  infile.beginCheckpoint();
+	  ndim = infile.readInt32();
+	  infile.endCheckpoint();
+	  
+	  infile.beginCheckpoint();
+	  nPar = infile.readInt32();
+	  infile.endCheckpoint();
+	  
+	  if (!quiet)
+	    cout << "    NUMCPU=" << nCpu <<  " NUMDIM=" << ndim << " NumPart=" << nPar << endl;
+	  
+	  totPart += nPar;
+	  id++;
+	}
+      catch (const NoSuchFileException& e)
+	{
+	  break;
+	}
+
+    }
+
+  assert (totPart <= ((~(size_t)0)/sizeof(FCoordinates)));
+  size_t memSize = sizeof(FCoordinates)*(size_t)(totPart+totPart/nCpu);
+  if (!quiet)
+    cout << " Needing " << memSize / 1024 << " kBytes" << endl;
+  gd->pos = (FCoordinates *)malloc(sizeof(FCoordinates)*totPart);
+  assert(gd->pos != 0);
+  gd->NumPart = totPart;
+  gd->BoxSize = L0*1000; 
+  gd->hubble = hubble;
+
+  if (!quiet)
+    cout << " Total number part=" << totPart << endl
+	 << "Loading particles ..." << endl;
+  
+  uint32_t curPos = 0;
+  id = 1;
+  while (1)
+    {
+      ostringstream ss_fname;  
+      ss_fname << basename << "/part_" << setfill('0') << setw(5) << outputId << ".out" << setfill('0') << setw(5) << id;
+     
+      string fname = ss_fname.str();
+      int *idP;
+
+      if (!quiet)
+	(cout <<  " ... " << id).flush();
+      
+      try
+	{
+	  UnformattedRead infile(fname);
+	  
+	  int nCpu, ndim, nPar;
+	  
+	  infile.beginCheckpoint();
+	  nCpu = infile.readInt32();
+	  infile.endCheckpoint();
+	  
+	  infile.beginCheckpoint();
+	  ndim = infile.readInt32();
+	  infile.endCheckpoint();
+	  
+	  infile.beginCheckpoint();
+	  nPar = infile.readInt32();
+	  infile.endCheckpoint();
+	  
+	  infile.beginCheckpoint();
+          for (int i = 0; i < 4; i++) infile.readInt32();
+	  infile.endCheckpoint();
+
+	  infile.beginCheckpoint();
+          infile.readInt32();
+	  infile.endCheckpoint();
+
+	  infile.beginCheckpoint();
+          infile.readReal64();
+	  infile.endCheckpoint();
+
+	  infile.beginCheckpoint();
+          infile.readReal64();
+	  infile.endCheckpoint();
+
+	  infile.beginCheckpoint();
+          infile.readInt32();
+	  infile.endCheckpoint();
+
+	  FCoordinates *s = new FCoordinates[nPar];
+	  
+	  for (int k = 0; k < 3; k++)
+	    {
+	      infile.beginCheckpoint();
+	      for (uint32_t i = 0; i < nPar; i++)
+		{
+		  s[i][k] = dp ? infile.readReal64() : infile.readReal32();
+		  s[i][k] *= gd->BoxSize;
+		}
+	      infile.endCheckpoint();
+	    }
+
+	  // SKIP VELOCITIES
+	  for (int k = 0; k < 3; k++) {	
+	    infile.beginCheckpoint();
+	    for (uint32_t i = nPar; i > 0; i--)
+	      {
+		(dp ? infile.readReal64() : infile.readReal32());
+	      }
+	    infile.endCheckpoint();
+	  }
+	  
+	  float minMass = INFINITY;
+	  infile.beginCheckpoint();
+	  for (uint32_t i = nPar; i > 0; i--)
+	    {
+	      float dummyF = dp ?  infile.readReal64() : infile.readReal32();
+	      if (dummyF < minMass) minMass = dummyF;
+	    }
+	  infile.endCheckpoint();
+	  
+	  infile.beginCheckpoint();
+	  for (uint32_t i = 0; i < nPar; i++)
+	    {
+	      int id = infile.readInt32();
+	      id--;
+	      assert(id >= 0);
+	      assert(id < totPart);
+	      memcpy(&gd->pos[id], &s[i], sizeof(FCoordinates));
+	    }
+	  infile.endCheckpoint();
+      
+	  delete[] s;
+	  
+	  curPos += nPar;
+	} catch (const NoSuchFileException& e)
+	{
+	  break;
+	}
+      id++;
+    }
+
+  if (!quiet)
+    cout << endl;
+
+  return gd;
+}
+
+CosmoTool::PhaseSpaceData *CosmoTool::loadRamsesPhase(const char *basename, int outputId, bool quiet)
+{
+  PhaseSpaceData *gd = (PhaseSpaceData *)malloc(sizeof(PhaseSpaceData));
+  int id = 1;
+  uint32_t totPart = 0;
+  int nCpu = 0;
+  InfoData info;
+  
+  static const double CM_IN_MPC = 3.08e24;
+
+  if (!readInfoFile(basename, outputId, info))
+    return 0;
+
+  double hubble = info.aexp*info.aexp/info.unit_t / (1e5/CM_IN_MPC);
+  double L0 = info.boxSize*info.unitLength*hubble/(100*CM_IN_MPC)/info.aexp;
+  double unit_vel = 100*L0/info.aexp;
+  if (!quiet) {
+    cout << "L0=" << L0 << " Mpc" << endl;
+    cout << "H=" << hubble << " km/s/Mpc" << endl;
+    cout << "unit_vel=" << unit_vel << " km/s" << endl;
+  }
+
+  if (!quiet)
+    cout << "Detecting number of files and particles..." << endl;
+  while (1)
+    {
+      ostringstream ss_fname;  
+      ss_fname << basename << "/part_" << setfill('0') << setw(5) << outputId << ".out" << setfill('0') << setw(5) << id;
+      string fname = ss_fname.str();
+      
+      if (!quiet)
+	cout <<  " ... " << fname << endl;
+      
+
+      try
+	{
+	  UnformattedRead infile(fname);
+	  
+	  int ndim, nPar;
+	  
+	  infile.beginCheckpoint();
+	  nCpu = max(1,infile.readInt32());
+	  infile.endCheckpoint();
+	  
+	  infile.beginCheckpoint();
+	  ndim = infile.readInt32();
+	  infile.endCheckpoint();
+	  
+	  infile.beginCheckpoint();
+	  nPar = infile.readInt32();
+	  infile.endCheckpoint();
+	  
+	  if (!quiet)
+	    cout << "    NUMCPU=" << nCpu <<  " NUMDIM=" << ndim << " NumPart=" << nPar << endl;
+	  
+	  totPart += nPar;
+	  id++;
+	}
+      catch (const NoSuchFileException& e)
+	{
+	  break;
+	}
+
+    }
+
+  assert (totPart <= ((~(size_t)0)/sizeof(FCoordinates)));
+  size_t memSize = sizeof(FCoordinates)*(size_t)(totPart+totPart/nCpu);
+  if (!quiet)
+    cout << " Needing " << memSize / 1024 << " kBytes" << endl;
+  gd->pos = (FCoordinates *)malloc(sizeof(FCoordinates)*totPart);
+  assert(gd->pos != 0);
+  gd->vel = (FCoordinates *)malloc(sizeof(FCoordinates)*totPart);
+  assert(gd->vel != 0);
+  gd->NumPart = totPart;
+  gd->BoxSize = L0*1000; 
+  gd->hubble = hubble;
+  
+  if (!quiet)
+    cout << " Total number part=" << totPart << endl
+	 << "Loading particles ..." << endl;
+  
+  uint32_t curPos = 0;
+  id = 1;
+  while (1)
+    {
+      ostringstream ss_fname;  
+      ss_fname << basename << "/part_" << setfill('0') << setw(5) << outputId << ".out" << setfill('0') << setw(5) << id;
+     
+      string fname = ss_fname.str();
+      int *idP;
+
+      if (!quiet)
+	(cout <<  " ... " << id).flush();
+      
+      try
+	{
+	  UnformattedRead infile(fname);
+	  
+	  int nCpu, ndim, nPar;
+	  
+	  infile.beginCheckpoint();
+	  nCpu = infile.readInt32();
+	  infile.endCheckpoint();
+	  
+	  infile.beginCheckpoint();
+	  ndim = infile.readInt32();
+	  infile.endCheckpoint();
+	  
+	  infile.beginCheckpoint();
+	  nPar = infile.readInt32();
+	  infile.endCheckpoint();
+	  
+
+	  FCoordinates *s = new FCoordinates[nPar];
+	  FCoordinates *vel = new FCoordinates[nPar];
+	  
+	  for (int k = 0; k < 3; k++)
+	    {
+	      infile.beginCheckpoint();
+	      for (uint32_t i = 0; i < nPar; i++)
+		{
+		  s[i][k] = infile.readReal32();
+		  s[i][k] *= gd->BoxSize;
+		}
+	      infile.endCheckpoint();
+	    }
+
+	  // SKIP VELOCITIES
+	  for (int k = 0; k < 3; k++) {	
+	    infile.beginCheckpoint();
+	    for (uint32_t i = 0; i < nPar; i++)
+	      {
+		vel[i][k] = infile.readReal32()*unit_vel;
+	      }
+	    infile.endCheckpoint();
+	  }
+	  
+	  float minMass = INFINITY;
+	  infile.beginCheckpoint();
+	  for (uint32_t i = nPar; i > 0; i--)
+	    {
+	      float dummyF = infile.readReal32();
+	      if (dummyF < minMass) minMass = dummyF;
+	    }
+	  infile.endCheckpoint();
+	  
+	  infile.beginCheckpoint();
+	  for (uint32_t i = 0; i < nPar; i++)
+	    {
+	      int id = infile.readInt32();
+	      id--;
+	      assert(id >= 0);
+	      assert(id < totPart);
+	      memcpy(&gd->pos[id], &s[i], sizeof(FCoordinates));
+	      memcpy(&gd->vel[id], &vel[i], sizeof(FCoordinates));
+	    }
+	  infile.endCheckpoint();
+      
+	  delete[] vel;
+	  delete[] s;
+	  
+	  curPos += nPar;
+	} catch (const NoSuchFileException& e)
+	{
+	  break;
+	}
+      id++;
+    }
+
+  if (!quiet)
+    cout << endl;
+
+  return gd;
+}
+
+
+CosmoTool::PhaseSpaceDataID *CosmoTool::loadRamsesPhase1(const char *basename, int outputId, int cpuid, bool dp, bool quiet)
+{
+  PhaseSpaceDataID *gd = (PhaseSpaceDataID *)malloc(sizeof(PhaseSpaceDataID));
+  int id = 1;
+  uint32_t totPart = 0;
+  int nCpu = 0;
+  InfoData info;
+  
+  static const double CM_IN_MPC = 3.08e24;
+
+  if (!readInfoFile(basename, outputId, info))
+    return 0;
+
+  double hubble = info.aexp*info.aexp/info.unit_t / (1e5/CM_IN_MPC);
+  double L0 = info.boxSize*info.unitLength*hubble/100/CM_IN_MPC/info.aexp;
+  double unit_vel = 100*L0/info.aexp;
+  if (!quiet) {
+    cout << "L0=" << L0 << " Mpc" << endl;
+    cout << "H=" << hubble << " km/s/Mpc" << endl;
+    cout << "unitvel=" << unit_vel << " km/s" << endl;
+  }
+
+  if (!quiet)
+    cout << "Detecting number of files and particles..." << endl;
+  id = cpuid;
+  {
+    ostringstream ss_fname;  
+    ss_fname << basename << "/part_" << setfill('0') << setw(5) << outputId << ".out" << setfill('0') << setw(5) << id;
+    string fname = ss_fname.str();
+    
+    if (!quiet)
+      cout <<  " ... " << fname << endl;
+    
+    
+    try
+      {
+	UnformattedRead infile(fname);
+	
+	int ndim, nPar;
+	
+	infile.beginCheckpoint();
+	nCpu = max(1,infile.readInt32());
+	infile.endCheckpoint();
+	
+	infile.beginCheckpoint();
+	ndim = infile.readInt32();
+	infile.endCheckpoint();
+	
+	infile.beginCheckpoint();
+	nPar = infile.readInt32();
+	infile.endCheckpoint();
+	
+	if (!quiet)
+	  cout << "    NUMCPU=" << nCpu <<  " NUMDIM=" << ndim << " NumPart=" << nPar << endl;
+	
+	totPart += nPar;
+	id++;
+      }
+    catch (const NoSuchFileException& e)
+      {
+	return 0;
+      }
+  }
+  
+  assert (totPart <= ((~(size_t)0)/sizeof(FCoordinates)));
+  size_t memSize = sizeof(FCoordinates)*(size_t)(totPart+totPart/nCpu);
+  if (!quiet)
+    cout << " Needing " << memSize / 1024 << " kBytes" << endl;
+  gd->pos = (FCoordinates *)malloc(sizeof(FCoordinates)*totPart);
+  assert(gd->pos != 0);
+  gd->vel = (FCoordinates *)malloc(sizeof(FCoordinates)*totPart);
+  assert(gd->vel != 0);
+  gd->ID = (int *)malloc(sizeof(int)*totPart);
+  assert(gd->ID != 0);
+  gd->NumPart = totPart;
+  gd->BoxSize = L0*1000; 
+  gd->hubble = hubble;
+  
+  uint32_t curPos = 0;
+  id = cpuid;
+  {
+    ostringstream ss_fname;  
+    ss_fname << basename << "/part_" << setfill('0') << setw(5) << outputId << ".out" << setfill('0') << setw(5) << id;
+    
+    string fname = ss_fname.str();
+    int *idP;
+    
+    if (!quiet)
+      (cout <<  " ... " << id).flush();
+    
+    try
+      {
+	UnformattedRead infile(fname);
+	
+	int nCpu, ndim, nPar;
+	
+	infile.beginCheckpoint();
+	nCpu = infile.readInt32();
+	infile.endCheckpoint();
+	
+	infile.beginCheckpoint();
+	ndim = infile.readInt32();
+	infile.endCheckpoint();
+	
+	infile.beginCheckpoint();
+	nPar = infile.readInt32();
+	infile.endCheckpoint();
+
+	infile.beginCheckpoint();
+	for (int i = 0; i < 4; i++) infile.readInt32();
+	infile.endCheckpoint();
+	
+	infile.beginCheckpoint();
+	infile.readInt32();
+	infile.endCheckpoint();
+	
+	infile.beginCheckpoint();
+	infile.readReal64();
+	infile.endCheckpoint();
+	
+	infile.beginCheckpoint();
+	infile.readReal64();
+	infile.endCheckpoint();
+	
+	infile.beginCheckpoint();
+	infile.readInt32();
+	infile.endCheckpoint();
+
+	gd->pos = new FCoordinates[nPar];
+	gd->vel = new FCoordinates[nPar];
+	
+	for (int k = 0; k < 3; k++)
+	  {
+	    infile.beginCheckpoint();
+	    for (uint32_t i = 0; i < nPar; i++)
+	      {
+		gd->pos[i][k] = (dp ? infile.readReal64() : infile.readReal32())*gd->BoxSize;
+	      }
+	    infile.endCheckpoint();
+	  }
+	
+	// SKIP VELOCITIES
+	for (int k = 0; k < 3; k++) {	
+	  infile.beginCheckpoint();
+	  for (uint32_t i = 0; i < nPar; i++)
+	    {
+	      gd->vel[i][k] = (dp ? infile.readReal64() : infile.readReal32())*unit_vel;
+	    }
+	  infile.endCheckpoint();
+	}
+	
+	float minMass = INFINITY;
+	infile.beginCheckpoint();
+	for (uint32_t i = nPar; i > 0; i--)
+	  {
+	    float dummyF = (dp ? infile.readReal64() : infile.readReal32());
+	    if (dummyF < minMass) minMass = dummyF;
+	  }
+	infile.endCheckpoint();
+	
+	infile.beginCheckpoint();
+	for (uint32_t i = 0; i < nPar; i++)
+	  {
+	    gd->ID[i] = infile.readInt32();
+	  }
+	infile.endCheckpoint();
+	
+	curPos += nPar;
+      } 
+    catch (const NoSuchFileException& e) 
+      {
+	return 0;
+      }
+  }
+  
+  if (!quiet)
+    cout << endl;
+
+  return gd;
+}
diff --git a/external/cosmotool/src/loadRamses.hpp b/external/cosmotool/src/loadRamses.hpp
new file mode 100644
index 0000000..15e6275
--- /dev/null
+++ b/external/cosmotool/src/loadRamses.hpp
@@ -0,0 +1,18 @@
+#ifndef _LOAD_RAMSES_HPP
+#define _LOAD_RAMSES_HPP
+
+#include "load_data.hpp"
+#include "loadSimu.hpp"
+
+namespace CosmoTool {
+
+  GadgetData *loadRamses(const char *name, bool quiet = false);
+  PurePositionData *loadRamsesPosition(const char *fname, int id, bool quiet = false, bool dp = true);
+  PhaseSpaceData *loadRamsesPhase(const char *fname, int id, bool quiet = false);
+
+  PhaseSpaceDataID *loadRamsesPhase1(const char *fname, int id, int cpuid, bool dp = true, bool quiet = false);
+
+  SimuData *loadRamsesSimu(const char *basename, int id, int cpuid, bool dp, int flags);
+};
+
+#endif
diff --git a/external/cosmotool/src/loadSimu.hpp b/external/cosmotool/src/loadSimu.hpp
new file mode 100644
index 0000000..a7db941
--- /dev/null
+++ b/external/cosmotool/src/loadSimu.hpp
@@ -0,0 +1,48 @@
+#ifndef __COSMOTOOLBOX_HPP
+#define __COSMOTOOLBOX_HPP
+
+
+namespace CosmoTool
+{
+  static const int NEED_GADGET_ID = 1;
+  static const int NEED_POSITION = 2;
+  static const int NEED_VELOCITY = 4;
+  static const int NEED_TYPE = 8;
+
+  class SimuData 
+  {
+  public:
+    float BoxSize;
+    float time;
+    float Hubble;
+
+    float Omega_M;
+    float Omega_Lambda;
+
+    long NumPart;
+    long TotalNumPart;
+    int *Id;
+    float *Pos[3];
+    float *Vel[3];
+    int *type;
+  public:
+    SimuData() : Id(0),NumPart(0),type(0)  { Pos[0]=Pos[1]=Pos[2]=0; Vel[0]=Vel[1]=Vel[2]=0; }
+    ~SimuData() 
+    {
+      for (int j = 0; j < 3; j++)
+	{
+	  if (Pos[j])
+	    delete[] Pos[j];
+	  if (Vel[j])
+	    delete[] Vel[j];
+	}
+      if (type)
+	delete[] type;
+      if (Id)
+	delete[] Id;
+    }    
+  };
+
+};
+
+#endif
diff --git a/external/cosmotool/src/load_data.cpp b/external/cosmotool/src/load_data.cpp
new file mode 100644
index 0000000..28c7dc6
--- /dev/null
+++ b/external/cosmotool/src/load_data.cpp
@@ -0,0 +1,635 @@
+#include 
+#include 
+#include 
+#include "load_data.hpp"
+
+using namespace CosmoTool;
+
+//#define LARGE_CONTROL
+#define LITTLE_ENDIAN
+
+#define NEW(t,n)  ((t *)malloc(sizeof(t)*n))
+#define SKIP(f) fread(&dummy,sizeof(dummy),1,f);
+#define WRITE_DUM(f) fwrite(&dummy, sizeof(dummy),1,f);
+
+static int dummy;
+
+void CosmoTool::writeGadget(GadgetData *data, const char *fname)
+{
+  FILE *f;
+  int k, n, p;
+
+  f = fopen(fname, "w");
+  if (f == NULL) {
+    fprintf(stderr, "Cannot write gadget to file %s\n", fname);
+    return;
+  }
+
+  dummy = 256;
+  WRITE_DUM(f);
+  fwrite(&data->header, sizeof(data->header), 1, f);
+  WRITE_DUM(f);
+
+  dummy = sizeof(float)*3*data->NumPart;
+  WRITE_DUM(f);
+  for(k=0,p=0;k<5;k++) {
+    for(n=0;nheader.npart[k];n++) {
+      fwrite(&data->particles[p].Pos[0], sizeof(float), 3, f);
+      p++;
+    }
+  }
+  WRITE_DUM(f);
+
+  dummy = sizeof(float)*3*data->NumPart;  
+  WRITE_DUM(f);
+  for(k=0,p=0;k<6;k++) {
+    for(n=0;nheader.npart[k];n++) {
+      fwrite(&data->particles[p].Vel[0], sizeof(float), 3, f);
+      p++;
+    }
+  }
+  WRITE_DUM(f);
+
+  dummy = sizeof(int)*data->NumPart;
+  WRITE_DUM(f);
+  for(k=0,p=0;k<6;k++)
+    {
+      for(n=0;nheader.npart[k];n++)
+	{
+	  fwrite(&data->particles[p].Id, sizeof(int), 1, f);
+	  p++;
+	}
+    }
+  WRITE_DUM(f);
+
+  if(data->ntot_withmasses>0) {
+    dummy = sizeof(float)*data->NumPart;
+    WRITE_DUM(f);
+  }
+  for(k=0, p=0; k<6; k++)
+    {
+      for(n=0;nheader.npart[k];n++)
+	{
+	  if(data->header.mass[k]==0)
+	    fwrite(&data->particles[p].Mass, sizeof(float), 1, f);
+	  p++;
+	}
+    }
+  if(data->ntot_withmasses>0)
+    WRITE_DUM(f);      
+
+  if(data->header.npart[0]>0) {
+    dummy = data->header.npart[0]*sizeof(float);
+    WRITE_DUM(f);
+    for(n=0, p=0; nheader.npart[0];p++,n++) {
+      fwrite(&data->particles[p].U, sizeof(float), 1, f);
+    }
+    WRITE_DUM(f);
+    
+    WRITE_DUM(f);
+    for(n=0, p=0; nheader.npart[0];p++,n++) {
+      fwrite(&data->particles[p].Rho, sizeof(float), 1, f);
+    }
+    WRITE_DUM(f);
+    
+    if(data->header.flag_cooling) {
+      WRITE_DUM(f);
+      for(n=0, p=0; nheader.npart[0];p++,n++) {
+	fwrite(&data->particles[p].Ne, sizeof(float), 1, f);
+      }
+      WRITE_DUM(f);
+    }
+  }
+  
+  fclose(f);
+}
+
+GadgetData *CosmoTool::loadGadget(const char *fname)
+{
+  FILE *f;
+  GadgetData *data;
+  int p, k, n;
+
+  f = fopen(fname, "r");
+  if (f == NULL)
+    return NULL;
+  
+  data = NEW(GadgetData, 1);
+  SKIP(f);
+  fread(&data->header, sizeof(data->header), 1, f);
+  SKIP(f);
+  
+  for(k=0, data->ntot_withmasses=0; k<5; k++) {
+    if(data->header.mass[k]==0)
+      data->ntot_withmasses+= data->header.npart[k];
+  }
+
+  for(k=0, data->NumPart=0; k<5; k++)
+    data->NumPart+= data->header.npart[k];
+  
+  data->particles = NEW(ParticleState, data->NumPart);
+
+  SKIP(f);
+  for(k=0,p=0;k<5;k++) {
+    for(n=0;nheader.npart[k];n++) {
+      fread(&data->particles[p].Pos[0], sizeof(float), 3, f);
+      p++;
+    }
+  }
+  SKIP(f);
+  
+  SKIP(f);
+  for(k=0,p=0;k<6;k++) {
+    for(n=0;nheader.npart[k];n++) {
+      fread(&data->particles[p].Vel[0], sizeof(float), 3, f);
+      p++;
+    }
+  }
+  SKIP(f);
+  
+
+  SKIP(f);
+  for(k=0,p=0;k<6;k++)
+    {
+      for(n=0;nheader.npart[k];n++)
+	{
+	  fread(&data->particles[p].Id, sizeof(int), 1, f);
+	  p++;
+	}
+    }
+  SKIP(f);
+
+  if(data->ntot_withmasses>0)
+    SKIP(f);
+  for(k=0, p=0; k<6; k++)
+    {
+      for(n=0;nheader.npart[k];n++)
+	{
+	  data->particles[p].Type=k;
+
+	  if(data->header.mass[k]==0)
+	    fread(&data->particles[p].Mass, sizeof(float), 1, f);
+	  else
+	    data->particles[p].Mass= data->header.mass[k];
+	  p++;
+	}
+    }
+  if(data->ntot_withmasses>0)
+    SKIP(f);      
+
+  if(data->header.npart[0]>0)
+    {
+      SKIP(f);
+      for(n=0, p=0; nheader.npart[0];p++,n++) {
+	  fread(&data->particles[p].U, sizeof(float), 1, f);
+	}
+      SKIP(f);
+
+      SKIP(f);
+      for(n=0, p=0; nheader.npart[0];p++,n++) {
+	fread(&data->particles[p].Rho, sizeof(float), 1, f);
+      }
+      SKIP(f);
+
+      if(data->header.flag_cooling)
+	{
+	  SKIP(f);
+	  for(n=0, p=0; nheader.npart[0];p++,n++)
+	    {
+	      fread(&data->particles[p].Ne, sizeof(float), 1, f);
+	    }
+	  SKIP(f);
+	}
+      else
+	for(n=0, p=0; nheader.npart[0];p++,n++)
+	  {
+	    data->particles[p].Ne= 1.0;
+	  }
+    }
+
+
+  fclose(f);
+
+  return data;
+}
+
+void CosmoTool::freeGadget(GadgetData *data)
+{
+  free(data->particles);
+  free(data);
+}
+
+void CosmoTool::writePersoSet(ParticleSet *set, const char *fname)
+{
+  FILE *f;
+  int i;
+
+  f = fopen(fname, "w");
+  if (f == NULL) {
+    perror("writePersoSet");
+    return;
+  }
+
+  fwrite(&set->header, sizeof(set->header), 1, f);
+  fwrite(set->Npart, sizeof(set->Npart[0]), set->header.Ntypes, f);
+  
+  for (i=0;iheader.Ntypes;i++)
+    fwrite(set->particles[i], sizeof(ParticleState), set->Npart[i], f);
+
+  fclose(f);
+}
+
+ParticleSet *CosmoTool::loadPersoSet(const char *fname)
+{
+  ParticleSet *set;
+  FILE *f;
+  int i;
+
+  f = fopen(fname, "r");
+  if (f == NULL) {
+    perror("loadPersoSet");
+    return NULL;
+  }
+
+  set = NEW(ParticleSet, 1);
+  fread(&set->header, sizeof(set->header), 1, f);
+
+  set->Npart = NEW(int, set->header.Ntypes);
+  fread(set->Npart, sizeof(set->Npart[0]), set->header.Ntypes, f);;
+  
+  set->particles = NEW(ParticleState *, set->header.Ntypes);
+  for (i=0;iheader.Ntypes;i++) {
+    set->particles[i] = NEW(ParticleState, set->Npart[i]);
+    fread(set->particles[i], sizeof(ParticleState), set->Npart[i], f);
+  }
+
+  fclose(f);
+
+  return set;
+}
+
+void CosmoTool::freePersoSet(ParticleSet *set)
+{
+  int i;
+
+  for (i=0;iheader.Ntypes;i++) {
+    free(set->particles[i]);
+  }
+  if (set->Npart != NULL) {
+    free(set->particles);
+    free(set->Npart);
+  }
+}
+
+#ifdef WANT_MAIN
+int main(int argc, char **argv) {
+  GadgetData *data;
+  FILE *plot;
+  int i;
+  double bl;
+  int N;
+  double rms;
+
+  if (argc < 3) {
+      fprintf(stderr, "Usage: %s [GADGET DATA FILE] [BOXSIZE] [N PARTIC]\n", argv[0]);
+      return -1;
+  }
+
+  plot = fopen("plot", "w");
+  
+  bl = atof(argv[2]);
+  data = loadGadget(argv[1]);
+
+  printf("Redshift: %lg\n", data->header.redshift);
+  rms = 0;
+  N = atoi(argv[3]);
+  for (i=0;iNumPart;i++) {
+    if (i == data->header.npart[0])
+	    fprintf(plot,"\n\n");
+
+    fprintf(plot, "%f %f %f\n", data->particles[i].Pos[0], data->particles[i].Pos[1], data->particles[i].Pos[2]);
+
+
+    /* Compute the RMS */
+    {
+      /* First find the nearest grid node. */
+      int k;
+      int x;
+      double dx;
+
+      for (k=0;k<3;k++) {
+	x = data->particles[i].Pos[k] / bl * N;
+	dx = data->particles[i].Pos[k]-x*bl/N;
+	rms += dx*dx;
+      }
+    }
+  }
+
+  printf("delta rms = %e\n", sqrt(rms/data->NumPart));
+  freeGadget(data);
+
+  fclose(plot);
+
+  return 0;
+}
+#endif
+
+#define LEN0 200.0
+
+GadgetData *CosmoTool::loadSimulationData(const char *fname)
+{
+  GadgetData *gd = NEW(GadgetData, 1);
+  FILE *f;
+  int lineNo;
+  char line[1024];
+  int i;
+  int j;
+  
+  gd->header.BoxSize = LEN0;
+
+  f = fopen(fname, "r");
+  lineNo = 0;
+  while (!feof(f))
+    {
+      fgets(line, sizeof(line), f);
+      lineNo++;
+    }
+  lineNo--;
+  rewind(f);
+
+  gd->NumPart = lineNo;
+  gd->particles = NEW(ParticleState, lineNo);
+
+  i = 0;
+  while (!feof(f))
+    {
+      fgets(line, sizeof(line), f);
+      int r = sscanf(line, "%*d %*d %f %f %f %f %f %f %f %f %f %*f %*f %*f %f %f %f",
+		     &gd->particles[i].Pos[0], &gd->particles[i].Pos[1], &gd->particles[i].Pos[2],
+		     &gd->particles[i].Init[0], &gd->particles[i].Init[1], &gd->particles[i].Init[2],
+		     &gd->particles[i].Vel[0], &gd->particles[i].Vel[1], &gd->particles[i].Vel[2],
+		     &gd->particles[i].VelInit[0], &gd->particles[i].VelInit[1], &gd->particles[i].VelInit[2]
+		     );
+      if (r != 12)
+	{
+	  printf("line %d: '%s'\n", i, line);
+	  printf("returned r=%d\n", r);
+	  abort();
+	}
+      assert(r == 12);
+      for (j = 0; j < 3; j++)
+	{
+	  gd->particles[i].Vel[j] *= 100.0 * LEN0 / (0.9641010);
+	  gd->particles[i].VelInit[j] *= 100.0 * 1/71. * LEN0 / (0.9641010);
+	  gd->particles[i].Pos[j] *= LEN0;
+	  gd->particles[i].Init[j] *= LEN0;
+	}
+
+      gd->particles[i].Type = 0;
+      gd->particles[i].Mass = 1.0;
+      gd->particles[i].Id = i;
+
+      i++;
+    }
+  fclose(f);
+
+  return gd;
+}
+
+#ifndef LITTLE_ENDIAN 
+#define read_buf(b, n) \
+{ \
+  int k; \
+  control_size -= n; \
+  for (k = (n-1); k >= 0; k--) \
+    fread(&b[k], 1, 1, infile); \
+}
+#else
+#define read_buf(b, n) \
+{ \
+  int k; \
+  control_size -= n; \
+  for (k = 0; k < n; k++) \
+    fread(&b[k], 1, 1, infile); \
+}
+#endif
+
+#define read_int(i) \
+{ \
+  char *o = (char*)&(i); \
+  read_buf(o, 4); \
+}
+
+#define read_real(f) \
+{ \
+  char *o = (char*)&(f); \
+  read_buf(o, 4); \
+}
+
+#define read_characters(c, n) { \
+  int k; \
+  control_size -= n; \
+  fread(c, 1, n, outfile); \
+}
+
+#define push_dummy_control(id) \
+{ int control_size = 0;
+
+#define pop_dummy_control() }
+
+#if defined(LARGE_CONTROL) && defined(LITTLE_ENDIAN)
+#define push_control(id) \
+{ \
+  int control_size = 0; \
+  int control_size2 = 0; \
+  char *intbuf = (char*)&control_size; \
+  fread(&control_size, 8, 1, infile);
+
+#define pop_control(id) \
+  fread(&control_size2, 8, 1, infile); \
+  assert(control_size == 0); \
+}
+#elif !defined(LARGE_CONTROL) && defined(LITTLE_ENDIAN)
+#define push_control(id) \
+{ \
+  int control_size = 0; \
+  int control_size2 = 0; \
+  char *intbuf = (char*)&control_size; \
+  fread(&control_size, 4, 1, infile);
+
+#define pop_control(id) \
+  fread(&control_size2, 4, 1, infile); \
+  assert(control_size == 0); \
+}
+
+#elif defined(LARGE_CONTROL) && !defined(LITTLE_ENDIAN)
+#define push_control(id) \
+{ \
+  int control_size = 0; \
+  int control_size2 = 0; \
+  char *intbuf = (char*)&control_size; \
+  fread(&control_size, 8, 1, infile);
+
+#define pop_control(id) \
+  fread(&control_size2, 8, 1, infile); \
+  assert(control_size == 0); \
+}
+
+#elif !defined(LARGE_CONTROL) && !defined(LITTLE_ENDIAN)
+#define push_control(id) \
+{ \
+  int control_size = 0; \
+  int control_size2 = 0; \
+  char *intbuf = (char*)&control_size; \
+  fread(&control_size, 4, 1, infile);
+
+#define pop_control(id) \
+  fread(&control_size2, 4, 1, infile); \
+  assert(control_size == 0); \
+}
+
+#endif
+
+GadgetData *CosmoTool::loadHydra(const char *fname)
+{
+  GadgetData *gd = NEW(GadgetData, 1);
+  FILE *f;
+  int version0, version1, version2;
+  int irun, nobj, ngas, ndark, intl, nlmx, perr;
+  float dtnorm, sft0, sftmin, sftmax;
+  int pad3;
+  float h100, box100, zmet0;
+  int lcool;
+  float rmnorm0;
+  int pad4, pad5;
+  float tstart, omega0, xlambda0, h0t0, rcen, rmax2;
+  float rmbary;
+  int j;
+  float atime;
+
+  f = fopen(fname, "r");
+#define infile f
+  push_control(0);
+  read_int(version0);
+  read_int(version1);
+  read_int(version2);
+  pop_control(0);
+
+  if (version0 != 4)
+    {
+      fclose(f);
+      return NULL;
+    }
+  push_control(1);
+  
+  for (j = 0; j < 200; j++)
+    {
+      int mydummy;
+      read_int(mydummy);
+    }
+  for (j = 0; j < 5; j++)
+  {
+	  float mydummy;
+	  read_real(mydummy);
+  }
+  read_real(atime);
+  gd->header.time = atime;
+  gd->header.redshift = 1/atime - 1;
+
+  for (j = 6; j < 100; j++)
+    {
+      int mydummy;
+      read_int(mydummy);
+    }
+  read_int(irun);
+  read_int(nobj);
+  read_int(ngas);
+  read_int(ndark);
+  read_int(intl);
+  read_int(nlmx);
+  read_int(perr);
+  read_real(dtnorm);
+  read_real(sft0);
+  read_real(sftmin);
+  read_real(sftmax);
+  read_int(pad3);
+  read_real(h100);
+  read_real(box100);
+  read_real(zmet0);
+  read_int(lcool);
+  read_real(rmbary);
+  read_real(rmnorm0);
+  read_int(pad4);
+  read_int(pad5);
+  read_real(tstart);
+  read_real(omega0);
+  read_real(xlambda0);
+  read_real(h0t0);
+  read_real(rcen);
+  read_real(rmax2);
+  for (j = 0; j < 74; j++)
+    {
+      int mydummy;
+      read_int(mydummy);
+    }
+  pop_control(1);
+
+  gd->header.npart[1] = ndark;
+  gd->header.npart[0] = ngas;
+  gd->header.num_files = 1;
+  gd->header.flag_cooling = lcool;
+  gd->header.BoxSize = box100 * 1000;
+  gd->header.HubbleParam = h100;
+  gd->header.Omega0 = omega0;
+  gd->header.OmegaLambda = xlambda0;
+
+  push_control(2);
+  for (j = 0; j < nobj; j++)
+    {
+      int mydummy;
+      read_int(mydummy);
+    }
+  pop_control(2);
+
+  gd->NumPart = nobj;
+  gd->ntot_withmasses = nobj;
+  gd->particles = NEW(ParticleState, nobj);
+
+  push_control(3);
+  for (j = 0; j < nobj; j++)
+    {
+      float rm;
+      gd->particles[j].Id = j;
+      read_real(gd->particles[j].Mass);
+    }
+  pop_control(3);
+
+  push_control(4);
+  for (j = 0; j < nobj; j++)
+    {
+      int k;
+      for (k = 0; k < 3; k++)
+	{
+	  read_real(gd->particles[j].Pos[k]);
+	  gd->particles[j].Pos[k] *= gd->header.BoxSize;
+	}
+    }
+  pop_control(4);
+
+  push_control(5);
+  for (j = 0; j < nobj; j++)
+    {
+      int k;
+      for (k = 0; k < 3; k++)
+	{
+	  read_real(gd->particles[j].Vel[k]);
+	  gd->particles[j].Vel[k] *= 100.0 * box100  / h0t0 * atime;
+	}
+    }
+  pop_control(5);
+
+  fclose(f);
+#undef infile
+
+  return gd;
+}
diff --git a/external/cosmotool/src/load_data.hpp b/external/cosmotool/src/load_data.hpp
new file mode 100644
index 0000000..c4bc611
--- /dev/null
+++ b/external/cosmotool/src/load_data.hpp
@@ -0,0 +1,97 @@
+#ifndef _LOAD_GADGET_DATA_HPP
+#define _LOAD_GADGET_DATA_HPP
+
+#include "config.hpp"
+
+namespace CosmoTool {
+
+  struct GadgetHeader
+  {
+    int      npart[6];
+    double   mass[6];
+    double   time;
+    double   redshift;
+    int      flag_sfr;
+    int      flag_feedback;
+    int      npartTotal[6];
+    int      flag_cooling;
+    int      num_files;
+    double   BoxSize;
+    double   Omega0;
+    double   OmegaLambda;
+    double   HubbleParam; 
+    char     fill[256- 6*4- 6*8- 2*8- 2*4- 6*4- 2*4 - 4*8];  /* fills to 256 Bytes */
+  };
+
+  struct ParticleState
+  {
+    float  Pos[3];
+    float  Init[3];
+    float  Vel[3];
+    float  VelInit[3];
+    float  Mass;
+    int    Type;
+    
+    float  Rho, U, Temp, Ne;
+    int Id;
+  };
+  
+  struct GadgetData {
+    GadgetHeader header;
+    ParticleState *particles;
+    int NumPart;
+    int ntot_withmasses;
+  };
+  
+  struct ParticleSetHeader {
+    int Ntypes;
+    float BoxSize;
+    float RedShift;
+    char header[256 - 4 - 2*4];
+  };
+
+  struct ParticleSet {
+    ParticleSetHeader header;
+    // Particle description
+    int *Npart;
+    ParticleState **particles;
+  };
+
+  struct PurePositionData {
+    unsigned int NumPart;
+    double BoxSize;
+    double hubble;
+    FCoordinates *pos;
+  };
+
+  struct PhaseSpaceData {
+    unsigned int NumPart;
+    double hubble;
+    double BoxSize;
+    FCoordinates *pos;
+    FCoordinates *vel;
+  };
+
+  struct PhaseSpaceDataID {
+    unsigned int NumPart;
+    double hubble;
+    double BoxSize;
+    FCoordinates *pos;
+    FCoordinates *vel;
+    int *ID;
+  };
+
+  void writeGadget(GadgetData *data, const char *fname);
+  GadgetData *loadGadget(const char *fname);
+  void freeGadget(GadgetData *data);
+
+  GadgetData *loadSimulationData(const char *fname);
+  GadgetData *loadHydra(const char *fname);
+
+  void writePersoSet(ParticleSet *set, const char *fname);
+  ParticleSet *loadPersoSet(const char *fname);
+  void freePersoSet(ParticleSet *set);
+
+};
+
+#endif
diff --git a/external/cosmotool/src/mach.hpp b/external/cosmotool/src/mach.hpp
new file mode 100644
index 0000000..abcb22a
--- /dev/null
+++ b/external/cosmotool/src/mach.hpp
@@ -0,0 +1,20 @@
+#ifndef __COSMO_MACHINE_TEST_HPP
+#define __COSMO_MACHINE_TEST_HPP
+
+#include 
+
+template
+T mach_epsilon()
+{
+  T eps = (T)1;
+
+  do
+    {
+      eps /= 2;
+    }
+  while ((T)(1 + (eps/2)) != (T)1);
+
+  return eps;
+}
+
+#endif
diff --git a/external/cosmotool/src/miniargs.cpp b/external/cosmotool/src/miniargs.cpp
new file mode 100644
index 0000000..4ad71c6
--- /dev/null
+++ b/external/cosmotool/src/miniargs.cpp
@@ -0,0 +1,55 @@
+#include 
+#include 
+#include 
+#include "miniargs.hpp"
+#include 
+
+using namespace CosmoTool;
+using namespace std;
+
+int CosmoTool::parseMiniArgs(int argc, char **argv, MiniArgDesc *desc)
+{
+  int numMini;
+  for (numMini = 0; desc[numMini].name != 0; numMini++);
+
+  if ((argc-1) != numMini)
+    {
+      cerr << "Usage: ";
+      for (int i = 0; i < numMini; i++)
+	{
+	  cerr << '[' << desc[i].name << "] ";
+	}
+      cerr << endl;
+      return 0;
+    }
+
+  for (int i = 0; i < numMini; i++)
+    {
+      switch (desc[i].argType)
+	{
+	case MINIARG_STRING:
+	  *((char **)desc[i].data) = strdup(argv[i+1]);
+	  break;
+	case MINIARG_INT:
+	  *((int *)desc[i].data) = strtol(argv[i+1], NULL, 0);
+	  break;
+	case MINIARG_DOUBLE:
+	  *((double *)desc[i].data) = strtod(argv[i+1], NULL);
+	  break;
+        case MINIARG_FLOAT:
+	  *((float *)desc[i].data) = strtod(argv[i+1], NULL);
+          break;
+	case MINIARG_DOUBLE_3D_VECTOR:
+	  {
+	    double *d_array = (double *)(desc[i].data);
+
+	    if (sscanf(argv[i+1], "(%lg,%lg,%lg)", &d_array[0], &d_array[1], &d_array[2]) != 3)
+	      return 0;
+	    break;
+	  }
+	}
+    }
+
+  return 1;
+}
+
diff --git a/external/cosmotool/src/miniargs.hpp b/external/cosmotool/src/miniargs.hpp
new file mode 100644
index 0000000..b746e55
--- /dev/null
+++ b/external/cosmotool/src/miniargs.hpp
@@ -0,0 +1,26 @@
+#ifndef _MAK_MINIARGS_HPP
+#define _MAK_MINIARGS_HPP
+
+namespace CosmoTool
+{
+  typedef enum 
+    {
+      MINIARG_NULL,
+      MINIARG_STRING,
+      MINIARG_INT,
+      MINIARG_DOUBLE,
+      MINIARG_FLOAT,
+      MINIARG_DOUBLE_3D_VECTOR
+    } MiniArgType;
+  
+  typedef struct
+  {
+    const char *name;
+    void *data;
+    MiniArgType argType;
+  } MiniArgDesc;
+
+  int parseMiniArgs(int argc, char **argv, MiniArgDesc *desc);
+};
+
+#endif
diff --git a/external/cosmotool/src/mykdtree.hpp b/external/cosmotool/src/mykdtree.hpp
new file mode 100644
index 0000000..c2087b5
--- /dev/null
+++ b/external/cosmotool/src/mykdtree.hpp
@@ -0,0 +1,183 @@
+#ifndef __HV_KDTREE_HPP
+#define __HV_KDTREE_HPP
+
+#include 
+#include "config.hpp"
+#include "bqueue.hpp"
+
+namespace CosmoTool {
+
+  template 
+  struct KDDef
+  {
+    typedef CType CoordType;
+    typedef float KDCoordinates[N];
+  };
+  
+  template
+  struct KDCell
+  {
+    bool active;
+    ValType val;
+    typename KDDef::KDCoordinates coord;
+  };
+
+  class NotEnoughCells: public Exception
+  {
+  public:
+    NotEnoughCells() : Exception() {}
+    ~NotEnoughCells() throw () {}
+  };
+
+  class InvalidOnDiskKDTree : public Exception
+  {
+  public:
+    InvalidOnDiskKDTree() : Exception() {}
+    ~InvalidOnDiskKDTree() throw () {}
+  };
+
+  template
+  struct KDTreeNode
+  {
+    KDCell *value;
+    KDTreeNode *children[2];
+    typename KDDef::KDCoordinates minBound, maxBound;
+#ifdef __KD_TREE_NUMNODES
+    uint32_t numNodes;
+#endif
+  };
+
+  template
+  class RecursionInfoCells
+  {
+  public:
+
+    typename KDDef::KDCoordinates x;
+    typename KDDef::CoordType r, r2;
+    KDCell **cells;
+    typename KDDef::CoordType *distances;
+    uint32_t currentRank;
+    uint32_t numCells;
+  };
+  
+
+  template
+  class RecursionMultipleInfo
+  {
+  public:
+    const typename KDDef::KDCoordinates& x;
+    BoundedQueue< KDCell *, typename KDDef::CoordType> queue;
+    int traversed;
+
+    RecursionMultipleInfo(const typename KDDef::KDCoordinates& rx,
+			  KDCell **cells,
+			  uint32_t numCells)
+      : x(rx), queue(cells, numCells, INFINITY),traversed(0)
+    {      
+    }
+  };
+
+  template 
+  struct KD_default_cell_splitter
+  {
+    void operator()(KDCell **cells, uint32_t Ncells, uint32_t& split_index, int axis, typename KDDef::KDCoordinates minBound, typename KDDef::KDCoordinates maxBound);
+  };
+
+  template >
+  class KDTree
+  {
+  public:
+    typedef typename KDDef::CoordType CoordType;
+    typedef typename KDDef::KDCoordinates coords;
+    typedef KDCell Cell;
+    typedef KDTreeNode Node;
+    
+    CellSplitter splitter;
+
+    KDTree(Cell *cells, uint32_t Ncells);
+    ~KDTree();
+
+    uint32_t getIntersection(const coords& x, CoordType r, 
+			     Cell **cells,
+			     uint32_t numCells)
+      throw (NotEnoughCells);
+    uint32_t getIntersection(const coords& x, CoordType r, 
+			     Cell **cells,
+			     CoordType *distances,
+			     uint32_t numCells)
+      throw (NotEnoughCells);
+    uint32_t countCells(const coords& x, CoordType r);
+
+    Cell *getNearestNeighbour(const coords& x);
+
+    void getNearestNeighbours(const coords& x, uint32_t NumCells,
+			      Cell **cells);
+    void getNearestNeighbours(const coords& x, uint32_t NumCells,
+			      Cell **cells,
+			      CoordType *distances);
+
+    Node *getRoot() { return root; }
+
+    void optimize();
+
+    Node *getAllNodes() { return nodes; }
+    uint32_t getNumNodes() const { return lastNode; }
+
+    uint32_t countActives() const;
+
+#ifdef __KD_TREE_NUMNODES
+    uint32_t getNumberInNode(const Node *n) const { return n->numNodes; }
+#else
+    uint32_t getNumberInNode(const Node *n) const {
+      if (n == 0) 
+        return 0;
+      return 1+getNumberInNode(n->children[0])+getNumberInNode(n->children[1]);
+    }
+#endif
+
+#ifdef __KD_TREE_SAVE_ON_DISK
+    KDTree(std::istream& i, Cell *cells, uint32_t Ncells)
+      throw (InvalidOnDiskKDTree);
+
+    void saveTree(std::ostream& o) const;
+#endif
+  protected:
+    Node *nodes;
+    uint32_t numNodes;
+    uint32_t lastNode;
+
+    Node *root;
+    Cell **sortingHelper;
+    Cell *base_cell;
+
+    Node *buildTree(Cell **cell0,
+		    uint32_t NumCells,
+		    uint32_t depth,
+		    coords minBound,
+		    coords maxBound);
+    
+    template
+    void recursiveIntersectionCells(RecursionInfoCells& info,
+				    Node *node,
+				    int level)
+      throw (NotEnoughCells);
+
+    CoordType computeDistance(const Cell *cell, const coords& x) const;
+    void recursiveNearest(Node *node,
+			  int level,
+			  const coords& x,
+			  CoordType& R2,
+			  Cell*& cell);
+    void recursiveMultipleNearest(RecursionMultipleInfo& info, Node *node,
+				  int level);    
+
+  };
+
+  template
+  uint32_t gatherActiveCells(KDCell **cells, uint32_t numCells);
+
+};
+
+#include "mykdtree.tcc"
+
+#endif
diff --git a/external/cosmotool/src/mykdtree.tcc b/external/cosmotool/src/mykdtree.tcc
new file mode 100644
index 0000000..a5e3abb
--- /dev/null
+++ b/external/cosmotool/src/mykdtree.tcc
@@ -0,0 +1,599 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace CosmoTool {
+
+  template
+  class CellCompare
+  {
+  public:
+    CellCompare(int k)
+    {
+      rank = k;
+    }
+
+    bool operator()(const KDCell *a, const KDCell *b) const
+    {
+      return (a->coord[rank] < b->coord[rank]);
+    }
+  protected:
+    int rank;
+  };
+
+  template
+  KDTree::~KDTree()
+  {
+  }
+
+  template
+  KDTree::KDTree(Cell *cells, uint32_t Ncells)
+  {
+    
+    base_cell = cells;
+    numNodes = Ncells;
+    nodes = new Node[numNodes];
+
+    sortingHelper = new Cell *[Ncells];
+    for (uint32_t i = 0; i < Ncells; i++)
+	sortingHelper[i] = &cells[i];    
+
+    optimize();
+  }
+
+  template
+  void KDTree::optimize()
+  {
+    coords absoluteMin, absoluteMax;
+
+    std::cout << "Optimizing the tree..." << std::endl;
+    uint32_t activeCells = gatherActiveCells(sortingHelper, numNodes);
+    std::cout << "  number of active cells = " << activeCells << std::endl;
+
+    lastNode = 0;
+    for (int i = 0; i < N; i++)
+      {
+	absoluteMin[i] = std::numeric_limits::max();
+	absoluteMax[i] = -std::numeric_limits::max();
+      }
+    // Find min and max corner
+    for (uint32_t i = 0; i < activeCells; i++)
+      {
+        KDCell *cell = sortingHelper[i];
+
+        for (int k = 0; k < N; k++) {
+          if (cell->coord[k] < absoluteMin[k])
+            absoluteMin[k] = cell->coord[k];
+          if (cell->coord[k] > absoluteMax[k])
+            absoluteMax[k] = cell->coord[k];
+        }
+      }
+    
+    std::cout << "   rebuilding the tree..." << std::endl;
+    root = buildTree(sortingHelper, activeCells, 0, absoluteMin, absoluteMax);    
+    std::cout << "   done." << std::endl;
+  }
+
+  template
+  uint32_t KDTree::getIntersection(const coords& x, CoordType r, 
+						    KDTree::Cell **cells,
+						    uint32_t numCells)
+    throw (NotEnoughCells)
+  {
+    RecursionInfoCells info;
+
+    memcpy(info.x, x, sizeof(x));
+    info.r = r;
+    info.r2 = r*r;
+    info.cells = cells;
+    info.currentRank = 0;
+    info.numCells = numCells;
+    info.distances = 0;
+
+    recursiveIntersectionCells(info, root, 0);
+    return info.currentRank;
+  }
+
+  template
+  uint32_t KDTree::getIntersection(const coords& x, CoordType r, 
+						    Cell **cells,
+						    CoordType *distances,
+						    uint32_t numCells)
+    throw (NotEnoughCells)
+  {
+    RecursionInfoCells info;
+
+    memcpy(info.x, x, sizeof(x));
+    info.r = r;
+    info.r2 = r*r;
+    info.cells = cells;
+    info.currentRank = 0;
+    info.numCells = numCells;
+    info.distances = distances;
+
+    recursiveIntersectionCells(info, root, 0);
+    return info.currentRank;
+  }
+
+  template
+  uint32_t KDTree::countCells(const coords& x, CoordType r)
+  {
+    RecursionInfoCells info;
+
+    memcpy(info.x, x, sizeof(x));
+    info.r = r;
+    info.r2 = r*r;
+    info.cells = 0;
+    info.currentRank = 0;
+    info.numCells = 0;
+    info.distances = 0;
+
+    recursiveIntersectionCells(info, root, 0);
+    return info.currentRank;
+  }
+
+  template
+  template
+  void KDTree::recursiveIntersectionCells(RecursionInfoCells& info, 
+							   Node *node,
+							   int level)
+    throw (NotEnoughCells)
+  {
+    int axis = level % N;
+    CoordType d2 = 0;
+
+    if (node->value->active) 
+      {
+	for (int j = 0; j < 3; j++)
+	  {
+	    CoordType delta = info.x[j]-node->value->coord[j];
+	    d2 += delta*delta;
+	  }
+	if (d2 < info.r2)
+	  {
+	    if (!justCount)
+	      {
+		if (info.currentRank == info.numCells)
+		  throw NotEnoughCells();
+		info.cells[info.currentRank] = node->value;
+		if (info.distances)
+		  info.distances[info.currentRank] = d2;
+	      }
+	    info.currentRank++;
+	  }
+      }
+
+    // The hypersphere intersects the left child node
+    if (((info.x[axis]+info.r) > node->minBound[axis]) &&
+	((info.x[axis]-info.r) < node->value->coord[axis]))
+      {
+	if (node->children[0] != 0)
+	  recursiveIntersectionCells(info, node->children[0],
+				     level+1);
+      }
+    if (((info.x[axis]+info.r) > node->value->coord[axis]) &&
+	((info.x[axis]-info.r) < node->maxBound[axis]))
+      {
+	if (node->children[1] != 0)
+	  recursiveIntersectionCells(info, node->children[1],
+					       level+1);
+      }
+  }
+
+  template
+  uint32_t gatherActiveCells(KDCell **cells,
+			     uint32_t Ncells)
+  {
+    uint32_t swapId = Ncells-1;
+    uint32_t i = 0;
+
+    while (!cells[swapId]->active && swapId > 0)
+      swapId--;
+
+    while (i < swapId)
+      {
+	if (!cells[i]->active)
+	  {
+	    std::swap(cells[i], cells[swapId]);
+	    while (!cells[swapId]->active && swapId > i)
+	      {
+		swapId--;
+	      }
+	  }
+	i++;
+      }
+    return swapId+1;
+  }
+
+  template
+  void  KD_default_cell_splitter::operator()(KDCell **cells, uint32_t Ncells, uint32_t& split_index, int axis, typename KDDef::KDCoordinates minBound, typename KDDef::KDCoordinates maxBound)
+  {
+    CellCompare compare(axis);
+    std::sort(cells, cells+Ncells, compare);
+    split_index = Ncells/2;
+  }
+
+
+  template
+  KDTreeNode *KDTree::buildTree(Cell **cell0,
+								  uint32_t Ncells,
+								  uint32_t depth,
+								  coords minBound,
+								  coords maxBound)
+  {
+    if (Ncells == 0)
+      return 0;
+
+    int axis = depth % N;
+    Node *node = &nodes[lastNode++];
+    uint32_t mid;
+    coords tmpBound;
+    
+    // Isolate the environment
+    splitter(cell0, Ncells, mid, axis, minBound, maxBound);
+
+    node->value = *(cell0+mid);
+    memcpy(&node->minBound[0], &minBound[0], sizeof(coords));
+    memcpy(&node->maxBound[0], &maxBound[0], sizeof(coords));
+
+    memcpy(tmpBound, maxBound, sizeof(coords));
+    tmpBound[axis] = node->value->coord[axis];
+
+    depth++;
+    node->children[0] = buildTree(cell0, mid, depth, minBound, tmpBound);
+    
+    memcpy(tmpBound, minBound, sizeof(coords));
+    tmpBound[axis] = node->value->coord[axis];
+    node->children[1] = buildTree(cell0+mid+1, Ncells-mid-1, depth, 
+				  tmpBound, maxBound);
+
+#ifdef __KD_TREE_NUMNODES
+    node->numNodes = (node->children[0] != 0) ? node->children[0]->numNodes : 0;
+    node->numNodes += (node->children[1] != 0) ? node->children[1]->numNodes : 0;
+    node->numNodes++;
+#endif
+
+    return node;
+  }
+
+  template
+  uint32_t KDTree::countActives() const
+  {
+    uint32_t numActive = 0;
+    for (uint32_t i = 0; i < lastNode; i++)
+      {
+	if (nodes[i].value->active)
+	  numActive++;
+      }
+    return numActive;
+  }
+
+  template
+  typename KDDef::CoordType
+  KDTree::computeDistance(const Cell *cell, const coords& x) const
+  {
+    CoordType d2 = 0;
+
+    for (int i = 0; i < N; i++)
+      {
+	CoordType delta = cell->coord[i] - x[i];
+	d2 += delta*delta;
+      }
+    return d2;
+  }
+
+  template
+  void
+  KDTree::recursiveNearest(
+				       Node *node,
+				       int level,
+				       const coords& x,
+				       CoordType& R2,
+				       Cell *& best)
+  {
+    CoordType d2 = 0;
+    int axis = level % N;
+    Node *other, *go;
+
+    if (x[axis] < node->value->coord[axis])
+      {
+	// The best is potentially in 0.
+	go = node->children[0];
+	other = node->children[1];
+      }
+    else
+      {
+	// If not it is in 1.
+	go = node->children[1];
+	other = node->children[0];
+	if (go == 0)
+	  {
+	    go = other;
+	    other = 0;
+	  }
+      }
+
+    if (go != 0)
+      {
+	recursiveNearest(go, level+1,
+			 x, R2,best);
+      }
+    else
+      {
+	CoordType thisR2 = computeDistance(node->value, x);
+	if (thisR2 < R2)
+	  {
+	    R2 = thisR2;
+	    best = node->value;
+	  }
+	return;
+      }
+    
+    // Check if current node is not the nearest
+    CoordType thisR2 = 
+      computeDistance(node->value, x);
+    
+    if (thisR2 < R2)
+      {
+	R2 = thisR2;
+	best = node->value;
+      }    
+    
+    // Now we found the best. We check whether the hypersphere
+    // intersect the hyperplane of the other branch
+
+    CoordType delta1;
+
+    delta1 = x[axis]-node->value->coord[axis];
+    if (delta1*delta1 < R2)
+      {
+	// The hypersphere intersects the hyperplane. Try the
+	// other branch
+	if (other != 0)
+	  {
+	    recursiveNearest(other, level+1, x, R2, best);
+	  }
+      }
+  }
+
+ template
+ KDCell *
+ KDTree::getNearestNeighbour(const coords& x)
+ {
+   CoordType R2 = INFINITY;
+   Cell *best = 0;
+
+   recursiveNearest(root, 0, x, R2, best);   
+   
+   return best;
+ }
+
+  template
+  void
+  KDTree::recursiveMultipleNearest(RecursionMultipleInfo& info, Node *node,
+					       int level)
+  {
+    CoordType d2 = 0;
+    int axis = level % N;
+    Node *other, *go;
+
+    if (info.x[axis] < node->value->coord[axis])
+      {
+	// The best is potentially in 0.
+	go = node->children[0];
+	other = node->children[1];
+      }
+    else
+      {
+	// If not it is in 1.
+	go = node->children[1];
+	other = node->children[0];
+	//	if (go == 0)
+	//	  {
+	//	go = other;
+	//other = 0;
+	//}
+      }
+
+    if (go != 0)
+      {
+	recursiveMultipleNearest(info, go, level+1);
+      }
+    
+    // Check if current node is not the nearest
+    CoordType thisR2 = 
+      computeDistance(node->value, info.x);
+    info.queue.push(node->value, thisR2);
+    info.traversed++;
+    //    if (go == 0)
+    //      return;
+    
+    // Now we found the best. We check whether the hypersphere
+    // intersect the hyperplane of the other branch
+
+    CoordType delta1;
+
+    delta1 = info.x[axis]-node->value->coord[axis];
+    if (delta1*delta1 < info.queue.getMaxPriority())
+      {
+	// The hypersphere intersects the hyperplane. Try the
+	// other branch
+	if (other != 0)
+	  {
+	    recursiveMultipleNearest(info, other, level+1);
+	  }
+      }    
+  }
+
+ template
+ void KDTree::getNearestNeighbours(const coords& x, uint32_t N2,
+						    Cell **cells)
+ {
+   RecursionMultipleInfo info(x, cells, N2);
+   
+   for (int i = 0; i < N2; i++)
+     cells[i] = 0;
+
+   recursiveMultipleNearest(info, root, 0);
+
+   //   std::cout << "Traversed = " << info.traversed << std::endl;
+ }
+
+ template
+ void KDTree::getNearestNeighbours(const coords& x, uint32_t N2,
+						    Cell **cells,
+						    CoordType *distances)
+ {
+   RecursionMultipleInfo info(x, cells, N2);
+   
+   for (int i = 0; i < N2; i++)
+     cells[i] = 0;
+
+   recursiveMultipleNearest(info, root, 0);
+   memcpy(distances, info.queue.getPriorities(), sizeof(CoordType)*N2);
+
+   //   std::cout << "Traversed = " << info.traversed << std::endl;
+ }
+
+#ifdef __KD_TREE_SAVE_ON_DISK
+#define KDTREE_DISK_SIGNATURE "KDTREE"
+#define KDTREE_DISK_SIGNATURE_LEN 7
+  
+  template
+  struct KDTreeOnDisk
+  {
+    long cell_id;
+    long children_node[2];
+    typename KDDef::KDCoordinates minBound, maxBound;
+  };  
+
+  struct KDTreeHeader
+  {
+    char id[KDTREE_DISK_SIGNATURE_LEN];
+    long nodesUsed, numCells;
+    long rootId;
+  };
+
+  template
+  void KDTree::saveTree(std::ostream& o) const
+  {
+    KDTreeHeader h;
+
+    strncpy(h.id, KDTREE_DISK_SIGNATURE, KDTREE_DISK_SIGNATURE_LEN);
+    h.nodesUsed = lastNode;
+    h.numCells = numNodes;
+    h.rootId = root - nodes;
+    o.write((char*)&h, sizeof(h));
+
+    for (long i = 0; i < lastNode; i++)
+      {
+	KDTreeOnDisk node_on_disk;
+
+	node_on_disk.cell_id = nodes[i].value - base_cell;
+	if (nodes[i].children[0] == 0)
+	  node_on_disk.children_node[0] = -1L;
+	else
+	  node_on_disk.children_node[0] = nodes[i].children[0] - nodes;
+       assert((node_on_disk.children_node[0] == -1) || ((node_on_disk.children_node[0] >= 0) &&  (node_on_disk.children_node[0] < lastNode)));
+
+	if (nodes[i].children[1] == 0)
+	  node_on_disk.children_node[1] = -1L;
+	else
+	  node_on_disk.children_node[1] = nodes[i].children[1] - nodes;
+       assert((node_on_disk.children_node[1] == -1) || ((node_on_disk.children_node[1] >= 0) &&  (node_on_disk.children_node[1] < lastNode)));
+
+	memcpy(node_on_disk.minBound, nodes[i].minBound, sizeof(coords));
+	memcpy(node_on_disk.maxBound, nodes[i].maxBound, sizeof(coords));
+
+	o.write((char *)&node_on_disk, sizeof(node_on_disk));
+      }
+  }
+
+  template
+  KDTree::KDTree(std::istream& in, Cell *cells, uint32_t Ncells)
+    throw (InvalidOnDiskKDTree)
+  {
+    KDTreeHeader h;
+
+    if (!in)
+      throw InvalidOnDiskKDTree();
+
+    in.read((char *)&h, sizeof(h));
+    if (!in || strncmp(h.id, KDTREE_DISK_SIGNATURE, KDTREE_DISK_SIGNATURE_LEN) != 0)
+      {
+         std::cerr << "KDTree Signature invalid" << std::endl;
+         throw InvalidOnDiskKDTree();
+      } 
+
+    if (h.numCells != Ncells || h.nodesUsed < 0) {
+      std::cerr << "The number of cells has changed (" << h.numCells << " != " << Ncells << ") or nodesUsed=" << h.nodesUsed << std::endl;
+      throw InvalidOnDiskKDTree();
+    }
+
+    base_cell = cells;
+    nodes = new Node[h.nodesUsed];
+    lastNode = h.nodesUsed;
+    numNodes = Ncells;
+
+    for (long i = 0; i < lastNode; i++)
+      {
+	KDTreeOnDisk node_on_disk;
+
+	in.read((char *)&node_on_disk, sizeof(node_on_disk));
+
+	if (!in) {
+           std::cerr << "End-of-file reached" << std::endl;
+           delete[] nodes;
+           throw InvalidOnDiskKDTree();
+        }
+        if (node_on_disk.cell_id > numNodes || node_on_disk.cell_id < 0 ||
+	    node_on_disk.children_node[0] > lastNode || node_on_disk.children_node[0] < -1 ||
+	    node_on_disk.children_node[1] > lastNode || node_on_disk.children_node[1] < -1)
+	  {
+	    delete[] nodes;
+            std::cerr << "Invalid cell id or children node id invalid" << std::endl;
+            std::cerr << node_on_disk.cell_id << std::endl << node_on_disk.children_node[0] << std::endl << node_on_disk.children_node[1] << std::endl;
+	    throw InvalidOnDiskKDTree();
+	  }
+
+	nodes[i].value = base_cell + node_on_disk.cell_id;
+	if (node_on_disk.children_node[0] == -1)
+	  nodes[i].children[0] = 0;
+	else
+	  nodes[i].children[0] = nodes + node_on_disk.children_node[0];
+
+	if (node_on_disk.children_node[1] == -1)
+	  nodes[i].children[1] = 0;
+	else
+	  nodes[i].children[1] = nodes + node_on_disk.children_node[1];
+
+	memcpy(nodes[i].minBound, node_on_disk.minBound, sizeof(coords));
+	memcpy(nodes[i].maxBound, node_on_disk.maxBound, sizeof(coords));
+
+	int c;
+	for (c = 0; c < N; c++)
+	  if (nodes[i].value->coord[c] < nodes[i].minBound[c] ||
+	      nodes[i].value->coord[c] > nodes[i].maxBound[c])
+	    break;
+	if (c != N)
+	  {
+	    delete[] nodes;
+            std::cerr << "Coordinates of the cell inconsistent with the boundaries" << std::endl
+                      << "   X=" << nodes[i].value->coord[0] << " B=[" << nodes[i].minBound[0] << "," << nodes[i].maxBound[0] << "]" << std::endl
+                      << "   Y=" << nodes[i].value->coord[1] << " B=[" << nodes[i].minBound[1] << "," << nodes[i].maxBound[1] << "]" << std::endl
+                      << "   Z=" << nodes[i].value->coord[2] << " B=[" << nodes[i].minBound[2] << "," << nodes[i].maxBound[2] << "]" << std::endl;
+	    throw InvalidOnDiskKDTree();
+	  }
+      }
+
+    root = &nodes[h.rootId];
+    
+    sortingHelper = new Cell *[Ncells];
+    for (uint32_t i = 0; i < Ncells; i++)
+      sortingHelper[i] = &cells[i];    
+  }
+#endif
+
+};
diff --git a/external/cosmotool/src/newton.hpp b/external/cosmotool/src/newton.hpp
new file mode 100644
index 0000000..3e861f0
--- /dev/null
+++ b/external/cosmotool/src/newton.hpp
@@ -0,0 +1,30 @@
+#ifndef _COSMOTOOL_NEWTON_HPP
+#define _COSMOTOOL_NEWTON_HPP
+
+#include 
+
+namespace CosmoTool
+{
+  template
+  T newtonSolver(T x0, FunT function, double residual = 1e-3)
+  {
+    T x, xold = x0;
+    T f_x = function.eval(x0);
+    T df_x = function.derivative(x0);
+    
+    x = xold - f_x/df_x;
+
+    while (std::abs(xold-x) > residual)
+      {	
+	xold = x;
+	f_x = function.eval(x);
+	df_x = function.derivative(x);
+	x = xold - f_x/df_x;
+      }
+	   
+    return x;
+  }
+
+};
+
+#endif
diff --git a/external/cosmotool/src/octTree.cpp b/external/cosmotool/src/octTree.cpp
new file mode 100644
index 0000000..4ef929d
--- /dev/null
+++ b/external/cosmotool/src/octTree.cpp
@@ -0,0 +1,175 @@
+#include 
+#include 
+#include 
+#include "config.hpp"
+#include "octTree.hpp"
+
+using namespace std;
+using namespace CosmoTool;
+
+//#define VERBOSE
+
+static uint32_t mypow(uint32_t i, uint32_t p)
+{
+  if (p == 0)
+    return 1;
+  else if (p == 1)
+    return i;
+
+  uint32_t k = p/2;
+  uint32_t j = mypow(i, k);
+  if (2*k==p)
+    return j*j;
+  else
+    return j*j*i;  
+}
+
+OctTree::OctTree(const FCoordinates *particles, octPtr numParticles, 
+		 uint32_t maxMeanTreeDepth, uint32_t maxAbsoluteDepth,
+		 uint32_t threshold)
+{
+  cout << "MeanTree=" << maxMeanTreeDepth << endl;
+  numCells = mypow(8, maxMeanTreeDepth);
+  assert(numCells < invalidOctCell);
+  //#ifdef VERBOSE
+  cerr << "Allocating " << numCells << " octtree cells" << endl;
+  //#endif
+  
+  for (int j = 0; j < 3; j++)
+    xMin[j] = particles[0][j];
+
+  for (octPtr i = 1; i < numParticles; i++)
+    {
+      for (int j = 0; j < 3; j++)
+	{
+	  if (particles[i][j] < xMin[j])
+	    xMin[j] = particles[i][j];      
+	}
+    }
+  
+  lenNorm = 0;
+  for (octPtr i = 0; i < numParticles; i++)
+    {
+      for (int j = 0; j < 3; j++)
+	{
+	  float delta = particles[i][j]-xMin[j];
+	  if (delta > lenNorm)
+	    lenNorm = delta;
+	}
+    }
+  cout << xMin[0] << " " << xMin[1] << " " << xMin[2] << " lNorm=" << lenNorm << endl; 
+
+  cells = new OctCell[numCells];
+  Lbox = (float)(octCoordTypeNorm+1);
+
+  cells[0].numberLeaves = 0;
+  for (int i = 0; i < 8; i++)
+    cells[0].children[i] = emptyOctCell;
+
+  lastNode = 1;
+  this->particles = particles;
+  this->numParticles = numParticles;
+  buildTree(maxAbsoluteDepth);
+  //#ifdef VERBOSE
+  cerr << "Used " << lastNode << " cells" << endl;
+  //#endif
+}
+
+OctTree::~OctTree()
+{
+  delete cells;
+}
+
+void OctTree::buildTree(uint32_t maxAbsoluteDepth)
+{
+  for (octPtr i = 0; i < numParticles; i++)
+    {
+      OctCoords rootCenter = { octCoordCenter, octCoordCenter, octCoordCenter };
+      insertParticle(0, // root node
+		     rootCenter,
+		     octCoordCenter,
+		     i,
+		     maxAbsoluteDepth);
+    }
+}
+
+
+void OctTree::insertParticle(octPtr node, 
+			     const OctCoords& icoord,
+			     octCoordType halfNodeLength,
+			     octPtr particleId,
+			     uint32_t maxAbsoluteDepth)
+{
+  
+#ifdef VERBOSE
+  cout << "Entering " << node << " (" << icoord[0] << "," << icoord[1] << "," << icoord[2] << ")" << endl;
+#endif
+  int octPos = 0;
+  int ipos[3] = { 0,0,0};
+  octPtr newNode;
+  OctCoords newCoord;
+  
+  cells[node].numberLeaves++;
+  if (maxAbsoluteDepth == 0)
+    {
+      // All children must be invalid.
+      for (int i = 0 ; i < 8; i++)
+	cells[node].children[i] = invalidOctCell;
+      
+      return;
+    }
+  
+  for (int j = 0; j < 3; j++)
+    {
+      float treePos = (particles[particleId][j]-xMin[j])*Lbox/lenNorm;
+      if ((octPtr)(treePos) > icoord[j])
+	{
+	  octPos |= (1 << j);
+	  ipos[j] = 1;
+	}
+    }
+  
+  if (cells[node].children[octPos] == emptyOctCell)
+    {
+      // Put the particle there.
+      cells[node].children[octPos] = particleId | octParticleMarker;
+      return;
+    }
+  
+  // If it is a node, explores it.
+  if (!(cells[node].children[octPos] & octParticleMarker))
+    {
+      assert(halfNodeLength >= 2);
+      // Compute coordinates
+      for (int j = 0; j < 3; j++)
+	newCoord[j] = icoord[j]+(2*ipos[j]-1)*halfNodeLength/2;
+      insertParticle(cells[node].children[octPos], newCoord, halfNodeLength/2, 
+		     particleId, maxAbsoluteDepth-1);
+      return;
+    }
+  
+  // We have a particle there. 
+  // Make a new node and insert the old particle into this node.
+  // Insert the new particle into the node also
+  // Finally put the node in place
+  
+  newNode = lastNode++;
+  assert(lastNode != numCells);
+  
+  for (int j = 0; j < 8; j++)
+    cells[newNode].children[j] = emptyOctCell;
+  cells[newNode].numberLeaves = 0;
+  
+  // Compute coordinates
+  for (int j = 0; j < 3; j++)
+    newCoord[j] = icoord[j]+(2*ipos[j]-1)*halfNodeLength/2;
+  
+  octPtr oldPartId = cells[node].children[octPos] & octParticleMask;
+  
+  insertParticle(newNode, newCoord, halfNodeLength/2,
+		 oldPartId, maxAbsoluteDepth-1);
+  insertParticle(newNode, newCoord, halfNodeLength/2,
+		 particleId, maxAbsoluteDepth-1);  
+  cells[node].children[octPos] = newNode;
+}
+
diff --git a/external/cosmotool/src/octTree.hpp b/external/cosmotool/src/octTree.hpp
new file mode 100644
index 0000000..447a6b0
--- /dev/null
+++ b/external/cosmotool/src/octTree.hpp
@@ -0,0 +1,145 @@
+#ifndef __COSMOTOOL_AMR_HPP
+#define __COSMOTOOL_AMR_HPP
+
+#include "config.hpp"
+
+namespace CosmoTool
+{
+
+  typedef uint32_t octPtr;
+  typedef uint16_t octCoordType;
+  static const uint16_t octCoordTypeNorm = 0xffff;
+  static const uint16_t octCoordCenter = 0x8000;
+
+  // This is also the root cell, but this one
+  // is never referenced (really ??).
+  static const octPtr invalidOctCell = 0xffffffff;
+  static const octPtr emptyOctCell = 0;
+  static const octPtr octParticleMarker = 0x80000000;
+  static const octPtr octParticleMask = 0x7fffffff;
+
+  typedef octCoordType OctCoords[3];
+
+  struct OctCell
+  {
+    octPtr numberLeaves;
+    octPtr children[8];
+  };
+
+  class OctTree
+  {
+  public:
+    OctTree(const FCoordinates *particles, octPtr numParticles,
+	    uint32_t maxTreeDepth,  uint32_t maxAbsoluteDepth,
+	    uint32_t threshold = 1);
+    ~OctTree();
+
+    void buildTree(uint32_t maxAbsoluteDepth);
+    void insertParticle(octPtr node, 
+			const OctCoords& icoord,
+			octCoordType halfNodeLength,
+			octPtr particleId,
+			uint32_t maxAbsoluteDepth);
+
+
+    octPtr getNumberLeaves() const {
+      return cells[0].numberLeaves;
+    }
+
+    static bool unconditioned(const FCoordinates&, octPtr, float, bool)
+    {
+      return true;
+    }
+
+    template
+    void walkTree(FunT f)
+    {
+      walkTree(f, unconditioned);
+    }
+
+    template
+    void walkTree(FunT f, CondT condition)
+    {
+      OctCoords rootCenter = { octCoordCenter, octCoordCenter, octCoordCenter };
+
+      walkTreeElements(f, condition, 0, rootCenter, octCoordCenter);
+    }
+
+    
+
+  protected:
+    const FCoordinates *particles;
+    octPtr numParticles;
+    OctCell *cells;
+    float Lbox;
+    octPtr lastNode;
+    octPtr numCells;  
+    float lenNorm;
+    float xMin[3];
+
+
+    static bool unconditioned()
+    {
+      return true;
+    }
+
+    template
+    void walkTreeElements(FunT f, CondT condition,
+			  octPtr node, 
+			  const OctCoords& icoord,
+			  octCoordType halfNodeLength)
+    {
+      OctCoords newCoord;
+      FCoordinates center, realCenter;
+
+      for (int j = 0; j < 3; j++)
+	{
+	  center[j] = icoord[j]/(2.*octCoordCenter);
+	  realCenter[j] = xMin[j] + center[j]*lenNorm;
+	}
+
+      f(realCenter, cells[node].numberLeaves, lenNorm*halfNodeLength/(float)octCoordCenter,
+	cells[node].children[0] == invalidOctCell, // True if this is a meta-node
+	false);
+     
+      if (!condition(realCenter, cells[node].numberLeaves,
+		     lenNorm*halfNodeLength/(float)octCoordCenter,
+		     cells[node].children[0] == invalidOctCell))
+	return;
+       
+      for (int i = 0; i < 8; i++)
+	{
+	  octPtr newNode = cells[node].children[i];
+	  int ipos[3] = { (i&1), (i&2)>>1, (i&4)>>2 };
+	  
+	  if (newNode == emptyOctCell || newNode == invalidOctCell)
+	    continue;
+	  	  
+	  for (int j = 0; j < 3; j++)
+	    newCoord[j] = icoord[j]+(2*ipos[j]-1)*halfNodeLength/2;
+	  
+	  if (newNode & octParticleMarker)
+	    {
+	      for (int j = 0; j < 3; j++)
+		{
+		  center[j] = newCoord[j]/(2.*octCoordCenter);
+		  realCenter[j] = xMin[j] + lenNorm*center[j];
+		}
+
+	      f(realCenter, 
+		1, lenNorm*halfNodeLength/(2.*octCoordCenter),
+		false, true);
+	      continue;
+	    }
+
+	  walkTreeElements(f, condition, cells[node].children[i], newCoord, halfNodeLength/2);
+	}
+      
+    }
+
+  };
+  
+};
+
+
+#endif
diff --git a/external/cosmotool/src/pool.hpp b/external/cosmotool/src/pool.hpp
new file mode 100644
index 0000000..5d2203b
--- /dev/null
+++ b/external/cosmotool/src/pool.hpp
@@ -0,0 +1,166 @@
+#ifndef __COSMO_POOL_HPP
+#define __COSMO_POOL_HPP
+
+#include 
+#include "config.hpp"
+
+namespace CosmoTool
+{
+
+  template
+  struct PoolNode
+  {
+    T *data;
+    uint32_t last_free, size;
+    PoolNode *next;
+  };
+
+  template class MemoryPool;
+
+  template
+  class MemoryIterator
+  {
+  private:
+    friend  class MemoryPool;
+
+    PoolNode *cur, *previous;
+    uint32_t in_node;
+
+    MemoryIterator(PoolNode *h) 
+    {
+      cur = h; 
+      previous = h; 
+      in_node = 0;
+    }
+  public:
+    MemoryIterator() { cur = 0; }
+    ~MemoryIterator() {}
+
+    const MemoryIterator& operator=(const MemoryIterator& i)
+    {
+      cur = i.cur;
+      previous = i.previous;
+      in_node = i.in_node;
+    }
+    
+    bool operator==(const MemoryIterator& i) const
+    {
+      return (cur == i.cur) && (in_node == i.in_node);
+    }
+
+    MemoryIterator& operator++()
+    {
+      if (cur == 0)
+	return *this;
+
+      in_node++;
+      if (in_node == cur->size)
+	{
+	  in_node = 0;
+	  previous = cur;
+	  cur = cur->next;
+	}
+      return *this;
+    }
+
+    T& operator*()
+    {
+      return cur->data[in_node];
+    }
+
+    T& operator->()
+    {
+      return cur->data[in_node];
+    }
+
+    
+    
+  };
+
+  // This is bare simple memory pools
+  template
+  class MemoryPool
+  {
+  private:
+    uint32_t m_allocSize;
+    PoolNode *head, *current;
+    typedef MemoryIterator iterator;
+  public:
+    MemoryPool(uint32_t allocSize)
+      : m_allocSize(allocSize), head(0), current(0) {}
+
+    ~MemoryPool()
+    {
+      free_all();
+    }
+
+    void free_all()
+    {
+      PoolNode *node = head;
+
+      while (node != 0)
+	{
+	  PoolNode *next = node->next;
+	  	  
+	  delete[] node->data;
+	  delete node;
+	  node = next;
+	}
+      current = head = 0;
+    }
+
+    T *alloc()
+    {
+      T *ret = alloc_in_node();
+      return (ret == 0) ? alloc_new_in_node() : ret;
+    }
+    
+    iterator begin()
+    {
+      return iterator(head);
+    }
+    
+    iterator end()
+    {
+      return iterator(0);
+    }    
+
+  protected:
+    T *alloc_in_node()
+    {
+      if (current == 0 || current->last_free == current->size)
+	return 0;
+      return ¤t->data[current->last_free++];
+    }
+
+    T *alloc_new_in_node()
+    {
+      PoolNode *newNode = new PoolNode;
+      if (newNode == 0)
+	return 0;
+
+      newNode->last_free = 1;
+      newNode->size = m_allocSize;
+      newNode->data = new T[m_allocSize];
+      if (newNode->data == 0)
+	{
+	  delete newNode;
+	  return 0;
+	}
+      newNode->next = 0;
+
+      if (current == 0)
+	current = head = newNode;
+      else
+	{
+	  current->next = newNode;
+	  current = newNode;
+	}
+      return &newNode->data[0];
+    }
+
+  };
+  
+};
+
+#endif
diff --git a/external/cosmotool/src/powerSpectrum.cpp b/external/cosmotool/src/powerSpectrum.cpp
new file mode 100644
index 0000000..9f3d7c8
--- /dev/null
+++ b/external/cosmotool/src/powerSpectrum.cpp
@@ -0,0 +1,654 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "powerSpectrum.hpp"
+
+using namespace std;
+
+#define USE_GSL
+#define TOLERANCE 1e-6
+#define NUM_ITERATION 8000
+
+#define POWER_EFSTATHIOU 1
+#define HU_WIGGLES 2
+#define HU_BARYON 3
+#define OLD_POWERSPECTRUM 4
+#define POWER_BARDEEN 5
+#define POWER_SUGIYAMA 6
+#define POWER_BDM 7
+#define POWER_TEST 8
+
+#define POWER_SPECTRUM HU_WIGGLES
+
+namespace Cosmology {
+
+double n = 1.0;
+double K0 = 1;
+double V0 = 627;
+
+  double CMB_VECTOR[3] = {
+    56.759,
+    -540.02,
+    313.50
+  };
+
+
+// WMAP5
+double h = 0.719;
+double SIGMA8 = 0.77;
+double OMEGA_B = 0.043969;
+double OMEGA_C = 0.21259;
+
+
+// WMAP5-modification
+//double h = 0.719;
+//double SIGMA8 = 0.77;
+//double OMEGA_B = 0;
+//double OMEGA_C = 0.21259+0.043969;
+
+// LCDM STRAUSS ?
+//double h = 0.67;
+//double SIGMA8 = 0.67;
+//double OMEGA_B = 0;
+//double OMEGA_C = 0.30; 
+
+// SCDM STRAUSS
+//double h = 0.5;
+//double SIGMA8= 1.05;
+//double OMEGA_B = 0;
+//double OMEGA_C = 1;
+
+// Sugiyama test
+//double h = 0.5;
+//double SIGMA8= 0.5;//1.05;
+//double OMEGA_B = 0.0125*4;
+//double OMEGA_C = 0.1-OMEGA_B;
+
+// HU TEST
+//double h = 0.5;
+//double SIGMA8 = 0.5;
+//double OMEGA_B = 0.09;
+//double OMEGA_C = 0.21;
+
+// HDM STRAUSS
+//double h = 0.5;
+//double SIGMA8 = 0.86;
+//double OMEGA_B = 0;
+//double OMEGA_C = 1;
+
+// FOR "BEST FIT"
+//double h = 0.82;
+//double SIGMA8 = 0.76;
+//double OMEGA_B = 0.043969;
+//double OMEGA_C = 0.15259;
+
+
+// FOR JUSZKIEWICZ CHECKING (CDM) ! WARNING ! He smoothes 
+// with a gaussian filter the density field, i.e. one has
+// to multiply P(k) by exp(-k^2 R^2) with R the radius
+// of the filter. Dammit !
+//double h = 0.5;
+//double SIGMA8=1/2.5;
+//double OMEGA_B=0.;
+//double OMEGA_C=1;
+//#define JUSZKIEWICZ_PATCH
+//#define RJUSZ 6.0
+
+// (BDM)
+//double h = 0.5;
+//double SIGMA8=1;
+//double OMEGA_B=0.0;
+//double OMEGA_C=0.4;
+
+
+// FOR HU CHECKING
+//double h = 0.5;
+//double SIGMA8= 1;
+//double OMEGA_B=0.09;
+//double OMEGA_C=0.21;
+
+
+double OMEGA_0 = OMEGA_B+OMEGA_C;
+double Omega = OMEGA_0;
+double Theta_27 = 2.728 / 2.7;
+double beta = pow(OMEGA_0, 5./9);
+double OmegaEff = OMEGA_0 * h * h;
+double Gamma0 = OMEGA_0 * h * h;
+
+
+/*
+ * This is \hat{tophat}
+ */
+double tophatFilter(double u)
+{
+  if (u != 0)    
+    return 3 / (u*u*u) * (sin(u) - u * cos(u));
+  else
+    return 1;
+}
+
+/*
+ * This is \tilde{w}
+ */
+double externalFilter(double u)
+{
+  if (u != 0)
+    return 1 - sin(u)/u;
+  return 0.;
+}
+
+double powC(double q, double alpha_c)
+{
+  return 14.2 / alpha_c + 386 / (1 + 69.9 * pow(q, 1.08));
+}
+
+double T_tilde_0(double q, double alpha_c, double beta_c)
+{
+  double a = log(M_E + 1.8 * beta_c * q);
+  return a / ( a  + powC(q, alpha_c) * q * q);
+}
+
+double j_0(double x)
+{
+  if (x == 0)
+    return 1.0;
+  return sin(x)/x;
+}
+
+double powG(double y)
+{
+  double a = sqrt(1 + y);
+
+  return y * (-6 * a + (2 + 3 * y) *log((a + 1)/(a - 1)));
+}
+
+/*
+ * This function returns the power spectrum evaluated at k (in Mpc, not in Mpc/h).
+ */
+double powerSpectrum(double k, double normPower)
+{
+#if POWER_SPECTRUM == POWER_EFSTATHIOU
+  double a = 6.4/Gamma0;
+  double b = 3/Gamma0;
+  double c = 1.7/Gamma0;
+  double nu = 1.13;
+  
+  double f = (a*k) + pow(b*k,1.5) + pow(c*k,2);
+
+  // EFSTATHIOU  ET AL. (1992)
+  return normPower * pow(k,n) * pow(1+pow(f,nu),(-2/nu));
+#endif
+
+  // EISENSTEIN ET HU (1998)
+  // FULL POWER SPECTRUM WITH BARYONS AND WIGGLES
+
+#if POWER_SPECTRUM == HU_WIGGLES
+  // EISENSTEIN ET HU (1998)
+  // FULL POWER SPECTRUM WITH BARYONS AND WIGGLES
+
+  double k_silk = 1.6 * pow(OMEGA_B * h * h, 0.52) * pow(OmegaEff, 0.73) * (1 + pow(10.4 * OmegaEff, -0.95));
+  double z_eq = 2.50e4 * OmegaEff * pow(Theta_27, -4);
+  double s = 44.5 * log(9.83 / OmegaEff) / (sqrt(1 + 10 * pow(OMEGA_B * h * h, 0.75)));
+  double f = 1 / (1 + pow(k * s / 5.4, 4));
+  double k_eq = 7.46e-2 * OmegaEff * pow(Theta_27, -2);
+  double a1 = pow(46.9 * OmegaEff, 0.670) * (1 + pow(32.1 * OmegaEff, -0.532));
+  double a2 = pow(12.0 * OmegaEff, 0.424) * (1 + pow(45.0 * OmegaEff, -0.582));
+  double alpha_c = pow(a1, -OMEGA_B/ OMEGA_0) * pow(a2, -pow(OMEGA_B / OMEGA_0, 3));  
+
+  double q = k / (13.41 * k_eq);
+  double b1_betac = 0.944 * 1/(1 + pow(458 * OmegaEff, -0.708));
+  double b2_betac = pow(0.395 * OmegaEff, -0.0266);
+  double beta_c = 1/ ( 1 + b1_betac * (pow(OMEGA_C / OMEGA_0, b2_betac) - 1)   );
+  double T_c = f * T_tilde_0(q, 1, beta_c) + (1 - f) * T_tilde_0(q, alpha_c, beta_c);
+
+  double b1_zd = 0.313 * pow(OmegaEff, -0.419) * (1 + 0.607 * pow(OmegaEff, 0.674));
+  double b2_zd = 0.238 * pow(OmegaEff, 0.223);
+  double z_d = 1291 * pow(OmegaEff, 0.251) / (1 + 0.659 * pow(OmegaEff, 0.828)) * (1 + b1_zd * pow(OmegaEff, b2_zd));
+  double R_d = 31.5 * OMEGA_B * h * h * pow(Theta_27, -4) * 1e3 / z_d;
+
+  double alpha_b = 2.07 * k_eq * s * pow(1 + R_d, -0.75) * powG((1 + z_eq)/(1 + z_d));
+  double beta_b = 0.5 + OMEGA_B / OMEGA_0 + (3 - 2 * OMEGA_B / OMEGA_0) * sqrt(pow(17.2 * OmegaEff, 2) + 1);
+  double beta_node = 8.41 * pow(OmegaEff, 0.435);
+  double s_tilde = s * pow(1 + pow(beta_node / (k * s), 3), -1./3);
+
+  double T_b = (T_tilde_0(q, 1, 1) / (1 + pow(k * s / 5.2, 2)) + alpha_b / (1 + pow(beta_b / (k * s), 3)) * exp(-pow(k/k_silk, 1.4))) * j_0(k * s_tilde);  
+
+  double T_k = OMEGA_B/OMEGA_0 * T_b + OMEGA_C/OMEGA_0 * T_c;  
+
+  return normPower * pow(k,n) * T_k * T_k;
+#endif
+
+  // EISENSTEIN ET AL. (2008), SHAPED POWER SPECTRUM WITH BARYON, WITHOUT WIGGLES
+#if POWER_SPECTRUM == HU_BARYON
+  double s = 44.5 * log(9.83 / OmegaEff) / (sqrt(1 + 10 * pow(OMEGA_B * h * h, 0.75)));
+  double alpha_Gamma = 1 - 0.328 * log(431 * OmegaEff) * OMEGA_B / OMEGA_0 + 0.38 * log(22.3 * OmegaEff) * pow(OMEGA_B / OMEGA_0, 2);
+  double GammaEff = OMEGA_0 * h * (alpha_Gamma + (1 - alpha_Gamma)/(1 + pow(0.43 * k * s, 4)));
+  double q = k/(h*GammaEff) * pow(Theta_27, 2);
+  double L_0 = log(2 * M_E + 1.8 * q);
+  double C_0 = 14.2 + 731 / (1 + 62.5 * q);
+  double T0 = L_0 / (L_0 + C_0 * q * q);
+  
+  return normPower * pow(k,n) * T0 * T0;
+#endif
+
+#if POWER_SPECTRUM == OLD_POWERSPECTRUM
+  // OLD FUNCTION: 
+  static const double l = 1/(Omega * h*h);
+  static const double alpha = 1.7 * l, beta = 9.0 * pow(l, 1.5), gamma = l*l;
+  return normPower * pow(k,n) * pow(1 + alpha * k +  beta * pow(k,1.5) + gamma *k*k,-2);
+#endif
+
+#if POWER_SPECTRUM == POWER_SUGIYAMA
+  double q = k * Theta_27*Theta_27 / (OmegaEff * exp(-OMEGA_B - sqrt(h/0.5)*OMEGA_B/OMEGA_0));
+  double L0 = log(2*M_E + 1.8 * q);
+  double C0 = 14.2 + 731 / (1 + 62.5 * q);
+  double T_k = L0 / (L0 + C0 * q*q);
+
+      //  double poly = 1 + 3.89 * q + pow(16.1*q,2) + pow(5.46*q,3) + pow(6.71*q,4);
+      //  double T_k = log(1+2.34*q)/(2.34*q) * pow(poly,-0.25);
+
+  return normPower * pow(k,n) * T_k * T_k;
+
+#endif
+
+#if POWER_SPECTRUM == POWER_BARDEEN
+  double q = k / (OmegaEff);
+
+  double poly = 1 + 3.89 * q + pow(16.1*q,2) + pow(5.46*q,3) + pow(6.71*q,4);
+  double T_k = log(1+2.34*q)/(2.34*q) * pow(poly,-0.25);
+
+  return normPower * pow(k,n) * T_k * T_k;
+
+#endif
+
+#if POWER_SPECTRUM == POWER_BDM
+
+  k /= h*h;
+  double alpha1 = 190;
+  double Gmu = 4.6;
+  double alpha2 = 11.5;
+  double alpha3 = 11;
+  double alpha4 = 12.55;
+  double alpha5 = 0.0004;
+  return normPower*k*alpha1*alpha1*Gmu*Gmu/(1+(alpha2*k)+pow(alpha3*k,2)+pow(alpha4*k,3))*pow(1+pow(alpha5/k,2), -2);
+#endif
+
+#if POWER_SPECTRUM == POWER_TEST
+  return 1/(1+k*k);
+#endif
+}
+
+/*
+ * This function computes the normalization of the power spectrum. It requests
+ * a sigma8 (density fluctuations within 8 Mpc/h)
+ */
+double gslPowSpecNorm(double k, void *params)
+{
+  double f = tophatFilter(k*8.0/h);
+  return powerSpectrum(k, 1.0)*k*k*f*f;
+}
+
+double computePowSpecNorm(double sigma8)
+{
+  int Nsteps = 30000;
+  double normVal = 0;
+
+#ifndef USE_GSL
+  for (int i = 1; i <= Nsteps; i++)
+    {
+      double t = i * 1.0/(Nsteps+1);
+      // Change of variable !
+      double k = (1-t)/t * K0;
+
+      // The filter
+      double filter_val = tophatFilter(k*8.0/h);
+      // The powerspectrum
+      double powVal = powerSpectrum(k, 1.0);
+      // Multiply by the tophat filter
+      powVal *= filter_val*filter_val;
+     
+      powVal *= k*k;
+      
+      // Account for change of variable
+      powVal /= (t*t);
+  
+      // Integrate !
+      normVal += powVal;
+    }
+
+  normVal /= 2*M_PI*M_PI;
+
+  // The dt element
+  normVal *= 1.0/(Nsteps+1) * K0;
+#else
+  double abserr;
+  gsl_integration_workspace *w = gsl_integration_workspace_alloc(NUM_ITERATION);
+  gsl_function f;
+
+  f.function = gslPowSpecNorm;
+  f.params = 0;
+
+  gsl_integration_qagiu(&f, 0, 0, TOLERANCE, NUM_ITERATION, w, &normVal, &abserr);
+
+  gsl_integration_workspace_free(w);
+
+  normVal /= (2*M_PI*M_PI);
+#endif
+
+  return sigma8*sigma8/normVal;
+}
+
+/*
+ * This function computes the variance of the Local Group velocity components
+ * for a survey which depth is topHatRad1 (in Mpc/h). This variance should
+ * be multiplied by (H \beta)^2 to be equal to a velocity^2.
+ */
+double gslVariance(double k, void *params)
+{
+  double R1 = *(double *)params;
+  double f = externalFilter(k * R1 / h);
+
+  double a = f*f;
+
+#ifdef JUSZKIEWICZ_PATCH
+  a *= exp(-k*k*(RJUSZ*RJUSZ/(h*h)));
+#endif
+  a *= powerSpectrum(k, 1.0);
+
+  return a;
+}
+
+double computeVariance(double powNorm,  double topHatRad1)
+{
+  int Nsteps = 100000;
+  double varVal = 0;
+
+#ifndef USE_GSL
+  
+  for (int i = 1; i <= Nsteps; i++)
+    {
+      double t = i * 1.0/(Nsteps+1);
+      // Change of variable !
+      double k = (1-t)/t * K0;
+      double powVal = powerSpectrum(k, powNorm);
+
+      double filter1Val = externalFilter(k*topHatRad1/h);
+
+#ifdef JUSZKIEWICZ_PATCH
+      powVal *= exp(-k*k*(RJUSZ*RJUSZ/(h*h)));
+#endif
+      powVal *= filter1Val*filter1Val;
+      
+      powVal /= (t*t);
+
+      varVal += powVal;
+    }
+
+  varVal *= 1.0/(Nsteps) * K0;
+  varVal *= 1.0/(6*M_PI*M_PI);
+
+#else
+
+  double abserr;
+  gsl_integration_workspace *w = gsl_integration_workspace_alloc(NUM_ITERATION);
+  gsl_function f;
+  
+  f.function = gslVariance;
+  f.params = &topHatRad1;
+
+  gsl_integration_qagiu(&f, 0, 0, TOLERANCE, NUM_ITERATION, w, &varVal, &abserr);
+
+  gsl_integration_workspace_free(w);
+
+  varVal *= powNorm/(6*M_PI*M_PI);
+
+#endif
+
+  return varVal;
+}
+
+/*
+ * This function computes the same quantity as computeVariance but
+ * for a survey infinitely deep.
+ */
+double gslVarianceZero(double k, void *params)
+{
+  double a = 1.0;
+
+#ifdef JUSZKIEWICZ_PATCH
+  a *= exp(-k*k*(RJUSZ*RJUSZ/(h*h)));
+#endif
+  a *= powerSpectrum(k, 1.0);
+
+  return a;
+}
+
+double computeVarianceZero(double powNorm)
+{
+  int Nsteps = 100000;
+  double varVal = 0;
+  
+#ifndef USE_GSL
+
+  for (int i = 1; i <= Nsteps; i++)
+    {
+      double t = i * 1.0/(Nsteps+1);
+      // Change of variable !
+      double k = (1-t)/t * K0;
+      double powVal = powerSpectrum(k, powNorm);
+      
+#ifdef JUSZKIEWICZ_PATCH
+      powVal *= exp(-k*k*(RJUSZ*RJUSZ/h*h));
+#endif
+
+      powVal /= (t*t);
+
+      varVal += powVal;
+    }
+
+  varVal *= 1.0/(Nsteps+1) * K0;
+  varVal *= 1.0/(6*M_PI*M_PI);
+
+#else
+ 
+  double abserr;
+  gsl_integration_workspace *w = gsl_integration_workspace_alloc(NUM_ITERATION);
+  gsl_function f;
+  
+  f.function = gslVarianceZero;
+  f.params = 0;
+
+  gsl_integration_qagiu(&f, 0, 0, TOLERANCE, NUM_ITERATION, w, &varVal, &abserr);
+
+  gsl_integration_workspace_free(w);
+
+  varVal *= powNorm/(6*M_PI*M_PI);
+#endif
+
+  return varVal;
+}
+
+/*
+ * This function computes the correlation between the infinitely deep
+ * velocity of the Local Group and the one estimated from a survey
+ * which depth is topHatRad1.
+ * This corresponds to \gamma.
+ * This quantity must be multiplied by H \beta to be equal to a velocity^2.
+ */
+double gslCorrel(double k, void *params)
+{
+  double R1 = ((double *)params)[0];
+  double a = externalFilter(k * R1 / h);// * externalFilter(k * R2 / h);
+
+#ifdef JUSZKIEWICZ_PATCH
+  a *= exp(-k*k*(RJUSZ*RJUSZ/(h*h)));
+#endif
+  a *= powerSpectrum(k, 1.0);
+
+  return a;
+}
+
+double gslCorrelBis(double t, void *params)
+{
+  double k = (1-t)/t;
+  double v = gslCorrel(k, params);
+
+  return v/(t*t);
+}
+
+double gslCorrel2(double k, void *params)
+{
+  double R1 = ((double *)params)[0];
+  double R2 = ((double *)params)[1];
+  double a = externalFilter(k * R1 / h) * externalFilter(k * R2 / h);        
+  
+#ifdef JUSZKIEWICZ_PATCH
+  a *= exp(-k*k*(RJUSZ*RJUSZ/(h*h)));
+#endif
+  a *= powerSpectrum(k, 1.0) ;
+  
+  return a;
+}
+
+double gslCorrel2bis(double t, void *params)
+{
+  double k = (1-t)/t;
+  double v = gslCorrel2(k, params);
+
+  return v/(t*t);
+}
+
+
+double computeCorrel(double powNorm,  double topHatRad1)
+{
+  int Nsteps = 100000;
+  double varVal = 0;
+  
+#ifndef USE_GSL
+
+  for (int i = 1; i <= Nsteps; i++)
+    {
+      double t = i * 1.0/(Nsteps+1);
+      // Change of variable !
+      double k = (1-t)/t * K0;
+      double powVal = powerSpectrum(k, powNorm);
+
+      double filter1Val = externalFilter(k*topHatRad1/h);
+
+#ifdef JUSZKIEWICZ_PATCH
+      powVal*=exp(-k*k*(RJUSZ*RJUSZ/h*h));
+#endif
+
+      powVal *= filter1Val;
+      
+      powVal /= (t*t);
+
+      varVal += powVal;
+    }
+
+  varVal *= 1.0/(Nsteps) * K0;
+  varVal *= 1.0/(6*M_PI*M_PI);
+#else
+
+  double abserr;
+  gsl_integration_workspace *w = gsl_integration_workspace_alloc(NUM_ITERATION);
+  gsl_function f;
+  
+  f.params = &topHatRad1;
+
+#if 1
+  f.function = gslCorrelBis;
+  gsl_integration_qag (&f, 0, 1, 0, TOLERANCE, NUM_ITERATION, GSL_INTEG_GAUSS61, w, &varVal, &abserr);
+#else
+  f.function = gslCorrel;
+  gsl_integration_qagiu(&f, 0, 0, TOLERANCE, NUM_ITERATION, w, &varVal, &abserr);
+#endif
+
+  gsl_integration_workspace_free(w);
+
+  varVal *= powNorm/(6*M_PI*M_PI);
+
+#endif
+  return varVal;
+}
+
+/*
+ * This function computes the correlation between the infinitely deep
+ * velocity of the Local Group and the one estimated from a survey
+ * which depth is topHatRad1.
+ * This corresponds to \gamma.
+ * This quantity must be multiplied by H \beta to be equal to a velocity^2.
+ */
+double computeCorrel2(double powNorm,  double topHatRad1, double topHatRad2)
+{
+  int Nsteps = 100000;
+  double varVal = 0;
+#ifndef USE_GSL
+  
+  for (int i = 1; i <= Nsteps; i++)
+    {
+      double t = i * 1.0/(Nsteps+1);
+      // Change of variable !
+      double k = (1-t)/t * K0;
+      double powVal = powerSpectrum(k, powNorm);
+
+      double filter1Val = externalFilter(k*topHatRad1/h);
+      double filter2Val = externalFilter(k*topHatRad2/h);
+
+      powVal *= filter1Val * filter2Val;
+      
+      powVal /= (t*t);
+
+      varVal += powVal;
+    }
+
+  varVal *= 1.0/(Nsteps) * K0;
+  varVal *= 1.0/(6*M_PI*M_PI);
+
+#else
+
+  double abserr;
+  gsl_integration_workspace *w = gsl_integration_workspace_alloc(NUM_ITERATION);
+  gsl_function f;
+  double rads[] = {topHatRad1, topHatRad2 };
+
+  f.params = rads;
+
+#if 1  
+  f.function = gslCorrel2bis;
+  gsl_integration_qag (&f, 0, 1, 0, TOLERANCE, NUM_ITERATION, GSL_INTEG_GAUSS61, w, &varVal, &abserr);
+#else
+  f.function = gslCorrel2;
+  gsl_integration_qagiu(&f, 0, 0, TOLERANCE, NUM_ITERATION, w, &varVal, &abserr);
+#endif
+
+  gsl_integration_workspace_free(w);
+
+  varVal *= powNorm/(6*M_PI*M_PI);
+
+#endif
+
+  return varVal;
+}
+
+
+  void updateCosmology()
+  {
+    OMEGA_0 = OMEGA_B+OMEGA_C;
+    Omega = OMEGA_0;
+    Theta_27 = 2.728 / 2.7;
+    beta = pow(OMEGA_0, 5./9);
+    OmegaEff = OMEGA_0 * h * h;
+    Gamma0 = OMEGA_0 * h * h;
+
+#if 0
+    cout << "Cosmology is :" << endl
+	 << "  O0=" << OMEGA_0 << " Theta=" << Theta_27 << " beta=" << beta << " h=" << h << " G0=" << Gamma0 << endl
+	 << "  OmegaB=" << OMEGA_B << " Omega_C=" << OMEGA_C << endl;
+#endif
+  }
+
+};
diff --git a/external/cosmotool/src/powerSpectrum.hpp b/external/cosmotool/src/powerSpectrum.hpp
new file mode 100644
index 0000000..ad0b0a9
--- /dev/null
+++ b/external/cosmotool/src/powerSpectrum.hpp
@@ -0,0 +1,35 @@
+#ifndef _POWERSPECTRUM_HPP
+#define _POWERSPECTRUM_HPP
+
+namespace Cosmology {
+
+  extern double n;
+  extern double K0;
+  extern double V0;
+
+  extern double CMB_VECTOR[3];
+
+  // WMAP5
+  extern double h;
+  extern double SIGMA8;
+  extern double OMEGA_B;
+  extern double OMEGA_C;
+
+  extern double OMEGA_0;
+  extern double Omega;
+  extern double Theta_27;
+  extern double beta;
+  extern double OmegaEff;
+  extern double Gamma0;
+
+  void updateCosmology();
+  double tophatFilter(double u);
+  double powerSpectrum(double k, double normPower);
+  double computePowSpecNorm(double sigma8);
+  double computeVariance(double powNorm,  double topHatRad1);
+  double computeVarianceZero(double powNorm);
+  double computeCorrel(double powNorm,  double topHatRad1);
+  double computeCorrel2(double powNorm,  double topHatRad1, double topHatRad2);
+};
+
+#endif
diff --git a/external/cosmotool/src/sparseGrid.hpp b/external/cosmotool/src/sparseGrid.hpp
new file mode 100644
index 0000000..bbe3abf
--- /dev/null
+++ b/external/cosmotool/src/sparseGrid.hpp
@@ -0,0 +1,87 @@
+#ifndef __VOID_SPARSE_GRID_HPP
+#define __VOID_SPARSE_GRID_HPP
+
+#include 
+#include "config.hpp"
+#include 
+
+namespace CosmoTool 
+{
+  
+  template 
+  struct bracketAccessor
+  {
+    typedef typename T::value_type result_type;
+
+    result_type
+    operator()(T const& V, size_t const N) const throw ()
+    {
+      return V[N];
+    }
+  };
+
+  template
+  struct GridElement
+  {
+    std::list eList;
+  };
+
+  template
+  struct GridDesc
+  {
+    GridElement *grid;
+    uint32_t gridRes;
+    CType boxSize;
+  };
+
+  template
+  class SparseIterator
+  {
+  protected:
+    typedef CType coord[N];
+
+    int32_t index_min[N], index_max[N], index_cur[N];
+    GridElement *eCur;
+    GridDesc desc;
+    typename std::list::iterator eIterator;
+
+    GridElement *getElement();
+  public:
+    SparseIterator(GridDesc& d, coord& c, CType R);
+    SparseIterator();
+    ~SparseIterator();
+    
+    SparseIterator& operator++();
+
+    bool last();
+
+    T& operator*() { return *eIterator; }
+    T *operator->() { return eIterator.operator->(); }
+  };
+
+  template >
+  class SparseGrid
+  {
+  public:
+    typedef CType coord[N];
+    typedef SparseIterator iterator;
+    CAccessor acc;
+
+    SparseGrid(CType L, uint32_t gridRes);
+    ~SparseGrid();
+    
+    void addElementInGrid(T& e);
+
+    iterator locateElements(coord& c, CType R);
+    void clear();
+
+  protected:
+    GridDesc grid;
+  };
+
+};
+
+#include "sparseGrid.tcc"
+
+#endif
diff --git a/external/cosmotool/src/sparseGrid.tcc b/external/cosmotool/src/sparseGrid.tcc
new file mode 100644
index 0000000..c72488c
--- /dev/null
+++ b/external/cosmotool/src/sparseGrid.tcc
@@ -0,0 +1,145 @@
+#include 
+#include 
+
+namespace CosmoTool {
+   
+   template
+   SparseGrid::SparseGrid(CType L, uint32_t gridRes)
+   {
+     uint32_t Ntot = 1;
+     for (int i = 0; i < N; i++)
+       Ntot *= gridRes;
+
+     grid.grid = new GridElement[Ntot];
+     grid.boxSize = L;
+     grid.gridRes = gridRes;
+   }
+
+   template
+   SparseGrid::~SparseGrid()
+   {
+     delete[] grid.grid;
+   }
+    
+   template
+   void SparseGrid::addElementInGrid(T& e)
+   {
+     uint32_t index[N];
+     uint32_t idx = 0;
+
+     for (int i = N-1; i >= 0; i--)
+       {
+	 CType x = acc(e, i) * grid.gridRes / grid.boxSize;
+	 
+	 index[i] = (int32_t)std::floor((double)x);
+	 assert(index[i] >= 0);
+	 assert(index[i] < grid.gridRes);
+	 idx = (idx*grid.gridRes) + index[i];
+       }
+     assert(idx < grid.gridRes*grid.gridRes*grid.gridRes);
+
+     grid.grid[idx].eList.push_back(e);
+   }
+
+   template
+   SparseIterator
+      SparseGrid::locateElements(coord& c, CType R)
+   {
+     return SparseIterator(grid, c, R);
+   }
+
+  template
+  void SparseGrid::clear()
+  {
+    uint32_t N3 = grid.gridRes*grid.gridRes*grid.gridRes;
+
+    for (uint32_t i = 0; i < N3; i++)
+      grid.grid[i].eList.clear();
+  }
+
+
+  template
+  SparseIterator::SparseIterator(GridDesc& d, coord& c, CType R)
+  {
+    desc = d;
+    
+    for (uint32_t i = 0; i < N; i++)
+      {
+	CType x_min = (c[i] - R) * d.gridRes / d.boxSize;
+	CType x_max = (c[i] + R) * d.gridRes / d.boxSize;
+	
+	index_min[i] = (int32_t) std::floor((double)x_min);
+	index_max[i] = (int32_t) std::ceil((double)x_max) + 1;
+	index_cur[i] = index_min[i];
+      }
+
+    eCur = getElement();
+    eIterator = eCur->eList.begin();
+    if (eIterator == eCur->eList.end())
+      operator++();
+  }
+  
+  template
+  SparseIterator::~SparseIterator()
+  {
+  }
+  
+  template
+  bool SparseIterator::last()
+  {
+    return (index_cur[N-1] == index_max[N-1]);
+  }
+
+ 
+  template
+  GridElement *SparseIterator::getElement()
+  {
+    uint32_t idx = 0;
+    for (int i = (N-1); i >= 0; i--)
+      {
+	int32_t k = (index_cur[i] + desc.gridRes) % desc.gridRes;	
+	idx = (idx*desc.gridRes) + k;
+      }
+    assert(idx < desc.gridRes*desc.gridRes*desc.gridRes);
+    
+    return &desc.grid[idx];
+  }
+
+  template
+  SparseIterator& SparseIterator::operator++()
+  {
+    if (last())
+      return *this;
+    ++eIterator;
+    if (eIterator != eCur->eList.end())
+      return *this;
+      
+    do
+      {		
+	index_cur[0]++;
+	for (int i = 0; i < (N-1); i++)
+	  if (index_cur[i] == index_max[i])
+	    {
+	      index_cur[i] = index_min[i];
+	      index_cur[i+1]++;
+	    }
+	
+	if (last())
+	  return *this;
+	
+	eCur = getElement();
+	eIterator = eCur->eList.begin();
+      }
+    while (eIterator == eCur->eList.end());
+
+    return *this;
+  }
+ 
+
+
+};
diff --git a/external/cosmotool/src/sphSmooth.hpp b/external/cosmotool/src/sphSmooth.hpp
new file mode 100644
index 0000000..7d14b8a
--- /dev/null
+++ b/external/cosmotool/src/sphSmooth.hpp
@@ -0,0 +1,105 @@
+#ifndef __COSMOTOOL_SPH_SMOOTH_HPP
+#define __COSMOTOOL_SPH_SMOOTH_HPP
+
+#include "config.hpp"
+#include "mykdtree.hpp"
+
+namespace CosmoTool
+{
+  template 
+  class SPHSmooth
+  {
+  public:
+    typedef struct
+    {
+      ComputePrecision weight;
+      ValType pValue;
+    } FullType;
+
+    typedef KDTree SPHTree;
+    typedef KDTreeNode SPHNode;
+    typedef KDCell  SPHCell;
+    typedef typename KDTree::CoordType CoordType;
+
+    typedef ComputePrecision (*computeParticleValue)(const ValType& t);
+    typedef void (*runParticleValue)(ValType& t);
+
+  public:
+    SPHSmooth(SPHTree *tree, uint32_t Nsph);
+    virtual ~SPHSmooth();   
+
+    void fetchNeighbours(const typename SPHTree::coords& c);
+    void fetchNeighbours(const typename SPHTree::coords& c, uint32_t newNsph);
+    void fetchNeighboursOnVolume(const typename SPHTree::coords& c, ComputePrecision radius);
+    const typename SPHTree::coords& getCurrentCenter() const
+    {
+      return currentCenter;
+    }
+
+    ComputePrecision computeSmoothedValue(const typename SPHTree::coords& c,
+					  computeParticleValue fun);    
+    ComputePrecision computeInterpolatedValue(const typename SPHTree::coords& c,
+					      computeParticleValue fun);    
+    ComputePrecision getMaxDistance(const typename SPHTree::coords& c, 
+				    SPHNode *node) const;
+
+    ComputePrecision getSmoothingLen() const
+    {
+      return smoothRadius;
+    }
+
+    // TO USE WITH EXTREME CARE !
+    void setSmoothingLen(ComputePrecision len)
+    {
+      smoothRadius = len;
+    }
+    // END
+
+    void runForEachNeighbour(runParticleValue fun);
+    void addGridSite(const typename SPHTree::coords& c);
+
+    bool hasNeighbours() const;
+
+    virtual ComputePrecision getKernel(ComputePrecision d) const;    
+
+    SPHTree *getTree() { return tree; }
+
+    void changeNgb(uint32_t newMax) {
+      delete[] ngb;
+      delete[] distances;
+      ngb = new SPHCell *[newMax];
+      distances = new CoordType[newMax];
+      maxNgb = newMax;
+    }
+
+    uint32_t getCurrent() const { return currentNgb; }
+
+    uint32_t getNgb() const { return maxNgb; }
+ 
+  protected:
+    SPHCell **ngb;
+    CoordType *distances;
+    uint32_t Nsph;
+    uint32_t deltaNsph;
+    uint32_t maxNgb;
+    uint32_t currentNgb;
+    SPHTree *tree;
+    ComputePrecision smoothRadius;
+    typename SPHTree::coords currentCenter;
+    
+    ComputePrecision computeWValue(const typename SPHTree::coords & c,
+				   SPHCell& cell,
+				   CoordType d,
+				   computeParticleValue fun);
+    void runUnrollNode(SPHNode *node,
+		       runParticleValue fun);
+  };
+
+  template
+  bool operator<(const SPHSmooth& s1, const SPHSmooth& s2);
+
+};
+
+#include "sphSmooth.tcc"
+
+#endif
diff --git a/external/cosmotool/src/sphSmooth.tcc b/external/cosmotool/src/sphSmooth.tcc
new file mode 100644
index 0000000..0cf7de8
--- /dev/null
+++ b/external/cosmotool/src/sphSmooth.tcc
@@ -0,0 +1,214 @@
+#include 
+
+namespace CosmoTool {
+
+template
+SPHSmooth::SPHSmooth(SPHTree *tree, uint32_t Nsph)
+{
+  this->Nsph = Nsph;
+  this->tree = tree;
+  this->currentNgb = 0;
+
+  this->maxNgb = Nsph;
+  ngb = new SPHCell *[maxNgb];  
+  distances = new CoordType[maxNgb];
+}
+
+template
+SPHSmooth::~SPHSmooth()
+{
+  delete[] ngb;
+  delete[] distances;
+}
+
+template
+ComputePrecision SPHSmooth::computeWValue(const typename SPHTree::coords& c,
+							 SPHCell& cell,
+							 CoordType d,
+							 computeParticleValue fun)
+{
+  CoordType weight;
+
+  d /= smoothRadius;
+  weight = getKernel(d);
+
+  if (cell.val.weight != 0)
+    return weight * fun(cell.val.pValue) / cell.val.weight;
+  else
+    return 0;
+}
+
+template
+void
+SPHSmooth::fetchNeighbours(const typename SPHTree::coords& c, uint32_t newNngb)
+{
+  ComputePrecision d2, max_dist = 0;
+  uint32_t requested = newNngb;
+
+  if (requested > maxNgb)
+    {
+      delete[] ngb;
+      delete[] distances;
+
+      maxNgb = requested;
+      ngb = new SPHCell *[maxNgb];
+      distances = new CoordType[maxNgb];
+    }
+
+  memcpy(currentCenter, c, sizeof(c));
+  tree->getNearestNeighbours(c, requested, ngb, distances);
+
+  currentNgb = 0;
+  for (uint32_t i = 0; i < requested && ngb[i] != 0; i++,currentNgb++)
+    {
+      distances[i] = sqrt(distances[i]);
+      d2 = distances[i];
+      if (d2 > max_dist)
+        max_dist = d2;
+    }
+
+  smoothRadius = max_dist / 2;  
+}
+
+template
+void
+SPHSmooth::fetchNeighbours(const typename SPHTree::coords& c)
+{
+  ComputePrecision d2, max_dist = 0;
+  uint32_t requested = Nsph;
+
+  memcpy(currentCenter, c, sizeof(c));
+  tree->getNearestNeighbours(c, requested, ngb, distances);
+
+  currentNgb = 0;
+  for (uint32_t i = 0; i < requested && ngb[i] != 0; i++,currentNgb++)
+    {
+      distances[i] = sqrt(distances[i]);
+      d2 = distances[i];
+      if (d2 > max_dist)
+	max_dist = d2;
+    }
+
+  smoothRadius = max_dist / 2;  
+}					
+
+template
+void
+SPHSmooth::fetchNeighboursOnVolume(const typename SPHTree::coords& c,
+						  ComputePrecision radius)
+{
+  uint32_t numPart;
+  ComputePrecision d2, max_dist = 0;
+
+  memcpy(currentCenter, c, sizeof(c));
+
+  currentNgb = tree->getIntersection(c, radius, ngb, distances,
+				  maxNgb);
+
+  for (uint32_t i = 0; i < currentNgb; i++)
+    {
+      distances[i] = sqrt(distances[i]);
+      d2 = distances[i];
+      if (d2 > max_dist)
+	max_dist = d2;
+    }
+  smoothRadius = max_dist / 2;  
+}				
+
+template
+ComputePrecision 
+SPHSmooth::computeSmoothedValue(const typename SPHTree::coords& c,
+					       computeParticleValue fun)
+{
+  ComputePrecision outputValue = 0;
+  ComputePrecision max_dist = 0;
+  ComputePrecision r3 = smoothRadius * smoothRadius * smoothRadius;
+
+  for (uint32_t i = 0; i < currentNgb; i++)
+    {
+      outputValue += computeWValue(c, *ngb[i], distances[i], fun);
+    }
+
+  return outputValue / r3;
+}
+
+template
+ComputePrecision interpolateOne(const ValType& t)
+{
+  return 1.0;
+}
+
+// WARNING ! Cell's weight must be 1 !!!
+template
+ComputePrecision SPHSmooth::computeInterpolatedValue(const typename SPHTree::coords& c,
+								    computeParticleValue fun)
+{
+  ComputePrecision outputValue = 0;
+  ComputePrecision max_dist = 0;
+  ComputePrecision weight = 0;
+
+  for (uint32_t i = 0; i < currentNgb; i++)
+    {
+      outputValue += computeWValue(c, *ngb[i], distances[i], fun);
+      weight += computeWValue(c, *ngb[i], distances[i], interpolateOne);
+    }
+
+  return (outputValue == 0) ? 0 : (outputValue / weight);  
+}
+
+template
+void SPHSmooth::runForEachNeighbour(runParticleValue fun)
+{
+  for (uint32_t i = 0; i < currentNgb; i++)
+    {
+      fun(ngb[i]);
+    }
+}
+
+
+template
+void SPHSmooth::addGridSite(const typename SPHTree::coords& c)
+{
+  ComputePrecision outputValue = 0;
+  ComputePrecision max_dist = 0;
+  
+  ComputePrecision r3 = smoothRadius * smoothRadius * smoothRadius;
+
+  for (uint32_t i = 0; i < currentNgb; i++)
+    {
+      ComputePrecision d = distances[i];
+      SPHCell& cell = *ngb[i];
+      cell.val.weight += getKernel(d/smoothRadius) / r3;
+
+    }
+}
+
+template
+ComputePrecision 
+SPHSmooth::getKernel(ComputePrecision x) const
+{
+  // WARNING !!! This is an unnormalized version of the kernel.
+  if (x < 1)
+    return 1 - 1.5 * x * x + 0.75 * x * x * x;
+  else if (x < 2)
+    {
+      ComputePrecision d = 2 - x;
+      return 0.25 * d * d * d;
+    }
+  else
+    return 0;
+}
+
+template
+bool SPHSmooth::hasNeighbours() const
+{
+  return (currentNgb != 0);
+}
+
+template
+bool operator<(const SPHSmooth& s1, const SPHSmooth& s2)
+{
+  return (s1.getSmoothingLen() < s2.getSmoothingLen());
+}
+
+};
diff --git a/external/cosmotool/src/yorick.hpp b/external/cosmotool/src/yorick.hpp
new file mode 100644
index 0000000..25734f7
--- /dev/null
+++ b/external/cosmotool/src/yorick.hpp
@@ -0,0 +1,196 @@
+#ifndef __YORICK_HELPERS_HPP
+#define __YORICK_HELPERS_HPP
+
+#include "config.hpp"
+#include 
+#include 
+#include 
+
+
+namespace CosmoTool 
+{
+
+  class ProgressiveDoubleOutputImpl
+  {
+  public:
+    virtual ~ProgressiveDoubleOutputImpl();
+    virtual void addDouble(double d) = 0;
+  };
+
+  template
+  class ProgressiveInputImpl
+  {
+  public:
+    virtual ~ProgressiveInputImpl() {}
+    virtual T read() = 0;
+    virtual void seek(uint32_t *pos) = 0;
+  };
+
+  template
+  class ProgressiveOutputImpl
+  {
+  public:
+    virtual ~ProgressiveOutputImpl() {}
+    virtual void put(T a) = 0;
+  };
+
+  class ProgressiveDoubleOutput
+  {
+  private:
+    bool initialized;
+    int *ref;
+    ProgressiveDoubleOutputImpl *impl;
+    
+    friend ProgressiveDoubleOutput saveDoubleArrayProgressive(const char *fname, uint32_t *dimList, uint32_t rank);
+    void decRef();
+  public:
+    ProgressiveDoubleOutput();
+    ProgressiveDoubleOutput(ProgressiveDoubleOutputImpl *i);
+    ProgressiveDoubleOutput(const ProgressiveDoubleOutput& o);
+    ~ProgressiveDoubleOutput();
+
+    virtual void addDouble(double a);
+
+    const ProgressiveDoubleOutput& operator=(const ProgressiveDoubleOutput& b); 
+  };
+
+  template
+  class ProgressiveInput
+  {
+  private:
+    int *ref;
+    ProgressiveInputImpl *impl;
+     
+    void decRef()
+    {
+      if (ref == 0)
+	return;
+      
+      (*ref)--;
+      if (*ref == 0)
+	{
+      	  delete ref;
+	  delete impl;
+	}
+      impl = 0;
+      ref = 0;
+    }
+
+  public:
+    static ProgressiveInput 
+    loadArrayProgressive(const std::string& fname, uint32_t *&dimList, 
+			 uint32_t& rank);
+
+    ProgressiveInput() {
+      impl = 0; 
+      ref = 0; 
+    }
+    ProgressiveInput(ProgressiveInputImpl *i) {
+      impl = i; 
+      ref = new int; 
+      *ref = 1;
+    }
+    ProgressiveInput(const ProgressiveInput& o) {
+      ref = o.ref;
+      impl = o.impl;
+      (*ref)++;
+    }
+    ~ProgressiveInput() {
+      decRef();
+    }
+
+    T read()
+    {
+      return impl->read();
+    }
+
+    void seek(uint32_t *pos)
+    {
+      impl->seek(pos);
+    }
+
+    const ProgressiveInput& operator=(const ProgressiveInput& b)
+    {
+      decRef();
+      ref = b.ref;
+      impl = b.impl;
+      if (ref != 0)
+	(*ref)++;
+      return *this;
+    }
+  };
+
+  template
+  class ProgressiveOutput
+  {
+  private:
+    int *ref;
+    ProgressiveOutputImpl *impl;
+     
+    void decRef()
+    {
+      if (ref == 0)
+	return;
+      
+      (*ref)--;
+      if (*ref == 0)
+	{
+      	  delete ref;
+	  delete impl;
+	}
+      impl = 0;
+      ref = 0;
+    }
+
+  public:
+    static ProgressiveOutput 
+    saveArrayProgressive(const std::string& fname, uint32_t *dimList, 
+			 uint32_t rank);
+
+    ProgressiveOutput() {
+      impl = 0; 
+      ref = 0; 
+    }
+    ProgressiveOutput(ProgressiveOutputImpl *i) {
+      impl = i; 
+      ref = new int; 
+      *ref = 1;
+    }
+    ProgressiveOutput(const ProgressiveOutput& o) {
+      ref = o.ref;
+      impl = o.impl;
+      (*ref)++;
+    }
+    ~ProgressiveOutput() {
+      decRef();
+    }
+
+    void put(T a)
+    {
+      impl->put(a);
+    }
+
+    const ProgressiveOutput& operator=(const ProgressiveOutput& b)
+    {
+      decRef();
+      ref = b.ref;
+      impl = b.impl;
+      if (ref != 0)
+	(*ref)++;
+      return *this;
+    }
+  };
+
+  template
+  void saveArray(const std::string& fname,
+		 T *array, uint32_t *dimList, uint32_t rank);
+
+  template
+  void loadArray(const std::string& fname,
+		 T*& array, uint32_t *& dimList, uint32_t& rank)
+    throw (NoSuchFileException);
+
+  ProgressiveDoubleOutput saveDoubleArrayProgressive(const char *fname, uint32_t *dimList, uint32_t rank);  
+};
+
+#endif
diff --git a/external/cosmotool/src/yorick_nc3.cpp b/external/cosmotool/src/yorick_nc3.cpp
new file mode 100644
index 0000000..d760009
--- /dev/null
+++ b/external/cosmotool/src/yorick_nc3.cpp
@@ -0,0 +1,274 @@
+#include "ctool_netcdf_ver.hpp"
+#include "config.hpp"
+#ifdef NETCDFCPP4
+#include 
+using namespace netCDF
+#else
+#include 
+#endif
+#include 
+#include "yorick.hpp"
+#include 
+
+using namespace CosmoTool;
+using namespace std;
+
+class NetCDF_handle
+{
+public:
+  NcFile *outFile;
+  NcVar *curVar;
+  long *curPos;
+  long *counts;
+  long *dimList;
+  uint32_t rank;
+  
+  NetCDF_handle(NcFile *f, NcVar *v, long *dimList, uint32_t rank);
+  virtual ~NetCDF_handle();
+};
+
+NetCDF_handle::NetCDF_handle(NcFile *f, NcVar *v, long *dimList, uint32_t rank)
+{
+  this->outFile = f;
+  this->curVar = v;
+  this->dimList = dimList;
+  this->rank = rank;
+  this->counts = new long[rank];
+  this->curPos = new long[rank];
+
+  for (long i = 0; i < rank; i++)
+    this->curPos[i] = 0;
+
+  for (long i = 0; i < rank; i++)
+    this->counts[i] = 1;
+}
+
+NetCDF_handle::~NetCDF_handle()
+{
+  delete[] dimList;
+  delete outFile;
+}
+
+template
+class InputGenCDF: public NetCDF_handle, public ProgressiveInputImpl
+{
+public:
+  InputGenCDF(NcFile *f, NcVar *v, long *dimList, uint32_t rank)
+    : NetCDF_handle(f,v,dimList,rank)
+  {}
+  virtual ~InputGenCDF() {} 
+
+  virtual T read()
+  {
+    T a;
+
+    curVar->set_cur(curPos);
+    curVar->get(&a, counts);
+    
+    curPos[rank-1]++;
+    for (long i = rank-1; i >= 1; i--)
+      {
+	if (curPos[i] == dimList[i])
+	  {
+	    curPos[i-1]++;
+	  curPos[i] = 0;
+	}
+      }    
+    return a;
+  }
+
+  virtual void seek(uint32_t *pos)
+  {
+    for (long i = rank-1; i >= 0; i--)
+      curPos[i] = pos[rank-1-i];
+  }
+};
+
+template
+class OutputGenCDF: public NetCDF_handle, public ProgressiveOutputImpl
+{
+public:
+  OutputGenCDF(NcFile *f, NcVar *v, long *dimList, uint32_t rank)
+    : NetCDF_handle(f,v,dimList,rank)
+  {}
+  virtual ~OutputGenCDF() {} 
+
+  virtual void put(T a)
+  {
+    curVar->set_cur(curPos);
+    curVar->put(&a, counts);
+    
+    curPos[rank-1]++;
+    for (long i = rank-1; i >= 1; i--)
+      {
+	if (curPos[i] == dimList[i])
+	  {
+	    curPos[i-1]++;
+	  curPos[i] = 0;
+	}
+      }    
+  }
+};
+
+template
+class NetCDF_type
+{
+public:
+  static const NcType t = (NcType)-1;
+};
+
+template<>
+class NetCDF_type
+{
+public:
+  static const NcType t = ncInt;
+};
+
+template<>
+class NetCDF_type
+{
+public:
+  static const NcType t = ncInt;
+};
+
+template<>
+class NetCDF_type
+{
+public:
+  static const NcType t = ncFloat;
+};
+
+template<>
+class NetCDF_type
+{
+public:
+  static const NcType t = ncDouble;
+};
+
+namespace CosmoTool {
+  template
+  ProgressiveOutput
+  ProgressiveOutput::saveArrayProgressive(const std::string& fname, uint32_t *dimList,
+					     uint32_t rank)
+  {
+    NcFile *f = new NcFile(fname.c_str(), NcFile::Replace);
+    
+    assert(f->is_valid());
+    
+    const NcDim **dimArray = new const NcDim *[rank];
+    for (uint32_t i = 0; i < rank; i++)
+      {
+	char dimName[255];
+	
+	sprintf(dimName, "dim%d", i);
+	dimArray[i] = f->add_dim(dimName, dimList[rank-1-i]);
+      }
+    
+    NcVar *v = f->add_var("array", NetCDF_type::t, rank, dimArray);  
+
+    long *ldimList = new long[rank];
+
+    for (uint32_t i = 0; i < rank; i++)
+      ldimList[rank-1-i] = dimList[i];
+
+    OutputGenCDF *impl = new OutputGenCDF(f, v, ldimList, rank);
+    return ProgressiveOutput(impl);   
+  }
+
+  template
+  ProgressiveInput
+  ProgressiveInput::loadArrayProgressive(const std::string& fname, uint32_t *&dimList,
+					      uint32_t& rank)
+  {
+    NcFile *f = new NcFile(fname.c_str(), NcFile::ReadOnly);
+
+    assert(f->is_valid());
+
+    NcVar *v = f->get_var("array");
+
+    rank = v->num_dims();
+    long *ldimList = v->edges();
+    dimList = new uint32_t[rank];
+    for (uint32_t i = 0; i < rank; i++)
+      {
+	dimList[rank-i-1] = ldimList[i];
+      }
+    InputGenCDF *impl = new InputGenCDF(f, v, ldimList, rank);
+
+    return ProgressiveInput(impl);
+  }
+
+  template
+  void saveArray(const std::string& fname,
+		 T *array, uint32_t *dimList, uint32_t rank)
+  {
+    NcFile f(fname.c_str(), NcFile::Replace);
+    
+    assert(f.is_valid());
+    
+    const NcDim **dimArray = new const NcDim *[rank];
+    for (uint32_t i = 0; i < rank; i++)
+      {
+	char dimName[255];
+	
+	sprintf(dimName, "dim%d", i);
+	dimArray[i] = f.add_dim(dimName, dimList[i]);
+      }
+
+    NcVar *v = f.add_var("array", NetCDF_type::t, rank, dimArray);
+    
+    long *edge = v->edges();
+    v->put(array, edge);
+    delete[] edge;  
+  }
+
+  template
+  void loadArray(const std::string& fname,
+		 T*&array, uint32_t *&dimList, uint32_t& rank)
+	throw (NoSuchFileException)
+  {
+    NcFile f(fname.c_str(), NcFile::ReadOnly);
+    
+    if (!f.is_valid())
+      throw NoSuchFileException(fname);
+    
+    NcVar *v = f.get_var("array"); 
+    rank = v->num_dims();
+    long *edge = v->edges();
+    uint32_t fullSize = 1;
+    dimList = new uint32_t[rank];
+    for (int i = 0; i < rank; i++)
+      {
+	dimList[i] = edge[i];
+	fullSize *= edge[i];
+      }
+    if (fullSize != 0) {
+      array = new T[fullSize];
+      v->get(array, edge);
+    }
+    delete[] edge;   
+  }
+
+  template class ProgressiveInput;
+  template class ProgressiveInput;
+  template class ProgressiveInput;
+
+  template class ProgressiveOutput;
+  template class ProgressiveOutput;
+  template class ProgressiveOutput;
+
+  template void loadArray(const std::string& fname,
+			       int*& array, uint32_t *&dimList, uint32_t& rank);
+  template void loadArray(const std::string& fname,
+				 float*& array, uint32_t *&dimList, uint32_t& rank);
+  template void loadArray(const std::string& fname,
+				  double*& array, uint32_t *&dimList, uint32_t& rank);
+
+  template void saveArray(const std::string& fname,
+			       int *array, uint32_t *dimList, uint32_t rank);
+  template void saveArray(const std::string& fname,
+				 float *array, uint32_t *dimList, uint32_t rank);
+  template void saveArray(const std::string& fname,
+				  double *array, uint32_t *dimList, uint32_t rank);
+  
+}
diff --git a/external/cosmotool/src/yorick_nc4.cpp b/external/cosmotool/src/yorick_nc4.cpp
new file mode 100644
index 0000000..8bdeda0
--- /dev/null
+++ b/external/cosmotool/src/yorick_nc4.cpp
@@ -0,0 +1,237 @@
+#include "ctool_netcdf_ver.hpp"
+#include "config.hpp"
+#include 
+using namespace netCDF;
+#include 
+#include "yorick.hpp"
+#include 
+
+using namespace CosmoTool;
+using namespace std;
+
+class NetCDF_handle
+{
+public:
+  NcFile *outFile;
+  NcVar curVar;
+  vector curPos;
+  vector counts;
+  vector dimList;
+  uint32_t rank;
+  
+  NetCDF_handle(NcFile *f, NcVar v, vector& dimList, uint32_t rank);
+  virtual ~NetCDF_handle();
+};
+
+NetCDF_handle::NetCDF_handle(NcFile *f, NcVar v, vector& dimList, uint32_t rank)
+{
+  this->outFile = f;
+  this->curVar = v;
+  this->dimList = dimList;
+  this->rank = rank;
+  this->counts.resize(rank);
+  this->curPos.resize(rank);
+
+  for (long i = 0; i < rank; i++)
+    this->curPos[i] = 0;
+
+  for (long i = 0; i < rank; i++)
+    this->counts[i] = 1;
+}
+
+NetCDF_handle::~NetCDF_handle()
+{
+  delete outFile;
+}
+
+template
+class InputGenCDF: public NetCDF_handle, public ProgressiveInputImpl
+{
+public:
+  InputGenCDF(NcFile *f, NcVar v, vector& dimList, uint32_t rank)
+    : NetCDF_handle(f,v,dimList,rank)
+  {}
+  virtual ~InputGenCDF() {} 
+
+  virtual T read()
+  {
+    T a;
+
+    curVar.getVar(curPos, counts, &a);
+    
+    curPos[rank-1]++;
+    for (long i = rank-1; i >= 1; i--)
+      {
+	if (curPos[i] == dimList[i].getSize())
+	  {
+	    curPos[i-1]++;
+	  curPos[i] = 0;
+	}
+      }    
+    return a;
+  }
+
+  virtual void seek(uint32_t *pos)
+  {
+    for (long i = rank-1; i >= 0; i--)
+      curPos[i] = pos[rank-1-i];
+  }
+};
+
+template
+class OutputGenCDF: public NetCDF_handle, public ProgressiveOutputImpl
+{
+public:
+  OutputGenCDF(NcFile *f, NcVar v, vector& dimList, uint32_t rank)
+    : NetCDF_handle(f,v,dimList,rank)
+  {}
+  virtual ~OutputGenCDF() {} 
+
+  virtual void put(T a)
+  {
+    curVar.putVar(curPos, counts, &a);
+    
+    curPos[rank-1]++;
+    for (long i = rank-1; i >= 1; i--)
+      {
+	if (curPos[i] == dimList[i].getSize())
+	  {
+	    curPos[i-1]++;
+	  curPos[i] = 0;
+	}
+      }    
+  }
+};
+
+template NcType& get_NetCDF_type();
+
+#define IMPL_TYPE(T,ncT) \
+template<> \
+NcType& get_NetCDF_type() \
+{ \
+  return ncT; \
+}
+
+IMPL_TYPE(int,ncInt);
+IMPL_TYPE(unsigned int,ncInt);
+IMPL_TYPE(double,ncDouble);
+IMPL_TYPE(float,ncFloat);
+
+namespace CosmoTool {
+  template
+  ProgressiveOutput
+  ProgressiveOutput::saveArrayProgressive(const std::string& fname, uint32_t *dimList,
+					     uint32_t rank)
+  {
+    NcFile *f = new NcFile(fname, NcFile::replace);
+    
+    vector dimArray;
+    for (uint32_t i = 0; i < rank; i++)
+      {
+	char dimName[255];
+	
+	sprintf(dimName, "dim%d", i);
+	dimArray.push_back(f->addDim(dimName, dimList[rank-1-i]));
+      }
+    
+    NcVar v = f->addVar("array", get_NetCDF_type(), dimArray);  
+
+    vector ldimList;
+
+    for (uint32_t i = 0; i < rank; i++)
+      ldimList.push_back(dimArray[rank-1-i]);
+
+    OutputGenCDF *impl = new OutputGenCDF(f, v, ldimList, rank);
+    return ProgressiveOutput(impl);   
+  }
+
+  template
+  ProgressiveInput
+  ProgressiveInput::loadArrayProgressive(const std::string& fname, uint32_t *&dimList,
+					      uint32_t& rank)
+  {
+    NcFile *f = new NcFile(fname, NcFile::read);
+
+    NcVar v = f->getVar("array");
+
+    rank = v.getDimCount();
+    vector vdimlist = v.getDims();
+    dimList = new uint32_t[rank];
+    for (uint32_t i = 0; i < rank; i++)
+      {
+	dimList[rank-i-1] = vdimlist[i].getSize();
+      }
+    InputGenCDF *impl = new InputGenCDF(f, v, vdimlist, rank);
+
+    return ProgressiveInput(impl);
+  }
+
+  template
+  void saveArray(const std::string& fname,
+		 T *array, uint32_t *dimList, uint32_t rank)
+  {
+    NcFile f(fname.c_str(), NcFile::replace);
+    
+    vector dimArray;
+    for (uint32_t i = 0; i < rank; i++)
+      {
+	char dimName[255];
+	
+	sprintf(dimName, "dim%d", i);
+	dimArray.push_back(f.addDim(dimName, dimList[i]));
+      }
+
+    NcVar v = f.addVar("array", get_NetCDF_type(), dimArray);
+    
+    v.putVar(array);
+  }
+
+  template
+  void loadArray(const std::string& fname,
+		 T*&array, uint32_t *&dimList, uint32_t& rank)
+	throw (NoSuchFileException)
+  {
+    NcFile f(fname.c_str(), NcFile::read);
+    
+    //if (!f.is_valid())
+    //  throw NoSuchFileException(fname);
+    
+    NcVar v = f.getVar("array"); 
+    vector dims = v.getDims();
+    rank = v.getDimCount();
+    uint32_t fullSize = 1;
+    dimList = new uint32_t[rank];
+    for (int i = 0; i < rank; i++)
+      {
+	dimList[i] = dims[i].getSize();
+	fullSize *= dimList[i];
+      }
+    if (fullSize != 0) {
+      array = new T[fullSize];
+      v.getVar(array);
+    }
+  }
+
+  template class ProgressiveInput;
+  template class ProgressiveInput;
+  template class ProgressiveInput;
+
+  template class ProgressiveOutput;
+  template class ProgressiveOutput;
+  template class ProgressiveOutput;
+
+  template void loadArray(const std::string& fname,
+			       int*& array, uint32_t *&dimList, uint32_t& rank);
+  template void loadArray(const std::string& fname,
+				 float*& array, uint32_t *&dimList, uint32_t& rank);
+  template void loadArray(const std::string& fname,
+				  double*& array, uint32_t *&dimList, uint32_t& rank);
+
+  template void saveArray(const std::string& fname,
+			       int *array, uint32_t *dimList, uint32_t rank);
+  template void saveArray(const std::string& fname,
+				 float *array, uint32_t *dimList, uint32_t rank);
+  template void saveArray(const std::string& fname,
+				  double *array, uint32_t *dimList, uint32_t rank);
+  
+}
diff --git a/external/external_build.cmake b/external/external_build.cmake
new file mode 100644
index 0000000..64a6fe3
--- /dev/null
+++ b/external/external_build.cmake
@@ -0,0 +1,186 @@
+SET(INTERNAL_FFTW OFF)
+SET(INTERNAL_GSL ON)
+SET(INTERNAL_BOOST ON)
+SET(INTERNAL_NETCDF ON)
+SET(INTERNAL_HDF5 ON)
+
+IF(INTERNAL_HDF5)
+  SET(HDF5_URL "http://www.hdfgroup.org/ftp/HDF5/current/src/hdf5-1.8.9.tar.gz" CACHE STRING "URL to download HDF5 from")
+ENDIF(INTERNAL_HDF5)
+
+IF(INTERNAL_NETCDF)
+  SET(NETCDF_URL "http://www.unidata.ucar.edu/downloads/netcdf/ftp/netcdf-4.1.3.tar.gz" CACHE STRING "URL to download NetCDF from")
+ENDIF(INTERNAL_NETCDF)
+
+IF(INTERNAL_BOOST)
+  SET(BOOST_URL "http://sourceforge.net/projects/boost/files/boost/1.49.0/boost_1_49_0.tar.gz/download" CACHE STRING "URL to download Boost from")
+ELSE(INTERNAL_BOOST)
+  find_package(Boost 1.49.0 COMPONENTS format spirit phoenix python FATAL_ERROR)
+ENDIF(INTERNAL_BOOST)
+
+IF(INTERNAL_GSL)
+  SET(GSL_URL "ftp://ftp.gnu.org/gnu/gsl/gsl-1.15.tar.gz" CACHE STRING "URL to download GSL from ")
+ENDIF(INTERNAL_GSL)
+
+find_library(ZLIB_LIBRARY z)
+find_program(GENGETOPT gengetopt)
+
+SET(CONFIGURE_CPP_FLAGS "${EXTRA_CPP_FLAGS}")
+SET(CONFIGURE_LD_FLAGS "${EXTRA_LD_FLAGS}")
+
+###############
+# Build HDF5
+###############
+
+if (INTERNAL_HDF5)
+  SET(HDF5_SOURCE_DIR ${CMAKE_BINARY_DIR}/hdf5-prefix/src/hdf5)
+  SET(HDF5_BIN_DIR ${CMAKE_BINARY_DIR}/ext_build/hdf5)
+  ExternalProject_Add(hdf5
+    URL ${HDF5_URL}
+    CONFIGURE_COMMAND ${HDF5_SOURCE_DIR}/configure --disable-shared --enable-cxx --prefix=${HDF5_BIN_DIR} CPPFLAGS=${CONFIGURE_CPP_FLAGS} CC=${CMAKE_C_COMPILER} CXX=${CMAKE_CXX_COMPILER}
+    BUILD_IN_SOURCE 1
+    INSTALL_COMMAND make install
+  )
+  SET(cosmotool_DEPS ${cosmotool_DEPS} hdf5)
+  SET(hdf5_built hdf5)
+  set(HDF5_LIBRARY ${HDF5_BIN_DIR}/lib/libhdf5.a CACHE STRING "HDF5 lib" FORCE)
+  set(HDF5HL_LIBRARY ${HDF5_BIN_DIR}/lib/libhdf5_hl.a CACHE STRING "HDF5-HL lib" FORCE)
+  SET(HDF5_INCLUDE_PATH ${HDF5_BIN_DIR}/include CACHE STRING "HDF5 include path" FORCE)
+  SET(ENV{HDF5_ROOT} ${HDF5_BIN_DIR})
+  SET(HDF5_ROOTDIR ${HDF5_BIN_DIR})
+  SET(CONFIGURE_LDFLAGS "${CONFIGURE_LDFLAGS} -L${HDF5_BIN_DIR}/lib")
+else(INTERNAL_HDF5)
+  find_path(HDF5_INCLUDE_PATH hdf5.h)
+  find_library(HDF5_LIBRARY hdf5)
+  find_library(HDF5HL_LIBRARY hdf5_hl)
+endif (INTERNAL_HDF5)
+SET(CONFIGURE_CPP_FLAGS "${CONFIGURE_CPP_FLAGS} -I${HDF5_INCLUDE_PATH}")
+
+###############
+# Build NetCDF
+###############
+
+
+if (INTERNAL_NETCDF)
+  SET(NETCDF_SOURCE_DIR ${CMAKE_BINARY_DIR}/netcdf-prefix/src/netcdf)
+  SET(NETCDF_BIN_DIR ${CMAKE_BINARY_DIR}/ext_build/netcdf)
+  SET(CONFIGURE_CPP_FLAGS "${CONFIGURE_CPP_FLAGS} -I${NETCDF_BIN_DIR}/include")
+  SET(CONFIGURE_LDFLAGS "${CONFIGURE_LDFLAGS} -L${NETCDF_BIN_DIR}/lib")
+  SET(EXTRA_NC_FLAGS CPPFLAGS=${CONFIGURE_CPP_FLAGS} LDFLAGS=${CONFIGURE_LDFLAGS})
+  ExternalProject_Add(netcdf
+    DEPENDS ${hdf5_built}
+    URL ${NETCDF_URL}
+    CONFIGURE_COMMAND ${NETCDF_SOURCE_DIR}/configure --prefix=${NETCDF_BIN_DIR} --enable-netcdf-4 --disable-shared --disable-dap --disable-cdmremote --disable-rpc --disable-examples ${EXTRA_NC_FLAGS} CC=${CMAKE_C_COMPILER} CXX=${CMAKE_CXX_COMPILER}
+    BUILD_IN_SOURCE 1
+    INSTALL_COMMAND make install
+  )
+  SET(CONFIGURE_CPP_LDFLAGS "${CONFIGURE_LDFLAGS}")
+  SET(EXTRA_NC_FLAGS CPPFLAGS=${CONFIGURE_CPP_FLAGS} LDFLAGS=${CONFIGURE_CPP_LDFLAGS})
+  SET(cosmotool_DEPS ${cosmotool_DEPS} netcdf)
+  SET(NETCDF_LIBRARY ${NETCDF_BIN_DIR}/lib/libnetcdf.a CACHE STRING "NetCDF lib" FORCE)
+  SET(NETCDFCPP_LIBRARY ${NETCDF_BIN_DIR}/lib/libnetcdf_c++.a CACHE STRING "NetCDF-C++ lib" FORCE)
+  SET(NETCDF_INCLUDE_PATH ${NETCDF_BIN_DIR}/include CACHE STRING "NetCDF include" FORCE)
+  SET(NETCDFCPP_INCLUDE_PATH ${NETCDF_INCLUDE_PATH} CACHE STRING "NetCDF C++ include path" FORCE)
+
+ELSE(INTERNAL_NETCDF)
+  find_library(NETCDF_LIBRARY netcdf)
+  find_library(NETCDFCPP_LIBRARY netcdf_c++)
+  find_path(NETCDF_INCLUDE_PATH NAMES netcdf.h)
+  find_path(NETCDFCPP_INCLUDE_PATH NAMES netcdf)
+  SET(CONFIGURE_CPP_FLAGS ${CONFIGURE_CPP_FLAGS} -I${NETCDF_INCLUDE_PATH} -I${NETCDFCPP_INCLUDE_PATH})
+endif (INTERNAL_NETCDF)
+
+##################
+# Build BOOST
+##################
+
+if (INTERNAL_BOOST)
+  SET(BOOST_SOURCE_DIR ${CMAKE_BINARY_DIR}/boost-prefix/src/boost)
+  ExternalProject_Add(boost
+    URL ${BOOST_URL}
+    CONFIGURE_COMMAND ${BOOST_SOURCE_DIR}/bootstrap.sh --prefix=${CMAKE_BINARY_DIR}/ext_build/boost
+    BUILD_IN_SOURCE 1
+    BUILD_COMMAND ${BOOST_SOURCE_DIR}/b2 --with-exception --with-python
+    INSTALL_COMMAND echo "No install"
+  )
+  set(Boost_INCLUDE_DIRS ${BOOST_SOURCE_DIR} CACHE STRING "Boost path" FORCE)
+  set(Boost_LIBRARIES ${BOOST_SOURCE_DIR}/stage/lib/libboost_python.a)
+endif (INTERNAL_BOOST)
+
+##################
+# Build GSl
+##################
+
+IF(INTERNAL_GSL)
+  SET(GSL_SOURCE_DIR ${CMAKE_BINARY_DIR}/gsl-prefix/src/gsl)
+  ExternalProject_Add(gsl
+    URL ${GSL_URL}
+    CONFIGURE_COMMAND ${GSL_SOURCE_DIR}/configure --prefix=${CMAKE_BINARY_DIR}/ext_build/gsl --disable-shared CPPFLAGS=${CONFIGURE_CPP_FLAGS} CC=${CMAKE_C_COMPILER} CXX=${CMAKE_CXX_COMPILER}
+    BUILD_IN_SOURCE 1
+    BUILD_COMMAND make
+    INSTALL_COMMAND make install
+  )
+  SET(GSL_INTERNAL_LIBS ${CMAKE_BINARY_DIR}/ext_build/gsl/lib)
+  SET(GSL_LIBRARY ${GSL_INTERNAL_LIBS}/libgsl.a CACHE STRING "GSL internal path" FORCE)
+  SET(GSLCBLAS_LIBRARY ${GSL_INTERNAL_LIBS}/libgslcblas.a CACHE STRING "GSL internal path" FORCE)
+  set(GSL_INCLUDE_PATH ${CMAKE_BINARY_DIR}/ext_build/gsl/include CACHE STRING "GSL internal path" FORCE)
+  SET(cosmotool_DEPS ${cosmotool_DEPS} gsl)
+ELSE(INTERNAL_GSL)
+  find_library(GSL_LIBRARY gsl)
+  find_library(GSLCBLAS_LIBRARY gslcblas)
+  find_path(GSL_INCLUDE_PATH NAMES gsl/gsl_blas.h)
+ENDIF(INTERNAL_GSL)
+
+##################
+# Build CosmoTool
+##################
+
+
+ExternalProject_Add(cosmotool
+  DEPENDS ${cosmotool_DEPS}
+  SOURCE_DIR ${CMAKE_SOURCE_DIR}/external/cosmotool
+  CMAKE_ARGS -DHDF5_DIR=${HDF5_ROOTDIR} -DHDF5_ROOTDIR=${HDF5_ROOTDIR} -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/ext_build/cosmotool -DNETCDF_INCLUDE_PATH=${NETCDF_INCLUDE_PATH} -DNETCDFCPP_INCLUDE_PATH=${NETCDFCPP_INCLUDE_PATH} -DGSL_INCLUDE_PATH=${GSL_INCLUDE_PATH} -DGSL_LIBRARY=${GSL_LIBRARY} -DGSLCBLAS_LIBRARY=${GSLCBLAS_LIBRARY} -DNETCDF_LIBRARY=${NETCDF_LIBRARY} -DNETCDFCPP_LIBRARY=${NETCDFCPP_LIBRARY}
+)
+SET(COSMOTOOL_LIBRARY ${CMAKE_BINARY_DIR}/ext_build/cosmotool/lib/libCosmoTool.a)
+set(COSMOTOOL_INCLUDE_PATH ${CMAKE_BINARY_DIR}/ext_build/cosmotool/include)
+
+#################
+# Build cfitsio
+#################
+ExternalProject_Add(cfitsio
+  SOURCE_DIR ${CMAKE_SOURCE_DIR}/external/cfitsio
+  CONFIGURE_COMMAND ${CMAKE_SOURCE_DIR}/external/cfitsio/configure --prefix=${CMAKE_BINARY_DIR}/ext_build/cfitsio CPPFLAGS=${CONFIGURE_CPP_FLAGS} CC=${CMAKE_C_COMPILER} CXX=${CMAKE_CXX_COMPILER}
+  BUILD_COMMAND make
+  BUILD_IN_SOURCE 1
+  INSTALL_COMMAND make install
+)
+SET(CFITSIO_LIBRARY ${CMAKE_BINARY_DIR}/ext_build/cfitsio/lib/libcfitsio.a)
+
+#################
+# Build Healpix 
+#################
+
+ExternalProject_Add(healpix
+  DEPENDS cfitsio
+  SOURCE_DIR ${CMAKE_SOURCE_DIR}/external/healpix
+  CONFIGURE_COMMAND echo No configure
+  BUILD_COMMAND make HEALPIX_TARGET=sampler HEALPIX_CC=${CMAKE_C_COMPILER} HEALPIX_CXX=${CMAKE_CXX_COMPILER} HEALPIX_BASE_PATH=${CMAKE_BINARY_DIR} OMP_SUPPORT=${ENABLE_OPENMP} EXTRA_CPPFLAGS=${CONFIGURE_CPP_FLAGS} OMP_FLAGS=${OpenMP_C_FLAGS}
+  BUILD_IN_SOURCE 1
+  INSTALL_COMMAND ${CMAKE_COMMAND} -DHEALPIX_DIR:STRING=${CMAKE_SOURCE_DIR}/external/healpix -DDEST_DIR:STRING=${CMAKE_BINARY_DIR}/ext_build/healpix -P ${CMAKE_SOURCE_DIR}/external/install_healpix.cmake
+)
+set(HPIX_LIBPATH ${CMAKE_BINARY_DIR}/ext_build/healpix/lib)
+set(HEALPIX_LIBRARY ${HPIX_LIBPATH}/libhealpix_cxx.a)
+set(FFTPACK_LIBRARY ${HPIX_LIBPATH}/libfftpack.a)
+set(CXXSUPPORT_LIBRARY ${HPIX_LIBPATH}/libcxxsupport.a)
+set(PSHT_LIBRARY ${HPIX_LIBPATH}/libpsht.a)
+set(CUTILS_LIBRARY ${HPIX_LIBPATH}/libc_utils.a)
+
+SET(HEALPIX_INCLUDE_PATH ${CMAKE_BINARY_DIR}/ext_build/healpix/include)
+set(GSL_LIBRARIES ${GSL_LIBRARY} ${GSLCBLAS_LIBRARY})
+SET(NETCDF_LIBRARIES ${NETCDFCPP_LIBRARY} ${NETCDF_LIBRARY} ${HDF5HL_LIBRARY} ${HDF5_LIBRARY} ${ZLIB_LIBRARY})
+
+include_directories(${CMAKE_BINARY_DIR}/src 
+                    ${NETCDF_INCLUDE_PATH} ${GSL_INCLUDE_PATH} 
+                    ${HDF5_INCLUDE_PATH} ${COSMOTOOL_INCLUDE_PATH} 
+                    ${Boost_INCLUDE_DIRS})
+