/******************************************************************************
Gaussian Smooth

one argument: sigma

additional argument, BOOSTBLURFACTOR, can be used to multiply the value of
every pixel
******************************************************************************/

package yi.iu.processor.filter;

import java.awt.*;
import yi.iu.processor.*;
import yi.iu.processor.channel.*;
import yi.iu.SImage;
import yi.util.io.ReadArgu;
import yi.util.io.OutputResult;

public class GaussianSmooth extends ImageProcessor
{
    static double sigma= 2;        // Standard deviation of the gaussian kernel.
    static boolean VERBOSE= false;
    static double BOOSTBLURFACTOR= 1.0;

    public GaussianSmooth() {}

    public GaussianSmooth(double s)
  {
    sigma=s;
  }

    public GaussianSmooth(double s, double boost)
  {
    sigma=s;
    BOOSTBLURFACTOR= boost;
  }

    public int getArguNum() { return 1;}

    public void setArgument(int i, String st)
    {
        if (st.length()==0) return;
        if (i==0) sigma= Double.valueOf(st).doubleValue();
    }

    public String getArgument(int i)
    {
        if (i==0) return (new Double(sigma)).toString();
        return null;
    }

    public String getArguName(int i)
    {
        if (i==0) return "Sigma";
        return null;
    }

    public int[] getProcessedPseudoColor(int source[], Dimension d)
    {
        IntensityMaker im= new IntensityMaker();
        int[] in= im.getProcessed(source, d);
        int[] rst= getProcessed(in,d);
        yi.util.ImageUtil.makeImageFromGray(rst);
        return rst;
    }

    public int[] getProcessed(int[] image, Dimension d)
    {
        return getComponentProcessed(image, d.width, d.height);
    }

    /**
     * Gaussian smooth on 1-D data
     * @param data
     * @param isPeriod whether the data is periodic.  If yes, the windows of
     *                 those data on the boundary should contain both the
     *                 beginning and the end part of the data
     * @return
     */
    public double[] smoothing(double[] data, boolean isPeriodic) {
        double[] kernel=make_gaussian_kernel();  // A one dimensional gaussian kernel.
        int windowsize= kernel.length;
        int center = windowsize / 2;

        // Allocate a temporary buffer image and the smoothed image.
        int data_len= data.length;
        double[] tempim = new double[data_len];

        // Blur in the 1-D.
        if(VERBOSE) System.err.println("   Bluring the data.");

        // the sum of the kernal
        double default_kernal_sum = 0;
        for(int cc=(-center);cc<=center;cc++){
          default_kernal_sum += kernel[center+cc];
        }

        for(int i=0;i<data.length;i++){
            double dot = 0.0;
            double sum = default_kernal_sum;
            for(int cc=(-center);cc<=center;cc++){
              if (isPeriodic) {
                dot += (double)data[(i+cc+data_len)%data_len] * kernel[center+cc];
              } else {
                if(((i+cc) >= 0) && ((i+cc) < data.length)){
                  dot += (double)data[i+cc] * kernel[center+cc];
                } else {
                  sum -= kernel[center+cc];
                }
              }
            }
            tempim[i] = dot/sum;
        }
        return tempim;
    }

    //Gaussian smooth on 1-D data
    public double[] smoothing(double[] data) {
      return smoothing(data, false);
    }

    public static int[] smoothing(int[] p, int w, int h, double sigma) {
      GaussianSmooth g = new GaussianSmooth(sigma);
      return g.getProcessed(p, new Dimension(w,h));
    }

    //Gaussian smooth on 1-D data
    public double[] smoothing_period(double[] data) {
      return smoothing(data, true);
    }

    public double[] smoothing_period(int[] data) {
        double[] kernel=make_gaussian_kernel();  // A one dimensional gaussian kernel.
        int windowsize= kernel.length;
        int center = windowsize / 2;

        // Allocate a temporary buffer image and the smoothed image.
        int data_len= data.length;
        double[] tempim = new double[data_len];

        // Blur in the 1-D.
        if(VERBOSE) System.err.println("   Bluring the data.");
        for(int i=0;i<data.length;i++){
            double dot = 0.0;
            double sum = 0.0;
            for(int cc=(-center);cc<=center;cc++){
                dot += (double)data[(i+cc+data_len)%data_len] * kernel[center+cc];
                sum += kernel[center+cc];
            }
            tempim[i] = dot/sum;
        }
        return tempim;
    }

    public int[] getComponentProcessed(int[] image, int width, int height){
        int rows= height, cols= width;
        int r, c, rr, cc;   // Counter variables.
        double dot,         // Dot product summing variable.
            sum;            // Sum of the kernel weights variable.

        // Create a 1-dimensional gaussian smoothing kernel.
        if(VERBOSE) System.err.println("   Computing the gaussian smoothing kernel.");
        double[] kernel=make_gaussian_kernel();  // A one dimensional gaussian kernel.
        int windowsize= kernel.length;
        int center = windowsize / 2;

        // Allocate a temporary buffer image and the smoothed image.
        double[] tempim = new double[rows*cols];
        int[] smoothedim = new int[rows*cols];

        // Blur in the x - direction.
        if(VERBOSE) System.err.println("   Bluring the image in the X-direction.");
        for(r=0;r<rows;r++){
            for(c=0;c<cols;c++){
                dot = 0.0;
                sum = 0.0;
                for(cc=(-center);cc<=center;cc++){
                    if(((c+cc) >= 0) && ((c+cc) < cols)){
                        dot += (double)image[r*cols+(c+cc)] * kernel[center+cc];
                        sum += kernel[center+cc];
                    }
                }
                tempim[r*cols+c] = dot/sum;
            }
        }

        // Blur in the y - direction.
        if(VERBOSE) System.err.println("   Bluring the image in the Y-direction.");
        for(c=0;c<cols;c++){
            for(r=0;r<rows;r++){
                sum = 0.0;
                dot = 0.0;
                for(rr=(-center);rr<=center;rr++){
                    if(((r+rr) >= 0) && ((r+rr) < rows)){
                        dot += tempim[(r+rr)*cols+c] * kernel[center+rr];
                        sum += kernel[center+rr];
                    }
                }
                smoothedim[r*cols+c] = (int)(dot*BOOSTBLURFACTOR/sum + 0.5);
            }
        }
        tempim= null;
        System.gc();
        return smoothedim;

    }

    public double[] getProcessed(double[] image, int width, int height){
        int rows= height, cols= width;
        int r, c, rr, cc;   // Counter variables.
        double dot,         // Dot product summing variable.
            sum;            // Sum of the kernel weights variable.

        // Create a 1-dimensional gaussian smoothing kernel.
        if(VERBOSE) System.err.println("   Computing the gaussian smoothing kernel.");
        double[] kernel=make_gaussian_kernel();  // A one dimensional gaussian kernel.
        int windowsize= kernel.length;
        int center = windowsize / 2;

        // Allocate a temporary buffer image and the smoothed image.
        double[] tempim = new double[rows*cols];
        double[] smoothedim = new double[rows*cols];

        // Blur in the x - direction.
        if(VERBOSE) System.err.println("   Bluring the image in the X-direction.");
        for(r=0;r<rows;r++){
            for(c=0;c<cols;c++){
                dot = 0.0;
                sum = 0.0;
                for(cc=(-center);cc<=center;cc++){
                    if(((c+cc) >= 0) && ((c+cc) < cols)){
                        dot += (double)image[r*cols+(c+cc)] * kernel[center+cc];
                        sum += kernel[center+cc];
                    }
                }
                tempim[r*cols+c] = dot/sum;
            }
        }

        // Blur in the y - direction.
        if(VERBOSE) System.err.println("   Bluring the image in the Y-direction.");
        for(c=0;c<cols;c++){
            for(r=0;r<rows;r++){
                sum = 0.0;
                dot = 0.0;
                for(rr=(-center);rr<=center;rr++){
                    if(((r+rr) >= 0) && ((r+rr) < rows)){
                        dot += tempim[(r+rr)*cols+c] * kernel[center+rr];
                        sum += kernel[center+rr];
                    }
                }
                smoothedim[r*cols+c] = dot*BOOSTBLURFACTOR/sum;
            }
        }
        tempim= null;
        System.gc();
        return smoothedim;

    }

    public int[] getRGBProcessed(int[] image, Dimension d){
        return getRGBProcessed(image,d.width,d.height);
    }

    public int[] getRGBProcessed(int[] image, int width, int height){
        Dimension d= new Dimension(width, height);
        int r[]= (new RedChannelMaker()).getProcessed(image,d);
        int rr[]= getComponentProcessed(r,width,height);
        int g[]= (new GreenChannelMaker()).getProcessed(image,d);
        int gr[]= getComponentProcessed(g,width,height);
        int b[]= (new BlueChannelMaker()).getProcessed(image,d);
        int br[]= getComponentProcessed(b,width,height);
        int result[]= SImage.makeRGB(rr, gr, br);
        r=rr=g=gr=b=br=null;
        System.gc();
        return result;
    }



    public SImage getProcessed(SImage image)
    {
        int rst[];
        if (image.type==SImage.BAREGRAY) {
            rst= getComponentProcessed(image.data, image.width, image.height);
            return new SImage(image.width, image.height, SImage.BAREGRAY, rst);
        }
        else if (image.type==SImage.GRAY) {
            int[] bare= image.getBare();
            rst= getComponentProcessed(bare, image.width, image.height);
            return new SImage(image.width, image.height, SImage.BAREGRAY, rst);
        }
        else {
            rst= getRGBProcessed(image.data, image.width, image.height);
            return new SImage(image.width, image.height, SImage.RGB, rst);
        }
    }

    // PROCEDURE: make_gaussian_kernel
    // PURPOSE: Create a one dimensional gaussian kernel.
    static double[] make_gaussian_kernel()
    {
        int windowsize = (int)(1 + 2 * Math.ceil(2.5 * sigma)+0.5);
        int center = windowsize / 2;
        int i;
        double x, fx, sum=0.0;

        if(VERBOSE) System.err.println("      The kernel has %d elements."+windowsize);
        double[] kernel = new double[windowsize];
        for(i=0;i<windowsize;i++){
            x = (double)(i - center);
            fx = Math.exp(-0.5*x*x/(sigma*sigma)) / (sigma * Math.sqrt(2*Math.PI));
            kernel[i] = fx;
            sum += fx;
        }

        for(i=0;i<windowsize;i++) kernel[i] /= sum;
        if(VERBOSE){
            System.err.println("The filter coefficients are:");
            for(i=0;i<windowsize;i++)
                System.err.println("kernel["+i+"] = "+kernel[i]);
        }
        return kernel;
    }

  public int[] getXProcessed(int[] image, Dimension d)
    {
        int rows= d.height, cols= d.width;
        int r, c, rr, cc;   // Counter variables.
        double dot,         // Dot product summing variable.
            sum;            // Sum of the kernel weights variable.

        // Create a 1-dimensional gaussian smoothing kernel.
        if(VERBOSE) System.err.println("   Computing the gaussian smoothing kernel.");
        double[] kernel=make_gaussian_kernel();  // A one dimensional gaussian kernel.
        int windowsize= kernel.length;
        int center = windowsize / 2;

        // Allocate a temporary buffer image and the smoothed image.
        int[] tempim = new int[rows*cols];
        int[] smoothedim = new int[rows*cols];

        // Blur in the x - direction.
        if(VERBOSE) System.err.println("   Bluring the image in the X-direction.");
        for(r=0;r<rows;r++){
            for(c=0;c<cols;c++){
                dot = 0.0;
                sum = 0.0;
                for(cc=(-center);cc<=center;cc++){
                    if(((c+cc) >= 0) && ((c+cc) < cols)){
                        dot += (double)image[r*cols+(c+cc)] * kernel[center+cc];
                        sum += kernel[center+cc];
                    }
                }
                tempim[r*cols+c] = (int)(dot/sum+0.5);
            }
        }

        return tempim;
    }
    public int[] getYProcessed(int[] image, Dimension d)
    {
        int rows= d.height, cols= d.width;
        int r, c, rr, cc;   // Counter variables.
        double dot,         // Dot product summing variable.
            sum;            // Sum of the kernel weights variable.

        // Create a 1-dimensional gaussian smoothing kernel.
        if(VERBOSE) System.err.println("   Computing the gaussian smoothing kernel.");
        double[] kernel=make_gaussian_kernel();  // A one dimensional gaussian kernel.
        int windowsize= kernel.length;
        int center = windowsize / 2;

        // Allocate a temporary buffer image and the smoothed image.
        int[] smoothedim = new int[rows*cols];

        // Blur in the y - direction.
        if(VERBOSE) System.err.println("   Bluring the image in the Y-direction.");
        for(c=0;c<cols;c++){
            for(r=0;r<rows;r++){
                sum = 0.0;
                dot = 0.0;
                for(rr=(-center);rr<=center;rr++){
                    if(((r+rr) >= 0) && ((r+rr) < rows)){
                        dot += image[(r+rr)*cols+c] * kernel[center+rr];
                        sum += kernel[center+rr];
                    }
                }
                smoothedim[r*cols+c] = (int)(dot*BOOSTBLURFACTOR/sum + 0.5);
            }
        }
        return smoothedim;
    }

    public static void main(String[] 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);
      }

      int theta = 5;

      GaussianSmooth gau = new GaussianSmooth(theta);
      int[] filtered = new int[pixels.length];
      for (int i=0;i<filtered.length;i++) {
        filtered[i] = (pixels[i]!=0xffffffff)?255:0;
      }
      filtered = gau.getProcessed(filtered, d);
      int[] gau_img = yi.util.ImageUtil.makeNewImageFromGray(filtered);
      OutputResult.outputGrayImage("gau_theta_"+theta+".pgm",filtered, d.width, d.height);
      int[] binary = new int[pixels.length];
      for (int t=1;t<=10;t++) {
        int binary_threshold = (int)(255 * t / 10);
        for (int i=0;i<filtered.length;i++) {
          binary[i] = (filtered[i]>binary_threshold)?0xffffffff:0xff000000;
        }
        yi.util.io.OutputResult.outputGIFImage("gau_theta_"+theta+"_"+t+".pgm",binary, d.width, d.height);
      }

      System.exit(0);
    }

}