package yi.iu.processor.color.reg.merge;

import java.awt.Dimension;
import java.util.*;
import yi.stat.hist.*;
import yi.iu.processor.color.*;
import yi.iu.processor.morph.*;
import yi.iu.img.*;
import yi.ds.sum.*;
import yi.util.config.*;

/**
 * OneWayNeighborMerger merges the regions if they are taking the labels as
 * specified.  It is called one way because if a pixel is masked, then it
 * wouldn't be merged to others and others may be merged to it.
 */

public class OneWayNeighborMerger implements NeighborMerger, Configurable {

  public final static int MASKED = 1;

  public boolean DEMO_STEPS = false;

  public double SMALL_REGION_THRESHOLD = 0.005;

  public String[] getParameterNames()
  {
   return new String[] {
       "DEMO_STEPS",
       "SMALL_REGION_THRESHOLD",
   };
  }

  public OneWayNeighborMerger()
  {
   Configuration.configParameters(this);
  }

  /**
   * Constructs the  Partially neighour matrix.  It is called "one way" because
   * if the neighour of a un-masked pixel is masked (not considered), then this
   * relationship will be counted in the label of un-masked pixel, but not in
   * the label of masked pixel.  For example, if label[x][y] = i, mask[x][y] = 0
   * and label[x+1][y] = j, mask[x+1][y] = 1; then matrix[i][j] ++, but
   * matrix[j][i] will remain the same.  The matrix may be not symmetrical.
   * @param labels
   * @param d
   * @param mask
   * @return
   */
  public int[][] getNeighborMatrix(int[] labels, Dimension d, int[] mask) {
    int width = d.width, height = d.height;

    // init the neighour matrix
    int max_label = yi.stat.StatUtil.maximum(labels);
    int[][] neighour_matrix = new int[max_label + 1][max_label + 1];

    // only consider 4-connection neighours
    for (int y=0; y<height-1; y++) {
      for (int x=0; x<width-1; x++) {
        int label = labels[y*width + x];
        // to the right
        int right_label = labels[y*width + x + 1];
        // put into the neighour matrix
        putIntoNeighborMatrix(neighour_matrix, label, right_label, mask[y*width + x], mask[y*width + x + 1]);
        // to the bottom
        int down_label = labels[(y+1)*width + x];
        putIntoNeighborMatrix(neighour_matrix, label, down_label, mask[y*width + x], mask[(y+1)*width + x]);
      }
    }

    return neighour_matrix;
  }

  class TouchingStatistics {
    int[][] neighborMatrix;
    double[][] distanceMatrix;
    public TouchingStatistics(int[][] neighborMatrix, double[][] distanceMatrix) {
      this.neighborMatrix = neighborMatrix;
      this.distanceMatrix = distanceMatrix;
    }
  }

  /**
   * Constructs the partially distance matrix.  It is called "partially" because
   * if the neighour of a un-masked pixel is masked (not considered), then this
   * relationship will be counted in the label of un-masked pixel, but not in
   * the label of masked pixel.  For example, if label[x][y] = i, mask[x][y] = 0
   * and label[x+1][y] = j, mask[x+1][y] = 1; then
   * dist_matrix[i][j] += distance, but dist_matrix[j][i] will remain the same.
   * The matrix may be not symmetrical.
   * @param pixels
   * @param labels
   * @param d
   * @return
   */
  public TouchingStatistics getTouchingStatistics(int[] pixels, int[] labels, Dimension d, int[] mask) {
    int width = d.width, height = d.height;


    // init the neighour matrix
    int max_label = yi.stat.StatUtil.maximum(labels);
    double[][] distance_matrix = new double[max_label + 1][max_label + 1];
    int[][] neighbor_matrix = new int[max_label + 1][max_label + 1];

    // only consider 4-connection neighours
    for (int y = 0; y < height - 1; y++) {
      for (int x = 0; x < width - 1; x++) {
        int ptr = y * width + x;
        int ptr_right = y * width + x + 1;
        int label = labels[ptr];
        int right_label = labels[ptr_right];
        // put into the neighour matrix
        putIntoNeighborMatrix(neighbor_matrix, label, right_label, mask[ptr], mask[ptr_right]);
        putIntoDistanceMatrix(distance_matrix, label, right_label,
                           CIELab.distance(pixels[ptr], pixels[ptr_right]),
                           mask[ptr], mask[ptr_right]);
        // to the bottom
        int ptr_down = (y + 1) * width + x;
        int down_label = labels[ptr_down];
        // put into the neighour matrix
        putIntoNeighborMatrix(neighbor_matrix, label, down_label, mask[ptr], mask[ptr_down]);
        putIntoDistanceMatrix(distance_matrix, label, down_label,
                           CIELab.distance(pixels[ptr], pixels[ptr_down]),
                           mask[ptr], mask[ptr_down]);
      }
    }

    // average the distance
    average_distance_matrix(distance_matrix, neighbor_matrix);
//    yi.util.io.OutputResult.print("dist_matrix", distance_matrix, distance_matrix.length, distance_matrix.length);

    return new TouchingStatistics(neighbor_matrix, distance_matrix);

  }

  private void putIntoNeighborMatrix(int[][] matrix, int label1, int label2, int mask1, int mask2) {
    if (mask1 != 1) matrix[label1][label2] ++;
    if (mask2 != 1) matrix[label2][label1] ++;
  }

  private void putIntoDistanceMatrix(double[][] dist_matrix, int label1, int label2, double dist, int mask1, int mask2) {
    if (mask1 != 1) dist_matrix[label1][label2] += dist;
    if (mask2 != 1) dist_matrix[label2][label1] += dist;
  }

  private void average_distance_matrix(double[][] distance_matrix, int[][] neighbor_matrix) {
    for (int y=0; y<distance_matrix.length; y++) {
      for (int x=0; x<distance_matrix.length; x++) {
        distance_matrix[y][x] /= neighbor_matrix[y][x];
      }
    }
  }

  public void mergeLabels(int[] cluster_map, Dimension d, int from, int to, int[] cc_labels, int[] mask) {
    int width = d.width, height = d.height;

    HashSet cc_labels_to_merge = getCCRegionsToBeMerged(cluster_map, d, from, to, cc_labels, mask);

    for (int i=0; i<cluster_map.length; i++) {
      if (cluster_map[i] != from) continue;
      if (cc_labels_to_merge.contains(new Integer(cc_labels[i]))) cluster_map[i] = to;
    }
  }

  /**
   * Change the label of pixels from "from" to label of "to".
   * @param labels the labels of the pixels
   * @param cc_labels the connected components labels of pixels
   * @param cc_infos the neigboring information
   * @param from the label to change
   * @param to the lable changed to
   * @param from_size_thrsh if the size of a cc is >= this threshold, then it is
   *        ignored.  give -1 to ignore this parameter
   * @param to_size_thrsh if the size of a cc (merged to) is < this threshold,
   *        it is ignored.  give -1 to ignore this parameter
   */
  public void mergeLabels(int[] labels, int[] cc_labels, NeighboringCCInfo[] ncc_infos, int from, int to,
                          int from_size_thrsh, int to_size_thrsh) {
    // cc label -> label map
    int cc_labels_num = ncc_infos.length;
    int[] cc_labels_map = new int[cc_labels_num];
    for (int i=0; i<labels.length; i++) cc_labels_map[cc_labels[i]] = labels[i];

    HashSet merge_from_set = new HashSet();
    for (int i=0; i<ncc_infos.length; i++) {
      // the cc has the correct label
      int label_from = cc_labels_map[i];
      if (label_from != from) continue;
      // the cc satisfy the size threshold
      if (from_size_thrsh >= 0 && ncc_infos[i].size >= from_size_thrsh) continue;

      NeighboringCCInfo dists = ncc_infos[i];
      for (Iterator it = dists.keySet().iterator(); it.hasNext(); ) {
        // the label of the region merged to
        Integer key = (Integer) it.next();
        int merge_to = key.intValue();
        if (cc_labels_map[merge_to] != to) continue;
        if (to_size_thrsh >= 0 && ncc_infos[merge_to].size < to_size_thrsh) continue;
        merge_from_set.add(new Integer(i));
      }
    }

    for (int i=0; i<labels.length; i++) {
      if (merge_from_set.contains(new Integer(cc_labels[i]))) {
        labels[i] = to;
      }
    }
  }

  /**
   * Calculate the size summary.
   * @param sizes store the region_index -> size map
   * @param region_indexes a collection of region index
   * @return the total size of those regions given in the region_indexes
   */
  private int getTotalArea(int[] sizes, Collection region_indexes) {
    int sum = 0;
    for (Iterator i=region_indexes.iterator(); i.hasNext(); ) {
      int index = ((Integer)i.next()).intValue();
      sum += sizes[index];
    }
    return sum;
  }

  public void demoNeighborMatrix(int[] pixels, int[] labels, int[] cc_labels, Dimension d, int[] mask) {
    int width = d.width, height = d.height;

    TouchingStatistics stat = getTouchingStatistics(pixels, labels, d, mask);
    int[][] neighbor_matrix = stat.neighborMatrix;
    double[][] distance_matrix = stat.distanceMatrix;

    ConnectingRegionStat rgn_stat = getConnectingRegionStatistics(labels, d,
        neighbor_matrix, cc_labels, mask);
    int[][] from_count = rgn_stat.from_count;
    int[][] from_area = rgn_stat.from_area;
    int[][] to_count = rgn_stat.to_count;
    int[][] to_area = rgn_stat.to_area;

    int matrix_size = neighbor_matrix.length;

    // only consider 4-connection neighours
    for (int y=0; y<matrix_size; y++) {
      for (int x=0; x<matrix_size; x++) {
        if (y == x) continue;
        if (neighbor_matrix[y][x] != 0) {
          System.out.println("" + y + "," + x + ": " +
                           neighbor_matrix[y][x] + "," +
                           distance_matrix[y][x] + "," +
                           from_count[y][x] + "," +
                           (double)from_area[y][x]/from_count[y][x] + "," +
                           to_count[y][x] + "," +
                           (double)to_area[y][x]/to_count[y][x]);
//          demoNeighborMatrix(pixels, labels, cc_labels, d, y, x, mask);
        }
      }
    }

/*
    int matrix_size = neighbor_matrix.length;

    // get the size of each connected components
    SimpleHistogram hist = new SimpleHistogram(cc_labels);
    int[] sizes = hist.getHistogram();

    // only consider 4-connection neighours
    for (int y=0; y<matrix_size; y++) {
      for (int x=0; x<matrix_size; x++) {
        if (y==x) continue;
        if (neighbor_matrix[y][x] != 0) {
          NeighborRegionStatistics rgn_stat = getNeighborRegionStatistics(labels, d, y, x, cc_labels, mask);
          HashSet regions_from = rgn_stat.regions_from;
          HashSet regions_to = rgn_stat.regions_to;
          int regions_from_size = getSize(sizes, regions_from);
          int regions_to_size = getSize(sizes, regions_to);
          System.out.println("" + y + "," + x + ": " +
                           neighbor_matrix[y][x] + "," +
                           distance_matrix[y][x] + "," +
                           regions_from.size() + "," +
                           (double)regions_from_size/regions_from.size() + "," +
                           regions_to.size() + "," +
                           (double)regions_to_size/regions_to.size());
          demoNeighborMatrix(pixels, labels, cc_labels, d, y, x, mask);
        }
      }
    }
 */

  }

  public ConnectingRegionStat getConnectingRegionStatistics(int[] labels, Dimension d,
      int[][] neighbor_matrix, int[] cc_labels, int[] mask) {

    int matrix_size = neighbor_matrix.length;

    int[][] count1 = new int[matrix_size][matrix_size];
    int[][] area1 = new int[matrix_size][matrix_size];
    int[][] count2 = new int[matrix_size][matrix_size];
    int[][] area2 = new int[matrix_size][matrix_size];

    // get the size of each connected components
    SimpleHistogram hist = new SimpleHistogram(cc_labels);
    int[] sizes = hist.getHistogram();
//    int small_region_threshold = (int) (d.height * d.width * SMALL_REGION_THRESHOLD);
//    // to calcuate the connecting region size, remove the big region size
//    for (int i=0; i<sizes.length; i++) {
//      if (sizes[i] >= small_region_threshold) {
//        sizes[i] = small_region_threshold;
//      }
//    }
    // only consider 4-connection neighours
    for (int y = 0; y < matrix_size; y++) {
      for (int x = 0; x < matrix_size; x++) {
        if (y == x)
          continue;
        if (neighbor_matrix[y][x] != 0) {
          ConnectingRegions rgn_stat = getConnectingRegions(
              labels,
              d, y, x, cc_labels, mask);
          HashSet regions_from = rgn_stat.regions_from;
          HashSet regions_to = rgn_stat.regions_to;
          count1[y][x] = regions_from.size();
          area1[y][x] = getTotalArea(sizes, regions_from);
          count2[y][x] = regions_to.size();
          area2[y][x] = getTotalArea(sizes, regions_to);
        }
      }
    }

    return new ConnectingRegionStat(count1, area1, count2, area2);

  }

  public void demoNeighborMatrix(int[] pixels, int[] labels, int[] cc_labels, Dimension d, int from, int to, int[] mask) {
    int width = d.width, height = d.height;

    HashSet cc_labels_to_merge = getCCRegionsToBeMerged(labels, d, from, to, cc_labels, mask);

    int[] pixels1 = new int[pixels.length], pixels2 = new int[pixels.length];
    int[] merged = new int[pixels.length], merged_labels = new int[pixels.length];

    for (int i = 0; i < labels.length; i++) {
      pixels1[i] = pixels2[i] = merged[i] = merged_labels[i] = 0xff000000;
      if (labels[i] == from) {
        pixels1[i] = pixels[i];
        if (cc_labels_to_merge.contains(new Integer(cc_labels[i]))) {
          merged[i] = pixels[i];
          merged_labels[i] = 0xffff0000;
        }
        else {
          merged_labels[i] = 0xff00ff00;
        }
      }
      else if (labels[i] == to) {
        pixels2[i] = pixels[i];
        merged_labels[i] = 0xff0000ff;
        merged[i] = pixels[i];
      }
    }
    int[][] images = new int[][]{
        pixels1, pixels2, merged, merged_labels
    };
    int[] demo = yi.util.ImageUtil.makeTiledImage(images, d.width, d.height, 2, 2);
    //System.out.println("neighbor"+"."+from+"."+to+".ppm");
    yi.util.io.OutputResult.outputColorImage("neighbor"+"."+from+"."+to+".ppm", demo, d.width*2, d.height*2);
  }

  /**
   * When it is decided that label "from" will be merged to label "to", not all
   * the pixels labeled "from" will be merged, but only those are neighbor to
   * label "to".  This is done with the help of connnected components.  It a
   * i-labeled region is near to a j-labeled region, then all the pixels in the
   * first region will be changed to j-labeled.  This function returns the
   * connected component region indexes which will be merged (in other words,
   * neighbor to j-labeled regions)
   * @param cluster_map
   * @param d
   * @param from
   * @param to
   * @param cc_labels
   * @param mask
   * @return
   */
  private HashSet getCCRegionsToBeMerged(int[] labels, Dimension d, int from, int to, int[] cc_labels, int[] mask) {
    int width = d.width, height = d.height;

    HashSet cc_labels_to_merge = new HashSet();

    // only consider 4-connection neighours
    for (int y=0; y<height-1; y++) {
      for (int x=0; x<width-1; x++) {
        int ptr = y*width + x;
        int ptr_right = y*width + x + 1;
        int ptr_down  = (y+1)*width + x;

        int label = labels[ptr];
        // put into the neighour matrix
        int right_label = labels[ptr_right];
        if (label == from && right_label == to && mask[ptr] != MASKED) {
          cc_labels_to_merge.add(new Integer(cc_labels[ptr]));
        }
        else if (label == to && right_label == from && mask[ptr_right] != MASKED) {
          cc_labels_to_merge.add(new Integer(cc_labels[ptr_right]));
        }

        int down_label = labels[ptr_down];
        if (label == from && down_label == to && mask[ptr] != MASKED) {
          cc_labels_to_merge.add(new Integer(cc_labels[ptr]));
        }
        else if (label == to && down_label == from && mask[ptr_down] != MASKED) {
          cc_labels_to_merge.add(new Integer(cc_labels[ptr_down]));
        }
      }
    }
    return cc_labels_to_merge;

  }

  /**
   * NeighborRegionStatistics return the cc_label of the regions to be merged
   * and the regions merged to
   */
  class ConnectingRegions {
    HashSet regions_from;
    HashSet regions_to;
    public ConnectingRegions(HashSet regions_from, HashSet regions_to) {
      this.regions_from = regions_from;
      this.regions_to = regions_to;
    }
  }

  class ConnectingRegionStat {
    int[][] from_count;
    int[][] from_area;
    int[][] to_count;
    int[][] to_area;
    public ConnectingRegionStat(int[][] from_count, int[][] from_area,
                              int[][] to_count, int[][] to_area) {
      this.from_count = from_count;
      this.from_area = from_area;
      this.to_count = to_count;
      this.to_area = to_area;
    }
  }

  private ConnectingRegions getConnectingRegions(int[] labels, Dimension d,
      int from, int to, int[] cc_labels, int[] mask) {
    int width = d.width, height = d.height;

    HashSet cc_labels_to_merge = new HashSet();
    HashSet cc_labels_merged_to = new HashSet();

    // only consider 4-connection neighours
    for (int y=0; y<height-1; y++) {
      for (int x=0; x<width-1; x++) {
        int ptr = y*width + x;
        int ptr_right = y*width + x + 1;
        int ptr_down  = (y+1)*width + x;

        int label = labels[ptr];
        // put into the neighour matrix
        int right_label = labels[ptr_right];
        if (label == from && right_label == to && mask[ptr] != MASKED) {
          cc_labels_to_merge.add(new Integer(cc_labels[ptr]));
          cc_labels_merged_to.add(new Integer(cc_labels[ptr_right]));
        }
        else if (label == to && right_label == from && mask[ptr_right] != MASKED) {
          cc_labels_to_merge.add(new Integer(cc_labels[ptr_right]));
          cc_labels_merged_to.add(new Integer(cc_labels[ptr]));
        }

        int down_label = labels[ptr_down];
        if (label == from && down_label == to && mask[ptr] != MASKED) {
          cc_labels_to_merge.add(new Integer(cc_labels[ptr]));
          cc_labels_merged_to.add(new Integer(cc_labels[ptr_down]));
        }
        else if (label == to && down_label == from && mask[ptr_down] != MASKED) {
          cc_labels_to_merge.add(new Integer(cc_labels[ptr_down]));
          cc_labels_merged_to.add(new Integer(cc_labels[ptr]));
        }
      }
    }
    return new ConnectingRegions(cc_labels_to_merge, cc_labels_merged_to);

  }

  private int[] getBigRegionMask(int[] cc_labels, Dimension d, int threshold) {
    // get the region size
    SimpleHistogram region_size = new SimpleHistogram(cc_labels);
    int[] region_sizes = region_size.getHistogram();

    //
    int[] mask = new int[d.width * d.height];
    for (int i=0; i<mask.length; i++) {
      if (region_sizes[cc_labels[i]] >= threshold) {
        mask[i] = 1;
      }
    }
    return mask;
  }

  /**
   *
   * @param stat
   * @param rgn_stat
   * @return Dimension.width: merge_from, Dimension.height: merge_to
   */
  private Dimension findClosestRelation(TouchingStatistics stat, ConnectingRegionStat rgn_stat) {
    int[][] neighbor_matrix = stat.neighborMatrix;
    double[][] distance_matrix = stat.distanceMatrix;
    int[][] count1 = rgn_stat.from_count;
    int[][] area1 = rgn_stat.from_area;
    int[][] area2 = rgn_stat.to_area;

    int size = neighbor_matrix.length;
    double highest_score = 0;
    int highest_score_y = 0, highest_score_x = 0;
    for (int y=0; y<size; y++) {
      for (int x=0; x<size; x++) {
        if (x==y) continue;
        int close_to_itself = neighbor_matrix[y][y];
        int close_to_others = neighbor_matrix[y][x];
        if (close_to_itself == 0) close_to_itself = 1;
        double  ratio = (double)close_to_others/Math.sqrt(close_to_itself);
        if ( ratio> highest_score) {
          highest_score = ratio;
          highest_score_x = x;
          highest_score_y = y;
        }
      }
    }
    if (highest_score <= 0) return null;
    System.out.println("closest: " + highest_score_y + "," + highest_score_x +
                   " = " + highest_score);
//
//    for (int y=0; y<size; y++) {
//      for (int x=0; x<size; x++) {
//        if (x==y) continue;
//        // only merge small regions to big regions
//        if (area1[y][x] * area1[x][y] > area1[x][y] * area2[y][x]) continue;
//        double  score = count1[y][x];
//        if ( score > highest_score) {
//          highest_score = score;
//          highest_score_y = y;
//          highest_score_x = x;
//        }
//      }
//    }
//    if (highest_score <= 20) return null;
//    System.out.println("closest: " + highest_score_y + "," + highest_score_x +
//                   " = " + highest_score);
    return new Dimension(highest_score_y, highest_score_x);
  }


  private int[] generateTwoLabels(int[] labels, int l1, int l2, int[] mask) {
    int[] demo = new int[labels.length];
    for (int i=0; i<demo.length; i++) {
      if (mask[i] == 1) continue;
      if (labels[i] == l1) demo[i] = 1;
      else if (labels[i] == l2) demo[i] = 2;
      else demo[i] = 0;
    }
    return demo;
  }

  private int[] generateTwoLabels(int[] labels, int l1, int l2) {
    int[] demo = new int[labels.length];
    for (int i=0; i<demo.length; i++) {
      if (labels[i] == l1) demo[i] = 1;
      else if (labels[i] == l2) demo[i] = 2;
      else demo[i] = 0;
    }
    return demo;
  }

  public boolean oneStepMerge(int[] pixels, int[] labels, Dimension d) {

    int width = d.width, height = d.height;

    // find the connected components
    ConnectComponent cc = new ConnectComponent();
    int[] cc_labels = cc.compactLabeling(labels, d, false);

    // mask the big regions
    int small_region_threshold = (int)(d.height * d.width * SMALL_REGION_THRESHOLD);
    int[] mask = getBigRegionMask(cc_labels, d, small_region_threshold);

//    TouchingStatistics stat = getTouchingStatistics(pixels, labels, d, mask);
//    int[][] neighbor_matrix = stat.neighborMatrix;
//    double[][] distance_matrix = stat.distanceMatrix;
//
//
//    ConnectingRegionStat rgn_stat = getConnectingRegionStatistics(labels, d,
//        neighbor_matrix, cc_labels, mask);
//
//    Dimension closest = findClosestRelation(stat, rgn_stat);

    //CIELabDifferentialImage diffs = new CIELabDifferentialImage(pixels, d);
    DumbDifferentialImage diffs = new DumbDifferentialImage();
    NeighboringCCInfo[] ncc_infos = NeighboringCCInfo.getNeighboringCCInfos(
        cc_labels, diffs, d, small_region_threshold, -1);
    NeighboringLabelInfo[][] nli_infos = NeighboringLabelInfo.getNeighboringLabelInfo(
      labels, cc_labels, ncc_infos, small_region_threshold);

//  try {
//    System.out.println(Class.forName(
//        "yi.iu.processor.color.reg.merge.OneWayNeighborMerger$ClosestRelationPicker"));
//  } catch (ClassNotFoundException e) {
//    e.printStackTrace();
//  }
  ClosestRelationPicker crp = new ConnectionPairCRP();
  //ClosestRelationPicker crp = new RegionNumCRP();
  //ClosestRelationPicker crp = new RegionNumCRP2();
  //ClosestRelationPicker crp = new RegionAreaCRP();
  //ClosestRelationPicker crp = new RegionAreaCRP2();
  //ClosestRelationPicker crp = new RelativeRegionAreaCRP();
  //ClosestRelationPicker crp = new RelativeRegionAreaCRP2();

  Dimension closest = crp.findClosestRelation(nli_infos);

    // closest.height is the label to be merged, closest.width is the label
    // merged to
    if (closest == null) {
      return false;
    }

    int closest_from = closest.width;
    int closest_to = closest.height;

    if (DEMO_STEPS) {

      int[] two_labels = generateTwoLabels(labels, closest_from,
                                           closest_to);
      int[] two_labels_demo = yi.util.ImageUtil.makeNewImageFromLabel(
          two_labels);
      yi.util.io.OutputResult.outputGIFImage("merge", two_labels_demo,
                                             d.width,
                                             d.height, true);
    }

    //mergeLabels(labels, d, closest.height, closest.width, cc_labels, mask);
    mergeLabels(labels, cc_labels, ncc_infos, closest_from, closest_to,
                small_region_threshold, -1);

    if (DEMO_STEPS) {
      int[] two_labels = generateTwoLabels(labels, closest_from,
                                           closest_to);
      int[] two_labels_demo = yi.util.ImageUtil.makeNewImageFromLabel(
          two_labels);
      yi.util.io.OutputResult.outputGIFImage("merge", two_labels_demo,
                                             d.width,
                                             d.height, true);
    }
    return true;

  }

  public abstract class ClosestRelationPicker {
    public Dimension findClosestRelation(NeighboringLabelInfo[][] nl_infos) {
      int size = nl_infos.length;
      double highest_score = 0;
      int highest_score_from = 0, highest_score_to = 0;

      for (int y = 0; y < size; y++) {
        for (int x = 0; x < size; x++) {
          if (x == y)
            continue;
          double  score = score(nl_infos, y, x);
          if ( score > highest_score) {
            highest_score = score;
            highest_score_from = y;
            highest_score_to = x;
          }
        }
      }

      if (highest_score <= 0) return null;
      System.out.println("closest: " + highest_score_from + "," + highest_score_to +
               " = " + highest_score);
      return new Dimension(highest_score_from, highest_score_to);
    }

    abstract public double score(NeighboringLabelInfo[][] nl_infos, int from, int to);
  }

  public class ConnectionPairCRP extends ClosestRelationPicker {
    public double score(NeighboringLabelInfo[][] nl_infos, int from, int to){
      // to itself
      int close_to_itself = nl_infos[from][from].neighboringInstNumber;
      if (close_to_itself == 0) close_to_itself = 1;
      // to "to"
      int close_to_others = nl_infos[from][to].neighboringInstNumber;
      double  ratio = (double)close_to_others/(double)Math.sqrt(close_to_itself);
      return ratio;
    }
  }

  public class RegionNumCRP extends ClosestRelationPicker {
    public double score(NeighboringLabelInfo[][] nl_infos, int from, int to){
      return (double)nl_infos[from][to].regionNum;
    }
  }

  public class RegionNumCRP2 extends ClosestRelationPicker {
    public double score(NeighboringLabelInfo[][] nl_infos, int from, int to){
      if (nl_infos[from][to].regionArea == 0) return 0.0;

      // when it is possible to change "to" to "from", see whether the changed
      // area is smaller than that change
      if (nl_infos[to][from].regionArea > 0 &&
          (nl_infos[from][to].regionArea>
           nl_infos[to][from].regionArea)) {
        return 0.0;
      }

      return (double)nl_infos[from][to].regionNum;
    }
  }

  public class RegionAreaCRP extends ClosestRelationPicker {
    public double score(NeighboringLabelInfo[][] nl_infos, int from, int to){
      if (nl_infos[from][to].regionArea == 0) return 0.0;
      // suppose the criteria uses the average region area:
      // area_sum / region_num
      // The smaller, the better.  So inversed version is used
      return (double)nl_infos[from][to].regionNum / (double)nl_infos[from][to].regionArea;
    }
  }

  public class RegionAreaCRP2 extends ClosestRelationPicker {
    public double score(NeighboringLabelInfo[][] nl_infos, int from, int to){
      if (nl_infos[from][to].regionArea == 0) return 0.0;
      // suppose the criteria uses the average region area:
      // area_sum / region_num
      // The smaller, the better.  So inversed version is used
      // multipled by neighboringInstNumber, so that the more connection, the better
      return (double)nl_infos[from][to].regionNum / (double)nl_infos[from][to].regionArea
           * nl_infos[from][to].neighboringInstNumber;
    }
  }

  public class RelativeRegionAreaCRP extends ClosestRelationPicker {
    public double score(NeighboringLabelInfo[][] nl_infos, int from, int to){
      if (nl_infos[from][to].regionArea == 0) return 0.0;
      // suppose the criteria uses the average region area:
      // area_sum / neighbor_area_num
      // The smaller, the better.  So inversed version is used
      return (double)nl_infos[from][to].neighborAreaSum / (double)nl_infos[from][to].regionArea;
    }
  }

  public class RelativeRegionAreaCRP2 extends ClosestRelationPicker {
    public double score(NeighboringLabelInfo[][] nl_infos, int from, int to){
      if (nl_infos[from][to].regionArea == 0) return 0.0;
      // suppose the criteria uses the average region area:
      // area_sum / neighbor_area_num
      // The smaller, the better.  So inversed version is used
      // multipled by neighboringInstNumber, so that the more connection, the better
      return (double)nl_infos[from][to].neighborAreaSum / (double)nl_infos[from][to].regionArea
          * nl_infos[from][to].neighboringInstNumber;
    }
  }
}
