//Source file: D:\\Projects\\src\\yi\\iu\\processor\\clc\\LineClusterSets.java

package yi.iu.processor.clc;

import java.util.*;
import yi.iu.region.*;
import java.awt.*;
import yi.util.config.*;
import java.io.*;
import yi.iu.ft.local.*;
import yi.util.io.*;
import yi.util.io.input.*;
import yi.ds.*;
import java.awt.geom.*;
import yi.graph.polygon.*;
import yi.graph.polygon.gpc.*;
import yi.iu.processor.ort.ORTLine;
import yi.iu.imgdb.learn.FeatureRepresentable;
import yi.iu.processor.ort.*;
import yi.stat.hist.*;
import yi.iu.imgdb.learn.*;
import yi.iu.processor.color.ColorPair;

/**
 * Represent serveral line cluster sets.  A LineClusterSet in a consistent line
 * cluster.  A LineClusterSets usually is used to represent the classification
 * result from a LineClusterSet because after clustering, one line cluster may
 * be split to several line clusters.
 * LineClusterSets is implemented by HashMap.  This implementation provides a
 * easy way to put a line into the correct cluster.  Because all the
     * LineClusterSet(s) in a "LineClusterSets" have a corresponding key, and a line
 * can be put into the correct LineClusterSet using its key.  For example,
 * each LineClusterSet can have a color pair as its key which means every line
     * in a LineClusterSet has the same color feature (color pair).  So if the color
 * pair of a line is known, it is easy to put the line into the correct
 * LineClusterSet.  If the key is not important, just put the same thing for
 * key and value.
 */
public class LineClusterSets
    extends java.util.HashMap
    implements Configurable, PolygonFeatureRepresentable, EllipseFeatureRepresentable,
    LabeledRgnFtRepresentable, FeaturePickDemoable, ImageDemoCapable {
  public int GLOBALIZE_FTH1 = 1;
  public int GLOBALIZE_FTH2 = 2;
  public int GLOBALIZE_FTH3 = 3;
  public int GLOBALIZE_METHOD = GLOBALIZE_FTH1;
  public String GLOBALIZE_SUFFIX[] = new String[] {
      new String("FTH0"),
      new String("FTH1"),
      new String("FTH2"),
      new String("FTH3"),
  };
  public static int FTH1_BIN_NUM = 72;
  public static int FTH2_BIN_NUM = 30;
  public static int FTH3_BIN_NUM = 11 * 11;

  public int REUSE = DIU.INPUT_NONE;

  protected int m_imgWidth;
  protected int m_imgHeight;

  static final long serialVersionUID = -7548735211224893950L;


  /**
   * This function will only be used for castor
   */
  public LineClusterSets() {
    super();
    Configuration.configParameters(this);
  }

  /**
   */
  public LineClusterSets(int image_width, int image_height) {
    super();
    m_imgWidth = image_width;
    m_imgHeight = image_height;
    Configuration.configParameters(this);
  }

  public int getImageWidth() {
    return m_imgWidth;
  }

  /**
      @param w
   */
  public void setImageWidth(int w) {
    m_imgWidth = w;
  }

  /**
      @return int
   */
  public int getImageHeight() {
    return m_imgHeight;
  }

  /**
      @param h
   */
  public void setImageHeight(int h) {
    m_imgHeight = h;
  }

  /**
   * @return String[]
   */
  public String[] getParameterNames() {
    return new String[] {
        "GLOBALIZE_METHOD",
        "REUSE",
    };
  }

  /**
   * Add the line to the corresponding line cluster set
   * @param key
   * @param l
   * @roseuid 3F99E171037A
   */
  public void add(Object key, ORTLine l) {
    if (containsKey(key)) {
      LineClusterSet the_set = (LineClusterSet) get(key);
      the_set.add(l);
    }
    else {
      LineClusterSet the_set = new LineClusterSet(key);
      the_set.add(l);
      put(key, the_set);
    }
  }

  /**
   * Filter the lines in each line cluster set so that the remaining lines
   * having the the specified minimum length.  Filter the line set so that the
   * remaining ones having the specified total length of the lines
   * @param min_length the minimum length of the remaining lines
   * @param min_total_len the minimum total length of lines in the remaining
   * line sets
   * @return the line set number remained.
   * @roseuid 3F99E171038B
   */
  public int filter(double min_length, double min_total_len) {

    Iterator i = keySet().iterator();
    while (i.hasNext()) {
      Object key = i.next();
      LineClusterSet line_set = (LineClusterSet) get(key);
      if (min_length > 0)
        line_set.filter(min_length);
      if (min_total_len > 0 && line_set.getTotalLen() < min_total_len)
        i.remove();
    }
    return size();
  }

  /**
   * @return java.util.HashMap
   * @roseuid 3F99E171039B
   */
  public HashMap getLineClusterSets() {
    return this;
  }

  /**
   * A line cluster set (the parent) can be clustered to sevearl line cluster
   * sets.  Add those resultatnt line clusters in (in both key and value
   * position) because we want to keep them seperate when clusters from
   * different parents are combined.  For example, several different color
   * line cluster set will be clustered again using orientation information.
       * If we add those results in and using orientation as key, then clusters from
   * different color consistent line clusters may be mixed up.
   * @param line_clusters
   * @roseuid 3F99E17103A9
   */
  public void add(LineClusterSets line_clusters) {
    Iterator i = line_clusters.values().iterator();
    while (i.hasNext()) {
      LineClusterSet lines = (LineClusterSet) i.next();
      put(lines, lines);
    }
  }

  /**
   * When a LineClusterSets is obtained from clustering of a LineClusterSet, we
   * want to copy the properties of the parent to all the children (like the
   * color, oritentation and position information)
   * @param line_cluster
   * @roseuid 3F99E17103B9
   */
  public void inheritProperties(LineClusterSet line_cluster) {
    Iterator i = values().iterator();
    while (i.hasNext()) {
      LineClusterSet lines = (LineClusterSet) i.next();
      lines.inheritProperties(line_cluster);
    }
  }

  /**
   * @return String
   * @roseuid 3F99E17103C9
   */
  public String toString() {
    StringBuffer s = new StringBuffer("line cluster set:" +
                                      System.getProperty("line.separator"));
    Iterator i = values().iterator();
    while (i.hasNext()) {
      LineClusterSet lines = (LineClusterSet) i.next();
      s.append(lines.toString());
    }
    return s.toString();
  }

  /**
   * Return an image with different line cluster is drawn with different color.
   * The background (pixels are not on any line) will be left as black
   * (0xff000000)
   * @return the image
   * @roseuid 3F99E17103D8
   */
  public int[] demo() {

    if (size() == 0) {
      return new int[1];
    }

    // get the image width and height
    Iterator i = values().iterator();
    LineClusterSet line_cluster = (LineClusterSet) i.next();
    int w = line_cluster.getImageWidth(), h = line_cluster.getImageHeight();

    return demo(w, h);
  }

  /**
   *
   * @param w
   * @param h
   * @param draw_bounds whether the bounds of the line clusters will be draw
   * @param line_width width of the line
   * @return
   */
  public int[] demo(int w, int h, boolean draw_bounds, int line_width) {
    int[] lineimg = new int[h * w];

    int label = 1; // the labels of lines begins from 1
    for (Iterator i = values().iterator(); i.hasNext(); label++) {
      LineClusterSet line_cluster = (LineClusterSet) i.next();
      // draw
      int bound_color = draw_bounds ? label : 0;
//      int bound_color = draw_bounds? size() + 1 : 0;
      line_cluster.draw(lineimg, w, h, label, line_width, bound_color, 2);
    }

    // make the image
    return yi.util.ImageUtil.makeNewImageFromLabel(lineimg);
  }

  /**
   * Return an image with different line cluster is drawn with different color.
   * Line width is 1
   * @param w
   * @param h
   * @param draw_bounds whether the bounds of the line clusters will be draw
   * @return
   */
  public int[] demo(int w, int h, boolean draw_bounds) {
    return demo(w, h, draw_bounds, 1);
  }

  /**
   * Return an image with different line cluster is drawn with different color.
   * The background (pixels are not on any line) will be left as black
   * (0xff000000).  Each image is bounded by a rectangle
   * @param w
   * @param h
   * @return
   * @roseuid 3F99E1720003
   */
  public int[] demoRectangles(int w, int h) {

    int[] lineimg = new int[h * w];

    int label = 1; // the labels of lines begins from 1
    for (Iterator i = values().iterator(); i.hasNext(); label++) {
      LineClusterSet line_cluster = (LineClusterSet) i.next();
      // draw
      int bound_color = label;

      Rectangle bound = line_cluster.getBounds();
      DrawUtil.drawRectangle(lineimg, new Dimension(w, h),
                             bound.x - w / 2, bound.y - h / 2,
                             bound.x + bound.width - w / 2,
                             bound.y + bound.height - h / 2,
                             bound_color, 2);

      line_cluster.draw(lineimg, w, h, label, 1, 0, 0);
    }

    // make the image
    return yi.util.ImageUtil.makeNewImageFromLabel(lineimg);
  }

  /**
   * Return an image with different line cluster is drawn with different color.
   * The background (pixels are not on any line) will be left as black
   * (0xff000000)
   * @param w
   * @param h
   * @return the image
   * @roseuid 3F99E1720011
   */
  public int[] demo(int w, int h) {
    return demo(w, h, false);
  }


  /**
   * Convert the line clusters to labels.  Each line cluster will be first
   * represented by a polygon.  The line clusters will be sorted by their total
   * line length.  The smaller ones will be drawn first (so that when some of
   * the line cluster polygons are overlapped, the important ones will keep all
   * its region).  The background will be labeled as 0 and line cluster polygons
   * will be labeled as the index of the line cluster
   * @return
   */
  public int[] getLabels() {
    // new result
    int[] lineimg = new int[m_imgWidth * m_imgHeight];

    // get the regions to sort according to their area
    GenericPolygon[] regions = new GenericPolygon[size()];
    int[] to_label = new int[size()]; // the first one will be labeled as 1
                                      // even after the regions are sorted
    double[] lens = new double[size()];
    int label = 0;
    for (Iterator it = values().iterator(); it.hasNext(); label++) {
      LineClusterSet line_cluster = (LineClusterSet) it.next();
      lens[label] = line_cluster.getTotalLen();
      regions[label] = new GenericPolygon(new GenericContour(line_cluster.getPolygonBounds()));
      to_label[label] = label;
    }

    // sort the regions according to their total line length (the first is the
    // smallest region)
    for (int i = 0; i < regions.length; i++) {
      for (int j = i + 1; j < regions.length; j++) {
        if (lens[i] > lens[j]) {
          // swap length
          double t = lens[i];
          lens[i] = lens[j];
          lens[j] = t;
          // swap the polygon region
          GenericPolygon t2 = regions[i];
          regions[i] = regions[j];
          regions[j] = t2;
          // swap the label
          int t3= to_label[i]; to_label[i] = to_label[j]; to_label[j] = t3;
        }
      }
    }
    // draw the regions, the smallest one first (since the regions are sorted)
    // the biggest one will be always shown
    Arrays.fill (lineimg, 0); // set the background to 0
    for (int i = 0; i < regions.length; i++) {
      regions[i].filledDraw(lineimg, m_imgWidth, m_imgHeight, to_label[i]+1); // 0 for
    }

    return lineimg;
  }

  /**
   * Convert the line clusters to labels.  Each line cluster will be first
   * represented by a polygon.  The line clusters will be sorted by their total
   * line length.  The smaller ones will be drawn first (so that when some of
   * the line cluster polygons are overlapped, the important ones will keep all
   * its region).  The background will be labeled as 0 and line cluster polygons
   * will be labeled as the index of the line cluster
   * @param index2lcs the key is Integer of the cluster index (from 0, so must
   *                  add one to allow 0 as background).  the value is the
   *                  line cluster
   * @return
   */
  public int[] getLabels(HashMap index2lcs) {
    // new result
    int[] lineimg = new int[m_imgWidth * m_imgHeight];

    // get the regions to sort according to their area
    GenericPolygon[] regions = new GenericPolygon[index2lcs.size()];
    int[] to_label = new int[index2lcs.size()]; // the first one will be labeled as 1
                                      // even after the regions are sorted
    double[] lens = new double[index2lcs.size()];
    for (int i=0; i<index2lcs.size(); i++) {
      LineClusterSet line_cluster = (LineClusterSet) index2lcs.get(new Integer(i));
      lens[i] = line_cluster.getTotalLen();
      regions[i] = new GenericPolygon(new GenericContour(line_cluster.getPolygonBounds()));
      to_label[i] = i;
    }

    // sort the regions according to their total line length (the first is the
    // smallest region)
    for (int i = 0; i < regions.length; i++) {
      for (int j = i + 1; j < regions.length; j++) {
        if (lens[i] > lens[j]) {
          // swap length
          double t = lens[i];
          lens[i] = lens[j];
          lens[j] = t;
          // swap the polygon region
          GenericPolygon t2 = regions[i];
          regions[i] = regions[j];
          regions[j] = t2;
          // swap the label
          int t3= to_label[i]; to_label[i] = to_label[j]; to_label[j] = t3;
        }
      }
    }
    // draw the regions, the smallest one first (since the regions are sorted)
    // the biggest one will be always shown
    Arrays.fill (lineimg, 0); // set the background to 0
    for (int i = 0; i < regions.length; i++) {
      regions[i].filledDraw(lineimg, m_imgWidth, m_imgHeight, to_label[i]+1); // 0 for
    }

    return lineimg;
  }

  /**
   * Demo the line clusters using ellipses.  Each line cluster will be
   * represented by a ellipse.  The regions will be first sorted and the
   * smallest one will be drawn first so that the biggest one will be not
   * blocked by others.
   * @param w
   * @param h
   * @return
   * @roseuid 3F99E1720021
   */
  public int[] demoRegions(int w, int h) {

    // new result
    int[] lineimg = new int[w * h];

    // get the regions
    Region[] regions = new Region[size()];
    double[] lens = new double[size()];
    int label = 0;
    Iterator it = values().iterator();
    while (it.hasNext()) {
      LineClusterSet line_cluster = (LineClusterSet) it.next();
      lens[label] = line_cluster.getTotalLen();
      regions[label] = line_cluster.getRegion();
      label++;
    }

    // sort the regions according to their total line length (the first is the
    // smallest region)
    for (int i = 0; i < regions.length; i++) {
      for (int j = i + 1; j < regions.length; j++) {
        if (lens[i] > lens[j]) {
          double t = lens[i];
          lens[i] = lens[j];
          lens[j] = t;
          Region t2 = regions[i];
          regions[i] = regions[j];
          regions[j] = t2;
        }
      }
    }
    // draw the regions, the smallest one first (since the regions are sorted)
    // the biggest one will be always shown
    for (int i = 0; i < lineimg.length; i++)
      lineimg[i] = 0xffffffff;
    int[] mapc = yi.util.ImageUtil.labels2Colors(regions.length, true);
    for (int i = 0; i < regions.length; i++) {
      Region.drawRegions(lineimg, new Dimension(w, h), regions[i], mapc[i + 1]);
    }

    return lineimg;
  }

  /**
   * @param pixels
   * @param d
   * @param prefix
   * @param demo_region
   * @roseuid 3F99E1720030
   */
  public void outputClustersOneByOne(int[] pixels, Dimension d, String prefix,
                                     boolean demo_region) {
    outputClustersOneByOne(pixels, d, prefix, true, true, true, true,
                           demo_region);
  }

  /**
   * Output the line clusters one by one.  If the pixels == null, gif file will
   * be output; otherwise "ppm" format will be used
   * @param pixels
   * @param d
   * @param prefix
   * @param draw_lines
   * @param draw_boundingbox
   * @param draw_axis
   * @param draw_cp
   * @param demo_region whether to output ellipse regions to demo the clusters
   * @roseuid 3F99E172004F
   */
  public void outputClustersOneByOne(int[] pixels, Dimension d, String prefix,
                                     boolean draw_lines,
                                     boolean draw_boundingbox,
                                     boolean draw_axis, boolean draw_cp,
                                     boolean demo_region) {

    int label = 0;
    int label_str_len = Integer.toString(size() - 1).length();
    for (Iterator i = values().iterator(); i.hasNext(); label++) {
      LineClusterSet line_cluster = (LineClusterSet) i.next();

      // make the image with background
//      int[] lineimg = line_cluster.demo(pixels, d, draw_lines, draw_boundingbox,
//                                        draw_axis, draw_cp);
      int[] lineimg = line_cluster.demoWithPolygonBound(pixels, d, draw_lines,
          draw_cp);

      String label_str = yi.util.data.Conversion.toString(label, label_str_len);

      if (pixels == null) { // gif format will be used
        // output
        OutputResult.outputGIFImage(
            prefix + '.' + label_str + ".gif", lineimg, d.width, d.height);

        // output the region
        if (demo_region) {
          lineimg = line_cluster.demoRegion(pixels, d);
          OutputResult.outputGIFImage(
              prefix + ".rgn." + label_str + ".gif", lineimg, d.width, d.height);
        }

      }
      else {

        // output
        OutputResult.outputColorImage(
            prefix + '.' + label_str + ".ppm", lineimg, d.width, d.height);

        // output the region
        if (demo_region) {
          lineimg = line_cluster.demoRegion(pixels, d);
          OutputResult.outputColorImage(
              prefix + ".rgn." + label_str + ".ppm", lineimg, d.width, d.height);
        }
      }
    }
  }

  /**
   * Merge those clusters which have common lines.  The merge is transitive:
   * if cluster 2 and cluster 3 is merged 1, then at the end, those 3 will be
   * merged to one cluster.
   * @param line_clusters
   * @param rgb
   * @param width
   * @param height
   * @return
   * @roseuid 3F99E1720071
   */
  public static LineClusterSets mergeThoseHaveCommon(LineClusterSets
      line_clusters) {
    // post process result
    LineClusterSets pped = new LineClusterSets(line_clusters.getImageWidth(),
                                               line_clusters.getImageHeight());

    // convert line cluster hashmap to vector for easy processing
    Vector lcs = new Vector(line_clusters.values());
    int lcs_num = lcs.size();

    // merge if they have common lines
    Merge merge = new Merge(lcs_num);
    for (int i = 0; i < lcs_num - 1; i++) {
      LineClusterSet lc1 = (LineClusterSet) lcs.elementAt(i);
      for (int j = i + 1; j < lcs_num; j++) {
        LineClusterSet lc2 = (LineClusterSet) lcs.elementAt(j);
        boolean have_common = false;
        for (Iterator it = lc2.iterator(); it.hasNext(); ) {
          if (lc1.contains(it.next())) {
            have_common = true;
            break;
          }
        }
        if (have_common)
          merge.merge(i, j);
      }
    }

    // construct the results
    int[] merge_rst = merge.result();
    LineClusterSet[] rst = new LineClusterSet[lcs_num];
    for (int i = 0; i < lcs_num; i++) {
      int merge_to = merge_rst[i];
      if (rst[merge_to] == null) {
        rst[merge_to] = ( (LineClusterSet) lcs.elementAt(merge_to)).
            newInstanceWithSameProperties();
      }
      rst[merge_to].addAll( (LineClusterSet) lcs.elementAt(i));
    }

    for (int i = 0; i < lcs_num; i++) {
      if (rst[i] != null)
        pped.put(rst[i], rst[i]);
    }
    return pped;
  }

  /**
   * Merge line clusters by their rectangle coverage.  If the rectange bounds
   * of two clusters are overlapped higher than the "threshold", they will be
   * merged together
   * @param line_clusters
   * @param threshold
   * @return
   * @roseuid 3F99E172007E
   */
  public static LineClusterSets mergeThoseBeenCovered(LineClusterSets
      line_clusters, double threshold) {
    // post process result
    LineClusterSets pped = new LineClusterSets(line_clusters.getImageWidth(),
                                               line_clusters.getImageHeight());

    // convert line cluster hashmap to vector for easy processing
    Vector lcs = new Vector(line_clusters.values());
    int lcs_num = lcs.size();

    // cache the bound of the clusters
    Rectangle[] rects = new Rectangle[lcs_num];
    for (int i = 0; i < lcs_num; i++) {
      LineClusterSet lc = (LineClusterSet) lcs.elementAt(i);
      rects[i] = lc.getBounds();
      System.out.println("#" + i + ":" + rects[i].x + ", " + rects[i].y + ", " +
                         rects[i].getMaxX() + ", " + rects[i].getMaxY());
    }

    // merge if they have common lines
    Merge merge = new Merge(lcs_num);
    for (int i = 0; i < lcs_num - 1; i++) {
      LineClusterSet lc1 = (LineClusterSet) lcs.elementAt(i);
      double i_area = rects[i].getWidth() * rects[i].getHeight();
      for (int j = i + 1; j < lcs_num; j++) {
        Rectangle intersect = rects[i].intersection(rects[j]);
        if (intersect.isEmpty())
          continue;
        double intersect_area = intersect.getWidth() * intersect.getHeight();
        double j_area = rects[j].getWidth() * rects[j].getHeight();
        System.out.println("" + i + "," + j + "," + intersect_area / i_area +
                           "," + intersect_area / j_area);
        if (intersect_area >= i_area * threshold ||
            intersect_area >= j_area * threshold) {
          merge.merge(i, j);
        }
      }
    }

    // construct the results
    int[] merge_rst = merge.result();
    LineClusterSet[] rst = new LineClusterSet[lcs_num];
    for (int i = 0; i < lcs_num; i++) {
      int merge_to = merge_rst[i];
      if (rst[merge_to] == null) {
        rst[merge_to] = ( (LineClusterSet) lcs.elementAt(merge_to)).
            newInstanceWithSameProperties();
      }
      rst[merge_to].addAll( (LineClusterSet) lcs.elementAt(i));
    }

    for (int i = 0; i < lcs_num; i++) {
      if (rst[i] != null)
        pped.put(rst[i], rst[i]);
    }
    return pped;
  }

  /**
   * Merge line clusters by their polygon coverage.  If the polygon bounds
   * of two clusters are overlapped higher than the "threshold", they will be
   * merged together
   * @param line_clusters
   * @param threshold
   * @return
   * @roseuid 3F99E172008E
   */
  public static LineClusterSets mergeThoseBeenCovered2(LineClusterSets
      line_clusters, double threshold) {
    // post process result
    LineClusterSets pped = new LineClusterSets(line_clusters.getImageWidth(),
                                               line_clusters.getImageHeight());

    // convert line cluster hashmap to vector for easy processing
    Vector lcs = new Vector(line_clusters.values());
    int lcs_num = lcs.size();

    // cache the bound of the clusters
    GenericContour[] rects = new GenericContour[lcs_num];
    double[] areas = new double[lcs_num];
    for (int i = 0; i < lcs_num; i++) {
      LineClusterSet lc = (LineClusterSet) lcs.elementAt(i);
      rects[i] = new GenericContour(lc.getPolygonBounds());
      areas[i] = rects[i].getArea();
    }

//    if (true) {
//      LineClusterSet lc = (LineClusterSet)lcs.elementAt(0);
//      int width = lc.getImageWidth();
//      int height = lc.getImageHeight();
//      String img_name = Configuration.getStringParameter("SOURCE_NAME",
//          "unknown");
//
//      line_clusters.outputClustersOneByOne(null, new Dimension(width, height),
//                                           img_name + '.' + "line_struct",
//                                           true, false, false, true, false);
//    }

    // merge if they have common lines
    Merge merge = new Merge(lcs_num);
    for (int i = 0; i < lcs_num - 1; i++) {
      for (int j = i + 1; j < lcs_num; j++) {
        GpcPolygon poly1 = new GpcPolygon(rects[i]);
        GpcPolygon poly2 = new GpcPolygon(rects[j]);
        GpcPolygon intersect = GPC.gpcPolygonClip(GPC.GPC_INT, poly1, poly2);
        double intersect_area = (intersect == null) ? 0 : intersect.getArea();

        if (intersect_area >= areas[i] * threshold ||
            intersect_area >= areas[j] * threshold) {
          merge.merge(i, j);

//          System.out.println("" + i + "," + j + "," + (intersect_area / areas[i]) + "," + (intersect_area / areas[j]));
//          System.out.println("merged!");
//          // demo the line clusters
//          LineClusterSets lcss = new LineClusterSets();
//          lcss.put(new Integer(1), lcs.elementAt(i));
//          lcss.put(new Integer(2), lcs.elementAt(j));
//          if (true) {
//            LineClusterSet lc = (LineClusterSet)lcs.elementAt(i);
//            int width = lc.getImageWidth();
//            int height = lc.getImageHeight();
//            String img_name = Configuration.getStringParameter("SOURCE_NAME", "unknown");
//            int[] lineimg = lcss.demo(width, height, false);
//            OutputResult.outputGIFImage(
//                img_name + '.' + "inter." + yi.util.data.Conversion.toString(i, 2) + "."
//                + yi.util.data.Conversion.toString(j, 2) + ".gif", lineimg,
//                width, height);
//          }
//
//          if (true) {
//            LineClusterSet lc = (LineClusterSet)lcs.elementAt(i);
//            int width = lc.getImageWidth();
//            int height = lc.getImageHeight();
//            String img_name = Configuration.getStringParameter("SOURCE_NAME", "unknown");
//            int[] lineimg = lcss.demo(width, height, true);
//            OutputResult.outputGIFImage(
//                img_name + '.' + "interb." + yi.util.data.Conversion.toString(i, 2) + "."
//                + yi.util.data.Conversion.toString(j, 2) + ".gif",
//                lineimg, width, height);
//          }
        }

      }
    }

    // construct the results
    int[] merge_rst = merge.result();
    LineClusterSet[] rst = new LineClusterSet[lcs_num];
    for (int i = 0; i < lcs_num; i++) {
      int merge_to = merge_rst[i];
      if (rst[merge_to] == null) {
        rst[merge_to] = ( (LineClusterSet) lcs.elementAt(merge_to)).
            newInstanceWithSameProperties();
      }
      rst[merge_to].addAll( (LineClusterSet) lcs.elementAt(i));
    }

    for (int i = 0; i < lcs_num; i++) {
      if (rst[i] != null)
        pped.put(rst[i], rst[i]);
    }
    return pped;
  }

  /**
   * @return yi.iu.ft.local.GlobalHistogram
   * @roseuid 3F99E17200AB
   */
  public GlobalHistogram getGlobalized() {
    if (GLOBALIZE_METHOD == GLOBALIZE_FTH1)
      return getGlobalized_FTH1();
    if (GLOBALIZE_METHOD == GLOBALIZE_FTH2)
      return getGlobalized_FTH2();
    if (GLOBALIZE_METHOD == GLOBALIZE_FTH3)
      return getGlobalized_FTH3();
    else
      return null;
  }

  /**
   * @return yi.iu.ft.local.GlobalHistogram
   * @roseuid 3F99E17200BB
   */
  public GlobalHistogram getGlobalized_FTH1() {

    // store the histogram
    int[] feature_num_hist = new int[FTH1_BIN_NUM];
    double[] feature_size_hist = new double[FTH1_BIN_NUM];

    // construct the histogram
    Iterator i = values().iterator();
    while (i.hasNext()) {
      LineClusterSet line_cluster = (LineClusterSet) i.next();
      double direction = line_cluster.getBaseLine().Theta;
      Region region = line_cluster.getRegion();
      int bin_index = (int) (direction / Math.PI * FTH1_BIN_NUM);
      feature_num_hist[bin_index]++;
      feature_size_hist[bin_index] += line_cluster.getTotalLen();
    }

    // find image ares for normalization
    int img_size = 1;
    i = values().iterator();
    if (i.hasNext()) {
      LineClusterSet line_cluster = (LineClusterSet) i.next();
      img_size = (int) Math.max(line_cluster.m_imgHeight,
                                line_cluster.m_imgWidth);
    }

    // construct the global histogram
    GlobalHistogram global_hist = new GlobalHistogram();
    for (int j = 0; j < FTH1_BIN_NUM; j++) {
      global_hist.add(new Integer(feature_num_hist[j]));
    }
    for (int j = 0; j < FTH1_BIN_NUM; j++) {
      global_hist.add(new Double(feature_size_hist[j] / img_size));
    }
    return global_hist;
  }

  /**
   * @return yi.iu.ft.local.GlobalHistogram
   * @roseuid 3F99E17200CB
   */
  public GlobalHistogram getGlobalized_FTH2() {

    // store the histogram
    int[] feature_num_hist = new int[FTH2_BIN_NUM];
    double[] feature_size_hist = new double[FTH2_BIN_NUM];

    // construct the histogram
    Iterator i = values().iterator();
    while (i.hasNext()) {
      LineClusterSet line_cluster = (LineClusterSet) i.next();
      int size = line_cluster.size();
      Region region = line_cluster.getRegion();
      int bin_index = size - 1;
      if (bin_index >= FTH2_BIN_NUM)
        bin_index = FTH2_BIN_NUM - 1;
      feature_num_hist[bin_index]++;
      feature_size_hist[bin_index] += line_cluster.getTotalLen();
    }

    // find image ares for normalization
    int img_size = 1;
    i = values().iterator();
    if (i.hasNext()) {
      LineClusterSet line_cluster = (LineClusterSet) i.next();
      img_size = (int) Math.max(line_cluster.m_imgHeight,
                                line_cluster.m_imgWidth);
    }

    // construct the global histogram
    GlobalHistogram global_hist = new GlobalHistogram();
    for (int j = 0; j < FTH2_BIN_NUM; j++) {
      global_hist.add(new Integer(feature_num_hist[j]));
    }
    for (int j = 0; j < FTH2_BIN_NUM; j++) {
      global_hist.add(new Double(feature_size_hist[j] / img_size));
    }
    return global_hist;
  }

  /**
   * Construct histogram using cluster average length and line number.  Length
       * is quantized to [0, 5), [5, 10), ..., [50, ..) (11 totally) and line number
   * is quantized to [0, 3), [3, 6), ..., [30, ..) (11 totally).
   * @return
   * @roseuid 3F99E17200EA
   */
  public GlobalHistogram getGlobalized_FTH3() {

    // store the histogram
    int[] feature_num_hist = new int[FTH3_BIN_NUM];
    double[] feature_size_hist = new double[FTH3_BIN_NUM];

    // construct the histogram
    Iterator i = values().iterator();
    while (i.hasNext()) {
      LineClusterSet line_cluster = (LineClusterSet) i.next();
      int size = line_cluster.size();
      double average_length = line_cluster.getTotalLen() / size;
      Region region = line_cluster.getRegion();
      int bin_index1 = size / 3;
      if (bin_index1 >= 11)
        bin_index1 = 10;
      int bin_index2 = (int) average_length / 5;
      if (bin_index2 >= 11)
        bin_index2 = 10;
      int bin_index = bin_index1 * 11 + bin_index2;
      feature_num_hist[bin_index]++;
      feature_size_hist[bin_index] += line_cluster.getTotalLen();
    }

    // find image ares for normalization
    int img_size = 1;
    i = values().iterator();
    if (i.hasNext()) {
      LineClusterSet line_cluster = (LineClusterSet) i.next();
      img_size = (int) Math.max(line_cluster.m_imgHeight,
                                line_cluster.m_imgWidth);
    }

    // construct the global histogram
    GlobalHistogram global_hist = new GlobalHistogram();
    for (int j = 0; j < FTH3_BIN_NUM; j++) {
      global_hist.add(new Integer(feature_num_hist[j]));
    }
    for (int j = 0; j < FTH3_BIN_NUM; j++) {
      global_hist.add(new Double(feature_size_hist[j] / img_size));
    }
    return global_hist;
  }

  /**
   * The number of line direction histgram bins.  Remember that the center of
   * the first bin, not the begining of the first bin, is at 0l
   */
  public int DIR_HIST_SIZE = 4;

  /**
   *
   * @param processor_name
   * @param scale
   * @param img
   * @param mode
   * @return
   */
  public FeatureRepresentationList represent(String processor_name, int mode) throws
      Exception {
    Dimension fv_dim = new Dimension();
    double[][] fvs= generateFeatureVectors(mode, fv_dim);

    int ft_dim = fv_dim.width;
    FeatureRepresentationList list = new FeatureRepresentationList(ft_dim);
    for (int i=0; i<fvs.length; i++) {
      list.addFeatureRepresentation(new FeatureRepresentation(fvs[i]));
    }

    return list;
  }

  public FeatureRepresentable createFeatureFromFile(String processor_name, int scale,
      String img) throws Exception {
    return createFromFile(processor_name, scale, 0, img);
  }

  public FeatureRepresentable createFeatureFromFile(String processor_name, int scale, int rst_scale,
      String img) throws Exception {
    return createFromFile(processor_name, scale, rst_scale, img);
  }

  /** @todo representWithRegionInfo() is similar to  represent()*/
  public PolygonFeatureRepresentationList representPolygonFeatures(
      String processor_name, int mode) throws Exception {

    Dimension fv_dim = new Dimension();
    // generateFeatureVectors may only pick up important features, so use
    // m_representMap to remember which ones are used
    if (m_representMap == null) {
      m_representMap = new HashMap();
    } else {
      m_representMap.clear();
    }
    double[][] fvs = generateFeatureVectors(mode, fv_dim);
    int ft_dim = fv_dim.width;
    PolygonFeatureRepresentationList list = new
        PolygonFeatureRepresentationList(
        ft_dim, getImageWidth(), getImageHeight());

    if (m_representMap.size() == 0) {
      // no feature filtering happened in vector generation
      assert (fvs.length == size());
      // get out the features
      int i_index = 0;
      for (Iterator i = values().iterator(); i.hasNext(); i_index++) {
        LineClusterSet line_cluster = (LineClusterSet) i.next();
        list.addFeatureRepresentation(new PolygonFeatureRepresentation(fvs[
            i_index],
            line_cluster.getPolygonRegion()));
      }
      return list;
    } else {
      // feature filtering happened in vector generation.  use m_representMap
      assert(fvs.length == m_representMap.size());
      for (int i=0; i<fvs.length; i++) {
        LineClusterSet line_cluster = (LineClusterSet) m_representMap.get(new Integer(i));
        list.addFeatureRepresentation(new PolygonFeatureRepresentation(fvs[i],
            line_cluster.getPolygonRegion()));
      }
      return list;
    }
  }

  public static class Globalize {

    /**
     * @param args
     * @roseuid 3F99E17200FB
     */
    public static void main(String[] args) {
      // init configuration
      Configuration.init(args);

      // get the image name
      String img_name = ReadArgu.getOption('i', args);
      if (img_name == null) {
        System.err.println("Usage: -i image_name");
        System.exit(1);
      }
      Configuration.setParameter("SOURCE_NAME", img_name);

      try {
        // get local features
        LineClusterSets rst = (LineClusterSets) DIU.in(
            DIU.INPUT_XML,
            "CLC_Spatial",
            "LineClusterSets",
            Class.forName("yi.iu.processor.clc.LineClusterSets"));
        // get global histogram
        GlobalHistogram g = rst.getGlobalized();
        PrintStream out = new PrintStream(
            new FileOutputStream(img_name + ".LineClusterSets." +
                                 rst.GLOBALIZE_SUFFIX[rst.GLOBALIZE_METHOD]));
        g.output(out);
        out.close();
      }
      catch (ClassNotFoundException e) {
        e.printStackTrace();
      }
      catch (IOException e) {
        e.printStackTrace();
      }
      catch (DataInputException e) {
        e.printStackTrace();
      }
      System.exit(0);
    }
  }

  public static void main(String[] args) {
    try {
      LineClusterSets clss = new LineClusterSets(1, 1);
      //     FeatureRepresentationList data = clss.represent(
      //         "F:/groundtruth/arborgreens/arborgreens01.jpg");
      //     double[][] vectors = data.as2DDoubleArray();
      //     yi.util.io.OutputResult.print(System.out, vectors, 7, vectors.length);

      LineClusterSets ds = (new LineClusterSets()).createFromFile(
          "yi.iu.processor.clc.CLC_LineStruct", 0, 0,
          "F:/groundtruth/arborgreens/arborgreens01.jpg"
          );

      PolygonFeatureRepresentationList data2 = ds.representPolygonFeatures(
          "yi.iu.processor.clc.CLC_LineStruct",0);
      double[][] vectors = data2.as2DDoubleArray();
      yi.util.io.OutputResult.print(System.out, vectors, 7, vectors.length);
      int width = data2.imgWidth, height = data2.imgHeight;
      int[] pixels = data2.drawScoreDemo(null, new FeatureScoringCapable() { // a inner class give sequence score
        int count = 1;
        public double getFeatureScore(double[] feature, Object obj) {
          return count++;
        }
      }

      , 0, 1);
      int[] pixels_img = yi.util.ImageUtil.makeNewImageFromLabel(pixels, true);
      yi.util.io.OutputResult.outputGIFImage("score.gif", pixels_img, width,
                                             height);
    }
    catch (Exception e) {
      e.printStackTrace();
    }

  }

  /**
   * Read the pre-generated LineClusterSets from a serialized file
   * @param processor_name
   * @param scale
   * @param img
   * @return
   * @throws Exception
   */
  public LineClusterSets createFromFile(String processor_name, int scale, int rst_scale,
                                        String img) throws Exception {
    String scaled_img_name = img;
    if (scale != 0)
      scaled_img_name = img +
          yi.iu.processor.resample.Resampler.RESIZED_FILE_SUFFIX + scale;
    if (rst_scale != 0)
      scaled_img_name = scaled_img_name +
          ".r" + rst_scale;

    Configuration.setParameter("SOURCE_NAME", scaled_img_name);

    CLC_Clustering dp = (CLC_Clustering) Class.forName(processor_name).
        newInstance();

    LineClusterSets ds = (LineClusterSets) dp.reuse(DIU.INPUT_SER);

    return ds;
  }


  /**
   * When represent the features, sometimes some features are not presented
   * at all.  For example, the cluster is too small.  So a represent->feature
   * mapping is important for locate the feature from the representation index
   */
  transient protected HashMap m_representMap = new HashMap();

  /**
   * Generate the feature vectors.  Different mode corresponds to different
   * generation.  Default is 0.
   * feature_vector[0] is saved for background, so the first line cluster
   * feature vector will be saved from index of 1.
   * @param mode
   * @param d return the dimension of the data (width: the feature dimension,
   *          height: feature vector number)
   * @return
   */
  protected double[][] generateFeatureVectors(int mode, Dimension d) {

    if (mode <= 0) {
      // 7 features: total_len, num_line, mean_len, 4 direction hist (norm)
      double[][] data = new double[size()][3 + DIR_HIST_SIZE];
      d.height = size(); d.width = 3 + DIR_HIST_SIZE;

      // get out the features
      FeatureRepresentationList list = new FeatureRepresentationList(7);
      // for through each line cluster set
      int i_index = 0;
      for (Iterator i = values().iterator(); i.hasNext(); i_index++) {
        LineClusterSet line_cluster = (LineClusterSet) i.next();
        int[] direction_hist_helper = new int[DIR_HIST_SIZE * 2]; // make the size of histogram doubled first
        // loop through each line in a line cluster set
        for (Iterator j = line_cluster.iterator(); j.hasNext(); ) {
          ORTLine l = (ORTLine) j.next();
          int bin_index = (int) (l.getDirection() / Math.PI * DIR_HIST_SIZE * 2);
          direction_hist_helper[bin_index]++;
        }

        // combine the first and last together, others get combined too.
        int[] direction_hist = new int[DIR_HIST_SIZE];
        for (int j = 0; j < DIR_HIST_SIZE; j++) {
          direction_hist[j] = direction_hist_helper[j * 2] +
              direction_hist_helper[ (j * 2 - 1 + DIR_HIST_SIZE * 2) %
              (DIR_HIST_SIZE * 2)];
        }

        data[i_index][0] = line_cluster.getTotalLen(); // the total length of the lines
        data[i_index][1] = line_cluster.size();
        data[i_index][2] = data[i_index][0] / data[i_index][1];
        for (int j = 0; j < DIR_HIST_SIZE; j++) {
          data[i_index][j + 3] = direction_hist[j] / data[i_index][1]; // direction histogram normalized by total number
        }
      }
      return data;
    }
    else if (mode == 1) { // mode = 1: total length, num of lines, average length, orientation histogram
      // 7 features: total_len, num_line, mean_len, 4 direction hist (norm) with distributed weights
      double[][] data = new double[size()][3 + DIR_HIST_SIZE];
      d.height = size(); d.width = 3 + DIR_HIST_SIZE;

      // for through each line cluster set
      int i_index = 0;
      for (Iterator i = values().iterator(); i.hasNext(); i_index++) {
        LineClusterSet line_cluster = (LineClusterSet) i.next();
        DistributedWeightsHistogram hist = new DistributedWeightsHistogram(0,
            Math.PI / DIR_HIST_SIZE, DIR_HIST_SIZE, true);
        // loop through each line in a line cluster set
        for (Iterator j = line_cluster.iterator(); j.hasNext(); ) {
          ORTLine l = (ORTLine) j.next();
          hist.put(l.getDirection());
        }
        double[] h = hist.getHistogram().getWeightedHistogram();

        data[i_index][0] = line_cluster.getTotalLen(); // the total length of the lines
        data[i_index][1] = line_cluster.size();
        data[i_index][2] = data[i_index][0] / data[i_index][1];
        for (int j = 0; j < DIR_HIST_SIZE; j++) {
          data[i_index][j + 3] = h[j] / data[i_index][1]; // direction histogram normalized by total number
        }
      }
      return data;
    }

    else if (mode == 2) {
      // mode = 2:
      // total length (normalized), num of lines, total_area (normalized)
      // average length, density (num of lines/ area), density (line length / area)
      // orientation histogram, orientation-length histogram
      double[][] data = new double[size()][6 + DIR_HIST_SIZE * 2];
      d.height = size(); d.width = 6 + DIR_HIST_SIZE * 2;

      // get out the features
      // for through each line cluster set
      int i_index = 0;
      for (Iterator i = values().iterator(); i.hasNext(); i_index++) {
        LineClusterSet line_cluster = (LineClusterSet) i.next();
        DistributedWeightsHistogram hist = new DistributedWeightsHistogram(0,
            Math.PI / DIR_HIST_SIZE, DIR_HIST_SIZE, true);
        DistributedWeightsHistogram hist2 = new DistributedWeightsHistogram(0,
            Math.PI / DIR_HIST_SIZE, DIR_HIST_SIZE, true);
        // loop through each line in a line cluster set
        for (Iterator j = line_cluster.iterator(); j.hasNext(); ) {
          ORTLine l = (ORTLine) j.next();
          hist.put(l.getDirection());
          hist2.put(l.getDirection(), l.getLength());
        }
        double[] h = hist.getHistogram().getWeightedHistogram();
        double[] h2 = hist2.getHistogram().getWeightedHistogram();

        // the total length of the lines
        double total_length = line_cluster.getTotalLen();
        // the length is normalized by sqrt(image_width * image_height)
        double image_size = line_cluster.getImageHeight() *
            line_cluster.getImageWidth();
        double length_norm = Math.sqrt(image_size);
        data[i_index][0] = total_length / length_norm;
        // number of lines
        data[i_index][1] = line_cluster.size();
        // total area (normalized by image size)
        double total_area = line_cluster.getPolygonRegion().getArea();
        data[i_index][2] = total_area / image_size;
        // average length
        data[i_index][3] = data[i_index][0] / data[i_index][1];
        // density (num of lines/ area)
        data[i_index][4] = data[i_index][1] / data[i_index][2];
        // density (line length / area)
        data[i_index][5] = data[i_index][0] / data[i_index][2];

        // orientation histogram (values are the normalized line number)
        for (int j = 0; j < DIR_HIST_SIZE; j++) {
          data[i_index][j + 6] = h[j] / data[i_index][1]; // direction histogram normalized by total number
        }
        ;
        // orientation histogram (values are the line length)
        for (int j = 0; j < DIR_HIST_SIZE; j++) {
          data[i_index][j + 6 + DIR_HIST_SIZE] = h2[j] / total_length; // direction histogram normalized by total number
        }
      }
      return data;
    }

    else if (mode == 3) {
      // mode = 3:
      // this is for line clusters not merged.  such features are good for
      // learning objects based on the line structure features
      // lines density, average length (normalized), orientation,
      // intra-relationship (how many lines are heavilly overlapped by others / percentage),
      // inter-relationship (the number of maximum intersection it can forms
      // with another line cluster / percentage)
      // other possible add-on can be:
      //   number of line
      //   total area
      double[][] data = new double[size()][5];
      d.height = size(); d.width = 5;

      // get out the features
      // for through each line cluster set
      int i_index = 0;
      CLC_Intrarelationship intra_dummy = new CLC_Intrarelationship();
      for (Iterator i = values().iterator(); i.hasNext(); i_index++) {
        LineClusterSet line_cluster = (LineClusterSet) i.next();

        // the total length of the lines
        double total_length = line_cluster.getTotalLen();
        double polygon_area = line_cluster.getPolygonRegion().getArea();
        // the length is normalized by sqrt(image_width * image_height)
        double image_size = line_cluster.getImageHeight() *
            line_cluster.getImageWidth();
        double length_norm = Math.sqrt(image_size);

        // line density
        data[i_index][0] = (polygon_area == 0) ? 1 : total_length/polygon_area;
        // average length
        data[i_index][1] = total_length / line_cluster.size() / length_norm;
        // orientation
        // line cluster orientation is circular (not good for Gaussian learning),
        // change the split angle at (90+45)/2.  First it is not good make it
        // around 0 or 180.  From the perspective, it is specially not good
        // around horizontal lines.  It may be useful to avoid 45 and 135 also.
        // so (90+45)/2 ~= 65 is about right
        final double orientation_split = Math.PI * 65 / 180;
        double orientation = line_cluster.getBaseLine().getDirection();
        if (orientation < orientation_split) orientation += Math.PI;
        data[i_index][2] = orientation;

        // intra-relationship
        Intrarelationship intrarelation = Intrarelationship.intrarelation2(line_cluster, intra_dummy.OVERLAP_THRESHOLD);
        int overlapped = intrarelation.m_overlappingLineNum;
        data[i_index][3] = (double)overlapped / (double)line_cluster.size();

        // inter-relationship
        double max_inter = 0;
        for (Iterator j = values().iterator(); j.hasNext(); ) {
          LineClusterSet line_cluster_2 = (LineClusterSet) j.next();
          Interrelationship inter = Interrelationship.interrelation(line_cluster, line_cluster_2);
          double inter_percentage = (double)inter.m_intersectingLineNum1 / (double)inter.m_lineNum1;
          if (inter_percentage > max_inter) max_inter = inter_percentage;
        }
        data[i_index][4] = max_inter;
      }
      return data;
    }

    else {
      if (m_representMap == null) {
        m_representMap = new HashMap();
      } else {
        m_representMap.clear();
      }

      // mode = 4:
      // this is for line clusters not merged.  such features are good for
      // learning objects based on the line structure features
      // (the small clusters are discarded)
      // color1, color2, line num,
      // lines density, average length (normalized), orientation,
      // intra-relationship (how many lines are heavilly overlapped by others / percentage),
      // inter-relationship (the number of maximum intersection it can forms
      // with another line cluster / percentage)
      // other possible add-on can be:
      //   number of line
      //   total area

      // criterion for building
      // condition for intra:
      //    if (cluster_size > 5 && overlapped > cluster_size * 0.5)
      //    if (cluster_size > 15 && overlapped > cluster_size * 0.33)
      // condition for inter:
      //     if (s>=5 && (double)t/s >= 0.5) return true;
      //     if (s>=10 && (double)t/s >= 0.33) return true;
      // here use relaxed threshold:
      //     size >= 4 && overlap > .5
      //     size >= 12 && overlap > .33
      //     size >= 4 && inter > .5
      //     size >= 8 && inter > .33

      // first get rid of small clusters (size < 4)
      LinkedList big_clusters = new LinkedList();
      for (Iterator i = values().iterator(); i.hasNext();) {
        LineClusterSet line_cluster = (LineClusterSet) i.next();
        if (line_cluster.size() >= 4) big_clusters.add(line_cluster);
      }
      System.err.println("big clusters: " + big_clusters.size() + "/" + size());


      LinkedList data_list = new LinkedList();

      // get out the features
      // for through each line cluster set
      CLC_Intrarelationship intra_dummy = new CLC_Intrarelationship();
      int i_index = 0;
      for (Iterator i = big_clusters.iterator(); i.hasNext(); i_index++) {
        LineClusterSet line_cluster = (LineClusterSet) i.next();
        System.err.println("cluster " + i_index + ", size = " + line_cluster.size());

        double[] lc_data = new double[12];

        // color
        ColorPair cp = line_cluster.getColorPair();
        double[] lab1 = new double[3];
        double[] lab2 = new double[3];
        yi.iu.processor.color.CIELab.rgb2lab(cp.x, lab1);
        yi.iu.processor.color.CIELab.rgb2lab(cp.y, lab2);
        // switch the colors so that L of color 1 is not greater than L of color 2
        if (lab1[0] > lab2[0]) {
          double[] t = lab1;
          lab1 = lab2;
          lab2 = t;
        }
        lc_data[0] = lab1[0];
        lc_data[1] = lab1[1];
        lc_data[2] = lab1[2];
        lc_data[3] = lab2[0];
        lc_data[4] = lab2[1];
        lc_data[5] = lab2[2];
        int size = line_cluster.size();
        lc_data[6] = size;

        // the total length of the lines
        double total_length = line_cluster.getTotalLen();
        double polygon_area = line_cluster.getPolygonRegion().getArea();
        // the length is normalized by sqrt(image_width * image_height)
        double image_size = line_cluster.getImageHeight() *
            line_cluster.getImageWidth();
        double length_norm = Math.sqrt(image_size);

        // line density
        lc_data[7] = (polygon_area == 0) ? 1 : total_length/polygon_area;
        // average length
        lc_data[8] = total_length / line_cluster.size() / length_norm;
        // orientation
        // line cluster orientation is circular (not good for Gaussian learning),
        // change the split angle at (90+45)/2.  First it is not good make it
        // around 0 or 180.  From the perspective, it is specially not good
        // around horizontal lines.  It may be useful to avoid 45 and 135 also.
        // so (90+45)/2 ~= 65 is about right
        final double orientation_split = Math.PI * 65 / 180;
        double orientation = line_cluster.getBaseLine().getDirection();
        if (orientation < orientation_split) orientation += Math.PI;
        lc_data[9] = orientation;

        // intra-relationship
        Intrarelationship intrarelation = Intrarelationship.intrarelation2(line_cluster, intra_dummy.OVERLAP_THRESHOLD);
        int overlapped = intrarelation.m_overlappingLineNum;
        lc_data[10] = (double)overlapped / (double)line_cluster.size();

        // inter-relationship
        double max_inter = 0;
        for (Iterator j = big_clusters.iterator(); j.hasNext(); ) {
          LineClusterSet line_cluster_2 = (LineClusterSet) j.next();
          Interrelationship inter = Interrelationship.interrelation(line_cluster, line_cluster_2);
          double inter_percentage = (double)inter.m_intersectingLineNum1 / (double)inter.m_lineNum1;
          if (inter_percentage > max_inter) max_inter = inter_percentage;
        }
        lc_data[11] = max_inter;

        // filter
        // here use relaxed threshold:
        //     size >= 4 && overlap >= .5
        //     size >= 12 && overlap >= .33
        //     size >= 4 && inter >= .5
        //     size >= 8 && inter >= .33
        if (lc_data[10] >= 0.5 ||
            (size >= 12 && lc_data[10] >= 0.33)  ||
            lc_data[11] >= 0.5 ||
            (size >= 8 && lc_data[11] >= 0.33)) {
          m_representMap.put(new Integer(data_list.size()), line_cluster);
          data_list.add(lc_data);
        }

      }
      double[][] data = yi.util.data.Conversion.to2DDoubleArray(data_list);
      d.height = data.length; d.width = 12;
      return data;
    }

  }

  public LabeledRgnFtRepList representLabeledRgnFts(String processor_name, int mode) throws Exception {
    Dimension fv_dim = new Dimension();
    // generateFeatureVectors may only pick up important features, so use
    // m_representMap to remember which ones are used
    if (m_representMap == null) {
      m_representMap = new HashMap();
    } else {
      m_representMap.clear();
    }
    double[][] fvs = generateFeatureVectors(mode, fv_dim);
    int dim = fv_dim.width;

    int[] labels = null;
    if (m_representMap.size() == 0 && fvs.length == size()) {
      // no feature filtering happened in vector generation
      assert(fvs.length == size());
      labels = getLabels(); // label 0 is background, although fvs_orig[0] is for label 1
    } else {
      // feature filtering happened in vector generation.  use m_representMap
      assert(fvs.length == m_representMap.size());
      labels = getLabels(m_representMap);
    }
    LabeledRgnFtRepList list = new LabeledRgnFtRepList(fvs, dim, labels,
        m_imgWidth, m_imgHeight);
    return list;
  }

  /**
   * Represent the features extracted from an image with ellipses to show their
   * positions
   * @param processor_name
   * @param mode
   * @return
   */
  public EllipseFeatureRepresentationList representEllipseFeatures(String processor_name, int mode) {
    Dimension fv_dim = new Dimension();
    // generateFeatureVectors may only pick up important features, so use
    // m_representMap to remember which ones are used
    if (m_representMap == null) {
      m_representMap = new HashMap();
    } else {
      m_representMap.clear();
    }
    double[][] fvs = generateFeatureVectors(mode, fv_dim);
    int dim = fv_dim.width; // fvs has at least fvs[0] (for background) anyway
    Region[] ellipses = new Region[fvs.length];

    if (m_representMap.size() == 0) {
      // no feature filtering happened in vector generation
      assert(fvs.length == size());

      int label = 0;
      for (Iterator it = values().iterator(); it.hasNext(); label++) {
        LineClusterSet line_cluster = (LineClusterSet) it.next();
        ellipses[label] = line_cluster.getRegion();
      }

    } else {
      // feature filtering happened in vector generation.  use m_representMap
      assert(fvs.length == m_representMap.size());

      for (Iterator it = m_representMap.keySet().iterator(); it.hasNext();) {
        Integer index = (Integer)it.next();
        LineClusterSet line_cluster = (LineClusterSet) m_representMap.get(index);
        ellipses[index.intValue()] = line_cluster.getRegion();
      }

    }
    EllipseFeatureRepresentationList ft_list = new EllipseFeatureRepresentationList(dim, m_imgWidth, m_imgHeight);
    for (int i=0; i<fvs.length; i++) {
      ft_list.add(new EllipseFeatureRepresentation(fvs[i], ellipses[i]));
    }
    return ft_list;

  }

  /**
   * Test the method of representLabeledRgnFts
   */
  public static class TestRepresentLabeledRgnFts {
    public static void main(String[] args) {
      LineClusterSets lcs = new LineClusterSets();
      try {
        LineClusterSets ds = (new LineClusterSets()).createFromFile(
            "yi.iu.processor.clc.CLC_LineStruct",
            0, 0,
            "images/campusinfall14.jpg"
            );
        LabeledRgnFtRepList rplst = lcs.representLabeledRgnFts("yi.iu.processor.clc.CLC_LineStruct", 0);
        int[] labels = rplst.getLabels();
        int label_max = yi.stat.StatUtil.maximum(labels);
        int[] labels_img = yi.util.ImageUtil.makeNewImageFromLabel(labels, label_max, true);
        OutputResult.outputGIFImage("regions.gif", labels_img, rplst.imgWidth, rplst.imgHeight);
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }

  /**
   * Draw picked features.  The picked features will have "true" in the
   * corresponding "picked" boolean array.  Image name are given in case the
   * demo uses the original image pixels.  Those un-picked features will be
   * drawn by the "bg_color"
   * @param picked
   * @param img
   * @param scale
   * @param bg_color
   * @return
   */
  public int[] drawPickedFeatures(boolean[] picked, String img_name, int bg_color, Dimension d, int mode) {

    d.width = m_imgWidth; d.height = m_imgHeight;
    int[] lineimg = new int[m_imgWidth * m_imgHeight];

    /**
     * If the picked array is not the same as the size of clusters, then
     * that means the index in "picked" corresponds to the representation
     */
    if (picked.length != size()) {
      Dimension rep_d = new Dimension();
      double[][] rep = generateFeatureVectors(mode, rep_d);
      int label = 1; // the labels of lines begins from 1
      for (int i=0; i<picked.length; i++) {
        if (!picked[i]) continue;
        System.out.println("picked feature represent: ");
        OutputResult.print(System.out, rep[i]);
        LineClusterSet line_cluster = (LineClusterSet)m_representMap.get(new Integer(i));
        line_cluster.draw(lineimg, m_imgWidth, m_imgHeight, label, 2, 0, 0);
        label++;
      }
    } else {
      int i_index = 0;
      int label = 1; // the labels of lines begins from 1
      for (Iterator i = values().iterator(); i.hasNext(); i_index++) {
        LineClusterSet line_cluster = (LineClusterSet) i.next();
        if (picked[i_index]) {
          // draw
          line_cluster.draw(lineimg, m_imgWidth, m_imgHeight, label, 2, 0, 0);
          label++;
        }
      }
    }

    int[] lineimg_demo = yi.util.ImageUtil.makeNewImageFromLabel(lineimg);
    try {
      int[] orig_img = ImageIOUtil.read(img_name, d);
      if (d.width != m_imgWidth || d.height != m_imgHeight) {
        System.err.println(img_name + " (" + d.width + "," + d.height + ")" +
                           " not match lines (" + m_imgWidth + "," + m_imgHeight + ")");
        // make the image from line only
        return lineimg_demo;
      }

      int[] painted = new int[lineimg.length];

      for (int i = 0; i < lineimg.length; i++) {
        painted[i] = (lineimg[i] == 0) ? orig_img[i] : lineimg_demo[i];
      }
      return painted;
    } catch (IOException e) {
      System.err.println("can't read " + img_name);
      // make the image from line only
      return lineimg_demo;
    }
  }
  public int[] graphicDemo(Dimension d) {
    d.width = m_imgWidth; d.height = m_imgHeight;
    return demo(d.width, d.height, true, 1);
  }
  public int[] colorDemo(Dimension d) {
    d.width = m_imgWidth; d.height = m_imgHeight;
    return demo(d.width, d.height, false, 1);
  }

}