// FlowCalcError - calc error between 2 flows
// consider the vectors as spatiotemporal u,v,1, normalize their length,
// then angle between normalized lengths is the error quantity.
// 
// reads the header from experiment.flow, so if one of the files
// is valid only in a window put that one first.
// Put mask in special u-values in the second file
// (arbitrary, but this is really for the yosemite benchmark)
//
// -win corresponding to the default written by Flow is 32,283,25,225
// 80 is the below the cloudline in the 316x252 yos
// so no-cloud win is -win 5,310,80,246, but wrong because saveFlow
// only saves within the original window.  So, instead, 
//    -win 32,283,80,225



import java.io.*;
import java.util.ArrayList;

import GR.*;
import zlib.*;
import gnu.*;

public class FlowCalcError
{
  static final float	NULLVAL1 = 100.0f;
  static int verbose = 0;

  public static void calcError(String file1, String file2,
			       boolean flip, float umask, float[] meanvar, 
			       boolean dodiff, int x1,int x2, int y1, int y2)
    throws IOException
  {
    int[] header = new int[6];
    FlowUtil.readBFlowHeader(file1, header);

    float[][][] uv1 = FlowUtil.loadBFlow(file1);
    float[][][] uv2 = FlowUtil.loadBFlow(file2);
    int yres = uv1[0].length;
    int xres = uv1[0][0].length;
    zliberror.assert(yres == uv2[0].length);
    zliberror.assert(xres == uv2[0][0].length);
    if (verbose > 0)  System.out.println("flow resolution: " + xres+" "+yres);

    if (flip) {
      for( int iy=0; iy < yres; iy++ ) {
	for( int ix=0; ix < xres; ix++ ) {
	  if (uv1[0][iy][ix]==NULLVAL1) continue;
	  if (uv1[1][iy][ix]==NULLVAL1) continue;
	  //if (uv2[0] == umask) continue;

	  uv1[0][iy][ix] = -uv1[0][iy][ix];
	  uv1[1][iy][ix] = -uv1[1][iy][ix];
	}
      }
    }

    gr diff = null;
    if (dodiff)
      diff = new grShort("diff", xres, yres);

    zliberror.assert(xres == header[0]);
    zliberror.assert(yres == header[1]);
    int xwin = header[2];
    int ywin = header[3];
    int xoff = header[4];
    int yoff = header[5];

    if (x1 >= 0) {
      xoff = x1;
      yoff = y1;
      xwin = 1 + x2 - x1;
      ywin = 1 + y2 - y1;
      zliberror.assert((xoff+xwin) < xres);
      zliberror.assert((yoff+ywin) < yres);
      System.out.println("new win="+xwin+","+ywin+" off="+xoff+","+yoff);
    }

    float[][] ua = uv1[0];
    float[][] va = uv1[1];
    float[][] ub = uv2[0];
    float[][] vb = uv2[1];

    float[] v1 = new float[3];
    float[] v2 = new float[3];

    int count = 0;
    int mcount = 0;	// count the number of pixels not masked 
    double sum = 0.;
    float maxd = 0.f;
    for( int iy=0; iy < ywin; iy++ ) {
      int iyoff = iy+yoff;
      for( int ix=0; ix < xwin; ix++ ) {
	int ixoff = ix+xoff;
	v1[0] = ua[iyoff][ixoff];
	v1[1] = va[iyoff][ixoff];
	v1[2] = 1.f;

	v2[0] = ub[iyoff][ixoff];
	v2[1] = vb[iyoff][ixoff];
	v2[2] = 1.f;

	if (v2[0] == umask) continue;	mcount++;

	if ((v1[0]==NULLVAL1) && (v1[1]==NULLVAL1)) continue;
	if ((v2[0]==NULLVAL1) && (v2[1]==NULLVAL1)) continue;

	float d = dot(v1, v2);
	if (d > maxd) maxd = d;
	sum += d;
	count++;

	if (verbose > 0) {
	  System.out.println(count +" "+ d +" "+ sum);
	  //if (d == Float.NaN)
	    System.out.println("v1: "+v1[0]+" "+v1[1]+"  "+
			       "v2: "+v2[0]+" "+v2[1]);
	}
      }
    }

    float density = count / (float)(mcount);  // does not include barron window
    float mean = (float)(sum / (float)count);
    count = 0;
    sum = 0.;
    for( int iy=0; iy < ywin; iy++ ) {
      int iyoff = iy+yoff;
      for( int ix=0; ix < xwin; ix++ ) {
	int ixoff = ix+xoff;
	v1[0] = ua[iyoff][ixoff];
	v1[1] = va[iyoff][ixoff];
	v1[2] = 1.f;

	v2[0] = ub[iyoff][ixoff];
	v2[1] = vb[iyoff][ixoff];
	v2[2] = 1.f;

	if ((v1[0]==NULLVAL1) && (v1[1]==NULLVAL1)) continue;
	if ((v2[0]==NULLVAL1) && (v2[1]==NULLVAL1)) continue;
	if (v2[0] == umask) continue;

	float err = dot(v1, v2);

	if (diff != null)
	  diff.wr(ix, iy, (short)(gr.DTMAX * (err/maxd)));

	err = err - mean;
	sum += (err*err);
	count++;
      }
    }

    float var = (float)(sum / count);

    meanvar[0] = mean;
    meanvar[1] = (float)Math.sqrt(var);
    meanvar[2] = density;

    if (diff != null) grUtil.saveByExt(diff, "calcerrordiff.ppm");
  } //calcError

  private final static float dot(float[] v1, float[] v2)
  {
    zliberror.assert(v1[0] < 100.f  && v1[1] < 100.f);
    zliberror.assert(v2[0] < 100.f  && v2[1] < 100.f);
    float norm1 = (float)Math.sqrt(v1[0]*v1[0] + v1[1]*v1[1] + 1.);
    float norm2 = (float)Math.sqrt(v2[0]*v2[0] + v2[1]*v2[1] + 1.);

    float dot = (v1[0]*v2[0] + v1[1]*v2[1] + 1.f) / (norm1*norm2);

    if ((dot > 1.f) && (dot < 1.01f)) dot = 1.f;  // roundoff
    zliberror.assert(dot > -1.01f && dot <= 1.f);
    return (float)(Math.abs(Math.acos(dot) * (180./Math.PI)));
  } //dot

  //----------------------------------------------------------------

  static void usage(String errmsg)
  {
    if (errmsg != null)  System.err.println(errmsg);
    System.err.println("FlowCalcError experiment.flow groundtruth.flow [-flip]");
    System.err.println("-flip flips the flow of the experiment flow");
    System.err.println("reads the header from experiment.flow,");
    System.err.println("error is calculated over this window.");
    System.exit(1);
  }

  public static void main(String[] cmdline)
  {
    if (cmdline.length < 2) usage(null);
    try {

      Args args = new Args(cmdline);
      if (args.parse("+:str:experiment.flow:"+
		     "+:str:groundtruth.flow:"+
		     "-flip:flg:flip flowfield direction:"+
		     "-win:str:user-specified window:"+
		     "-diff:flg:write diff.pic:"+
		     "-umask:flt:ignore this u value:") == null)
	usage(null);

      String path1 = args.getRequiredString(0);
      String path2 = args.getRequiredString(1);
      boolean flip = false;
      Boolean oflip = args.getFlag("-flip");
      if (oflip != null) flip = oflip.booleanValue();

      boolean diff = false;
      Boolean odiff = args.getFlag("-diff");
      if (odiff != null) diff = odiff.booleanValue();

      String winstr = args.getOptionalString("-win");
      int x1=-1, x2=-1, y1=-1, y2=-1;
      if (winstr != null) {
	ArrayList l = zlib.stringSplit(winstr, ",");
	x1 = Integer.parseInt((String)l.get(0));
	x2 = Integer.parseInt((String)l.get(1));
	y1 = Integer.parseInt((String)l.get(2));
	y2 = Integer.parseInt((String)l.get(3));
	System.out.println("win " + x1+".."+x2 + ", " + y1+".."+y2);
      }

      // Ignore if the u component of flow has exactly this value.
      // This is mostly for the yosemite benchmark, where the
      // sky flow is indeterminate but is reported to have a +2.0 u flow,
      // but can use this as a general flow mask
      float umask = Float.POSITIVE_INFINITY;
      Float uMask = args.getOptionalFloat("-umask");
      if (uMask != null) {
	umask = uMask.floatValue();
	System.out.println("umask = " + umask);
      }

      float[] meanvar = new float[3];
      calcError(path1, path2, flip, umask,  meanvar, diff,
		x1,x2, y1,y2);

      System.out.println("mean err="+meanvar[0]+", stdev="+meanvar[1]);
      System.out.println("density="+meanvar[2]);
      System.exit(0);
    }
    catch(Exception x) { zliberror.die(x); }
  }
} //FlowCalcError
