
package descriptors;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
import preprocessing.Util;

public class hog {

	public double[] HOG (
			BufferedImage original,
			int rwt,
			int number_of_cells_x,
			int number_of_cells_y,
			int bins_per_cell,
			int new_height,
			boolean normalize_image,
			int option, /*0 - Nothing, 1 - Step, 2 - Bernstein*/
			boolean normalize_histogram_L1) {

		boolean debug = true;

		BufferedImage resize = null;

		if (new_height > 0) {
			resize = Util.imageResize (original, new_height);
		}
		else {
			resize = Util.imageScale (original, 1.0);
		}

		/*Resized image width*/
		int width  = resize.getWidth();

		/*Resized image height*/
		int height = resize.getHeight();

		assert(height == new_height);

		int n = width * height;

		double[] grey = new double[n];

		double[] grel = new double[n];

		double[] dx = new double[n];

		double[] dy = new double[n];

		double[] dnorm = new double[n];

		double[] dtheta = new double[n];

		double[] weight = new double[2*rwt+1];

		compute_weights (weight, rwt);

		/*for (int i = 0; i < 2*rwt+1; i++) {
                System.err.printf(" %8.6f ", weight[i]);
        }
        System.err.printf("\n");*/

		if (normalize_image) {
			get_grey_image (resize, grey);
			normalize_grey_image (grey, width, height, weight, rwt, grel);
		}
		else {
			get_grey_image (resize, grel);
		}

		/*Number of Cells*/
		int number_of_cells = number_of_cells_x * number_of_cells_y;

		int total_bins = number_of_cells*bins_per_cell;

		/*Creating a matrix to hold the cell histogram*/
		double[] cells_histogram = new double[total_bins];

		double eps = 0.02; /*nominal deviation of pixel noise*/

		for (int i = 0; i < total_bins; i++) {
			cells_histogram[i] = 0;
		}

		for (int x = 1; x < (width - 1); x++) {

			for (int y = 1; y < (height - 1); y++) {

				int position = y * width + x;
				int kxm = position - 1;
				int kxp = position + 1;
				int kym = position - width;
				int kyp = position + width;

				dx[position] = (grel[kxp] - grel[kxm])/2;

				dy[position] = (grel[kyp] - grel[kym])/2;

				/*Compute the gradient norm but return zero if too small*/
				double d2 = dx[position]*dx[position] + dy[position]*dy[position];

				if (d2 <= eps*eps) {
					dnorm[position] = 0.0;
				}
				else {
					dnorm[position] = Math.sqrt(d2 - eps*eps);
				}

				dtheta[position] = Math.atan2(dy[position], dx[position]);

				if (dtheta[position] < 0) {
					dtheta[position] += Math.PI;
				}	

				int bin = (int) Math.floor(bins_per_cell*(dtheta[position]/Math.PI+1)+0.5);
				bin = bin % bins_per_cell;
				assert ( (bin >= 0) && (bin < bins_per_cell));

				if (option > 0) {
					for (int cx = 0; cx < number_of_cells_x; cx++) {
						double wtx = 0.0;
						if (option == 1) {
							wtx = StepFunc (number_of_cells_x,cx,(x-0.5)/(width-2));
						}
						else {
							wtx = Bernstein (number_of_cells_x-1,cx,(x-0.5)/(width-2));
						}
						for (int cy = 0; cy < number_of_cells_y; cy++) {
							double wty = 0.0;
							if (option == 1) {
								wty = StepFunc(number_of_cells_y,cy,(y-0.5)/(height-2));
							}
							else {
								wty = Bernstein (number_of_cells_y-1,cy,(y-0.5)/(height-2));
							}	
							int c_pos = cy * number_of_cells_x + cx;

							int bin_pos = c_pos * bins_per_cell + bin;

							cells_histogram[bin_pos] += dnorm[position]*wtx*wty;
						}
					}
				}
				else {
					int cx = (number_of_cells_x * (x-1))/(width-1);
					assert( (cx >= 0) && (cx < number_of_cells_x)); 
					int cy = (number_of_cells_y * (y-1))/(height-1);
					assert( (cy >= 0) && (cy < number_of_cells_y));
					int c_pos = cy * number_of_cells_x + cx;
					int bin_pos = c_pos * bins_per_cell + bin;
					cells_histogram[bin_pos] += dnorm[position];
				}  
			}
		}

		/*Normalize the histogram of each cell to unit L1 or L2 norm*/
		for (int cy = 0; cy < number_of_cells_y; cy++) {

			for (int cx = 0; cx < number_of_cells_x; cx++) {

				int c_pos = cy * number_of_cells_x + cx;

				double sum = 0.0;

				for (int bin = 0; bin < bins_per_cell; bin++) {
					int bin_pos = c_pos * bins_per_cell + bin;
					double v = cells_histogram [bin_pos];
					sum += (normalize_histogram_L1 ? v : v * v);
				}

				double cell_norm = (normalize_histogram_L1 ? sum + 1.0e-100: Math.sqrt(sum + 1.0e-100));

				for (int bin = 0; bin < bins_per_cell; bin++) {
					int bin_pos = c_pos * bins_per_cell + bin;
					double v = cells_histogram [bin_pos];
					cells_histogram [bin_pos] = v/cell_norm;
					if (debug) {
						System.err.printf("cell : %d,%d bin : %d, sum : %f\n", cx, cy, bin, cells_histogram [bin_pos]);
					}
				}
			}
		}


		if (debug) {
			Util.writeDoubletoPGM (grel, width, height, 0.0, 1.0, "norm");
			Util.writeDoubletoPGM (dx, width, height, -1.0, 1.0, "dx");
			Util.writeDoubletoPGM (dy, width, height, -1.0, 1.0, "dy");
			Util.writeDoubletoPGM (dnorm, width, height, 0.0, 1.42, "dnorm");
			Util.writeDoubletoPGM (dtheta, width, height, 0, Math.PI, "dtheta");

			try {
				String outname1 = "original.png";
				String outname2 = "resized.png";
				ImageIO.write(original, "png", new File(outname1));
				ImageIO.write(resize, "png", new File(outname2));
			}
			catch (Exception e1) {
				System.err.println("error: fail to save the images");
			}
			print_histogram (cells_histogram, number_of_cells_x, number_of_cells_y, bins_per_cell);
		}

		return cells_histogram;
	}
	
	public void print_histogram (double[] histogram, int number_of_cells_x, int number_of_cells_y, int bins_per_cell) {
		
		int margin = 25;
		
		int width = 800;
		
		int height = 800;
		
		int middle_x = width/2;
		
		int middle_y = height/2;
		
		int n = width * height;
		
		int box_size = (width - 2*margin) / (bins_per_cell);
				
		BufferedImage temp = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		
		Graphics2D g = temp.createGraphics();
		
		g.setStroke(new BasicStroke(1.0f));		
		
		g.setColor(Color.black);
		
		Graphics2D grey_weak = temp.createGraphics();
		grey_weak.setStroke(new BasicStroke(1.0f));		
		float[] hsbvals = new float[3];
		Color.RGBtoHSB(173, 173, 173, hsbvals);
		grey_weak.setColor(Color.getHSBColor(hsbvals[0], hsbvals[1], hsbvals[2]));
		
		Graphics2D grey_dark = temp.createGraphics();
		grey_dark.setStroke(new BasicStroke(1.0f));	
		Color.RGBtoHSB(100, 100, 100, hsbvals);
		grey_dark.setColor(Color.getHSBColor(hsbvals[0], hsbvals[1], hsbvals[2]));
		
		/*White painting*/
		int white = 0xFFFFFF;
		for (int i = 0; i < n; i++) {
			int x = i % width;
			int y = i / width;
			temp.setRGB(x, y, white);
		}
		
		int radius = 100;
		
		drawCircle (middle_x, middle_y, radius, g); //radius = 50 pixels
		
		double degree = 360.0/(double)bins_per_cell;
		
		for (int bin = 0; bin < bins_per_cell; bin++) {
		
			double radians1 = degree * bin * Math.PI/180.0;
			
			double radians2 = degree * ((bin + 1) % bins_per_cell) * Math.PI/180.0;

			double x1 = middle_x + radius * Math.cos(radians1);

			double y1 = middle_y + radius * Math.sin(radians1);
			
			double x2 = middle_x + (radius + histogram[bin]*100) * Math.cos(radians1);

			double y2 = middle_y + (radius + histogram[bin]*100) * Math.sin(radians1);

			//g.drawLine((int)x1, (int)y1, (int)x2, (int)y2);
			
			double x3 = middle_x + radius * Math.cos(radians2);

			double y3 = middle_y + radius * Math.sin(radians2);
			
			double x4 = middle_x + (radius + histogram[bin]*100) * Math.cos(radians2);

			double y4 = middle_y + (radius + histogram[bin]*100) * Math.sin(radians2);
			
			Polygon polygon = new Polygon();
			
			polygon.addPoint((int)x1, (int)y1);
			
			polygon.addPoint((int)x2, (int)y2);
			
			polygon.addPoint((int)x4, (int)y4);
			
			polygon.addPoint((int)x3, (int)y3);
			
			if ((bin % 2) == 0) {
				grey_dark.fillPolygon(polygon);
			}
			else {
				grey_weak.fillPolygon(polygon);
			}
			

	
			
		}

		 
		//for (int y = 0; y < 1; y++) {
			//for (int x = 0; x < number_of_cells_x; x++) {
				/*for (int bin = 0; bin < bins_per_cell; bin++) {

					int[] x = new int[4];
					int[] y = new int[4];
					x[0] = bin*box_size+margin;
					
					g.drawRect(bin*box_size+margin, 190, box_size, -150);
					g.drawPolygon(xPoints, yPoints, nPoints);
					g.d
					System.err.printf("bin : %d, size : %d\n", bin, bin*box_size);

				}*/
			//}
		//}
		
		//g.draw
		
		g.dispose();
		
		try {
			ImageIO.write(temp, "png", new File("descriptor.png"));
		}
		catch (Exception e) { 
				System.err.printf("cannot write image\n");
		}
		
	}
	
	
	private void drawCircle (int x, int y, int radius, Graphics2D g){
		g.drawOval(x - radius, y - radius, radius*2, radius*2);
	}

	/*Divides the interval [0-1] into {n} equal parts and returns 1.0 if
	 *{z} is in part number {k} (0..n-1), 0 otherwise. */
	public double StepFunc (int n, int k, double z) {
		assert((k >= 0) && (k < n));
		return ( (k <= z*n) && (z*n < k+1) ? 1.0 : 0.0 );
	}

	/*Computes the Bernstein polynomial of degree {n} and index {k} for
	 *the argument {z}.*/
	public double Bernstein (int n, int k, double z) {
		assert((k >= 0) && (k <= n));
		double res = 1.0;
		for (int i = 0; i < k; i++) {
			res = (res * (n - i))/(i+1)*z;
		}
		return res*Math.pow(1-z,n-k);
	}
	
	/*AUXILIARY FUNCTIONS*/

	public void get_grey_image (BufferedImage image, double[] grey) {

		int w = image.getWidth(null);
		int h = image.getHeight(null);
		for (int y = 0; y < h; y++) {
			for (int x = 0; x < w; x++) {				
				int pixel = image.getRGB(x, y);
				int position = y * w + x;				
				double R = (pixel >> 16) & 255;
				double G = (pixel >> 8) & 255;
				double B = (pixel & 255);
				grey[position] = 0.299*R + 0.587*G + 0.114*B;
			}
		}
	}
	
	public void compute_weights (double[] weight, int rwt) {
		int nwt = 2*rwt+1;
		weight[0] = 1;
		for (int i = 1; i < nwt; i++) { 
			weight[i] = 0.0; 
			for (int j = i; j >=1; j--) {
				weight[j] = (weight[j] + weight[j-1])/2;
			}
			weight[0] /= 2;
		}
	}
	
	public void normalize_grey_image (double[] grey, int w, int h, double[] weight, int rwt, double[] grel) {
		double AVG, DEV;
		for (int y = 0; y < h; y++) {
			for (int x = 0; x < w; x++) {				
				int position = y * w + x;	
				AVG = get_grey_avg (grey, w, h, x, y, weight, rwt);
				DEV = get_grey_dev (grey, w, h, x, y, weight, rwt, AVG);
				grel[position] = (grey[position] - AVG)/DEV;
			}
		}
	
	}
	
	public double get_grey_avg (double[] grey, int w, int h, int x, int y, double[] weight, int rwt) {
		double sum_vwt = 0.0, sum_wt = 0.0;
		for (int dy = -rwt; dy <= rwt; dy++) {
			for (int dx = -rwt; dx <= rwt; dx++) {
				int x1 = x + dx;
				int y1 = y + dy;
				if ( (x1 >= 0) && (x1 < w) && (y1 >= 0) && (y1 < h)) {
				    int position = y1 * w + x1; 
				    double v = grey[position];
				    double wt = weight[rwt+dx]*weight[rwt+dx];
				    sum_vwt += v * wt;
				    sum_wt += wt; 
				}

			}
		}
		return sum_vwt/sum_wt;
	}
	
	public double get_grey_dev (double[] grey, int w, int h, int x, int y, double[] weight, int rwt, double AVG) {
		double sum_v2wt = 0.0, sum_wt = 0.0;
		for (int dy = -rwt; dy <= rwt; dy++) {
			for (int dx = -rwt; dx <= rwt; dx++) {
				int x1 = x + dx;
				int y1 = y + dy;
				if ( (x1 >= 0) && (x1 < w) && (y1 >= 0) && (y1 < h)) {
				    int position = y1 * w + x1; 
				    double v = grey[position]-AVG;
				    double wt = weight[rwt+dx]*weight[rwt+dx];
				    sum_v2wt += v * v * wt;
				    sum_wt += wt; 
				}

			}
		}
		double noise = 0.01; /*Assumed standard deviation of noise*/
		return Math.sqrt(sum_v2wt/sum_wt + noise*noise);
	}

}