package yi.iu.region;

import java.awt.Dimension;
import yi.util.config.*;
import yi.iu.processor.morph.*;
import yi.iu.processor.filter.GaussianSmooth;
import yi.util.label.Label;

/**
 * Class ConnectedRegionGenerator generate connected regions from a given
 * labeled image.  After the pixels of an image is clustered using its features
 * (e.g. color or texture), the spatial relation of them may be not considered
 * yet, then it is necessary to use connected component analysis to further
 * group them to get spatial information into consideration.  The procedure is
 * made of three steps: 1. morphology (e.g. closing); 2. connected component;
 * analysis; 3. filtering of small size regions.
 */
public class ConnectedRegionGenerator implements Configurable{

  public final static int BACKGROUND = 0;

  public final static int PRE_SMOOTH_NONE     = 0;
  public final static int PRE_SMOOTH_CLOSING  = 1;
  public final static int PRE_SMOOTH_GAUSSIAN = 2;
  public final static int PRE_SMOOTH_DILATION = 3;

  /**
   * Gussian smooth theta.  theta = GAUSSIAN_THETA * Math.sqrt(width*height).
   * Eg. width = 600, height = 400, theta = around 5
   */
  public double GAUSSIAN_THETA = 0.01;

  /**
   * filter threshold.  detail comparison see cv010
   */
  public double FILTER_THRESHOLD = 0.2;

  /**
   * If a region is smaller than x*y*MINIMUM_REGION_SIZE, it will be discarded.
   */
  public double MINIMUM_REGION_SIZE = 0.01;

  /**
   * The regions are first filtered using closing and this is the size of the
   * disk used.
   */
  public int    CLOSING_DISK_SIZE = 5;
  public int    DILATION_DISK_SIZE = 5;

  /**
   * Whether to generate the demonstration image of the procedure
   */
  public boolean DEMO_FILTERING = false;

  public String[] getParameterNames() {
    return new String[] {
      "DEMO_FILTERING"
    };
  }

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

  /**
   * How many resultant regions.  (including background region labeled as 0)
   */
  int m_regionNum;

  public int getRegionNum() {
    return m_regionNum;
  }

  /**
   * Generate connected regions (including morph filter and size filter).   The
   * passed-in parameter labels don't include background label and resultant
   * background will be returned as label 0 (the value is defined in BACKGROUND).
   * @param labels
   * @param width
   * @param height
   * @param pre_filter_method
   * @param ignoreZero ingore the region labeled as 0
   * @return pixel region labels.  #0 is
   */
  public int[] getConnectedRegions(int[] labels, int width, int height, int pre_filter_method, boolean ignoreZero) {
    int pnum = labels.length; // pixel numbers

    // find the maximum label
    int maxlabels = yi.stat.StatUtil.maximum(labels);

    // init the resultant array with background
    int[] filtered = new int[pnum];
    for (int i=0; i<pnum; i++) filtered[i] = BACKGROUND;

    // deal with each label seperatly
    m_regionNum = 0;
    for (int i = ignoreZero?1:0; i <= maxlabels; i++) {  // deal with the labels one by one
      // construct the binary image corresponding one cluster
      int[] binary = new int[pnum];
      for (int j=0; j<pnum; j++) binary[j] = (labels[j]==i) ? 1 : BACKGROUND;

      // demo the binary image
      if (DEMO_FILTERING) {
        int[] binary_img = yi.util.ImageUtil.makeNewImageFromBinary(binary);
        String img_name = Configuration.getStringParameter("SOURCE_NAME", "unknown");
        yi.util.io.OutputResult.outputGIFImage(img_name + "." + i + ".region.gif", binary_img, width, height);
      }

      // process on the binary image
      binary = getConnectedRegionsOnBinary(binary, width, height, pre_filter_method);

      // demonstrate the connected component analysis
      if (DEMO_FILTERING) {
        int[] filtered_img = yi.util.ImageUtil.makeNewImageFromLabel(binary);
        String img_name = Configuration.getStringParameter("SOURCE_NAME", "unknown");
        yi.util.io.OutputResult.outputGIFImage(img_name + "." + i + ".filtered.gif", filtered_img, width, height);
      }

      // gether the result on this label into the returning result
      int max = 0;
      for (int j=0; j<pnum; j++) {
        // in returned binary, 0 is background, other values should be changed
        // because other lables may have already generated regions
        if (binary[j]!=0 && labels[j]==i) filtered[j] = m_regionNum + binary[j];
        if (binary[j]>max) max = binary[j];
      }
//      System.out.println("label="+i+",max_label="+max_label+",start_label="+start_label);
      m_regionNum += max;
//      segimg = yi.util.ImageUtil.makeNewImageFromLabel_back(filtered);
    }

    m_regionNum ++; // pixels have been labeled [0, m_regionNum] before

    return filtered;
  }

  /**
   * Generate connected regions (including morph filter and size filter).   The
   * passed-in parameter labels don't include background label and resultant
   * background will be returned as label 0 (the value is defined in BACKGROUND).
   * @param labels
   * @param width
   * @param height
   * @param pre_filter_method
   * @return
   */
  public int[] getConnectedRegions(int[] labels, int width, int height, int pre_filter_method) {
    return getConnectedRegions(labels, width, height, pre_filter_method, false);
  }

  /**
   * Generate connected regions on a binary image.  Try to do dilating ...
   * In "binary" parameter, 0 is background, 1 is the region.  return 0 for
   * background
   * @param binary
   * @param width
   * @param height
   * @return
   */
  int[] getConnectedRegionsOnBinary(int[] binary, int width, int height,
          int pre_filter_method) {
    int pnum = binary.length;

    // preprocessing: smooth the binary image
    switch (pre_filter_method) {
      case PRE_SMOOTH_NONE:
        break;
      case PRE_SMOOTH_CLOSING:
        Closing close = new Closing(Morph.DISK, CLOSING_DISK_SIZE);
        binary = close.getProcessed(binary, new Dimension(width, height));
        break;
      case PRE_SMOOTH_GAUSSIAN:
        binary = GaussianDilation.dilation(binary, width, height, GAUSSIAN_THETA, FILTER_THRESHOLD);
        break;
      case PRE_SMOOTH_DILATION:
        Dilation dilation = new Dilation(Morph.DISK, DILATION_DISK_SIZE);
        binary = dilation.getProcessed(binary, new Dimension(width, height));
        break;
    }

    if (DEMO_FILTERING) {
      int[] dilated_img = yi.util.ImageUtil.makeNewImageFromBinary(binary);
      String img_name = Configuration.getStringParameter("SOURCE_NAME", "unknown");
      yi.util.io.OutputResult.outputGIFImage(img_name + ".dilated", dilated_img, width, height, true);
    }

    // connected component
    ConnectComponent cc = new ConnectComponent();
    // find connected components (these are also sequentially labeled)
    // 0 should be treated as background
    int[] cc_labels = cc.compactLabeling(binary, new Dimension(width, height), true);

    // filtering small component
    // count component size first
    int max_cc_label = cc.getMaxLabel();
    int[] sizes = new int[max_cc_label + 1];
    for (int i=0; i<pnum; i++) sizes[cc_labels[i]]++;

    int size_threshold = (int)(MINIMUM_REGION_SIZE * width * height);
    for (int i=1; i<=max_cc_label; i++) {
      if (sizes[i] < size_threshold) sizes[i] = BACKGROUND;
      else sizes[i] = i;
    }
    sizes[BACKGROUND]=0;  // background remain the same
    for (int i=0; i<pnum; i++) cc_labels[i] = sizes[cc_labels[i]];
    Label.sequential(cc_labels,null,true);

    return cc_labels;
  }

  /**
   * Test
   */
  public static void main(String[] args) {
    int[] p = {
      0,0,0,0,0,1,0,0,2,2,
      0,2,2,2,1,1,0,0,0,0,
      0,2,2,2,1,1,0,0,0,0,
      0,0,3,3,2,0,0,0,0,0,
      0,0,3,3,0,2,2,0,0,0,
      0,0,3,3,3,2,2,0,0,0,
      1,1,1,1,3,3,3,3,0,0,
      0,0,1,1,0,3,3,1,1,0,
      0,0,3,3,1,1,1,1,1,0,
      3,3,3,3,0,0,0,0,0,0
    };
    ConnectedRegionGenerator crg = new ConnectedRegionGenerator();
    crg.CLOSING_DISK_SIZE = 2;
    int[] regions = crg.getConnectedRegions(p, 10, 10, PRE_SMOOTH_DILATION);
    yi.util.io.OutputResult.print(System.out, regions, 10, 10);
  }

}