/* $Id: vcr.c,v 1.1 89/12/07 16:17:45 nesheim Rel-5-2 $ */
/*****************************************************************************
* 	  Copyright (c) 1988 Thinking Machines Corporation, Inc.,	     *
*		of Cambridge, Mass.   All rights reserved.		     *
*									     *
*  This notice is intended as a precaution against inadvertent publication   *
*  and does not constitute an admission or acknowledgement that publication  *
*  has occurred or constitute a waiver of confidentiality.		     *
*									     *
*  Connection Machine software is the proprietary and confidential property  *
*  of Thinking Machines Corporation.					     *
*****************************************************************************/

/*
 * vcr.c
 * Use the MaxVideo framegrabber via the VMEIO file server
 * to bring images into the CM.
 * Perform some elementary processing, and save the
 * interesting images on the DataVault.
 *
 * Bill Nesheim
 * 1989
 */

#include <cm/paris.h>
#include <cm/cmtypes.h>

#include <cm/cmfb.h>
#include <cm/cmsr.h>
#include <cm/display.h>

#include <cm/cm_stat.h>
#include <cm/cm_param.h>
#include <cm/cm_file.h>

#include <stdio.h>
#include <errno.h>


#define STATSINTERVAL 1000

/*
 * Define USE_STREAMING to bring in the data many frames at a time.
 * Causes more skew between acquisition time and display time
 * but less communication with the fileserver program
 */
#undef USE_STREAMING

/*
 * Can be 4 or 1
 * If you use 4, you have to link with the special CM_transpose.c module
 * provided rather than just the standard CMFS library.
 * A transpose of 4 images (32 bits) is the same cost in time
 * as transpose of 1 image (8 bits).
 */
#define NUMIMAGES 4

/*
 * Define this to print CM time info for every frame
 */
/* #define TIMING */

#define MAXSTREAMIMAGES 60

/* Maximum function */
#define MAX(a,b) ((a > b) ? a : b)

void shuffle_data();
void serial_to_parallel_rearrangement();

int HACK=1;	/* Flag to CM_transpose */

int movement_threshold = 64;
int save_threshold = 500;
int threshold = 128;
extern int CMFS_errno;
extern int errno;
extern int CMI_soft_error_print;

main(ac, av)
char **av;
{
    int maxfd;
    int n, i;
    int ufd;
    int dims[2];
    CM_field_id_t last_frame, f2, temp_field, save_buf;
    CM_geometry_id_t two_d_geom;
    CM_vp_set_id_t two_d_vp_set;
    int nproc, bits_per_proc;
    CMFB_buffer_id_t fb_buffer;
    int send_address_len;
    int bits_to_read, flags;
    int frame = 0;
    int dv_fd = -1;
    char *dvfile = NULL;
    int time_lapse_delay = 0;
    int use_framebuffer = 1;
    CM_timeval_t io_time, transpose_time, dv_time;
    double av_io_rt = 0.0, av_trans_rt = 0.0, av_dv_rt = 0.0;
    int global_frame_diff = 0;
    int bits_left, save_buf_size;
    int saved_frame_index = 0;
    char *device, *getenv();
    static int said_things_moved = 0;

    CMI_soft_error_print = 1;

    if (ac > 1)
	dvfile = av[1];

    if (ac > 2)
	time_lapse_delay = atoi(av[2]);

    if (ac > 3)
	use_framebuffer = 0;

    /* Deallocate the old display if it exists */
    if (CMSR_selected_display())
      CMSR_deallocate_display(CMSR_selected_display());

    CM_init();
    CM_set_context();
    nproc = CM_global_count_context();

    /* 
     * Set up a 2d geometry for manipulating 
     * and displaying the data as an image 
     */
    dims[0] = 512;
    dims[1] = 512;
    two_d_geom = CM_create_geometry(dims, 2);
    two_d_vp_set = CM_allocate_vp_set(two_d_geom);
    send_address_len = CM_geometry_send_address_length(two_d_geom);

    /* Allocate fields in the 2d geometry */
    CM_set_vp_set(two_d_vp_set);
    f2 = CM_allocate_heap_field(32);
    last_frame = CM_allocate_heap_field(8);
    temp_field = CM_allocate_heap_field(8);
    CM_u_move_zero_1L(last_frame, 8);
    bits_left = CM_available_memory()/2;
    bits_left = (bits_left / 8) * 8;
    save_buf = CM_allocate_heap_field(bits_left);
    save_buf_size = bits_left;


    /* Open the framegrabber device */
    if ((device = getenv("CMFRAMEGRABBER")) == NULL)
	device = "/dev/max";
    maxfd = CMFS_open(device, 2);
    if (maxfd < 0) {
	CMFS_perror("open framegrabber");
	exit(1);
    }

    /* Open the DataVault file */
    if (dvfile) {
	dv_fd = CMFS_open(dvfile, CMFS_O_WRONLY|CMFS_O_CREAT|CMFS_O_APPEND, 0666);
	if (dv_fd < 0) {
	    CMFS_perror(dvfile);
	    exit(1);
	}
	/*(void) CMFS_ftruncate_file(dv_fd, 8 * 500);	/* 500 frames */
    }

    /* Set up the Disply */
    CMSR_select_display(CMSR_create_display_menu (8, 512, 512));
    CMSR_image_ordering = CMSR_normal;
    
    /* Some special case stuff (kludges) */
    switch (CMSR_display_type()) {
    case CMSR_cmfb_display:
	{
	    int zoom_factor, max_size;

	    /* Zoom in on the region of interest */
	    max_size = MAX(CM_geometry_axis_length (two_d_geom, 0),
			   CM_geometry_axis_length (two_d_geom, 1));

	    zoom_factor =  MAX ( (int) (1024 / max_size) - 1, 0);
	    CMFB_set_zoom (CMSR_cmfb_display_display_id(),
			   zoom_factor, zoom_factor, FALSE);

	    /* Set the color map */
	    initialize_colormap(CMSR_cmfb_display_display_id());
	    break;
	}
    }


#ifdef USE_STREAMING
    /* Set up streaming I/O from the GPIO system */
    if ((flags = CMFS_fcntl(maxfd, CMFS_F_GETFL, 0)) < 0)	{
	CMFS_perror("Cannot get flag info about");
	exit(4);
    }
    if (CMFS_fcntl(maxfd, CMFS_F_SETFL, flags | CMFS_FSTREAMING) < 0)	{
	CMFS_perror("Error setting streaming mode");
	exit(4);
    }
#endif


    bits_to_read = -1;
    while(1) {
	CM_set_context();
	/*
	 * Read in NUMIMAGES frames
	 */

	CM_reset_timer();
	CM_start_timer(0);
#ifdef USE_STREAMING
	if (bits_to_read <= 0) {
	    /*
	     * Do a read_file followed by a streaming_iostat call
	     */

	    if (bits_to_read == 0) {
		if (CMFS_streaming_iostat(maxfd) < 0)	{
		    CMFS_perror("CMFS_streaming_iostat finishing call failed");
		    exit(4);
		}
	    }
	    
	    if (CMFS_read_file_always(maxfd, f2, 
				      NUMIMAGES * 8 * MAXSTREAMIMAGES) < 0){
		CMFS_perror("read /dev/max");
		if (CMFS_errno != EBADF)
		    continue;
		exit(1);
	    }
	    if ((n = CMFS_streaming_iostat(maxfd)) != 
		(NUMIMAGES * 8 * MAXSTREAMIMAGES)) {
		printf("CMFS_streaming_iostat returned %d instead of %d", 
		       n, 8 * MAXSTREAMIMAGES);
		if (n < 0)
		    CMFS_perror("CMFS_streaming_iostat failed");
		if (CMFS_errno != EBADF)
		    continue;
		exit(1);
	    }
	    bits_to_read = NUMIMAGES * 8 * MAXSTREAMIMAGES;
	} 

	if (CMFS_partial_read_file_always(maxfd, f2, NUMIMAGES * 8) < 0) {
	    CMFS_perror("CMFS_partial_read failed");
	    if (CMFS_errno != EBADF)
		continue;
	    exit(5);
	}
	bits_to_read -= (NUMIMAGES * 8);
#else USE_STREAMING
	if (CMFS_read_file_always(maxfd, f2, (NUMIMAGES * 8)) < 0) {
	    CMFS_perror("read failed");
	    if (CMFS_errno != EBADF)
		continue;
	    exit(1);
	}
#endif USE_STREAMING

	/*
	 * Transpose to NEWS ordering
	 */
	io_time = *(CM_stop_timer(0));
	CM_reset_timer();
	CM_start_timer(0);
	CMFS_transpose_always(f2, 8*NUMIMAGES, CMFS_IO_READ_FROM_SERIAL_DATA,
			      serial_to_parallel_rearrangement);
	transpose_time = *(CM_stop_timer(0));

	/*
	 * Image processing phase.
	 * Look for movement.
	 * We compare this frame with the last frame
	 * and keep a running average of the difference
	 */
	for (i = 0; i < NUMIMAGES; i++) {
	    int sum;
	    CM_field_id_t this_frame = 
		CM_add_offset_to_field_id(f2, (i*8));

	    /* 
	     * Take the absolute value of the difference
	     * between the current frame and the last frame
	     */
	    CM_s_subtract_3_1L(temp_field, 
			       this_frame,
			       last_frame, 8);
	    CM_s_abs_1_1L(temp_field, 8);
	    CM_s_gt_constant_1L(temp_field, movement_threshold, 8);
	    CM_logand_context_with_test();
	    sum = CM_global_u_add_1L(temp_field, 8);
	    CM_set_context();
	    
	    global_frame_diff = global_frame_diff - 
		(global_frame_diff + sum) / 2;
	    if (global_frame_diff < 0)
		global_frame_diff = -global_frame_diff;

	    /*
	     * Save the previous frame for comparison next time around.
	     */
	    CM_u_move_always_1L(last_frame, this_frame, 8);

	    if (global_frame_diff >= save_threshold) {
		/* 
		 * Something moved, save this frame
		 */
		CM_u_move_always_1L(CM_add_offset_to_field_id(save_buf, 
							      (8 * saved_frame_index++)),
				    this_frame, 8);
		if (!said_things_moved) {
		    int clock;
		    
		    time (&clock);
		    printf("Movement at %s", ctime(&clock));
		    said_things_moved++;
		}
	    } else {
		if (said_things_moved) {
		    int clock;
		    time( &clock);
		    printf("Movement stopped at %s", ctime(&clock));
		    said_things_moved = 0;
		    if (dv_fd > 0 && saved_frame_index > 0) {
			CM_reset_timer();
			CM_start_timer(0); 
			printf("\nWriting %d frames to DV\n", saved_frame_index);
			if (CMFS_write_file_always(dv_fd, 	
						   save_buf,
						   saved_frame_index * 8) < 0)
			    CMFS_perror("write datavault file");
			dv_time = *(CM_stop_timer(0));
			saved_frame_index = 0;
		    }
		}
	    }
	}	

	/*
	 * Flush save buffer to DV when we fill it up
	 */
	if (saved_frame_index >= ((save_buf_size/8) - 3)) {
	    if (dv_fd > 0) {
		CM_reset_timer();
		CM_start_timer(0); 
		printf("\nWriting %d frames to DV\n", saved_frame_index);
		if (CMFS_write_file_always(dv_fd, 	
					   save_buf,
					   saved_frame_index * 8) < 0)
		    CMFS_perror("write datavault file");
		dv_time = *(CM_stop_timer(0));
	    }
	    saved_frame_index = 0;
	}

	/*
	 * Statistics
	 */

	frame += NUMIMAGES;

	av_io_rt += io_time.cmtv_real;
	av_trans_rt += transpose_time.cmtv_real;
	av_dv_rt += dv_time.cmtv_real;
	
	if ((frame % STATSINTERVAL) == 0) {
	    /* print stats */
	    printf("\n\n*** I/O and transpose stats, VP ratio %d ***\n",
		   512*512 / nproc);
	    printf("I/O time %.2f msec/image (%.2f mb/sec) \n",
		   (av_io_rt/STATSINTERVAL) * 1000.0,
		   ((512*512*STATSINTERVAL)/1e6)/ av_io_rt);
	    printf("transpose time %.2f msec/image (%.2f mb/sec)\n",
		   (av_trans_rt/STATSINTERVAL) * 1000.0,
		   ((512*512*STATSINTERVAL)/1e6)/ av_trans_rt);
	    if (dv_fd > 0 && av_dv_rt > 0.0)
		printf("write to dv time %.2f msec/image (%.2f mb/sec) \n",
		       (av_dv_rt/STATSINTERVAL) * 1000.0,
		       ((512*512*STATSINTERVAL)/1e6) / av_dv_rt);
	    printf("I/O + transpose %.2f msec/frame (%.2f frames/sec)\n",
		   (av_io_rt + av_trans_rt) / STATSINTERVAL,
		   STATSINTERVAL /(av_io_rt + av_trans_rt));
	    printf("*********\n");
	    
	    av_io_rt = av_trans_rt = av_dv_rt =  0.0;
	}
	dv_time.cmtv_real = 0;
	    
	if (time_lapse_delay)
	    sleep(time_lapse_delay);

	/*
	 * show the frame
	 */
	if (said_things_moved && ((frame % 8) == 0)) {
	    int bpp = CMSR_display_bits_per_pixel();

	    if (bpp == 1) 
		dither(f2, 8);
	    printf("Completed frame %d  \n", frame);
	    fflush(stdout);
	    CMSR_write_to_display(f2);
	}
    }
    /* NOTREACHED */
    /*CMFS_close(maxfd);*/
}

/*
 * Set up a grayscale colormap for our 8bit data
 */
initialize_colormap(dis)
struct CMFB_display *dis;
{
    unsigned char colormap_array[256];
    int i;

    for(i = 0; i < 256; i++)
	colormap_array[i] = i;

    CMFB_write_color_table(dis, CMFB_red, colormap_array);
    CMFB_write_color_table(dis, CMFB_green, colormap_array);
    CMFB_write_color_table(dis, CMFB_blue, colormap_array);
}


/*
 * serial_to_parallel_rearrangement
 *
 * This routine is called from CMFS_transpose_always to
 * arrange the serial data into the parallel format we need.
 */

int columns = 512;

void
serial_to_parallel_rearrangement(dest_send_address)
CM_field_id_t dest_send_address;
{
    CM_field_id_t x_address;
    CM_field_id_t y_address;
    CM_geometry_id_t geom;

    /* 
     * The VMEIO manages to byte-swap everything, so we have to do this
     * to fix things up. The next rev board won't need this.
     */
    CM_lognot_1_1L(dest_send_address, 2);

    /* 
     * Allocate fields in CM memory to store the x and y coordinates 
     */
    x_address = CM_allocate_stack_field(CM_cube_address_length);
    y_address = CM_allocate_stack_field(CM_cube_address_length);

    /* 
     * Get the geometry from the send address 
     */
    geom = CM_vp_set_geometry(CM_field_vp_set(dest_send_address));

    /* 
     * From the position in the serial file of a data item, which is 
     * given by the send address (dest_send_address) of the processor in
     * which the transpose would ordinarily place the data item,
     * compute the x,y coordinate of the data item.  Since we know
     * the serial file stores the data in row-major order, we know
     * that x=send_address/columns and  y=mod(send_address, columns) 
     */
    CM_u_move_1L(x_address, dest_send_address, CM_cube_address_length);
    CM_u_truncate_constant_2_1L(x_address, columns, CM_cube_address_length);
    CM_u_move_1L(y_address, dest_send_address, CM_cube_address_length);
    CM_u_mod_constant_2_1L(y_address, columns, CM_cube_address_length);

    /* 
     * Clear dest_send_address, in preparation for supplying 
     * CMFS_transpose_always with the newly computed dest_send_address 
     */
    CM_u_move_zero_1L(dest_send_address, CM_cube_address_length);

    /* 
     * Compute the send-address
     * of the processor in the 2-D VP set that corresponds to the 
     * x,y coordinate.  Place this send-address in dest_send_address. 
     * With this accomplished, CMFS_transpose_always will arrange 
     * the data in 2-D order in the CM. 
     */
    CM_deposit_news_coordinate_1L(geom,
				  dest_send_address, 0,
				  y_address, CM_cube_address_length);
    CM_deposit_news_coordinate_1L(geom,
				  dest_send_address, 1, 
				  x_address, CM_cube_address_length);

    CM_deallocate_stack_through(x_address);
}


void
shuffle_data(dest_send_address)
{
    /* The VMEIO manages to byte-swap everything, so we have to do this
     * to fix things up. 
     */
    CM_lognot_1_1L(dest_send_address, 2);
}




