package yi.iu.processor.texture.reg;

import java.awt.*;
import java.util.*;

import yi.ds.*;
import yi.iu.processor.color.*;
import yi.iu.processor.color.quan.*;
import yi.iu.processor.morph.*;
import yi.iu.region.*;
import yi.math.*;
import yi.util.config.*;
import yi.util.io.*;
import yi.util.io.input.*;
import yi.util.io.output.*;
import yi.util.log.*;
import yi.iu.processor.resample.*;

/**
 * <P>
 * Class ColorPatternSeg is kind of texture based segmentation, but using color
 * information.  The idea is from the observation that some texture are made of
 * some specific colors.  So if, say, two color regions occupy the same region,
 * then they are probably from the same texture region.
 * </P>
 *
 * <P>
 * The image is first re-represented by only a few of colors.  This work can be
 * finished by yi.iu.processor.color.DPU_DominantColor,
 * yi.iu.processor.color.DPU_GongProminentColor, or
 * yi.iu.processor.color.quan.SmithColorQuantization.
 * </P>
 *
 * <P>
 * The relationship between the color regions are considered.  If two regions
 * are overlapped a lot (determined by threshold, COMBINE_THRESHOLD), then they
 * will be merged to one region.
 * </P>
 *
 * <P>
 * The resultant regions from this class is by no mean a perfect one in terms of
 * texture segmentation, but it can be the pre-step of other texture based (or
 * other feature based) segmentation algorithm.  For example, after getting the
 * color pattern regions, do further segmentation within each of them based on
 * Gabor feature to get more precise texture segmentation
 * </P>
 */
public class ColorPatternSeg implements Configurable, RstReusable{

  /**
   * In the procedure, two color regions will be combined if they are overlapped
   * a lot.  This is the threshold.
   */
  public double COMBINE_THRESHOLD = 0.5;

  /**
   * After the image is represented by dominant colors, connected component
   * analysis is performed to group one color pixels to different group
   * according their spatial location.  However, the pixels of one color within
   * a texture region may be very sparse and disjointed, so a dilation operation
   * is performed before the connected component analysis to make sure those
   * within-texture-color will be grouped into a single region.
   * In addition, color regions are dilated before the exploration of their
   * relationship.  As a result, two colors making up a texture region will be
   * overlapped.
   */
  public int    DILATION_DISK_SIZE = 5;

  /**
   * Gussian smooth theta.  theta = GAUSSIAN_THETA * Math.sqrt(width*height).
   * Eg. width = 600, height = 400, theta = around 5
   * Used in the pre-process of connected component analysis on the texture
   * segments.
   */
  public double GAUSSIAN_THETA = 0.01;

  /**
   * filter threshold.  detail comparison see cv010
   * Used in the pre-process of connected component analysis on the texture
   * segments.
   */
  public double FILTER_THRESHOLD = 0.2;

  public int DILATION_METHOD_2_FIND_CC = ConnectedRegionGenerator.PRE_SMOOTH_DILATION;

  /**
   * One method can be used to determine whether two region should be combined.
   * The regions will be first dilated (by the size of DILATION_DISK_SIZE) and
   * then the overlaping area is calculated.  It the overlapping area ratio is
   * greater than COMBINE_THRESHOLD, they will be combined.
   * @see #COMBINATION_METHOD
   */
  public final static int MORPHOLOGY_BASE_COMBINATION = 0;

  /**
   * One method can be used to determine whether two region should be combined.
   * The regions are first represented by ellipse.  The overlapping area of
   * the ellipses will be treated as the overlapping area of two regions.
   * @see #COMBINATION_METHOD
   */
  public final static int ELLIPSE_BASE_COMBINATION = 1;

  /**
   * The method to be used to combine connected color regions.
   * @ses #MORPHOLOGY_BASE_COMBINATION, #ELLIPSE_BASE_COMBINATION
   */
  public int COMBINATION_METHOD = MORPHOLOGY_BASE_COMBINATION;


  /**
   * Use Gong's prominent color finding algorithm
   * (yi.iu.processor.color.DPU_GongProminentColor) at the first step to
   * re-represent the image with only a few number of colors
   */
  public final static int GONG_PROMINENT_COLOR = 0;

  /**
   * Use Yi's dominant color finding algorithm
   * (yi.iu.processor.color.DPU_DominantColor) at the first step to
   * re-represent the image with only a few number of colors
   */
  public final static int DOMINANT_COLOR = 1;

  /**
   * Use Smith's color quantization algorithm
   * (yi.iu.processor.color.quan.SmithColorQuantization) at the first step to
   * re-represent the image with only a few number of colors
   */
  public final static int SMITH_COLOR_QUANTIZATION = 2;

  public final static int HUE_QUANTIZATION = 3;

  /**
   * The method to be used at the first step to
   * re-represent the image with only a few number of colors
   * @see #GONG_PROMINENT_COLOR, #DOMINANT_COLOR, #SMITH_COLOR_QUANTIZATION
   */
  public int COLOR_FINDING_ALGORITHM = GONG_PROMINENT_COLOR;


  /**
   * DEMO_CONNECTED_COLOR_REGIONS is a flag to indicate whether to output
   * the intermedia result of connected color regions
   */
  public boolean DEMO_CONNECTED_COLOR_REGIONS = false;

  /**
   * DEMO_COLOR_PATTERN_REGION_ONE_BY_ONE is a flag to indicate whether to
   * output the resultant color pattern regions one by one
   */
  public boolean DEMO_COLOR_PATTERN_REGION_ONE_BY_ONE = false;

  public int RESAMPLER = Resampler.NEAREST_RESAMPLER;



  public int    REUSE  = DIU.INPUT_NONE;
  public int    OUTPUT = DOU.OUTPUT_NONE; // only support SER yet;

  public int    LOG_LEVEL = 3;

  public String[] getParameterNames() {
    return new String[] {
      "COMBINE_THRESHOLD",
      "DILATION_DISK_SIZE",
      "COLOR_FINDING_ALGORITHM",
      "DILATION_METHOD_2_FIND_CC",
      "COMBINATION_METHOD",
      "DEMO_CONNECTED_COLOR_REGIONS",
      "DEMO_COLOR_PATTERN_REGION_ONE_BY_ONE",
      "RESAMPLER",
      "LOG_LEVEL",
      "REUSE",
      "OUTPUT",
    };
  }

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

  /**
   * Using color information to do texture based segmentation.  The image is
   * first re-represented by only a few of colors.  Then the relationship
   * between those color regions are considered.  If two regions are overlapped
   * a lot (determined by the threshold, COMBINE_THRESHOLD), then they will be
   * merged to one region.
   * @param pixels the rgb color pixels of the image
   * @param width image width
   * @param height image height
   * @return color pattern regions
   */
  public DS_ColorPatternRegions getColorPatternRegions(int[] pixels, int width, int height) {

    // re-represent the image with only a few colors
    int color_num;
    int[] dc_labels;  // dominant color numbers

    if (COLOR_FINDING_ALGORITHM == GONG_PROMINENT_COLOR) {
      DPU_DominantColor dc = new DPU_GongProminentColor();
      DS_DominantColor dc_rst = dc.getDominantColors(pixels, width, height);
      Collection colors = dc_rst.getDominantColors();
      color_num = colors.size();
      dc_labels = dc_rst.getColorMap();
    } else if (COLOR_FINDING_ALGORITHM == DOMINANT_COLOR) {
      DPU_DominantColor dc = new DPU_DominantColor();
      DS_DominantColor dc_rst = dc.getDominantColors(pixels, width, height);
      Collection colors = dc_rst.getDominantColors();
      color_num = colors.size();
      dc_labels = dc_rst.getColorMap();
    } else if (COLOR_FINDING_ALGORITHM == SMITH_COLOR_QUANTIZATION) {
      SmithColorQuantization cq = new SmithColorQuantization();
      dc_labels = cq.quantize(pixels, width, height, null);
      yi.util.label.Label.sequential(dc_labels, null, false);
      color_num = yi.stat.StatUtil.maximum(dc_labels) + 1;
    } else {
      HueQuantizer hq = new HueQuantizer(1.0/6, 16, 4);
      dc_labels = hq.quantize(pixels);
      yi.util.label.Label.sequential(dc_labels, null, false);
      color_num = yi.stat.StatUtil.maximum(dc_labels) + 1;
    }

    // find connected regions
    // region num 0: background
    int[] cc_labels = preConnectedComponentAnalysis(dc_labels, width, height);
    int cc_region_num = yi.stat.StatUtil.maximum(cc_labels) + 1;

//    // construct the connected region -> color label map
//    int[] cc2color_map = yi.util.label.Label.getMap(cc_labels, dc_labels);

    // demo the regions before combining
    if (DEMO_CONNECTED_COLOR_REGIONS) {
      int[] region_img = yi.util.ImageUtil.makeNewImageFromLabel(cc_labels);
      String img_name = Configuration.getStringParameter("SOURCE_NAME", "unknown");
      yi.util.io.OutputResult.outputGIFImage(img_name + "_color_cc.gif", region_img, width, height);
    }

//    // demo the regions one by one
//    int[] img = new int[region_label.length];
//    for (int i=1; i<=region_num; i++) {
//      for (int j=0; j<region_label.length; j++) {
//        if (region_label[j] == i) img[j] = 0xffffffff;
//        else img[j] = 0xff000000;
//      }
//      OutputResult.outputGIFImage(img_name + "_region_" + i + ".gif", img, width, height);
//    }


    // combine the connected color regions
    int[] cc2combined_map;
    if (COMBINATION_METHOD == ELLIPSE_BASE_COMBINATION) {
      // ellipse based combination
      cc2combined_map = ellipseBasedCombination(cc_labels, width, height, cc_region_num);
    }
    else { // COMBINATION_METHOD == MORPHOLOGY_BASE_COMBINATION
      // morphology based combination
      cc2combined_map = morphologyBasedCombination(cc_labels, width, height, cc_region_num);
    }

    int combined_label_num = yi.stat.StatUtil.maximum(cc2combined_map) + 1;

    // relabel the pixels according to the combination result
    int[] combined_label = new int[pixels.length];
    for (int i=0; i<combined_label.length; i++) {
      combined_label[i] = cc2combined_map[cc_labels[i]];
    }

    // recalculate the main colors in each region
    IntArrayCumulator[][] region_color_pattern = new IntArrayCumulator[combined_label_num][color_num];
    for (int i=0; i<pixels.length; i++) {
      int region_label = combined_label[i];
      int color_label = dc_labels[i];
      if (region_color_pattern[region_label][color_label] == null) {
        region_color_pattern[region_label][color_label] = new IntArrayCumulator();
      }
      int[] rgb = ColorUtil.RGB2IntArray(pixels[i]);
      region_color_pattern[region_label][color_label].add(rgb);
    }
    // construct ColorPatternRegion(s)
    Region[] regions = Region.getRegions(combined_label, combined_label_num-1, new Dimension(width, height));
    ColorPatternRegion[] color_pattern_regions = new ColorPatternRegion[combined_label_num];
    for (int i=0; i<combined_label_num; i++) {
      int color_count = 0;
      for (int j=0; j<region_color_pattern[i].length; j++) {
        if (region_color_pattern[i][j] != null) color_count ++;
      }
      int[] pattern_colors = new int[color_count];
      int[] counts = new int[color_count];
      color_count = 0;
      for (int j=0; j<region_color_pattern[i].length; j++) {
        if (region_color_pattern[i][j] != null) {
          pattern_colors[color_count] = ColorUtil.intArray2RGB(region_color_pattern[i][j].getMeans());
          counts[color_count] = region_color_pattern[i][j].getCount();
          color_count ++;
        }
      }
      color_pattern_regions[i] = new ColorPatternRegion(pattern_colors, counts, regions[i]);
    }

//    // convert the format of dominant colors
//    Object[] objects = colors.toArray();
//    CIELab[] colors_array = new CIELab[objects.length];
//    for (int i=0; i<objects.length; i++) colors_array[i] = (CIELab)objects[i];

    // return the results
    return new DS_ColorPatternRegions(
      combined_label, combined_label_num, color_pattern_regions, width, height);
  }

  protected int[] preConnectedComponentAnalysis(int[] labels, int width, int height) {
    ConnectedRegionGenerator crg = new ConnectedRegionGenerator();
    crg.DILATION_DISK_SIZE = DILATION_DISK_SIZE;
    crg.GAUSSIAN_THETA = GAUSSIAN_THETA;
    crg.FILTER_THRESHOLD = FILTER_THRESHOLD;
    // region num 0: background
    int[] cc_labels = crg.getConnectedRegions(labels, width, height, DILATION_METHOD_2_FIND_CC);
    return cc_labels;

  }

  /**
   * Return the neighour matrix of regions.
   * @param labels the region label of each pixel
   * @param label_num the number of regions
   * @param width  image width
   * @param height image height
   * @return the neighour matrix.  A true in [i][j] means region #i and #j are
   *         neighours.
   */
  private boolean[][] getNeibhourMatrix(int[] labels, int label_num, int width, int height) {
    boolean[][] neighour_matrix = new boolean[label_num][label_num];
    int i = 0;


    for (int y=0; y<height; y++) {
      for (int x=0; x<width; x++, i++) {
        if (x>0 && labels[i]!=labels[i-1]) neighour_matrix[labels[i]][labels[i-1]]=true;
        if (x+1<width && labels[i]!=labels[i+1]) neighour_matrix[labels[i]][labels[i+1]]=true;
        if (y>0 && labels[i]!=labels[i-width]) neighour_matrix[labels[i]][labels[i-width]]=true;
        if (y+1<height && labels[i]!=labels[i+width]) neighour_matrix[labels[i]][labels[i+width]]=true;
      }
    }
    // make it digonal
    for (i=0; i<label_num; i++) {
      for (int j=0; j<label_num; j++) {
        if (neighour_matrix[i][j]) neighour_matrix[j][i]=true;
        if (neighour_matrix[j][i]) neighour_matrix[i][j]=true;
      }
    }
    return neighour_matrix;

  }

  /**
   * Combine regions which are overlapped a lot.
   * @param region_label the region labels of every pixel
   * @param width the image width
   * @param height the image height
   * @param region_num the number of regions
   * @return the label mapping of the combination.  E.g. {0,1,1,2,2} means
   *         pixels with label #0 will be mapped to 0; those with #1 and #2 will
   *         be mapped to #1; those with #3 and #4 will be mapped to #2.
   */
  int[] morphologyBasedCombination(int[] region_label, int width, int height, int region_num) {
    // combine the regions (ignore 0, because it is background)
    Merge merge = new Merge(region_num);  // union-find structure

    Dilation dilation = new Dilation(Morph.DISK, DILATION_DISK_SIZE);

    // for two regions
    int[] binary1 = new int[region_label.length], binary2 = new int[region_label.length];

    // first see whether two regions are possible to be merged
    boolean[][] neighbour_matrix = getNeibhourMatrix(region_label, region_num+1, width, height);

    for (int i=1; i<=region_num; i++) { // don't touch #0 since it is background
      for (int k=0; k<region_label.length; k++) binary1[k] = (region_label[k] == i)?1:0;
      binary1 = dilation.getProcessed(binary1, new Dimension(width, height));
      for (int j=i+1; j<=region_num; j++) {
        if (!neighbour_matrix[i][j]) {
          continue;
        }
        for (int k=0; k<region_label.length; k++) binary2[k] = (region_label[k] == j)?1:0;
        binary2 = dilation.getProcessed(binary2, new Dimension(width, height));

        int region1_size = 0, region2_size = 0, overlap_area = 0;
        for (int k=0; k<region_label.length; k++) {
          if (binary1[k] != 0) region1_size ++;
          if (binary2[k] != 0) region2_size ++;
          if (binary1[k] != 0 && binary2[k] != 0) overlap_area ++;
        }

        boolean isToCombine = (overlap_area > region1_size * COMBINE_THRESHOLD) ||
                              (overlap_area > region2_size * COMBINE_THRESHOLD);

        if (isToCombine) {
          merge.merge(i, j);
//          int[] b1 = yi.util.ImageUtil.makeNewImageFromLabel(binary1, true);
//          OutputResult.outputGIFImage("bin1" + i + ".gif", b1, width, height);
//          int[] b2 = yi.util.ImageUtil.makeNewImageFromLabel(binary2, true);
//          OutputResult.outputGIFImage("bin2" + j + ".gif", b2, width, height);
        }
      }
    }

    // relabel the pixels according to the combination result
    int[] label_map = merge.decentResult();
    return label_map;
  }

  int[] morphologyBasedCombination2(int[] region_label, int width, int height, int region_num) {
    // combine the regions (ignore 0, because it is background)
    Merge merge = new Merge(region_num);  // union-find structure

    Dilation dilation = new Dilation(Morph.DISK, DILATION_DISK_SIZE);

    // for two regions
    int[] binary1 = new int[region_label.length], binary2 = new int[region_label.length];

    // first see whether two regions are possible to be merged
    boolean[][] neighbour_matrix = getNeibhourMatrix(region_label, region_num+1, width, height);

    int[][] overlap_count = new int[region_num+1][region_num+1];
    int[] dilated_size = new int[region_num+1];

    for (int i=1; i<=region_num; i++) { // don't touch #0 since it is background
      for (int k=0; k<region_label.length; k++) binary1[k] = (region_label[k] == i)?1:0;
      binary1 = dilation.getProcessed(binary1, new Dimension(width, height));

      int[] b1 = yi.util.ImageUtil.makeNewImageFromLabel(binary1, true);
      OutputResult.outputGIFImage("bin1" + i + ".gif", b1, width, height);

      for (int j=i+1; j<=region_num; j++) {
        if (!neighbour_matrix[i][j]) {
          continue;
        }
        for (int k=0; k<region_label.length; k++) binary2[k] = (region_label[k] == j)?1:0;
        binary2 = dilation.getProcessed(binary2, new Dimension(width, height));

        int region1_size = 0, region2_size = 0, overlap_area = 0;
        for (int k=0; k<region_label.length; k++) {
          if (binary1[k] != 0) region1_size ++;
          if (binary2[k] != 0) region2_size ++;
          if (binary1[k] != 0 && binary2[k] != 0) overlap_area ++;
        }

        dilated_size[i] = region1_size;
        dilated_size[j] = region2_size;
        overlap_count[i][j] = overlap_count[j][i] = overlap_area;
        System.out.println("size[" + i + "] = " + dilated_size[i]);
        System.out.println("size[" + j + "] = " + dilated_size[j]);
        System.out.println("overlap[" + i + "][" + j + "] = " + overlap_count[i][j] );
      }
    }

    // merge the overlapped regions
    // find the smallest region first, find its most overlapped region, if the
    // overlapped area is greater than the threshold, COMBINE_THRESHOLD, merge
    // them; otherwise, pick this region out (it will not be merged with others)
    // When a region is picked up (considered), mark it as -1; when it is merged
    // with others, mark it as 0
    while (true) {
      // find the smallest
      int smallest_area = width * height + 1;
      int smallest_index = -1;
      for (int i=1; i<=region_num; i++) {
        if (dilated_size[i] > 0 && dilated_size[i] < smallest_area) {
          smallest_area = dilated_size[i];
          smallest_index = i;
        }
      }
      // if no smallest exist, end the merge
      if (smallest_index < 0) break;

      // find the most overlapped region of the smallest region
      int max_overlapped_area = 0;
      int max_overlapped_index = -1;
      for (int i=1; i<=region_num; i++) {
        if (dilated_size[i] <= 0) continue;                 // if considered already
        if (overlap_count[smallest_index][i] > max_overlapped_area) {
          max_overlapped_area = overlap_count[smallest_index][i];
          max_overlapped_index = i;
        }
      }

      // see whether the overlapped area is greater than the threshold
      if (max_overlapped_area > dilated_size[smallest_index] * COMBINE_THRESHOLD) {
        // merge
        merge.merge(smallest_index, max_overlapped_index);
        System.out.println("merge " + smallest_index + " to " + max_overlapped_index);
        // combine the dilated_size
        dilated_size[max_overlapped_index] += dilated_size[smallest_index];
        dilated_size[smallest_index] = 0;
        // combine the overlap count
        for (int i=1; i<=region_num; i++) {
          if (i == max_overlapped_index || i == smallest_index) continue;
          overlap_count[max_overlapped_index][i] += overlap_count[smallest_index][i];
          overlap_count[i][max_overlapped_index] = overlap_count[max_overlapped_index][i];
        }
      } else {
        dilated_size[smallest_index] = -1;
      }

    }

    // relabel the pixels according to the combination result
    return merge.decentResult();
  }

  /**
   * Decide whether two regions represented in ellipse format (class Region)
   * should be combined.
   * @param r1 region #1
   * @param r2 region #2
   * @return true for combining and false for not
   */
  boolean isToCombine(Region r1, Region r2) {
    // while region uses the length of whole major/minor axis; ellipse uses a and b which is half
    MathEllipse e1 = new MathEllipse(r1.m_centroidX, r1.m_centroidY, r1.m_majLength/2, r1.m_minLength/2, r1.m_majAngle);
    MathEllipse e2 = new MathEllipse(r2.m_centroidX, r2.m_centroidY, r2.m_majLength/2, r2.m_minLength/2, r2.m_majAngle);
//    System.out.println(e1);
//    System.out.println(e2);

    double score1 = e1.overlap(e2, 11, 11);
    double score2 = e2.overlap(e1, 11, 11);
    if (score1 < score2) score1 = score2;
    System.out.print(score1);
    if (score1 > COMBINE_THRESHOLD) return true;
//    if (e1.overlap(e2, 11, 11) > COMBINE_THRESHOLD || e2.overlap(e1, 11, 11) > COMBINE_THRESHOLD) return true;
    else return false;
  }

  /**
   * Perform region combination based on their ellipse representation.  Each
   * pair of regions will be considered.  The main functionality to make the
   * decision is implemented in #isToCombine(Region, Region)
   *
   * @param region_label the region labels of every pixel
   * @param width the image width
   * @param height the image height
   * @param region_num the number of regions
   * @return the label mapping of the combination.  E.g. {0,1,1,2,2} means
   *         pixels with label #0 will be mapped to 0; those with #1 and #2 will
   *         be mapped to #1; those with #3 and #4 will be mapped to #2.
   */
  int[] ellipseBasedCombination(int[] region_label, int width, int height, int region_num) {
    // combine the regions (ignore 0, because it is background)
    Region[] regions = Region.getRegions(region_label, region_num + 1, new Dimension(width, height));
    Merge merge = new Merge(region_num);  // union-find structure
    for (int i=1; i<=region_num; i++) {
      for (int j=i+1; j<=region_num; j++) {
        System.out.print(String.valueOf(i) + "," + j + ": ");
        if (isToCombine(regions[i], regions[j])) {
          merge.merge(i, j);
          System.out.println("  - combined!");
//          Region[] regs = new Region[] {regions[i], regions[j]};
//          int[] colors2 = new int[] {0xffff0000, 0xff00ff00};
//          Arrays.fill(img, 0xff000000);
//          Region.drawRegions(img, new Dimension(width, height), regs, colors2);
//          OutputResult.outputGIFImage(img_name + "_c_" + i + "_" + j + ".gif", img, width, height);
        }
        else  System.out.println();
      }
    }

    // relabel the pixels according to the combination result
    int[] label_map = merge.decentResult();
    return label_map;
  }


  public Object reuse(int reuse_level) {
    if (reuse_level != DIU.INPUT_NONE) {
      try {
        DS_ColorPatternRegions rst = (DS_ColorPatternRegions)DIU.in(
            reuse_level,
            DOU.shortName(this.getClass().getName()),
            "DS_ColorPatternRegions",
            Class.forName("yi.iu.processor.texture.reg.DS_ColorPatternRegions"));
        return rst;
      }
      catch (Exception e) {
        LogHandler.log(this, LOG_LEVEL, 1, "Can't Reuse DS_ColorPatternRegions:\n" + e);
      }
    }
    return null;
  }

  void output(int output_level, DS_ColorPatternRegions ds){
    if (output_level != DOU.OUTPUT_NONE) {
      try {
        DOU.out(
            output_level,
            DOU.shortName(this.getClass().getName()),
            "DS_ColorPatternRegions",
            ds);
      } catch (DataOutputException e) {
        LogHandler.log(this, LOG_LEVEL, 1, e.toString());
      }
    }
  }

  public DS_ColorPatternRegions getReusableColorPatternRegions(int[] pixels, int width, int height) {
      DS_ColorPatternRegions rst = (DS_ColorPatternRegions)reuse(REUSE);

      if (rst == null) {
        rst = getColorPatternRegions(pixels, width, height);
      }

      output(OUTPUT, rst);

      return rst;

  }

  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);
    }

    // whether scale the image first
    String scale_str = ReadArgu.getOption('s', args);

    // read in image
    Image image = null;
    Dimension d = new Dimension();
    int[] pixels = null;
    try {
      image = yi.util.io.ImageReader.readImageFile(img_name);
      pixels = yi.util.io.ImageReader.getPixels(image, d);
    }
    catch (java.io.IOException e) {
      System.err.println("Can't read image " + img_name);
      e.printStackTrace();
      System.exit(2);
    }

    try {
      ColorPatternSeg dp = new ColorPatternSeg();

      if (scale_str != null) {
        int new_size = Integer.parseInt(scale_str);
        ResampledDescriptor resampled = Resampler.scaleDown(pixels, d, dp.RESAMPLER, new_size);
        if (resampled != null) {
          pixels = resampled.m_resampled;
          d = resampled.m_newDimension;
        }
        img_name = img_name + Resampler.RESIZED_FILE_SUFFIX + new_size;
      }

//      OutputResult.outputColorImage(img_name + ".ppm", pixels, d.width, d.height);

      // set global variables
      Configuration.setParameter("SOURCE_NAME", img_name);
      yi.util.ImageUtil.setPixels(pixels);
      yi.util.ImageUtil.setDimension(d);

      // process
      long startTime = System.currentTimeMillis();
      DS_ColorPatternRegions rst = dp.getReusableColorPatternRegions(pixels, d.width, d.height);
      long endTime = System.currentTimeMillis();
      System.out.println("running time: " + (endTime-startTime));

      // demo the result
      int[] labels = rst.m_labels;
      int[] region_img = yi.util.ImageUtil.makeNewImageFromLabel(labels);
      yi.util.io.OutputResult.outputGIFImage(img_name + "_cps.gif", region_img, d.width, d.height);

      // demo the regions one by one
      if (dp.DEMO_COLOR_PATTERN_REGION_ONE_BY_ONE) {
        int region_num = yi.stat.StatUtil.maximum(labels) + 1;
        Region.outputEachRegion(img_name + ".region", pixels, labels, region_num, d.width, d.height, 0xffffffff);
        // demo the colors for each region
        ColorPatternRegion[] regions = rst.getColorPatternRegions();
        for (int i=0; i<region_num; i++) {
          int[] colors = regions[i].m_colors;
          int[] color_spectrum = yi.iu.processor.color.ColorUtil.createSpectrum(colors, 20, 20);
          OutputResult.outputGIFImage(img_name + ".region.colors." + i + ".gif", color_spectrum, 20 * colors.length, 20);
        }
      }

      System.exit(0);
    } catch (Exception e) {
      System.err.println("ColorPatternRegion Terminated.");
      e.printStackTrace();
    }
  }


}