package yi.iu.processor.texture.laws;

import java.awt.*;

import yi.iu.processor.channel.*;
import yi.iu.processor.filter.*;
import yi.util.config.*;
import yi.util.io.*;
import yi.stat.hist.*;

/**
 * Class Laws calculates Laws Texture Energy Measures.  This is the revised
 * version of yi.iu.processor.LawsMaker.
 *
 * Part of the credits go to the program (written by yuyu ?):
 * /homes/rivers/yuyu/Law-Texture (on cs.washington.edu)
 *
 * Ref: Linda Shapiro, Computer Vision, Prentice Hall, 2001, p220-1
 */
public class Laws{
  public int PRE_SMOOTH_WIN_WIDTH  = 15;
  public int PRE_SMOOTH_WIN_HEIGHT = 15;
  public int POST_SMOOTH_WIN_WIDTH  = 15;
  public int POST_SMOOTH_WIN_HEIGHT = 15;

  /**
   * The length of convolution kernel.  For Laws, it is always 5.
   */
  static int MASK_LEN = 5;

  /**
   * 4 vectors used to calculated the 16 2D masks
   */
  static int[][] VECTORS = {
    { 1,  4, 6,  4,  1},  // L5 (Level)
    {-1, -2, 0,  2,  1},  // E5 (Edge)
    {-1,  0, 2,  0, -1},  // S5 (Spot)
    { 1, -4, 6, -4,  1}   // R5 (Ripple)
  };

  /**
   * The number of vectors. (should be 4 apparently)
   */
  static int VECTOR_NUM = VECTORS.length;

  /**
   * 16 5*5 masks.  The 1st Dimenson is the mask number; the 2nd is vertical pos;
   * and 3rd is horizontal pos.
   */
  static int[][][] MASKS = new int[VECTOR_NUM*VECTOR_NUM][MASK_LEN][MASK_LEN];
  /**
   * Generate those 16 masks.  The order are L5L5, L5E5, L5S5, L5R5, ..., R5R5.
   */
  static {
    int	i, j, m, n ;

    for (i = 0 ; i < VECTOR_NUM ; i++) {
      for (j = 0 ; j < VECTOR_NUM ; j++) {
        for (m = 0 ; m < MASK_LEN ; m++) {
          for (n = 0 ; n < MASK_LEN ; n++) {
            MASKS[i*VECTOR_NUM+j][m][n] = VECTORS[i][m] * VECTORS[j][n] ;
          }
        }
      }
    }
  }

  /**
   * Output the intermediate result of averaged image.
   */
  public boolean DEMO_AVERAGED = false;
  public boolean DEMO_CONVOLUTED = false;
  public boolean DEMO_POST_SMOOTHED = false;
  public boolean DEMO_MASK = false;
  public boolean DEMO_TEXTURE_ENERGY_MEASURES = false;

  public int[][][] getTextureEnergyMeasures(int[][] grays, int width, int height) {
    // The first step in the Laws procedure is to remove effects of illumination
    // by moving a small window around the image, and substracting the local
    // average from each pixel, to produce a preprocessed image, in which the
    // average intensity of each neighborhood is near to zero.  The size of the
    // window depends on the class of imagery; a 15*15 window was used for natural
    // scenes. (Linda Shapiro)
    int[][] average = Average.average(grays, width, height, PRE_SMOOTH_WIN_WIDTH, PRE_SMOOTH_WIN_HEIGHT);

    // save the preprocessed image in "gray" to save the memory
    for (int h=0; h<height; h++) {
      for (int w=0; w<width; w++) {
        grays[h][w] = grays[h][w] - average[h][w];
      }
    }
    average = null;

    // output the averaged result
    demoAveraged(grays, width, height);

    // After the proprocessing, each of the sixteen 5*5 masks are applied to the
    // preprocessed image, producing sixteen filtered images. ... Then the texture
    // energy map for filter i is defined Ek(L. Shapiro)
    int[][][] filtered = convolutions(grays, width, height);

    // once the 16 energy maps are produced, certain symmetric pairs are combined
    // to produce the nine final maps, replcing each pair with its average.
    for (int i = 0 ; i < height ; i++)
      for (int j = 0 ; j < width ; j++) {
        filtered[1][i][j] = (int)Math.round((filtered[1][i][j] + filtered[ 4][i][j])/2.0) ; // E5L5
        filtered[2][i][j] = (int)Math.round((filtered[2][i][j] + filtered[ 8][i][j])/2.0) ; // S5L5
        filtered[3][i][j] = (int)Math.round((filtered[3][i][j] + filtered[12][i][j])/2.0) ; // R5L5
        filtered[6][i][j] = (int)Math.round((filtered[6][i][j] + filtered[ 9][i][j])/2.0) ; // S5E5
        filtered[7][i][j] = (int)Math.round((filtered[7][i][j] + filtered[13][i][j])/2.0) ; // R5E5
        filtered[11][i][j]= (int)Math.round((filtered[11][i][j]+ filtered[14][i][j])/2.0) ; // R5S5
      }
    int[][][] rst = new int[][][] {
      filtered[ 5],  // E5E5
      filtered[10],  // S5S5
      filtered[15],  // R5R5
      filtered[ 1],  // E5L5
      filtered[ 2],  // S5L5
      filtered[ 3],  // R5L5
      filtered[ 6],  // S5E5
      filtered[ 7],  // R5E5
      filtered[11],  // R5S5
    };
    demoTextureEnergyMeasures(rst, width, height);
    return rst;
  }

  /**
   * After the proprocessing, each of the sixteen 5*5 masks are applied to the
   * preprocessed image, producing sixteen filtered images. ... Then the texture
   * energy map for filter i is defined Ek(L. Shapiro)
   *
   * @param source the source data
   * @param width the width of the source data
   * @param height the height of the source data
   * @return the convolution result
   */
  int[][][] convolutions(int[][] source, int width, int height) {
    int[][][] filtered = new int[MASKS.length][][];
    for (int i=0; i<MASKS.length; i++) {
      demoMask(i, MASKS[i], 5, 5);
      filtered[i] = convolution(source, width, height, MASKS[i]);
      demoConvoluted(i, filtered[i], width, height);
      filtered[i] = Average.averageOfAbsolutes(filtered[i], width, height,
        POST_SMOOTH_WIN_WIDTH, POST_SMOOTH_WIN_HEIGHT);
      demoPostSmoothed(i, filtered[i], width, height);
    }
    return filtered;
  }

  /**
   * Convolution calculation.
   * @param source the source data
   * @param width the width of the source data
   * @param height the height of the source data
   * @param mask the mask to be applied
   * @return the convolution result
   */
  int[][] convolution(int[][] source, int width, int height, int[][] mask) {
    int[][] result = new int[height][width];
    int half = MASK_LEN/2; // should be 2;
    int	i, j, m, n, sx, sy, ex, ey ;  // start-x,y; end-x,y
    int	cell_count, total_value ;

    for (i = 0 ; i < height ; i++) {
      for (j = 0 ; j < width ; j++) {
        sy = (i < half) ? (0) : (i - half) ;
        sx = (j < half) ? (0) : (j - half) ;
        ey = ((i + half) >= height) ? (height-1) : (i + half) ;
        ex = ((j + half) >= width) ? (width-1) : (j + half) ;
        total_value = 0 ;
        for (m = sy ; m <= ey ; m++)
          for (n = sx ; n <= ex ; n++)
            total_value += source[m][n]*mask[m-i+half][n-j+half] ;
        result[i][j] = total_value ;
      }
    }
    return result;
  }

  private void demoAveraged(int[][] grays, int width, int height) {
    if (DEMO_MASK) {
      int[] graph = yi.stat.graph.Graph.grayGraph(
                      yi.util.data.Conversion.from2Dto1D(grays, width, height)
                    );
      String filename = Configuration.getStringParameter("SOURCE_NAME", "img") + ".averaged";
      yi.util.io.OutputResult.outputGrayImage(filename + ".pgm", graph, width, height);
      yi.util.io.OutputResult.print(filename + ".rst", grays, width, height);
      yi.util.io.OutputResult.print(filename + ".1D.rst",
                                     yi.util.data.Conversion.from2Dto1D(grays, width, height), 1, width*height
                                   );
     }
  }

  private void demoMask(int i, int[][] grays, int width, int height) {
    if (DEMO_MASK) {
      int[] graph = yi.stat.graph.Graph.grayGraph(
                      yi.util.data.Conversion.from2Dto1D(grays, width, height), -36, 36
                    );
      int[] zoomed_in_graph = yi.util.ImageUtil.zoomIn(graph, width, height, 20, 20);
      String filename = "mask." + i;
      yi.util.io.OutputResult.outputGrayImage(filename + ".pgm", zoomed_in_graph, width*20, height*20);
      yi.util.io.OutputResult.print(filename + ".rst", grays, width, height);
     }
  }

  private void demoConvoluted(int i, int[][] grays, int width, int height) {
    if (DEMO_CONVOLUTED) {
      int[] graph = yi.stat.graph.Graph.grayGraph(
                      yi.util.data.Conversion.from2Dto1D(grays, width, height)
                    );
      String filename = Configuration.getStringParameter("SOURCE_NAME", "img") + ".conv" + "." + i;
      yi.util.io.OutputResult.outputGrayImage(filename + ".pgm", graph, width, height);
      yi.util.io.OutputResult.print(filename + ".rst", grays, width, height);
      yi.util.io.OutputResult.print(filename + ".1D.rst",
                                     yi.util.data.Conversion.from2Dto1D(grays, width, height), 1, width*height
                                   );
     }
  }

  private void demoPostSmoothed(int i, int[][] grays, int width, int height) {
    if (DEMO_POST_SMOOTHED) {
      int[] graph = yi.stat.graph.Graph.grayGraph(
                      yi.util.data.Conversion.from2Dto1D(grays, width, height)
                    );
      String filename = Configuration.getStringParameter("SOURCE_NAME", "img") + ".post" + "." + i;
      yi.util.io.OutputResult.outputGrayImage(filename + ".pgm", graph, width, height);
      yi.util.io.OutputResult.print(filename + ".rst", grays, width, height);
      yi.util.io.OutputResult.print(filename + ".1D.rst",
                                     yi.util.data.Conversion.from2Dto1D(grays, width, height), 1, width*height
                                   );
     }
  }

  private void demoTextureEnergyMeasures(int[][][] rst, int width, int height) {
    if (DEMO_TEXTURE_ENERGY_MEASURES) {
      for (int i=0; i<rst.length; i++) {
        int[] graph = yi.stat.graph.Graph.grayGraph(
                        yi.util.data.Conversion.from2Dto1D(rst[i], width, height)
                      );
        String filename = Configuration.getStringParameter("SOURCE_NAME", "img") + i + ".laws.";
        yi.util.io.OutputResult.outputColorImage(filename + ".pgm", graph, width, height);
        yi.util.io.OutputResult.print(filename + ".rst" , rst[i], width, height);
      }
    }
  }

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

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

    // process
    // change to 2D gray level image first
    pixels = CYMaker.getLuminance(pixels, d);               // change to gray
    int[][] pixels2D = yi.util.ImageUtil.oneD2twoD(pixels, d);  // change to 2D
    Configuration.setParameter("SOURCE_NAME", img_name);
    Laws dp = new Laws();
    long startTime = System.currentTimeMillis();
    int[][][] rst = dp.getTextureEnergyMeasures(pixels2D, d.width, d.height);
    long endTime = System.currentTimeMillis();
    System.out.println("running time: " + (endTime-startTime));

    for (int h =0; h<4; h++) {
      for (int w=0; w<4; w++) {
        System.out.print("("+w+","+h+"):");
        for (int i=0;i<rst.length;i++) {
          System.out.print(rst[i][h][w]);
          System.out.print(" ");
        }
        System.out.println();
      }
    }

    System.exit(0);
  }

}