//Source file: D:\\Projects\\src\\yi\\iu\\region\\LabeledRegionFeatures.java

package yi.iu.region;

import yi.ds.*;
import yi.iu.imgdb.learn.*;
import yi.util.label.*;
import java.awt.*;
import yi.util.io.*;
import yi.iu.processor.resample.*;
import yi.graph.polygon.GenericPolygon;


/**
 * Represent a set of region based features.  The posiition of the regions are
 * stored by a label map.
 */
public abstract class LabeledRegionFeatures  implements
    java.io.Serializable, LabeledRgnFtRepresentable, EllipseFeatureRepresentable,
    FeaturePickDemoable, FeatureScoreDemoable, ImageDemoCapable, FeatureScalable, PolygonFeatureRepresentable
{

  protected double[][] featureMeans;

  protected int m_ftVectDim;

  /**
   * Store the labels of each region
   */
  public int[] m_labels;

  /**
   * The width of the image
   */
  public int width;

  /**
   * The height of the image
   */
  public int height;


  /**
   * The width of the original image
   */
  public int originalWidth;

  /**
   * The height of the original image
   */
  public int originalHeight;

  public LabeledRegionFeatures() {};

  static final long serialVersionUID = 3254624637480127682L;
  /**
   * 0 will be always considered as background.  The labels get converted if
   * 0 are not when passed in
   * @param labels
   * @param w
   * @param h
   * @param old_w
   * @param old_h
   */
  public LabeledRegionFeatures(int[] labels, int w, int h, int old_w, int old_h, boolean zeroAsBg, int ft_vect_dim)
  {
    if (!zeroAsBg) {
      m_labels = (int[])labels.clone();
      for (int i=0; i<m_labels.length; i++) m_labels[i]++;
    } else {
      m_labels = labels;
    }
    width = w;
    height = h;
    originalWidth = old_w;
    originalHeight = old_h;
    m_ftVectDim = ft_vect_dim;
  }

  /**
   * Initialize with the feature vectors for each pixel
   * @param pixel_values
   * @param labels
   * @param w
   * @param h
   * @param old_w
   * @param old_h
   * @param zeroAsBg
   * @param ft_vect_dim
   * @deprecated use LabeledRegionFeatures(double[][] data, int[] labels,
   *             int w, int h, int old_w, int old_h, boolean zeroAsBg,
   *             int ft_vect_dim, boolean data_is_mean), in which the last
   *             parameter tells whether the data pixel-based or region-based.
   *             The data are the means of region if it is region-based
    this(labels, w, h, old_w, old_h, zeroAsBg, ft_vect_dim);

   */
  public LabeledRegionFeatures(double[][] pixel_values, int[] labels, int w, int h, int old_w, int old_h, boolean zeroAsBg, int ft_vect_dim)
  {
    this(labels, w, h, old_w, old_h, zeroAsBg, ft_vect_dim);
    featureMeans = getLabeledRgnMeans(pixel_values, m_labels, width, height);
  }

  /**
   * Initialize with the feature vectors for each pixel or for each region (mean)
   * @param data
   * @param labels
   * @param w
   * @param h
   * @param old_w
   * @param old_h
   * @param zeroAsBg
   * @param ft_vect_dim
   * @param data_is_mean specify whether the data given is the feature vectors
   *        for all the pixels or the mean feature vectors for all the regions
   */
  public LabeledRegionFeatures(double[][] data, int[] labels, int w, int h,
                               int old_w, int old_h, boolean zeroAsBg,
                               int ft_vect_dim, boolean data_is_mean) {
    this(labels, w, h, old_w, old_h, zeroAsBg, ft_vect_dim);

    if (data_is_mean) {
      if (!zeroAsBg) {
        featureMeans = new double[data.length + 1][];
        for (int i = 0; i < data.length; i++)
          featureMeans[i + 1] = data[i];
      }
      else {
        featureMeans = data;
      }
    } else {
      featureMeans = getLabeledRgnMeans(data, m_labels, width, height);
    }
  }

  /**
   * Scale the results.  This method is useful to save space when outputing
   * the results
   * @param scaled_width
   * @param scaled_height
   */
  public void scale(int scaled_width, int scaled_height) {
    if (scaled_width == width && scaled_height == height) return;

    // scaled result width and height
    double x_ratio = (double)scaled_width / (double)width;
    double y_ratio = (double)scaled_height / (double)height;

    // new label
    int[] scaled_labels = new int[scaled_width * scaled_height];
    int index = 0;
    for (int y=0; y<scaled_height; y++) {
      for (int x=0; x<scaled_width; x++, index++) {
        // corresponding coordinators in the results
        int corres_x = (int)Math.round(x / x_ratio);
        if (x<0) x = 0;
        if (x>=width) x=width -1;
        int corres_y = (int)Math.round(y / y_ratio);
        if (y<0) y = 0;
        if (y>=height) y=height -1;
        scaled_labels[index] = m_labels[corres_y * width + corres_x];
      }
    }
    m_labels = scaled_labels;
    width = scaled_width;
    height = scaled_height;

    // after rescale, some labels may be erased (no corresponding pixels).  In
    // that case, we'd like to sequenize the labels and modify the means
    int[] seq_map = new int[featureMeans.length];
    int seq_num = yi.util.label.Label.sequential(m_labels, seq_map, true) + 1;
    double[][] new_means = new double[seq_num][];
    for (int i = 0; i < seq_map.length; i++) {
      if (seq_map[i] < 0)
        continue;
      new_means[seq_map[i]] = featureMeans[i];
    }
    featureMeans = new_means;
  }


  /**
   * Returned the colored image to demo the regions
   * @return
   */
  public int[] demo() {
    int[] demo = yi.util.ImageUtil.makeNewImageFromLabel(m_labels, true);
    return demo;
  }

  public double[][] getFeatureMeans() {
    return featureMeans;
  }

  /**
   * Calcuate the means for each labeled regions.  Eg. the data can be the
   * RGB color values of each pixels and the label is the region label of each
   * pixel.  Then the mean rgb color value of each region is calculated and
   * returned.  The mean of label 0 (background) is calculated too, although it
   * may be useless.
   * @param data
   * @param labels
   * @param width
   * @param height
   * @return
   */
  public static double[][] getLabeledRgnMeans(double[][] data, int[] labels, int width, int height) {
    return yi.util.label.Label.getMeans(data, labels);
  }

  /**
   * Simply use means as feature vectors
   * @return
   */
  protected double[][] generateFeatureVectors(String processor_name, int mode, Dimension fv_dim) {
    fv_dim.width = m_ftVectDim;
    fv_dim.height = featureMeans.length;
    if (mode == 0)  return featureMeans;

    double[] normal_unit = null;
    if (processor_name.compareToIgnoreCase("ct") == 0) {
      normal_unit = new double[]{
        1,
        1,
        1,
        25.2,
        13.2,
        21.1,
        13.5,
        23.5,
        10.8,
        24.5,
        9.45,
        21.9,
        8.5,
        13.3,
        7.85
      };
    } else if (processor_name.compareToIgnoreCase("c2t") == 0) {
      normal_unit = new double[]{
        1,
        1,
        1,
        25.2,
        15.2,
        20.2,
        12.9,
        21.5,
        11.8,
        20.8,
        9.15,
        20.8,
        9.4,
        19.4,
        8.4
      };
    } else {
      return featureMeans;
    }

    for (int i=0; i<featureMeans.length; i++) {
      if (featureMeans[i].length != normal_unit.length) {
        System.err.println("The data length " + featureMeans[i].length +
                           " is different from that assumed by normalization " +
                           normal_unit.length + ".  Normalization skipped");
        break;
      }
      for (int j=0; j<featureMeans[i].length; j++) {
        featureMeans[i][j] /= normal_unit[j];
      }
    }
    return featureMeans;
  }

  /**
   * Convert the features of "scale" scaled image, img, generated by
   * "processor_name", to one kind of representation of mode "mode"
   * @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(processor_name, mode, fv_dim);
    int dim = fv_dim.width;

    FeatureRepresentationList list = new FeatureRepresentationList(fvs, dim);
    list.removeFirst(); // remove the background one
    return list;
  }

  public LabeledRgnFtRepList representLabeledRgnFts(String processor_name, int mode) throws Exception {
    Dimension fv_dim = new Dimension();
    double[][] fvs = generateFeatureVectors(processor_name, mode, fv_dim);
    int dim = fv_dim.width;

    LabeledRgnFtRepList list = new LabeledRgnFtRepList(fvs, dim, m_labels, width, height);
    list.removeFirst();
    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();
    double[][] fvs = generateFeatureVectors(processor_name, mode, fv_dim);
    int dim = fv_dim.width;

    Region[] ellipses = Region.getRegions(m_labels, yi.stat.StatUtil.maximum(m_labels), new Dimension(width, height));

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

  /**
   * 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) {

    // since label = 0 is background and label 1 corresponds to picked label 0
    // shift picked array by one position.  and make picked[background]=false
    boolean[] picked_with_bg = new boolean[picked.length + 1];
    for (int i=1; i<picked_with_bg.length; i++) picked_with_bg[i] = picked[i-1];
    picked[0] = false;

    try {
      int[] orig_img = ImageIOUtil.read(img_name, d);

      // rescale the image if necessary
      int scale = (width == originalWidth) ? 1 : Math.max(width, height);
      if (scale != 1 && scale != 0) {
         orig_img = Resampler.scaleDown(
            orig_img, new Dimension(d.width, d.height),
            Resampler.NEAREST_RESAMPLER, width, height);
        d.width = width;
        d.height = height;
      }

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

      for (int i = 0; i < m_labels.length; i++) {
        painted[i] = picked_with_bg[m_labels[i]] ? orig_img[i] : bg_color;
      }
      return painted;
    } catch (java.io.IOException e) {
      System.err.println("can read image " + img_name);
      return null;
    }
  }

  public int[] drawScoreDemo(Object obj, FeatureScoringCapable scoring, double shift, double scale) {
    /**@todo Implement this yi.iu.imgdb.learn.FeatureScoreDemoable method*/
    throw new java.lang.UnsupportedOperationException("Method drawScoreDemo() not yet implemented.");
  }

//  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);
//  }
//  abstract public LabeledRegionFeatures createFromFile(String processor_name, int scale, int rst_scale, String img) throws Exception;

  public int[] graphicDemo(Dimension d) {
    d.width = width; d.height = height;
    return demo();
  }
  /**
   * It is always true that labeled region features can return a color demo.
   * color based features can override this method to provide a color based demo.
   * @param d
   * @return
   */
  public int[] colorDemo(Dimension d) {
    return null;
  }

  /**
   * Minimum deviation in polygon fitting
   * Default is 1.0
   */
  public float POLYGON_MIN_ACCURACY=(float)2 ;

  /**
   * Minimum length of an edge in polygon fitting
   * Default is 3
   */
  public static int POLYGON_MIN_SEGMENT=5 ;

  public PolygonFeatureRepresentationList representPolygonFeatures(String processor_name, int mode) throws Exception {
    Dimension fv_dim = new Dimension();
    double[][] fvs = generateFeatureVectors(processor_name, mode, fv_dim);
    int dim = fv_dim.width;

    GenericPolygon[] polygons = GenericPolygon.getPolygons(m_labels, width, height,
        true, POLYGON_MIN_ACCURACY, POLYGON_MIN_SEGMENT);

    PolygonFeatureRepresentationList ft_list = new PolygonFeatureRepresentationList(dim, width, height);
    for (int i=1; i<fvs.length; i++) {
      ft_list.add(new PolygonFeatureRepresentation(fvs[i], polygons[i]));
    }
    return ft_list;
  }
}
