
/**************************************************************************
 *                                                                        *
 * Copyright (c) 1996 Michael Richmond and Richard Treffers               *
 *                                                                        *
 *                    This software may be copied and distributed for     *
 *                    educational, research and not for profit services   *
 *                    provided that this copyright and statement are      *
 *                    included in all such copies.                        *
 *                                                                        *
 **************************************************************************/


/*	
	shows CCD image on screen

		TV file [span] [zero] [box=d] [l=] [z=] [zoom=] [xo=] [yo=]
		         [hist] [dither] [nofork] [live=] [invert] [falsecolor]

	1/30/91 - added 'floor' to "span" and "zero" expressions, 
	          and included <math.h>   - MWR
	9/92/92 -  replace (*image_vfill) with X_fill -rrt
			  erase - keyword removed
	11/17/92 - remove call to exten()
	11/18/92 - insert image_save , before image_wait to clear garbage -mwr
	8/5/93  - change error message -rrt
	1/10/96  - add 'help' option.  MWR
	7/28/96  - fixed bug in init_lut which caused sf=0.0.  MWR
	8/1/96   - added "live=" option.  MWR
	10/5/96  - added "falsecolor" option.  MWR
	6/14/98  - fixed bug in loop conditions in binned image display.  MWR
*/

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <math.h>
#include "pcvista.h"
#include "pcvistax.h"
#include "pcvimage.h"
#include "fits.h"


#undef  DEBUG               /* will produce >= one line per pixel! */


#ifdef PROTO
	int main(int, char **);
	static void zoom_data( int, int, char *);
	static int find_maximum(void);
	static void histeq(int16 *data,int num,char *buf);
	static void open_histeq(char *,int);
	static void ditherow(int16 *data,int span,int zero, int npix,int zoom,char *buf);
	static void scale_data(int16 *data,int span,int zero, int npix, char *buf);
	static void init_lut(int span, int zero);
#else
	static void zoom_data( );
	static void histeq();
	static void open_histeq();
	static int find_maximum();
	static void ditherow();
	static void scale_data();
	static void init_lut();
#endif

	/* 
	 * we shouldn't have to declare this here, but on SunOS 5.5, we do
	 */
#ifdef SUN
extern int kill(pid_t proc_id, int signal);
#endif

	/* 
	 * we use this to store the "color" value associated with the
	 * each possible pixel value.  It is initialized by function
	 * "init_lut".
	 */
char intensity_lut[65536];

int16 data[NMAX];
char im_buf[NMAX];
extern int image_max_color;
extern int image_zero_pix;

char *progname = "tv";
char *usage = "usage: tv file [span] [zero] [box=] [l=] [z=] [xo=] [yo=] \n        [zoom=] [hist] [dither] [nofork] [invert] [live=] [falsecolor] [help]";	


int
main(argc, argv)
int argc;
char *argv[];
{
	int i, j, x, row, boxno, nrow, ncol, forkflag, invertflag;
	int live, falsecolor;
	pid_t child_pid;
	unsigned int win_id;
	FITS_HANDLE fh;
	static int sr, sc, nr, nc, endrow, endcol, zoom = 1, hist, bin = 1, dither;
	static unsigned int span, x_origin, y_origin, got_span, got_zero;
	static int zero;
	static char *gotit, buf[NBUF];
	double val;
	int temp_zero, temp_span;		

	if (argc == 1) {
		error(-1, usage);
	}
	if (strcmp(argv[1], "help") == 0) {
		printf("%s\n", usage);
		exit(0);
	}

	forkflag = 1;		/* default is to fork */
	invertflag = 0;		/* default is NOT to invert */
	live = 0;		/* default is NOT to have finite lifetime */
	falsecolor = 0;         /* default is to use greyscale, NOT color */

	fh = fits_open(argv[1], "r", &nrow, &ncol);
	nr = nrow;	
	nc = ncol;

	
	for (i = 2; i < argc; i++) {
		if (keyword("help", argv[i])) {
			printf("%s\n", usage);
			exit(0);
		}
		if ((gotit = find("box", argv[i])) != NULL) {
			boxno = (int) (evaluate(gotit) + 0.5);
			if (getbox(boxno, &sr, &sc, &nr, &nc))
				error(-1, "box not found");
			check_box(boxno, nrow, ncol);
			continue;
		}
		if ((gotit = find("l", argv[i])) != NULL) {
			span = (int) floor(evaluate(gotit) + 0.5);
			got_span = 1;
			continue;
		}
		if ((gotit = find("z", argv[i])) != NULL) {
			zero = (int) floor(evaluate(gotit) + 0.5);
			got_zero = 1;
			continue;
		}
		if ((gotit = find("zoom", argv[i])) != NULL) {
			val = evaluate(gotit);
			if (val <= 0.0)
				error(-1, "zoom must be greater than zero");
			if (val > 0.5)
				zoom = (int) (val + SMALL);
			else
				bin = (int) ((1.0/val) + SMALL);
			continue;
		}
		if ((gotit = find("xo", argv[i])) != NULL) {
			x_origin = (int) (evaluate(gotit) + 0.5);
			continue;
		}
		if ((gotit = find("yo", argv[i])) != NULL) {
			y_origin = (int) (evaluate(gotit) + 0.5);
			continue;
		}
		if (keyword("hist", argv[i])) {
			hist = 1;
			open_histeq(argv[1], image_max_color);
			continue;
		}
		if (keyword("dither", argv[i])) {
			dither = 1;
			continue;
		}
		if (keyword("nofork", argv[i])) {
			forkflag = 0;
			continue;
		}
		if (keyword("invert", argv[i])) {
			invertflag = 1;
			continue;
		}
		if ((gotit = find("live", argv[i])) != NULL) {
			live = (int) (evaluate(gotit) + 0.5);
			continue;
		}
		if (keyword("falsecolor", argv[i])) {
			falsecolor = 1;
			continue;
		}
		if (!isdigit(argv[i][0]) && (argv[i][0] != '+') && (argv[i][0] != '-')){
			fprintf(stderr,"tv: unknown code '%s'\n",argv[i]);
			exit(1);
		}
		if (i == 2) {
			span = atoi(argv[i]);
			got_span = 1;
		}
		if (i == 3) {
			zero = atoi(argv[i]);
			got_zero = 1;
		}
	}
	if (dither && hist) 
		error(-1, "DITHER and HIST are mutually exclusive");
	if (live < 0) 
		error(-1, "live option must have positive value");


	if (falsecolor == 0) {
		image_init(TV_GREYSCALE);
	}
	else {
		image_init(TV_FALSECOLOR);
	}


	/* we start up a child process here, and then trap the common
	   signals that could come from the parent, shell process so that
	   if the user types 'Control-C' to kill some other process later on,
	   it won't affect us. Use "kill -9" to get rid of the process if
	   something goes wrong - or just use the CANCEL X-Windows option
	   on the TV window. */

	if (forkflag) {
		if ((child_pid = fork()) != 0) {

			/* this is parent process */
			if (live == 0) {
				exit(0);
			} 
			else {
				/*
				 * we have to kill child after 
				 * specified number of seconds
				 */
				sleep(live);
				kill(child_pid, SIGKILL);
				exit(0);
			}
		}
		else {
			/* this is child process */
			signal(SIGINT, SIG_IGN);
			signal(SIGQUIT, SIG_IGN);
			signal(SIGTERM, SIG_IGN);
		}
	}

	if (invertflag)
		invert_cmap();

	if ((!got_span) && (hist == 0)) {	/* automatically find span and zero */
		autospan(fh, &temp_span, &temp_zero, sr, sc, nr, nc);
		printf( "Autospan=%4d zero=%4d\n", temp_span, temp_zero);
		fflush(stdout);
		if (!got_zero)
			zero = temp_zero;
		if (!got_span)
			span = temp_span;
	}
	win_id = open_window(argv[1], x_origin, y_origin, nr, nc, zoom, bin);
	set_win_id(win_id);
	endcol = sc + nc;
	endrow = sr + nr;

	if (!hist) {
		init_lut(span, zero);
	}

#ifdef DEBUG
printf ("span %d zero %d sc %d nc %d \n", span, zero, sc, nc);
#endif
	for (row = sr, x = 0; row < endrow; row += bin) {
		fits_get_data(fh, row, sc, data, nc);
#ifdef DEBUG
printf ("span %d zero %d sc %d nc %d \n", span, zero, sc, nc);
for (i = 0; i < 5; i++) { printf("%7d ", data[i]); } printf("\n");
#endif
		if (bin > 1) {
			for(i = 0, j = bin; i < nc/bin; i++, j += bin)
				data[i] = data[j];
		}
		if (dither) {
			for (j = 0; j < zoom; j++) {
				ditherow(data, span, zero, nc/bin, zoom, im_buf);
				X_fill(x++, 0, zoom*nc, im_buf);
			}
			continue;
		}
		if (hist)
			histeq(data, nc/bin, im_buf);
		else {
			scale_data(data, span, zero, nc/bin, im_buf);
		}
		if (zoom > 1) {
			zoom_data(zoom, nc, im_buf);
			for (i = 0; i < zoom; i++)
				X_fill(x++, 0, zoom*nc, im_buf);
		}
		else
			X_fill(x++, 0, nc/bin, im_buf);
	}

	strcpy(buf, argv[1]);
	store_window(buf, x_origin, y_origin, zoom, sr, endrow, sc, endcol, 
	               bin, zero, span, dither);
	image_save();
	image_wait();

	exit(0);
}

static void zoom_data(zoomfactor, nc, buf)
int zoomfactor, nc;
char *buf;
{
	register int j;
	char *p, *q;

	p = buf + zoomfactor*nc - 1;
	q = buf + nc - 1;
	for (j = 1; p >= buf; p--, j++) {
		*p = *q;
		if (j == zoomfactor) {
			j = 0;
			q--;
		}
	}
}



/*	
	routines to perform histogram equalization - used by the TV
	command for display of images. needs histogram file file.his
	to have already been created via the HIST command.
*/


static int bpoint[NHIST], nbreak;

static 
void
open_histeq(fname, max_color)
char *fname;
int max_color;
{
	FILE *fhist;
	int i, index;
	long dummy;
	double delta, integral,level;
	char histfile[NBUF], buf[NBUF];

	strcpy(histfile, fname);
	exten(histfile, ".his");
	if ((fhist = fopen(histfile, "r")) == NULL)
		error(-1,"can't open histogram file");

	nbreak = max_color + 1;
	delta = 1.0/(double)(nbreak + 1);	/* determine break point locations */	

	for (i = 0, level = delta; i < nbreak; ) {
		if (fgets(buf, NBUF, fhist) == NULL)
			break;
		sscanf(buf, "%u %ld %lf", &index, &dummy, &integral);
		if (integral > level) {
			bpoint[i] = index;
			i++;
			level = (i + 1)*delta;
		}
	}
	fclose(fhist);
}

static
void
histeq(data, num, image_buf)	/* performs histogram equalization */
int16 *data;
int num;		/* there are faster ways to do this but.... */
char *image_buf;
{
	int i, j, n, d;

	n = nbreak - 1;
	for (i = 0; i < num; i++) {
		d = data[i];
		for (j = 0; (j < n) && (d > bpoint[j]); j++)
			;
		image_buf[i] = (char) j;
	}
}



	/* note that 'image_max_color' is defined for each display in
	   the file 'image.c'. We assume here that it is some good opposite
	   to the color 0. 

	   Modified so that now (image_zero_pix + image_max_color) is used
	   as the 'on' pixel color.  Sigh.  I hate Sun's colormap.  MWR */



#define DITHER_ON    (image_zero_pix + image_max_color)	/* pixel value used for 'on' pixels */
#define DITHER_OFF    image_zero_pix	/* pixel value used for 'off' pixels */

static void 
ditherow(data, span, zero, npix, zoom, buf)
int16 *data;			/* Image row pixels */
int span;			/* span of pixel values to be displayed */
int zero;			/* Low limit to cut off the displayed information */
int npix;			/* Number of pixels in data row */
int zoom;
char *buf;			/* where to write the output data */
{
	int i,col, scale, r, f, out;
	static int first = 0, max;

	if (first == 0) {
		first = 1;
		max = find_maximum();
	}
    scale = max / span;

    for (col = 0, out = 0; col < npix; col++) {
    	f = data[col] - zero;
		for (i = 0; i < zoom; i++, out++) {
	    	if (f > 0) {
   		     	if (f > span)
 					buf[out] = (char) DITHER_ON;
				else {
					if (scale*f > (r=rand()))
						buf[out] = (char) DITHER_ON;
					else
						buf[out] = DITHER_OFF;
				}
			} else
				buf[out] = DITHER_OFF;
		}
	}
}

	/* return the probably maximum value of the 'rand()' function.  It may
	   be either 2^15-1, or 2^31-1. */

#define NUM_TRIES 100
#define MAX_1	  32767
#define MAX_2     2147483647

static int
find_maximum()
{
	int i, max, n;

	max = 0;
	for (i = 0; i < NUM_TRIES; i++) {
		if ((n = rand()) > max)
			max = n;
	}
	if (max <= MAX_1)
		return(MAX_1);
	else
		return(MAX_2);
}


	/* fills the CHAR array buf with scaled data (in preparation for
	   sending them to the display). if the span is less than 
	   the number of displayable colors, then the
	   data are multiplied and not divided by (span/image_max_color) */

	/* I've added a fudge factor ("image_zero_pix") to the calculation,
	   so that the pixel values can vary from 

	        image_zero_pix  -->  image_zero_pix + image_max_color

	   instead of only

	               0        -->      image_max_color

	   In other words, you can set the image to have pixels in a range
	   that does NOT start at zero (which is convenient if a "good" 
	   portion of a colormap does not start at zero...).  -- MWR 9/16/91 */

#define SHIFT_SPAN     8

static void
scale_data(indata, span, offset, count, buf)
int16 *indata;
int span, offset, count;
char *buf;
{
	register int i;
	register int index;
	int cspan;
	register int16 *datap;
	register char *bufp;

	cspan = (image_max_color - image_zero_pix) + 1;
	datap = indata;
	bufp = buf;

	for (i = 0; i < count; i++, bufp++) {
#ifdef DEBUG
		printf("   data = %7d   ", *datap);
#endif
		index = (*datap++) + (long) 32768;
		*bufp = intensity_lut[index];
#ifdef DEBUG
		if (*bufp < 0) {
			index = 256 + *bufp;
		}
		else {
			index = *bufp;
		}
		printf("   buf = %3d\n", index);
#endif
	}

}

	/*
	 * Initialize the color lookup table, for the given 
	 * "span" and "zero".
	 */

static void
init_lut(int span, int zero)
{
	int inten, step, lut_index;
	long pixval;
	int cspan;
	float sf;

	cspan = (image_max_color - image_zero_pix) + 1;
	sf = ((double) span)/(double) cspan;
	lut_index = 0;
#ifdef DEBUG
	printf("sf = %f\n", sf);
	printf("image_zero_pix = %d   zero = %d\n", 
			image_zero_pix, zero);
#endif


	/* 
	 * "i" is going to contain the possible color-intensity values.
	 * For each one, we go back and calculate the maximum pixel value that 
	 * would yield that color-intensity value.  
	 * Then we fill the "intensity_lut[]" table up to that maximum
	 * with values set to "i".
	 *
	 */
	step = 0;
	for (inten = image_zero_pix; inten <= image_max_color; inten++) {

		pixval = zero + sf*step++;
		if ((pixval += 32768) > 65536) {
			break;
		}
		while (lut_index <= pixval) {
#ifdef DEBUG2
			printf("inten = %5d, pixval %7ld lut[%7d] = %5d\n", 
					inten, pixval, lut_index, inten); 
#endif
			intensity_lut[lut_index++] = inten;
		}
	}
	while (lut_index < 65536) {
#ifdef DEBUG2
		printf("inten = %5d, pixval %7ld lut[%7d] = %5d\n", 
				inten, pixval, lut_index, inten); 
#endif
		intensity_lut[lut_index++] = image_max_color;
	}
#ifdef DEBUG
	fflush(stdout);
#endif

}
		
