From 51f6798f88e3b10c8981b27ab04e1cf63f9d1860 Mon Sep 17 00:00:00 2001 From: Guilhem Lavaux Date: Tue, 30 Oct 2012 14:17:11 -0400 Subject: [PATCH] Imported Healpix, cfitsio, cosmotool. Added cmake tool to build dependencies (cfitsio, hdf5, netcdf, boost, healpix, gsl, ..). Adjusted CMakeLists.txt --- external/cfitsio/License.txt | 25 + external/cfitsio/Makefile.in | 166 + external/cfitsio/README | 144 + external/cfitsio/README.MacOS | 70 + external/cfitsio/README.win32 | 74 + external/cfitsio/adler32.c | 167 + external/cfitsio/buffers.c | 1371 ++ external/cfitsio/cfileio.c | 6965 ++++++++++ external/cfitsio/cfitsio.doc | 9535 +++++++++++++ external/cfitsio/cfitsio.pc.in | 11 + external/cfitsio/cfitsio.tex | 10644 +++++++++++++++ external/cfitsio/cfitsio_mac.sit.hqx | 1 + external/cfitsio/cfortran.doc | 2088 +++ external/cfitsio/cfortran.h | 2515 ++++ external/cfitsio/changes.txt | 3844 ++++++ external/cfitsio/checksum.c | 508 + external/cfitsio/configure | 6682 +++++++++ external/cfitsio/configure.in | 507 + external/cfitsio/cookbook.c | 571 + external/cfitsio/cookbook.f | 772 ++ external/cfitsio/crc32.c | 440 + external/cfitsio/crc32.h | 441 + external/cfitsio/deflate.c | 1832 +++ external/cfitsio/deflate.h | 340 + external/cfitsio/drvrfile.c | 966 ++ external/cfitsio/drvrgsiftp.c | 522 + external/cfitsio/drvrgsiftp.h | 21 + external/cfitsio/drvrmem.c | 1184 ++ external/cfitsio/drvrnet.c | 2741 ++++ external/cfitsio/drvrsmem.c | 973 ++ external/cfitsio/drvrsmem.h | 179 + external/cfitsio/editcol.c | 2474 ++++ external/cfitsio/edithdu.c | 883 ++ external/cfitsio/eval.l | 545 + external/cfitsio/eval.y | 5837 ++++++++ external/cfitsio/eval_defs.h | 163 + external/cfitsio/eval_f.c | 2823 ++++ external/cfitsio/eval_l.c | 2252 +++ external/cfitsio/eval_tab.h | 42 + external/cfitsio/eval_y.c | 7333 ++++++++++ external/cfitsio/f77.inc | 31 + external/cfitsio/f77_wrap.h | 288 + external/cfitsio/f77_wrap1.c | 345 + external/cfitsio/f77_wrap2.c | 711 + external/cfitsio/f77_wrap3.c | 853 ++ external/cfitsio/f77_wrap4.c | 572 + external/cfitsio/fits_hcompress.c | 1858 +++ external/cfitsio/fits_hdecompress.c | 2618 ++++ external/cfitsio/fitscopy.c | 60 + external/cfitsio/fitscore.c | 9243 +++++++++++++ external/cfitsio/fitsfile.tpt | 8 + external/cfitsio/fitsio.doc | 6607 +++++++++ external/cfitsio/fitsio.h | 1934 +++ external/cfitsio/fitsio.pdf | Bin 0 -> 688002 bytes external/cfitsio/fitsio.ps | 11353 ++++++++++++++++ external/cfitsio/fitsio.tex | 7688 +++++++++++ external/cfitsio/fitsio.toc | 95 + external/cfitsio/fitsio2.h | 1205 ++ external/cfitsio/fpack.c | 387 + external/cfitsio/fpack.h | 171 + external/cfitsio/fpackutil.c | 2391 ++++ external/cfitsio/funpack.c | 168 + external/cfitsio/getcol.c | 1055 ++ external/cfitsio/getcolb.c | 2002 +++ external/cfitsio/getcold.c | 1677 +++ external/cfitsio/getcole.c | 1680 +++ external/cfitsio/getcoli.c | 1902 +++ external/cfitsio/getcolj.c | 3728 +++++ external/cfitsio/getcolk.c | 1895 +++ external/cfitsio/getcoll.c | 614 + external/cfitsio/getcols.c | 835 ++ external/cfitsio/getcolsb.c | 1991 +++ external/cfitsio/getcolui.c | 1908 +++ external/cfitsio/getcoluj.c | 1902 +++ external/cfitsio/getcoluk.c | 1917 +++ external/cfitsio/getkey.c | 3242 +++++ external/cfitsio/group.c | 6463 +++++++++ external/cfitsio/group.h | 65 + external/cfitsio/grparser.c | 1379 ++ external/cfitsio/grparser.h | 185 + external/cfitsio/histo.c | 2221 +++ external/cfitsio/imcompress.c | 9247 +++++++++++++ external/cfitsio/imcopy.c | 233 + external/cfitsio/infback.c | 632 + external/cfitsio/inffast.c | 340 + external/cfitsio/inffast.h | 11 + external/cfitsio/inffixed.h | 94 + external/cfitsio/inflate.c | 1480 ++ external/cfitsio/inflate.h | 122 + external/cfitsio/inftrees.c | 330 + external/cfitsio/inftrees.h | 62 + external/cfitsio/iraffits.c | 2073 +++ external/cfitsio/iter_a.c | 147 + external/cfitsio/iter_a.f | 224 + external/cfitsio/iter_a.fit | 1111 ++ external/cfitsio/iter_b.c | 114 + external/cfitsio/iter_b.f | 193 + external/cfitsio/iter_b.fit | Bin 0 -> 408960 bytes external/cfitsio/iter_c.c | 171 + external/cfitsio/iter_c.f | 347 + external/cfitsio/iter_c.fit | 701 + external/cfitsio/iter_image.c | 93 + external/cfitsio/iter_var.c | 100 + external/cfitsio/longnam.h | 592 + external/cfitsio/make_dfloat.com | 90 + external/cfitsio/make_gfloat.com | 88 + external/cfitsio/make_ieee.com | 87 + external/cfitsio/makefile.bc | 588 + external/cfitsio/makefile.vcc | 793 ++ external/cfitsio/makepc.bat | 87 + external/cfitsio/mkpkg | 74 + external/cfitsio/modkey.c | 1706 +++ external/cfitsio/pliocomp.c | 331 + external/cfitsio/putcol.c | 1929 +++ external/cfitsio/putcolb.c | 1013 ++ external/cfitsio/putcold.c | 1060 ++ external/cfitsio/putcole.c | 1074 ++ external/cfitsio/putcoli.c | 986 ++ external/cfitsio/putcolj.c | 1992 +++ external/cfitsio/putcolk.c | 1013 ++ external/cfitsio/putcoll.c | 369 + external/cfitsio/putcols.c | 303 + external/cfitsio/putcolsb.c | 974 ++ external/cfitsio/putcolu.c | 629 + external/cfitsio/putcolui.c | 969 ++ external/cfitsio/putcoluj.c | 977 ++ external/cfitsio/putcoluk.c | 993 ++ external/cfitsio/putkey.c | 3085 +++++ external/cfitsio/quantize.c | 3888 ++++++ external/cfitsio/quick.pdf | Bin 0 -> 279588 bytes external/cfitsio/quick.ps | 3850 ++++++ external/cfitsio/quick.tex | 2159 +++ external/cfitsio/quick.toc | 25 + external/cfitsio/region.c | 1747 +++ external/cfitsio/region.h | 82 + external/cfitsio/ricecomp.c | 1382 ++ external/cfitsio/sample.tpl | 121 + external/cfitsio/scalnull.c | 229 + external/cfitsio/smem.c | 77 + external/cfitsio/speed.c | 511 + external/cfitsio/swapproc.c | 247 + external/cfitsio/testf77.f | 2488 ++++ external/cfitsio/testf77.out | 746 + external/cfitsio/testf77.std | Bin 0 -> 66240 bytes external/cfitsio/testprog.c | 2588 ++++ external/cfitsio/testprog.out | 797 ++ external/cfitsio/testprog.std | 48 + external/cfitsio/testprog.tpt | 12 + external/cfitsio/trees.c | 1242 ++ external/cfitsio/trees.h | 128 + external/cfitsio/uncompr.c | 57 + external/cfitsio/vmsieee.c | 130 + external/cfitsio/vmsieeed.mar | 137 + external/cfitsio/vmsieeer.mar | 106 + external/cfitsio/wcssub.c | 1043 ++ external/cfitsio/wcsutil.c | 502 + external/cfitsio/winDumpExts.mak | 191 + external/cfitsio/windumpexts.c | 502 + external/cfitsio/zcompress.c | 504 + external/cfitsio/zconf.h | 426 + external/cfitsio/zlib.h | 1613 +++ external/cfitsio/zuncompress.c | 603 + external/cfitsio/zutil.c | 316 + external/cfitsio/zutil.h | 272 + external/cosmotool/.gitignore | 3 + .../cosmotool/GetGitRevisionDescription.cmake | 104 + .../GetGitRevisionDescription.cmake.in | 30 + external/cosmotool/LICENCE_CeCILL_V2 | 506 + external/cosmotool/sample/Hartmann_Matrix.txt | 7 + external/cosmotool/sample/testAlgo.cpp | 21 + external/cosmotool/sample/testBQueue.cpp | 33 + external/cosmotool/sample/testBSP.cpp | 15 + external/cosmotool/sample/testDelaunay.cpp | 30 + external/cosmotool/sample/testEskow.cpp | 67 + external/cosmotool/sample/testInterpolate.cpp | 31 + external/cosmotool/sample/testNewton.cpp | 27 + external/cosmotool/sample/testPool.cpp | 19 + external/cosmotool/sample/testReadFlash.cpp | 17 + external/cosmotool/sample/testSmooth.cpp | 68 + external/cosmotool/sample/test_fft_calls.cpp | 12 + external/cosmotool/sample/testkd.cpp | 123 + external/cosmotool/sample/testkd2.cpp | 128 + external/cosmotool/src/algo.hpp | 63 + external/cosmotool/src/bqueue.hpp | 30 + external/cosmotool/src/bqueue.tcc | 67 + external/cosmotool/src/bsp_simple.hpp | 167 + external/cosmotool/src/bsp_simple.tcc | 117 + external/cosmotool/src/cic.cpp | 205 + external/cosmotool/src/cic.hpp | 35 + external/cosmotool/src/config.hpp | 134 + external/cosmotool/src/dinterpolate.hpp | 94 + external/cosmotool/src/dinterpolate.tcc | 339 + external/cosmotool/src/eskow.hpp | 271 + external/cosmotool/src/field.hpp | 83 + external/cosmotool/src/fixArray.hpp | 40 + external/cosmotool/src/fortran.cpp | 354 + external/cosmotool/src/fortran.hpp | 113 + external/cosmotool/src/fourier/base_types.hpp | 102 + external/cosmotool/src/fourier/euclidian.hpp | 200 + .../cosmotool/src/fourier/fft/fftw_calls.hpp | 75 + external/cosmotool/src/fourier/healpix.hpp | 135 + external/cosmotool/src/growthFactor.cpp | 111 + external/cosmotool/src/growthFactor.hpp | 12 + external/cosmotool/src/h5_readFlash.cpp | 474 + external/cosmotool/src/h5_readFlash.hpp | 36 + external/cosmotool/src/hdf5_flash.h | 192 + external/cosmotool/src/interpolate.cpp | 278 + external/cosmotool/src/interpolate.hpp | 65 + external/cosmotool/src/interpolate3d.hpp | 105 + external/cosmotool/src/kdtree_leaf.hpp | 107 + external/cosmotool/src/kdtree_leaf.tcc | 308 + external/cosmotool/src/kdtree_splitters.hpp | 134 + external/cosmotool/src/loadFlash.cpp | 104 + external/cosmotool/src/loadFlash.hpp | 13 + external/cosmotool/src/loadFlash_dummy.cpp | 9 + external/cosmotool/src/loadGadget.cpp | 349 + external/cosmotool/src/loadGadget.hpp | 18 + external/cosmotool/src/loadRamses.cpp | 1002 ++ external/cosmotool/src/loadRamses.hpp | 18 + external/cosmotool/src/loadSimu.hpp | 48 + external/cosmotool/src/load_data.cpp | 635 + external/cosmotool/src/load_data.hpp | 97 + external/cosmotool/src/mach.hpp | 20 + external/cosmotool/src/miniargs.cpp | 55 + external/cosmotool/src/miniargs.hpp | 26 + external/cosmotool/src/mykdtree.hpp | 183 + external/cosmotool/src/mykdtree.tcc | 599 + external/cosmotool/src/newton.hpp | 30 + external/cosmotool/src/octTree.cpp | 175 + external/cosmotool/src/octTree.hpp | 145 + external/cosmotool/src/pool.hpp | 166 + external/cosmotool/src/powerSpectrum.cpp | 654 + external/cosmotool/src/powerSpectrum.hpp | 35 + external/cosmotool/src/sparseGrid.hpp | 87 + external/cosmotool/src/sparseGrid.tcc | 145 + external/cosmotool/src/sphSmooth.hpp | 105 + external/cosmotool/src/sphSmooth.tcc | 214 + external/cosmotool/src/yorick.hpp | 196 + external/cosmotool/src/yorick_nc3.cpp | 274 + external/cosmotool/src/yorick_nc4.cpp | 237 + external/external_build.cmake | 186 + 241 files changed, 243806 insertions(+) create mode 100644 external/cfitsio/License.txt create mode 100644 external/cfitsio/Makefile.in create mode 100644 external/cfitsio/README create mode 100644 external/cfitsio/README.MacOS create mode 100644 external/cfitsio/README.win32 create mode 100644 external/cfitsio/adler32.c create mode 100644 external/cfitsio/buffers.c create mode 100644 external/cfitsio/cfileio.c create mode 100644 external/cfitsio/cfitsio.doc create mode 100644 external/cfitsio/cfitsio.pc.in create mode 100644 external/cfitsio/cfitsio.tex create mode 100644 external/cfitsio/cfitsio_mac.sit.hqx create mode 100644 external/cfitsio/cfortran.doc create mode 100644 external/cfitsio/cfortran.h create mode 100644 external/cfitsio/changes.txt create mode 100644 external/cfitsio/checksum.c create mode 100755 external/cfitsio/configure create mode 100644 external/cfitsio/configure.in create mode 100644 external/cfitsio/cookbook.c create mode 100644 external/cfitsio/cookbook.f create mode 100644 external/cfitsio/crc32.c create mode 100644 external/cfitsio/crc32.h create mode 100644 external/cfitsio/deflate.c create mode 100644 external/cfitsio/deflate.h create mode 100644 external/cfitsio/drvrfile.c create mode 100644 external/cfitsio/drvrgsiftp.c create mode 100644 external/cfitsio/drvrgsiftp.h create mode 100644 external/cfitsio/drvrmem.c create mode 100644 external/cfitsio/drvrnet.c create mode 100644 external/cfitsio/drvrsmem.c create mode 100644 external/cfitsio/drvrsmem.h create mode 100644 external/cfitsio/editcol.c create mode 100644 external/cfitsio/edithdu.c create mode 100644 external/cfitsio/eval.l create mode 100644 external/cfitsio/eval.y create mode 100644 external/cfitsio/eval_defs.h create mode 100644 external/cfitsio/eval_f.c create mode 100644 external/cfitsio/eval_l.c create mode 100644 external/cfitsio/eval_tab.h create mode 100644 external/cfitsio/eval_y.c create mode 100644 external/cfitsio/f77.inc create mode 100644 external/cfitsio/f77_wrap.h create mode 100644 external/cfitsio/f77_wrap1.c create mode 100644 external/cfitsio/f77_wrap2.c create mode 100644 external/cfitsio/f77_wrap3.c create mode 100644 external/cfitsio/f77_wrap4.c create mode 100644 external/cfitsio/fits_hcompress.c create mode 100644 external/cfitsio/fits_hdecompress.c create mode 100644 external/cfitsio/fitscopy.c create mode 100644 external/cfitsio/fitscore.c create mode 100644 external/cfitsio/fitsfile.tpt create mode 100644 external/cfitsio/fitsio.doc create mode 100644 external/cfitsio/fitsio.h create mode 100644 external/cfitsio/fitsio.pdf create mode 100644 external/cfitsio/fitsio.ps create mode 100644 external/cfitsio/fitsio.tex create mode 100644 external/cfitsio/fitsio.toc create mode 100644 external/cfitsio/fitsio2.h create mode 100644 external/cfitsio/fpack.c create mode 100644 external/cfitsio/fpack.h create mode 100644 external/cfitsio/fpackutil.c create mode 100644 external/cfitsio/funpack.c create mode 100644 external/cfitsio/getcol.c create mode 100644 external/cfitsio/getcolb.c create mode 100644 external/cfitsio/getcold.c create mode 100644 external/cfitsio/getcole.c create mode 100644 external/cfitsio/getcoli.c create mode 100644 external/cfitsio/getcolj.c create mode 100644 external/cfitsio/getcolk.c create mode 100644 external/cfitsio/getcoll.c create mode 100644 external/cfitsio/getcols.c create mode 100644 external/cfitsio/getcolsb.c create mode 100644 external/cfitsio/getcolui.c create mode 100644 external/cfitsio/getcoluj.c create mode 100644 external/cfitsio/getcoluk.c create mode 100644 external/cfitsio/getkey.c create mode 100644 external/cfitsio/group.c create mode 100644 external/cfitsio/group.h create mode 100644 external/cfitsio/grparser.c create mode 100644 external/cfitsio/grparser.h create mode 100644 external/cfitsio/histo.c create mode 100644 external/cfitsio/imcompress.c create mode 100644 external/cfitsio/imcopy.c create mode 100644 external/cfitsio/infback.c create mode 100644 external/cfitsio/inffast.c create mode 100644 external/cfitsio/inffast.h create mode 100644 external/cfitsio/inffixed.h create mode 100644 external/cfitsio/inflate.c create mode 100644 external/cfitsio/inflate.h create mode 100644 external/cfitsio/inftrees.c create mode 100644 external/cfitsio/inftrees.h create mode 100644 external/cfitsio/iraffits.c create mode 100644 external/cfitsio/iter_a.c create mode 100644 external/cfitsio/iter_a.f create mode 100644 external/cfitsio/iter_a.fit create mode 100644 external/cfitsio/iter_b.c create mode 100644 external/cfitsio/iter_b.f create mode 100644 external/cfitsio/iter_b.fit create mode 100644 external/cfitsio/iter_c.c create mode 100644 external/cfitsio/iter_c.f create mode 100644 external/cfitsio/iter_c.fit create mode 100644 external/cfitsio/iter_image.c create mode 100644 external/cfitsio/iter_var.c create mode 100644 external/cfitsio/longnam.h create mode 100644 external/cfitsio/make_dfloat.com create mode 100644 external/cfitsio/make_gfloat.com create mode 100644 external/cfitsio/make_ieee.com create mode 100644 external/cfitsio/makefile.bc create mode 100644 external/cfitsio/makefile.vcc create mode 100644 external/cfitsio/makepc.bat create mode 100644 external/cfitsio/mkpkg create mode 100644 external/cfitsio/modkey.c create mode 100644 external/cfitsio/pliocomp.c create mode 100644 external/cfitsio/putcol.c create mode 100644 external/cfitsio/putcolb.c create mode 100644 external/cfitsio/putcold.c create mode 100644 external/cfitsio/putcole.c create mode 100644 external/cfitsio/putcoli.c create mode 100644 external/cfitsio/putcolj.c create mode 100644 external/cfitsio/putcolk.c create mode 100644 external/cfitsio/putcoll.c create mode 100644 external/cfitsio/putcols.c create mode 100644 external/cfitsio/putcolsb.c create mode 100644 external/cfitsio/putcolu.c create mode 100644 external/cfitsio/putcolui.c create mode 100644 external/cfitsio/putcoluj.c create mode 100644 external/cfitsio/putcoluk.c create mode 100644 external/cfitsio/putkey.c create mode 100644 external/cfitsio/quantize.c create mode 100644 external/cfitsio/quick.pdf create mode 100644 external/cfitsio/quick.ps create mode 100644 external/cfitsio/quick.tex create mode 100644 external/cfitsio/quick.toc create mode 100644 external/cfitsio/region.c create mode 100644 external/cfitsio/region.h create mode 100644 external/cfitsio/ricecomp.c create mode 100644 external/cfitsio/sample.tpl create mode 100644 external/cfitsio/scalnull.c create mode 100644 external/cfitsio/smem.c create mode 100644 external/cfitsio/speed.c create mode 100644 external/cfitsio/swapproc.c create mode 100644 external/cfitsio/testf77.f create mode 100644 external/cfitsio/testf77.out create mode 100644 external/cfitsio/testf77.std create mode 100644 external/cfitsio/testprog.c create mode 100644 external/cfitsio/testprog.out create mode 100644 external/cfitsio/testprog.std create mode 100644 external/cfitsio/testprog.tpt create mode 100644 external/cfitsio/trees.c create mode 100644 external/cfitsio/trees.h create mode 100644 external/cfitsio/uncompr.c create mode 100644 external/cfitsio/vmsieee.c create mode 100644 external/cfitsio/vmsieeed.mar create mode 100644 external/cfitsio/vmsieeer.mar create mode 100644 external/cfitsio/wcssub.c create mode 100644 external/cfitsio/wcsutil.c create mode 100644 external/cfitsio/winDumpExts.mak create mode 100644 external/cfitsio/windumpexts.c create mode 100644 external/cfitsio/zcompress.c create mode 100644 external/cfitsio/zconf.h create mode 100644 external/cfitsio/zlib.h create mode 100644 external/cfitsio/zuncompress.c create mode 100644 external/cfitsio/zutil.c create mode 100644 external/cfitsio/zutil.h create mode 100644 external/cosmotool/.gitignore create mode 100644 external/cosmotool/GetGitRevisionDescription.cmake create mode 100644 external/cosmotool/GetGitRevisionDescription.cmake.in create mode 100644 external/cosmotool/LICENCE_CeCILL_V2 create mode 100644 external/cosmotool/sample/Hartmann_Matrix.txt create mode 100644 external/cosmotool/sample/testAlgo.cpp create mode 100644 external/cosmotool/sample/testBQueue.cpp create mode 100644 external/cosmotool/sample/testBSP.cpp create mode 100644 external/cosmotool/sample/testDelaunay.cpp create mode 100644 external/cosmotool/sample/testEskow.cpp create mode 100644 external/cosmotool/sample/testInterpolate.cpp create mode 100644 external/cosmotool/sample/testNewton.cpp create mode 100644 external/cosmotool/sample/testPool.cpp create mode 100644 external/cosmotool/sample/testReadFlash.cpp create mode 100644 external/cosmotool/sample/testSmooth.cpp create mode 100644 external/cosmotool/sample/test_fft_calls.cpp create mode 100644 external/cosmotool/sample/testkd.cpp create mode 100644 external/cosmotool/sample/testkd2.cpp create mode 100644 external/cosmotool/src/algo.hpp create mode 100644 external/cosmotool/src/bqueue.hpp create mode 100644 external/cosmotool/src/bqueue.tcc create mode 100644 external/cosmotool/src/bsp_simple.hpp create mode 100644 external/cosmotool/src/bsp_simple.tcc create mode 100644 external/cosmotool/src/cic.cpp create mode 100644 external/cosmotool/src/cic.hpp create mode 100644 external/cosmotool/src/config.hpp create mode 100644 external/cosmotool/src/dinterpolate.hpp create mode 100644 external/cosmotool/src/dinterpolate.tcc create mode 100644 external/cosmotool/src/eskow.hpp create mode 100644 external/cosmotool/src/field.hpp create mode 100644 external/cosmotool/src/fixArray.hpp create mode 100644 external/cosmotool/src/fortran.cpp create mode 100644 external/cosmotool/src/fortran.hpp create mode 100644 external/cosmotool/src/fourier/base_types.hpp create mode 100644 external/cosmotool/src/fourier/euclidian.hpp create mode 100644 external/cosmotool/src/fourier/fft/fftw_calls.hpp create mode 100644 external/cosmotool/src/fourier/healpix.hpp create mode 100644 external/cosmotool/src/growthFactor.cpp create mode 100644 external/cosmotool/src/growthFactor.hpp create mode 100644 external/cosmotool/src/h5_readFlash.cpp create mode 100644 external/cosmotool/src/h5_readFlash.hpp create mode 100644 external/cosmotool/src/hdf5_flash.h create mode 100644 external/cosmotool/src/interpolate.cpp create mode 100644 external/cosmotool/src/interpolate.hpp create mode 100644 external/cosmotool/src/interpolate3d.hpp create mode 100644 external/cosmotool/src/kdtree_leaf.hpp create mode 100644 external/cosmotool/src/kdtree_leaf.tcc create mode 100644 external/cosmotool/src/kdtree_splitters.hpp create mode 100644 external/cosmotool/src/loadFlash.cpp create mode 100644 external/cosmotool/src/loadFlash.hpp create mode 100644 external/cosmotool/src/loadFlash_dummy.cpp create mode 100644 external/cosmotool/src/loadGadget.cpp create mode 100644 external/cosmotool/src/loadGadget.hpp create mode 100644 external/cosmotool/src/loadRamses.cpp create mode 100644 external/cosmotool/src/loadRamses.hpp create mode 100644 external/cosmotool/src/loadSimu.hpp create mode 100644 external/cosmotool/src/load_data.cpp create mode 100644 external/cosmotool/src/load_data.hpp create mode 100644 external/cosmotool/src/mach.hpp create mode 100644 external/cosmotool/src/miniargs.cpp create mode 100644 external/cosmotool/src/miniargs.hpp create mode 100644 external/cosmotool/src/mykdtree.hpp create mode 100644 external/cosmotool/src/mykdtree.tcc create mode 100644 external/cosmotool/src/newton.hpp create mode 100644 external/cosmotool/src/octTree.cpp create mode 100644 external/cosmotool/src/octTree.hpp create mode 100644 external/cosmotool/src/pool.hpp create mode 100644 external/cosmotool/src/powerSpectrum.cpp create mode 100644 external/cosmotool/src/powerSpectrum.hpp create mode 100644 external/cosmotool/src/sparseGrid.hpp create mode 100644 external/cosmotool/src/sparseGrid.tcc create mode 100644 external/cosmotool/src/sphSmooth.hpp create mode 100644 external/cosmotool/src/sphSmooth.tcc create mode 100644 external/cosmotool/src/yorick.hpp create mode 100644 external/cosmotool/src/yorick_nc3.cpp create mode 100644 external/cosmotool/src/yorick_nc4.cpp create mode 100644 external/external_build.cmake 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 0000000000000000000000000000000000000000..31ebd2b30bcc828cd933424fbdbb772cdb537328 GIT binary patch literal 688002 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Ⓢ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�U%5H@Q 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%0AS>?ZpPucCxj)@vkW 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%0AAuMVL}` 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�h1xdN$4#SXa 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 J|`eLtbUJgsOyhGj1;$zgcx(t$rA+ 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®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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..74dcd9d62a2072e3d9820d9d5a412157bdec1429
GIT binary patch
literal 408960
zcmeI*TW=fJ6$a4!lmqlZNl?eSI8It1540==1avV%25_E=5jnI5&}ex6=IkV;pVB>#aeAI+&Ad+2RlP}*)pE94FH*Zo
zr^E3mZQ5!%t=7|>H@+H<-wfa0f8N2%eBSbQaaFI=YL>2=c9U-M^J&#q>8ANm&v%aM
zKe!mY9ggy|d+XcZIj=va`j9Jq6`1jO*7e~(e2I6yk6AHd{Dh^@-_bc;>GiuqnAg2
z{rK#MgZ%7=qx`w=|MuzG%kRJYeBN<)9g{Y-*LAuWj0R~mY3k*qPM5P;lmE#b8GY>N
zrmG~4>&f+UHDA5E>x%B*m(u6+`s;n&T}IPpl{_Pp+rGfR9PID+2dwg!T5nyNesrqt
zh2QOTr*Fp2XM8|_009C72oQ)Z(A`dshL;yVrM*|fi}B#q*~wl}@$-C_yR6oCsja%3
z!awdIiTU|!fX|L*T~H#ha9$ydBh4ePop4k@1g{MdQj^{$pV9(4ic-FkIvZtcep?3U$=W+f2|)wOzv`j
z=-<;|bUYlU(S=Pkbdw*S(*=WP>M9+cahbGo_y#CU&={q-Jo^S5{Mh1Z*3{qUo|
z-tBq$`(ZyP_5bSc{rz~cz209=b}lx*xxe1+d6UKV%l@hB&3XD_aDLKVZ<8DUkNjh)
zEe_1zzty7uervCr&F+tvdw&?pgAdPw{~OKk1PBlyK!5-N
z0t5&UAP`BQ2<Jp
z5!!9qBQ-`X1PBlyK!5-N0t5&UAV8o9?KbVkfdByl1PBlyK!5-N0t5&|5-38uO?#xq
zsD%Il0t5&UAV7cs0RjXF6rtUw-8c{+K!5-N0t5&UAV7csfk*;HXt!yP)EKo8AV7cs
z0RjXF5FkK+00E)h3=kkdfB*pk1PBlyK!5;&paMdB(8ef>009C72oNAZfB*pk1PBQ2
zW`FsO}lX*K!5-N0t5&UAV7cs0RoW(iqLM;9;q>EAwYlt0RjXF5FkK+
z009C;Xt!xM4g?4gAV7cs0RjXF5FkJxl0XsKZQ3I>MlA#g5FkK+009C72oNAZKxj7u
z1PBlyK!5-N0t5&UAV46ffY2VaG0Gx9fB*pk1PBlyK!5-N0z$hPAV7cs0RjXF5FkK+
z009C)1%&pXjZqc>0t5&UAV7cs0RjXF5D?nU009C72oNAZfB*pk1PBlaDj>86ZH%%A
z5FkK+009C72oNAZfPm0$1_%%!K!5-N0t5&UAV7dXPywMmXk(N`fB*pk1PBlyK!5-N
z0tAZCKC9-NI_1YH76Jqa5FkK+009C72oNAZ;OPa5(B7_Z`^=ukPhXY_2oNAZfB*pk
z1PBlyK!5;&BDCAI8wUae2oNAZfB*pk1PBly5J{j2?KbU^8lx5h1PBlyK!5-N0t5&U
zAW(#Mn|9+sfB*pk1PBlyK!5-N0t6xn6rtUwJyK)TLVy4P0t5&UAV7cs0RjYy&~DRi
z90(8~K!5-N0t5&UAV7dXB!MEd+q6e&j9Lf~AV7cs0RjXF5FkK+KoQz)+KmGN0t5&U
zAV7cs0RjXF5QrpDgm#V%c1PBlyK!5-N0t5&U
z2r3}72W^b92oNAZfB*pk1PBlyK!AYIZUzVtAV7cs0RjXF5FkK+Ku`gpJ!oT;MSuVS
z0t5&UAV7cs0RjYsb~8YL009C72oNAZfB*pk1cC|(?Lix(ECK`w5FkK+009C72oNAp
zgm!1`&1PBly
zK!5-N0t5&UARx4x0RjXF5FkK+009C72oN9;R6uAC+8AXKAV7cs0RjXF5FkK+00E)h
z3=kkdfB*pk1PBlyK!5;&paMdB(8ef>009C72oNAZfB*pk1PBQ2W`F
+#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 0000000000000000000000000000000000000000..487e1ecebe39bbac30a9b9f465059b9d6a888503
GIT binary patch
literal 279588
zcma&sQmMG9ikM(T1(v8Y;h>>xxZO?YI5;cmew=pO9$lZTf)rT
zvCHjEN4QJx>B~?49#VTLd0w^PZ`slP4FoOV80Y-qv1wk;Sz?jV0ZQ9U
zGSnZEw67p5DtPeZ5`B^WG+r&){D|JK4G=-2@_TRo5vrW}AZinOALNFuA}8NA1i3rF#-Q#!KPJK+*+
z?OYK$oJLq`c~9Oui;akCh&{}xKR?VU=A>eY*>?0%YKdS{R>l~1v1~SniIawBmzBnX
z9Y3CL`bGF6jNQ5GC28s<7@F;b@)vfR(Ns438b29#UwwaK1SwD1CJIw4t(;{n#>FhG
zt=uVtu$0pki>elV$FQR4t9EW&x@Rj@P83t{D{y2t7sARjP
zQ$wNVO;D7cOk>X~3fgB<`s5V#WHfa;eYrLO2s6L{<53-a*XL!_y==*(WPZ;%F$zL8
zRI|9v7mCYQ}Hb(WbdskiQ3WRIrLB{V;MykPNEmksRI@TJb_Ikn^b$Y}|
z!|b)Akj?1s)q_@iHT_F@@g5qOlAc1oCW-mep`cbaB)O!kFu4KC%dK_&svR@7(k_n!
z8C_xHd%W5^If*P3A6;PRA|qA=+whk|Os_2>@8(=uOh!C_xsSegrdz{jTyqXw*#fT>
z_fH`)@Wid;Fc{0?g{Vj?w0Wep)I?k>(}lf%Gkc{b!od=;r{V(*eVyAH;Q*p+i_aig
zpgKx{5F9*2zaup5auOI#ksxdNWN9?Uty-Ge^6JAvg)TMl4J-{Fy8u$$fsIwTA{M=gQ2pl8z)^2lMl9=m0!aEWY2Ra(mH7GiS`?J
z2Rv*G7w#T}oN)W>1_oucG`4{U!V_oZvOO>Y^GO5xQk$
zBz4fTJsyoQc!Vds)tSXvB1Lw_Zy3h!lfSZrxGTFIL%`e3B8*G3_L*4s1_L1IJj*VE
z95UIE0iD2mvYTu0LP;tG3Be*qqNlSNJO|7w+(ZPah
z6TjObnzb4Y+s|Lb<6J5HyN1$-PP|)`x3H
z&(ro=X-#KzCH=5#xXVT*?rL%K3h`vS@8j)
zGD=`6DTN`6^8SVigfv?tHw=l?%g+qDOv@mZ4Ht5;`A;L1wRuLl3s%+-G9KpdiEDH1BidDJNo@a4m{s8di~lpj2M}^7&I9m{3#TjE-@GS4EBJ7
z+)R{e(gK}w_N-`ATrKJXfFBbUvCQmE|EIV7bN8=)^sg@TuPYNf6DQ37)R;N`y<0JJ
z{73z&RQt;X_ZaymoW?r5FrwKy0oOX5G}a02vLso)wjcpfnc7i?&~8~;j%i@I6yC~K
zLtt5$vy$Hqnw)vqBD2o)cRR&{h;8hmJA_ZY)0O9VLR!N&j5#-GfRM+Fe(nt)7k68(
zEXv2t*Sg2c#*W2H&jbFfh;TUSPyNqajvddQd#^}=Ma$1@&NZiVW6h~$_`K8Kk=9K`
z$ZtS0f)_XG0Q65D@0L4&Mvub4+NFc0O>-)>(7jS;R%FV64C>L2)SG?GV5YE%Bl8iI{Jj0q
zoew&D-F6#s=`r;Vq8BZA5txR_B3)Un)D9*aY7aj{TQa`P#$eHf`)`@9EcUk!w@1$i=;Wxt
z9RcD=U(Gw$XdLf6qayjeuCVG~mn~s)gL)b@4gj5j!+Rz?G~@FLFX*Ej+y@QXVgiFN
z=9UI;l3kCHB&fKW@o4ktIfT(NAw=Bs^(#Z=2pvIObxjKppJ_zv_)2P!H)t?I
z2?o3!ktEg~DdiJo-119NSXsp27P8l95np>u9vGHBfKwynM6M&m&%1xZq`(IU`ML82
zL27A9Aa#t+D$gMXP7BDT&QkLIc?@av9RuA_vS=$xrdbe2^o(U1Ry8)>NVxuhUN%

Y!>C@_NV-ONs6^yEhEyO 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

;k(JIM|OR7Oo`H{Vrja5m;0mWdG)1nqC$lQ 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 Me)EU_{I~D^ACqr8UH||9 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..24975a43aab48fb79331ce1b0cfd6df64375a734 GIT binary patch literal 66240 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 +#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 î þ + + +  +@ +€ +À @ Q b e v ˜ Ü  ¨ º Ì Ï á  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}) +