
    /*********************************************************
     * convert a data file from Tom's format to FITS.
     * Assumes the following format for the input file:
     *
     *   - chunks of 12,600 bytes
     *   - each chunk has 18-byte header, 
     *                    2086 2-byte entries for short exposure
     *                    2086 2-byte entries for medium exposure
     *                    2086 2-byte entries for long exposure
     *                    66   NULL bytes
     *                  ---------------------
     *                   12600 bytes
     *
     *   - inside each 2086-entry data area, entries
     *     32 - 2079 (zero-indexed) contain image data.
     *
     * The data is converted to 16-bit signed integer FITS,
     * with a minimal header.  
     * 
     * This version is revised, after helpful comments from Norman Molhant,
     * to read the data values correctly, and convert to signed 16-bit
     * integer.  There is a constant bias of BIAS added to each data
     * value.
     *
     *   Michael Richmond 7/14/1995
     *
     * I've added the function "pad_file", which adds NULL characters to
     * the end of the file so that it has an exact multiple of FITSMULT
     * characters.  Some FITS readers won't accept a file without this
     * padding.
     *             Michael Richmond 8/30/1995
     * 
     */

#include <stdio.h>
#include <string.h>      /* needed for "strncmp" function */

#undef  LINUX            /* define this if you are using LINUX */

#ifndef LINUX
#define SEEK_SET     0   /* "fseek" to move relative to beginning of file */
#define SEEK_CUR     1   /* "fseek" to move relative to current position */
#define SEEK_END     2   /* "fseek" to move relative to end of file */
#endif


#define CARDLEN     80   /* # of chars in each FITS header record */
#define NCARD       36   /* # of records in a minimal header */
#define FITSMULT  2880   /* FITS file must be multiple of this in bytes */

    /* these pertain to Tom's input data files */
#define CHUNK_LEN  12600 /* each chunk has this many bytes */
#define HDR_LEN     18   /* each chunk starts with header this many bytes */ 
#define NCOLS     2086   /* each exposure has this many 2-bytes entries */
#define CCDCOLS   2048   /* but only this many are real CCD data */
#define STARTCCD    32   /* must skip this many 2-byte entries to reach */
                         /*   true CCD data */
#define BIAS      3300   /* add this fixed bias to Tom's data values */

    /* the following must be a 16-bit signed quantity */
typedef short int int16;    
    /* and this should be a 16-bit unsigned quantity */
typedef unsigned short int uint16;

    /*
     * create a CARDLEN-byte line with the given string at its 
     * beginning, and padded with blanks to the end (which is 
     * NOT null-terminated).
     */

static void
make_line
    (
    char *buf,            /* must have enough space to hold CARDLEN chars */
    char *string          /* copy this (up to NULL) into 'buf' */
    )
{
    int i;

    for (i = 0; i < CARDLEN; i++) {
        buf[i] = ' ';
    }
    for (i = 0; i < strlen(string); i++) {
        buf[i] = string[i];
    }
}

    /*
     * print the given string to the stderr, and terminate execution
     * with status code -1.
     */

static void
die
    (
    char *string           /* print this on stderr */
    )
{
    fprintf(stderr, "%s\n", string);
    exit(-1);
}



    /* 
     * create a header for a FITS file with nrow-by-ncol entries,
     * using 16-bit signed integer data.
     *
     * if an error occurs, print an error message and quit.
     */

static int
create_header
    (
    FILE *fp,              /* write into this stream */
    int nrow,              /* image has this many rows */
    int ncol               /* image has this many columns */
    )
{
    int i;
    char buf[CARDLEN + 1], string[CARDLEN + 1];

    /* 
     * put a minimal header into place, including enough 
     * blank cards after the 'END' to fill the header's NCARD lines
     */
    make_line(buf, "SIMPLE  = T");
    if (fwrite(buf, 1, CARDLEN, fp) != CARDLEN) {
        die("create_header: fwrite fails on SIMPLE");
    }
    make_line(buf, "BITPIX  = 16");
    if (fwrite(buf, 1, CARDLEN, fp) != CARDLEN) {
        die("create_header: fwrite fails on BITPIX");
    }
    make_line(buf, "NAXIS   = 2");
    if (fwrite(buf, 1, CARDLEN, fp) != CARDLEN) {
        die("create_header: fwrite fails on NAXIS");
    }
    sprintf(string, "NAXIS1  = %d", ncol);
    make_line(buf, string);
    if (fwrite(buf, 1, CARDLEN, fp) != CARDLEN) {
        die("create_header: fwrite fails on NAXIS1");
    }
    sprintf(string, "NAXIS2  = %d", nrow);
    make_line(buf, string);
    if (fwrite(buf, 1, CARDLEN, fp) != CARDLEN) {
        die("create_header: fwrite fails on NAXIS2");
    }
    make_line(buf, "END");
    if (fwrite(buf, 1, CARDLEN, fp) != CARDLEN) {
        die("create_header: fwrite fails on END\n");
    }

    /* we've written 6 lines so far; let's write all the rest */
    make_line(buf, " ");
    for (i = 6; i < NCARD; i++) {
        if (fwrite(buf, 1, CARDLEN, fp) != CARDLEN) {
            die("create_header: fwrite fails on blank line");
        }
    }

    return(0);
}

    /*
     * add the given string as a COMMENT line in the header,
     * placing it at the position of the current "END" line,
     * and creating a new "END" line after it.  If we are
     * at the end of the first header block, don't do it,
     * and return -1.  Otherwise, return 0.
     */

static int
add_comment
    (
    FILE *fp,                /* FITS file whose header we edit */
    char *comment            /* the contents of the comment */
    )
{
    char buf[CARDLEN + 1], newline[CARDLEN + 1];
    int i;
    long oldpos, cardlen;
    
    /* save the current position of the FILE pointer */
    oldpos = ftell(fp);

    /* now go back to start of file and look for an END line */
    rewind(fp);
    cardlen = -CARDLEN;
    for (i = 0; i < NCARD - 1; i++) {
        if (fread(buf, 1, CARDLEN, fp) != CARDLEN) {
            die("add_comment: can't read from header");
        }
        if (strncmp(buf, "END ", 4) != 0) {
            continue;
        }

        /* okay, let's add that new COMMENT line */
        sprintf(newline, "COMMENT  %s", comment);
        make_line(buf, newline);
        fseek(fp, cardlen, SEEK_CUR);
        if (fwrite(buf, 1, CARDLEN, fp) != CARDLEN) {
            die("add_comment: fwrite fails on COMMENT");
        }
        /* and now add a new END line */
        make_line(buf, "END");
        if (fwrite(buf, 1, CARDLEN, fp) != CARDLEN) {
            die("add_comment: fwrite fails on END");
        }
        break;
    }

    /* put file pointer back to its position before we entered add_comment */
    fseek(fp, oldpos, SEEK_SET);

    if (i == NCARD - 1) {
        /* we ran out of space in the header; return -1 */
        return(-1);
    }
        
    return(0);
}

        
    
    
    /*
     * copy a sequence of 2-byte data values from the given buffer
     * to the given int16 array.   We convert them from the form
     * 
     *       low-byte   high-byte
     *
     * to signed integer, and add a constant BIAS term to bring all
     * values above 0.
     */

static void
copy_data
    (
    char *chunk,             /* data to copy is in this buffer */
    int start_byte,          /* start at this position in buffer */
    int nvalues,             /* copy this many 2-byte values */
    int16 *dat_array         /* place the values into this array */
    )
{
    unsigned char *ptr;
    int i, byte1, byte2;
    long temp;

    ptr = (unsigned char *) (chunk + start_byte);
    for (i = 0; i < nvalues; i++) {
        byte1 = *ptr++;  
        byte2 = *ptr++;
        temp = BIAS + byte1 + byte2*256;
        dat_array[i] = temp;
    }
}

    /*
     * pad the given FILE with null characters so that its
     * length is an integer multiple of FITSMULT bytes.
     */

static void
pad_file
    (
    FILE *fp                /* image file, all data + header already inside */
    )
{
    long offset, actual_length, desired_length;

    offset = 0;
    fseek(fp, offset, SEEK_END);
    actual_length = ftell(fp);
   
    desired_length = ((actual_length/FITSMULT) + 1)*FITSMULT;
    while (actual_length < desired_length) {
        fputc('\0', fp);
        actual_length++;
    }
}



    /*
     * read a chunk of data (short+med+long+other stuff) from
     * Tom's file.  Place the data into the passed arrays,
     * and place header info into the 'header' string.
     *
     * If reading reveals that our LAST read reached the end
     * of the file, then we're done, so return 1.
     * If reading yields > 0 but < CHUNK_LEN, something went
     * wrong, so return -1.
     * If reading yields exactly CHUNK_LEN, return 0.
     */

static int
read_chunk
    (
    FILE *fp,             /* read from this file stream */
    int16 *short_data,    /* place short-exposure data into this array */
    int16 *med_data,      /* place medium-exposure data into this array */
    int16 *long_data,     /* place long-exposure data into this array */
    char *header          /* place header info into this, and NULL-terminate */
    )
{
    int i, nbytes, start_byte;
    char chunk[CHUNK_LEN+1];

    if ((nbytes = fread(chunk, 1, CHUNK_LEN, fp)) != CHUNK_LEN) {
        if (nbytes == 0) {
            return(1);
        }
        else {
            return(-1);
        }
    }

    /* copy data from the header */
    for (i = 0; i < HDR_LEN; i++) {
        header[i] = chunk[i];
    }
    header[i] = '\0';

    /* now copy the data for the short-exposure image */
    start_byte = HDR_LEN + STARTCCD*2;
    copy_data(chunk, start_byte, CCDCOLS, short_data);

    /* now the medium_exposure data */
    start_byte = HDR_LEN + NCOLS*2 + STARTCCD*2;
    copy_data(chunk, start_byte, CCDCOLS, med_data);

    /* now the long-exposure data */
    start_byte = HDR_LEN + NCOLS*2 + NCOLS*2 + STARTCCD*2;
    copy_data(chunk, start_byte, CCDCOLS, long_data);

    /* all done */
    return(0);
}
    

    /*
     * write data (assumed to be CCDCOLS 2-byte integers) to the
     * given stream.
     */

static void
write_data
    (
    FILE *fp,               /* write to this stream */
    int16 *data             /* write this data in binary format */
    )
{

    if (fwrite(data, 2, CCDCOLS, fp) != CCDCOLS) {
        die("write_data fails");
    }
}
    

    /*
     * assuming that the given file has an integral number of CHUNK_LEN
     * units, return the number of such units.  If the file is not
     * an integral number of such units, print error message and quit.
     */

static int
find_rows
    (
    FILE *fp             /* search in this file */
    )
{
    char err_msg[100];
    int nrow;
    long offset, nbytes;

    offset = 0;
    fseek(fp, offset, SEEK_END);
    nbytes = ftell(fp);
    if (nbytes % CHUNK_LEN != 0) {
        sprintf(err_msg, "input file is not an integral number of %d bytes", 
                CHUNK_LEN);
        die(err_msg);
    }
    rewind(fp);

    nrow = nbytes/CHUNK_LEN;
    return(nrow);
}



    
    
    /*
     * the main program.  Usage:
     *
     *      tom2fits  tomfile  shortfits medfits longfits
     *
     * where the args are
     *
     *    tomfile        name of file with data in Tom's format
     *    shortfits      name of FITs file into which short-exposure data goes
     *    medfits         "   "   "    "    "    "    medium  "       "    "
     *    longfits        "   "   "    "    "    "    long    "       "    "
     *
     */
    
int 
main
    (
    int argc,
    char *argv[] 
    )
{
    char header[CARDLEN+1];       /* stores info on header of each chunk */
    char comment[CARDLEN+1];      /* holds a comment we add to FITS header */
    char err_msg[100]; 
    int i, nrow;
    int16 short_data[CCDCOLS];    /* data values from short-exposure */
    int16 med_data[CCDCOLS];      /* data values from short-exposure */
    int16 long_data[CCDCOLS];     /* data values from long-exposure */
    FILE *tom_fp;         /* input file stream */
    FILE *short_fp;       /* output FITS file for short-exposure data */
    FILE *med_fp;         /* output FITS file for medium-exposure data */
    FILE *long_fp;        /* output FITS file for long-exposure data */

    if (argc != 5) {
        fprintf(stderr, "usage: tom2fits tomfile shortfits medfits longfits\n");
        exit(-1);
    }

    if ((tom_fp = fopen(argv[1], "r")) == NULL) {
        fprintf(stderr, "can't open file %s\n", argv[1]);
        exit(-1);
    }

    if ((short_fp = fopen(argv[2], "w+")) == NULL) {
        fprintf(stderr, "can't create file %s\n", argv[2]);
        exit(-1);
    }
    
    if ((med_fp = fopen(argv[3], "w+")) == NULL) {
        fprintf(stderr, "can't create file %s\n", argv[2]);
        exit(-1);
    }
    
    if ((long_fp = fopen(argv[4], "w+")) == NULL) {
        fprintf(stderr, "can't create file %s\n", argv[2]);
        exit(-1);
    }

    nrow = find_rows(tom_fp);
    
    create_header(short_fp, nrow, CCDCOLS);
    create_header(med_fp, nrow, CCDCOLS);
    create_header(long_fp, nrow, CCDCOLS);

    for (i = 0; i < nrow; i++) {
        if (read_chunk(tom_fp, short_data, med_data, long_data, header) != 0) {
            fprintf(stderr, "error reading data at row %d", i);
            die(err_msg);
        }
        write_data(short_fp, short_data);
        write_data(med_fp, med_data);
        write_data(long_fp, long_data);

        /* if this is the first or last line, add a comment with header info */
        if (i == 0) {
            sprintf(comment, "first line starts %s", header);
            add_comment(short_fp, comment);
            add_comment(med_fp, comment);
            add_comment(long_fp, comment);
        }
        if (i == nrow -1) {
            sprintf(comment, "last  line starts %s", header);
            add_comment(short_fp, comment);
            add_comment(med_fp, comment);
            add_comment(long_fp, comment);
        }
    }

    /* finally, pad files to be integral multiple of FITSMULT bytes */
    pad_file(short_fp);
    pad_file(med_fp);
    pad_file(long_fp);
    

    fclose(tom_fp);
    fclose(short_fp);
    fclose(med_fp);
    fclose(long_fp);

    exit(0);
}
