/**************************************************************************
 * File: dissolve.c             DISSOLVE.C
 * Programmer: Justin Miller Cornell id# 356452
 * Project: Dissolve Detection
 *
 * Purpose: 
 * Limitations: 
 * This File: 
 *   -
 *   -
 * Last Sanity Check: never.
 *
 * ToDo: Make more stable.  Fix interface. 
 * 
 * Compile Time directives:
 *  - MULTIPROC - the multiple process version, uses shared memory to buffer
 *       results.
 *  - USING_GDB - turns off multiple process mode.
 *
 * See Also: 
 *
 * Copyright (c) 1995 Cornell University.  All Rights Reserved.
 *************************************************************************/
static char rcsid[] = "@(#)$Header: /usr/u/jmiller/prog/ImDiss/src/RCS/dissolvem.c,v 1.2 1995/07/11 00:11:06 jmiller Exp jmiller $";
char versionid[] = "Release Version 1.0.1.  Release date : 951112.";

#include "dissolvem.h"


/* GLOBAL VARIABLES*/
/* NONVOLATILE*/
int    dispX        = HORZ_CORR;
int    dispY        = VERT_CORR;
int    dilateDist   = DILATE_DEFAULT;
double sigma        = SIGMA_DEFAULT;
int    threshold    = THRESHOLD_DEFAULT;
int    minThreshold = DEFAULT_MINTHRESH;
int    maxThreshold = DEFAULT_MAXTHRESH;
double desiredP     = DEFAULT_DESIREDP;
unsigned char verbal = 0;

int edgeDetectMode = 0;
char *edgeDirName = (char *)NULL;
int edgeOutput = 0;
int threshMode = 0;
int threshModeCval = 0;
int threshModeCnum = 0;
comp *threshModeCcomp = (comp *)NULL;
int quickMode = 0;
int quickModeRadius = 1;
float quickModeCutoff;
int inOut = 0;



/* VOLATILE*/
_Frame _prevF;
Frame prevF = &_prevF;



/* Variables needed for constant run-time allocated memory*/
BinaryImage modelDglob = (BinaryImage)NULL;
BinaryImage imageDglob = (BinaryImage)NULL;
BinaryImage tempimglob = (BinaryImage)NULL;
double *forwardglob = (double *)NULL;
double *reverseglob = (double *)NULL;
LongImage censusVals;
DoubleImage hausdVals;
double **g_forward2d = (double **)NULL;
double **g_reverse2d = (double **)NULL;
double **g_forwardRef = (double **)NULL;
double **g_reverseRef = (double **)NULL;
int maxXglob=0;
int maxYglob=0;

/* PROCEDURES*/
#define saveEdges(i, n, t) saveEdges_((void *)(i),((i)->header),(n),(t))
void saveEdges_(void *edges, ImageHeader *header, char *outname, char* tag);
void find_second_peak(LongImage vals, int dispX, int dispY, comp *curr);

void
diss_next_frame(GrayImage gimage, comp *compPtr, char* newname){
  BinaryImage tempIM, forwIM, revIM;
  _Frame _newF,*newF;
  int x, y, dir, i;
  int rX = 0;
  int rY = 0;
  int newFnpts;
  
  newF = &_newF; newF->pgm = gimage;

  /* edge detect new image, THRESHOLDS only if threshMode == 0 */
  edgeDetect(newF); saveEdges(newF->edges,newname,".d");
  /* calculate the new thresholded images for both frames*/
  edgeThresher(compPtr, prevF, newF); saveEdges(newF->edges,newname,".e");

  /* dilate both images*/
  uchar_zero(modelDglob); uchar_zero(imageDglob); uchar_zero(tempimglob);
  manhattan_dilate_wstor(newF->edges, &modelDglob, &tempimglob, dilateDist);
  uchar_zero(tempimglob);
  manhattan_dilate_wstor(prevF->edges, &imageDglob, &tempimglob, dilateDist);
  saveEdges(modelDglob,newname,".ed");

  for(i= 0; i < (2*dispX+1)*(2*dispY+1); i++)
      forwardglob[i] = reverseglob[i] = -2.0;
 
  _hausd_2d_wstor(newF->edges, prevF->edges, modelDglob, imageDglob, 
		 dispX, dispY, forwardglob, reverseglob, 
		  quickModeCutoff, quickMode, &newFnpts);

  
  /* hausd_NEWdecide writes most statistics to the SM*/
  hausd_2d_NEWdecide(compPtr,forwardglob,reverseglob,dispX,dispY,
		     &x, &y,&dir);
  compPtr->percent = (float)newFnpts/(float)((maxXglob-2*dispX)*
					     (maxYglob-2*dispY));



  /* only generate the second pass if we need the edge images, 
     or we are not in quick mode or if we fail the quick cutoff.*/
  if(!(quickMode) || (compPtr->frac > quickModeCutoff) ||
     edgeDirName != (char *)NULL){
    if(dir > 0 || inOut){
      forwIM = tempIM = 
	  edgeDiff(newF->edges, prevF->edges, 
		   modelDglob, imageDglob, dispX, dispY, x, y);
      saveEdges(tempIM,newname,".fa"); 
      if(dir > 0) saveEdges(tempIM,newname,".f");
    }
    if(dir < 0 || inOut){
      revIM = tempIM = 
	  edgeDiff(prevF->edges, newF->edges, imageDglob, modelDglob, 
		   dispX, dispY, -x, -y);
      saveEdges(tempIM,newname,".fb");
      if(dir < 0) saveEdges(tempIM,newname,".f");
    }
    pbm_percentHalf(compPtr, tempIM);

    if(inOut){
      imFree(forwIM);
      imFree(revIM);
    }
    else      
	imFree(tempIM);
  }
#ifndef NO_TWOPASS
  else{
    compPtr->max_forward = 0.0;
    compPtr->max_reverse = 0.0;
  }
#endif

  /* Clean up*/
  killFrame(prevF);
  *prevF = *newF;
  return;
}

/* void edgeDetect(Frame newF)
 *
 * Canny edge detect the pgm image.  The image will be
 *   thresholded if the threshold mode is 0.  Initializes newF.
 *
 * Input: grayimage, and pointer to a _Frame structure.
 * Output: (none) modifies newF.
 * Debug Output: (none).
 * Global Variables Usage: threshMode, threshold, sigma.
 */
void edgeDetect(Frame newF){
  FloatImage temp;

  temp = boxGaussian(newF->pgm, sigma, NUM_BOXS);
  newF->threshold = 0;
  newF->q = ((edgeDetectMode == 1) ? laplacianEdge(temp) : 
      cannyEdge(temp, 0.0));
  newF->edges = newF->q->peaks;
  imFree(temp);

#ifndef NO_CENSUS
  if(useCensus){
    newF->census = census_transform(newF->pgm);
  }
#endif
  return;
}



/* double edgeThreshFirst(Frame newF)
 *
 * Calculates the threshold for the first image.
 *
 * Input: pointer to a _Frame structure.
 * Output: (none) modifies newF.
 * Debug Output: (none).
 * Global Variables Usage: threshMode, threshold.
 */
double edgeThreshFirst(Frame newF){
  int t;
  double r = 0.0;

  t = newF->optThreshold = calcThreshold(newF->q, (double)desiredP);

  switch(threshMode) {
  case 0 : {
    /* Already thresholded during the edge detection*/
    /*NULL STATEMENT*/
    thresholdFrame(newF,threshold);

    break;
  }
  case 1 : {
    thresholdFrame(newF, t);
    break;
  }
  case 2 : {
    thresholdFrameNew(newF, t);
    break;
  }
  case 3 : {
    thresholdFrameNew(newF, threshModeCval);
    break;
  }
  }
  return(r);
}



/*   double edgeThresher(comp *out, Frame prevF, Frame newF)
 *
 * Calculates the threshold for the previous and next frames.  Also
 *   updates the comp structure for the comparison.
 *
 * Input: pointer to a _Frame structure.
 * Output: (none) modifies newF.
 * Debug Output: (none).
 * Global Variables Usage: threshMode, threshold.
 * Warning: when the image is originally edge detected the threshold
 *  should have been 0.
 */
double edgeThresher(comp *out, Frame prevF, Frame newF){
  int t;
  int i;
  int cthresh = 0;
  double r = 0.0;
  
  t = out->bestthresh = newF->optThreshold = calcThreshold(newF->q,(double)desiredP);

  switch(threshMode) {
  case 0 : {
    /* Already thresholded during the edge detection*/
    /*NULL STATEMENT*/
    thresholdFrame(newF,threshold);
    out->threshold = threshold;
    break;
  }
  case 1 : {
    thresholdFrame(newF, t);
    out->threshold = t;
    break;
  }
  case 2 : {
    if(prevF->edges != prevF->q->peaks){
      imFree(prevF->edges);
    }
    prevF->edges = prevF->q->peaks;

    thresholdFrame(prevF, t);
    thresholdFrameNew(newF,t);
    out->threshold = t;
    break;
  }
  case 3 : {
    /* first we decide whether the threshold should change*/
    if(threshModeCcomp == (comp *)NULL){
      threshModeCcomp = out;
    }
    else if((int)(out-threshModeCcomp) > 4) {
      for(i = 1, cthresh=0; i <= 4 && (out-i)->frac <= 0.05 &&
	  (out-i)->bestthresh >= 36; i++)
	  cthresh += (int)(out-i)->bestthresh;
      if(i > 4){
	threshModeCval = cthresh/4;
	threshModeCnum++;
	if(prevF->edges != prevF->q->peaks){
	  imFree(prevF->edges);
	}
	prevF->edges = prevF->q->peaks;
	thresholdFrame(prevF, t);
      }
    }
    cthresh = threshModeCval;
    thresholdFrameNew(newF, cthresh);
    out->threshold = cthresh;
    break;
  }
  }
  return(r);
}
  

/* int calcThreshold(Quad q)
 *
 * Calculates the threshold necessary to obtain a desired percentage of edge
 *   pixels.  Histograms the sgmagnitude of the pixels.  
 *
 * Input: a Quad structure pointer.
 * Output: (none) modifies Quad.
 * Debug Output: (none).
 * Global Variables Usage: maxThreshold, desiredP.
 * Warning: when the image is originally edge detected the threshold
 *  should have been 0.
 */
int calcThreshold(Quad q, double desiredP){
  BinaryImage edges;
  FloatImage sgmag;
  int x,y, maxX, maxY;
  int *hist, overMax, temp, goal;
  int maxThreshSqr;
  
  edges = q->peaks;
  sgmag = q->sgMagnitude;
  maxThreshSqr = maxThreshold * maxThreshold;
  
  /* CALCULATE HISTOGRAM*/
  /* size is [0:1:maxThresh^2, over]*/
  hist = (int *)malloc(sizeof(int) * (maxThreshSqr + 2));  
  overMax = 0;
  for(temp = 0, x = maxThreshSqr+1; x >= 0; x--){
    hist[x] = 0;
  }
  maxX = imGetWidth(edges)-1;
  maxY = imGetHeight(edges)-1;
  for(y = 0; y < maxY; y++){
    for(x = 0; x < maxX; x++){
      if(imRef(edges,x,y)){
	temp = (int)(imRef(sgmag,x,y));
	if(temp > maxThreshSqr)
	    overMax++;
	else
	    (hist[temp])++;
      }
    }
  }
  hist[maxThreshSqr+1] = overMax;
  
  /* ANALYZE HISTOGRAM - find best threshold*/
  maxX++;maxY++;  /* this adjustment is necessary to account for the 
		     size of the deriv image versus original.*/
  goal = (int)(((double)(maxX * maxY)) * desiredP);
  for(temp = 0, x = maxThreshSqr+1; temp < goal && x >= 0; x--){
    temp += hist[x];
  }
  free(hist);
  return(x);
}


void killFrame(Frame f){
  
  if(f->q->peaks != f->edges){
    imFree(f->edges);
  }
  killQuad(f->q);
#ifndef NO_CENSUS
  if(useCensus){
    imFree(f->census);
  }
#endif
}




/* void saveEdges(BinaryImage edges, char *outname, char* tag)
 *
 * Will save a binaryimage to a file in edgedirectory/outname(concat)tag.
 *
 * Input: binary image, filename, and extension.
 * Output: (none) disk output.
 * Debug Output: (none).
 * Global Variables Usage: edgeDirName.
 * Warning: when the image is originally edge detected the threshold
 *  should have been 0.
 */    
void saveEdges_(void *edges, ImageHeader *header, char *outname, char* tag){
  char outFname[512];
  
  if(edgeDirName != (char *)NULL){
    strcpy(outFname, edgeDirName);
    strcat(outFname, "/");
    strcat(outFname, findLast(outname));
    strcat(outFname, tag);
    im_Save_(edges, header, outFname);
  }
}


#define SQRDIST(X,Y) ((X)*(X) + (Y)*(Y))
#define SQRDISTORIG(X,Y,W,Z) ((X-W)*(X-W) + (Y-Z)*(Y-Z))

void 
hausd_2d_NEWdecide(comp *out, double *forward, double *reverse,
		   int rangeX, int rangeY, 
		   int *rX, int *rY, int *direction){
  int dispX, dispY;
  double distF, distR;
  int bestX = 0;
  int bestY = 0;
  float maxForward   = -0.1;
  float maxReverse   = -0.1;
  float bestForward  = -0.1;
  float bestReverse  = -0.1;
  float bestFraction = -0.1;
  int update;

  for(dispY = 0; dispY < 2*rangeY+1; dispY++){
    for(dispX = 0; dispX < 2*rangeX+1; dispX++){      
      distF = *(forward++);
      distR = *(reverse++);

      if(((distR > 1.0) || (distF > 1.0)) ||
	 ((distR < 0.0) || (distF < 0.0))){
	continue;
      }
      update = 0;
      maxForward = MAX(maxForward,distF);
      maxReverse = MAX(maxReverse,distR);

      if(MIN(distF, distR) >= bestFraction){
	if(MIN(distF, distR) == bestFraction){
	  /* Then we have a tie*/
	  if((SQRDISTORIG(bestX,bestY,rangeX,rangeY) > 
	      SQRDISTORIG(dispX,dispY,rangeX,rangeY)))
	      update = 1;
	}
	else
	  update = 1;
	
	if(update){
	  bestX = dispX;
	  bestY = dispY;
	  bestFraction = MIN(distF, distR);
	  bestForward = distF;
	  bestReverse = distR;
	  if(distF <= distR)
	      *direction = 1;
	  else
	      *direction = -1;
	}
      }
    }
  }
    
  out->x = *rX = bestX - rangeX;
  out->y = *rY = bestY - rangeY;
  out->frac = 1.0 - bestFraction;
  out->forward = 1.0 - bestForward;
  out->reverse = 1.0 - bestReverse;
  out->max_forward = 1.0 - maxForward;
  out->max_reverse = 1.0 - maxReverse;
 
  return;
}



/* void pbm_percentHalf(comp *curr, BinaryImage inim)
 *
 * Counts the ratio of on pixels in the left half and the top half of the
 *   image versus the total for the whole image.
 *
 * Input: pointer to a comparison structure, and a binary image.
 * Output: modifies curr.
 * Debug Output: (none).
 * Global Variables Usage: none.
 */    
void pbm_percentHalf(comp *curr, BinaryImage inim){
  unsigned int TR,TL,BR,BL,temp;
  int x,y, maxX, maxY;

  TR=TL=BR=BL=0;
  maxX = (unsigned int)imGetWidth(inim);
  maxY = (unsigned int)imGetHeight(inim);

  for(y = 0; y < maxY/2; y++){
    for(x = 0; x < maxX/2; x++){
      TL+= imRef(inim, x, y);
    }
    for(; x < maxX; x++){
      TR += imRef(inim, x, y);      
    }
  }
  for(; y < maxY; y++){
    for(x = 0; x < maxX/2; x++){
      BL += imRef(inim, x, y);
    }
    for(; x < maxX; x++){
      BR += imRef(inim, x, y);      
    }
  }
  temp = (TL+TR+BL+BR);
  if(temp != 0){
    curr->histVert =(float)(TL + TR) / (float)temp;
    curr->histHorz =(float)(TL + BL) / (float)temp;
  }
  else
      curr->histVert = curr->histHorz = -0.1;
  
  return;
}

char *diss_version(void){
  return(versionid);
}

void diss_storage_init(int dx, int dy, int width, int height){
  int j; 
  
  dispX = dx;
  dispY = dy;
  maxXglob=width;
  maxYglob=height;
  forwardglob=(double *)malloc(sizeof(double)*(2*dispX+1)*(2*dispY+1));
  reverseglob=(double *)malloc(sizeof(double)*(2*dispX+1)*(2*dispY+1));
#ifndef NO_CENSUS
  censusVals =(LongImage)imNew(IMAGE_LONG,(unsigned)(2*dispX+1),(unsigned)(2*dispY+1));
#endif
  hausdVals =(DoubleImage)imNew(IMAGE_DOUBLE,(unsigned)(2*dispX+1),(unsigned)(2*dispY+1));
  g_forward2d=(double **)malloc(sizeof(double *)*(2*dispY+1));
  g_reverse2d=(double **)malloc(sizeof(double *)*(2*dispY+1));
  g_forwardRef=g_forward2d+dispY;
  g_reverseRef=g_reverse2d+dispX;
  for(j=0;j<(2*dispY+1);j++){
    g_forward2d[j] = forwardglob+(2*dispX+1)*j+dispX;
    g_reverse2d[j] = reverseglob+(2*dispX+1)*j+dispX;
  }
  modelDglob=(BinaryImage)imNew(IMAGE_BINARY,
				(unsigned)maxXglob,(unsigned)maxYglob);
  imageDglob=(BinaryImage)imNew(IMAGE_BINARY,
				(unsigned)maxXglob,(unsigned)maxYglob);
  tempimglob=(BinaryImage)imNew(IMAGE_BINARY,
				(unsigned)maxXglob,(unsigned)maxYglob);
  return;
}

void diss_storage_free(void){
  free(forwardglob);
  free(reverseglob);
#ifndef NO_CENSUS
  imFree(censusVals);
#endif
  imFree(hausdVals);
  imFree(modelDglob);
  imFree(imageDglob);
  imFree(tempimglob);
  return;
}

void diss_first_frame(GrayImage gimage){

  prevF->pgm = gimage;
  edgeDetect(prevF);
  edgeThreshFirst(prevF);

  return;
}
