package yi.util.label;

import yi.ds.*;
import yi.util.io.OutputResult;

/**
 * Helper functions for labeling
 * Parts of the codes are based on Todd's work
 * Title:        Object Image Generation
 * Description:  This project provides the required utilities to construct Object Images.  An Object Image is an image containing one or several predefined object sketch.  This kind of image is suitable to be used as experiment image when testing a learning algorithm.
 * Copyright:    Copyright (c) 2002
 * Company:      University of Washington
 * @author Yi Li
 * @version 1.0
 */

public class Label {

    /**
     * Relabel labels to make it sequential.  Negative labels will be ignored.
     * modifies: labels
     * @param labels original labels
     * @param map the map between the new label and old label; eg map[5] is the
     * the new label number for old label 5.  If map is null, then it will be
     * ignored.  The mapping will less than 0 if the label is empty
     * @param ignoreZero label 0 will be leave untouched (still be 0)
     * @return maxLabel (the lable number is maxLabel + 1 because of 0)
     */
    public static int sequential(int[] labels, int[] map, boolean ignoreZero)
    {
        int numLabels = labels.length;

        int count;

        // determine max label value
        int maxVal = yi.stat.StatUtil.maximum(labels);

        // create an array, the indices of which will
        // be the old labels from the 'lables' array, the values
        // in each field will be the new labels.
        int[] valArray = (map == null) ? (new int[maxVal+1]) : map;


        // initialize array to -1
        for(count=0; count<valArray.length; count++)
        {
            valArray[count] = -1;
        }

        // if ignoreZero then we will leave zero labels zero
        if(ignoreZero)
            valArray[0] = 0;

        // go through array of labels and assign new lables
        // (these will be sequential starting from 1)
        int index = ignoreZero? 1 :0;           // keep track of new label
        for(count=0; count<numLabels; count++)
        {
            if (labels[count] < 0) continue;    // ignore the negatives
            if(valArray[labels[count]]== -1)
            {
                // assign the next available label
                valArray[labels[count]]=index;
                index++;
            }
        }

        // assign the new labels
        for(count=0; count<numLabels; count++)
        {
            if (labels[count]<0) continue;
            labels[count]=valArray[labels[count]];
        }

        // return the max labels number
        return (index - 1);

    }

    /**
     * Return the maximum number in an integer array.  The result is inclusive
     * which means the returned number exists in the array.
     * @param data the integer array
     * @return the maximum
     */
    public static int getMax(int[] data) {
      return yi.stat.StatUtil.maximum(data);
    }

    /**
     * Construct the map from one int array to another.  It is useful when the
     * mapping between two label arrays are expected.  For example,
     * from = {0,0,1,1,2,2}, to = {0,0,0,0,1,1}, and the returned map will be
     * {0,0,1} because the value 0 in array "from" is mapped to value 0, 1 to 0,
     * and 2 to 1.  The caller is responsible for the consistency of the
     * mapping.  For example, a mapping from {0,1,1} to {0,1,2} is not
     * consistent since 1 is mapped 1 and 2.
     * @param from the label array mapped from
     * @param to the label array mapped to.
     * @return the mapping
     */
    public static int[] getMap(int[] from, int[] to) {
      int map_len = yi.stat.StatUtil.maximum(from) + 1;
      int[] map = new int[map_len];
      for (int i=0; i<from.length; i++) map[from[i]] = to[i];
      return map;
    }

    /**
     * Construct a label with width of "width" and height of "height".  The
     * label array is segmented to rectangle patches with width of "patch_width"
     * and height of "patch_height".  The top-left patch is labeled as "0", the
     * one right to it is labeled as "1", ..., and so on.  After reaching the
     * end of the first row, the labeling continues from the beginning of the
     * next row.
     * patches have a hig
     * @param width
     * @param height
     * @param patch_width
     * @param patch_height
     * @return
     */
    public static int[] labelPatches(int width, int height, int patch_width, int patch_height) {
      int[] label = new int[width * height];
      int patches_hnum = (width + patch_width - 1)/ patch_width;
      for (int y=0; y<height; y++) {
        int patches_y = y / patch_height;
        for (int x = 0; x < width; x++) {
          int patches_x = x / patch_width;
          int patch_label = patches_y * patches_hnum + patches_x;
          label[y * width + x] = patch_label;
        }
      }
      return label;
    }


    /**
     * Filter small labels if their counts are less than the threshold.  Those
     * labels will be set to 0.  So 0 must be left for this purpose.  Other
     * labels will be kept the same.  No sequencing is performed in this method.
     * @param labels
     * @param size_threshold
     */
    public static void filterSmallRegions(int[] labels, int size_threshold) {

      // filtering small component
      int max_cc_label = yi.stat.StatUtil.maximum(labels);
      int[] sizes = new int[max_cc_label + 1];
      for (int i = 0; i < labels.length; i++)
        sizes[labels[i]]++;

      for (int i = 1; i <= max_cc_label; i++) {
        if (sizes[i] < size_threshold)
          sizes[i] = 0;
        else
          sizes[i] = i;
      }
      sizes[0] = 0; // background remain the same
      for (int i = 0; i < labels.length; i++)
        labels[i] = sizes[labels[i]];
    }

    /**
     * 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
     * @return
     */
    public static double[][] getMeans(double[][] data, int[] labels) {

      // get the number of labels
      int n_labels = yi.stat.StatUtil.maximum(labels) + 1;

      // use DoubleArrayCumulator to calculate the means
      DoubleArrayCumulator[] cumulators = new DoubleArrayCumulator[n_labels];
      for (int i=0; i<n_labels; i++) cumulators[i]= new DoubleArrayCumulator();
      for (int i=0; i<data.length; i++) {
        cumulators[labels[i]].add(data[i]);
      }

      double[][] means = new double[n_labels][];
      for (int i=0; i<means.length; i++) {
        means[i] =(cumulators[i].getCount() != 0)? cumulators[i].getMeans(): null;
      }
      return means;
    }

    /**
     * Return the number of each label.
     * @param labels
     * @return
     */
    public static int[] getCounts(int[] labels) {
      // get the number of labels
      int n_labels = yi.stat.StatUtil.maximum(labels) + 1;

      int[] counts = new int[n_labels];
      for (int i=0; i<labels.length; i++) {
        counts[labels[i]]++;
      }
      return counts;
    }

}