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 java.io.IOException;
import javax.imageio.ImageIO;
import preprocessing.Lanczos;
import preprocessing.Util;

public class HoG {

	public double[] hog_descriptor (
			
			boolean debug,
			
			BufferedImage image, /*Original image*/
			
            int new_height,        
			
            int number_of_cells_x,
			int number_of_cells_y,
			int bins_per_cell,
			
			boolean image_normalization,
			String image_normalization_weight, 
			double image_normalization_weight_radius,
			
			String gradient_option,
			
			String histogram_normalization_metric,
			
			String weight_function,
			
			boolean deformable_weights ) {
	
		
		/*settings[0] = gradient image (0 - Dalal, 1 - Ours) */
		/*settings[1] = histogram binning (0 - Dalal, 1 - Ours) */
		/*settings[2] = use cell weight mask (for Dalal) for setting[1] true only */
		//int[] settings = {0, 0, 0}; 
		int[] settings = {1, 1, 1};
		
		/*Settings*/
		int safe_margin = 1;		//-> Best (graficos) -> [0-30]
		//int safe_margin = 0; /*pixels*/ //-> Test com esse Journal IEEE
		
		//double noise = 0.02; /*Assumed standard deviation of noise*/ //-> Best Journal IEEE
		//double noise = 0.03; /*Assumed standard deviation of noise*/ //-> Resultados gerados com isso [0-30]
		//double noise = 0.005; //-> Best (graficos)
		double noise = 0.002; //-> Best (graficos)
		
		double black_level = 0.02; /*Assumed black level of image*/
		
		
		boolean image_logscale = false;
		//boolean image_logscale = true;
		
		int number_of_cells = number_of_cells_x * number_of_cells_y; /*HOG cells*/

		int number_of_bins = number_of_cells * bins_per_cell; /*HOG bins*/
		
		/*Image resizing. If the new height is negative the region is not resized.*/
		BufferedImage image_resized = null;	 	

		if (new_height > 0) {
			/*Resizing by Lanczos interpolation.*/
			Lanczos lanczos = new Lanczos();
			image_resized = lanczos.lanczos(image, new_height, 1);
            assert(image_resized.getHeight() == new_height);
		}
		else {
			image_resized = Util.imageScale (image, 1.0);
		}
		/*End-resizing*/

		/*Getting the resized dimensions.*/
		int rwidth = image_resized.getWidth();
		
		int rheight = image_resized.getHeight();		
        
		/*Verifying if the image was resized to {new_height)}.*/
		assert((rheight == new_height) || (new_height ==  -1));

		/*Number of pixels of the resized image.*/
		int n = rwidth * rheight;

		/*Normalized image*/
		double[] grel = new double[n];

		/*Image norm.*/
		double[] dnorm = new double[n];

		/**/
		double[] dtheta = new double[n];
		
		/*Creating a matrix to hold the cells histogram.*/
		double[] cells_histogram = new double[number_of_bins];
		for (int i = 0; i < number_of_bins; i++) {
			cells_histogram[i] = 0;
		}
		if ( (rwidth <= 2) || (rheight <= 2) ) {
			return cells_histogram;
		}
		
		/*Computing weights {x_weight, y_weight} to normalize the resized image.*/
		
		int x_weight_rad = -1, y_weight_rad = -1;
		
		double x_weight[] = null, y_weight[] = null;
		
		if (image_normalization_weight.compareTo("Gauss") == 0) {		
			/*Gaussian weights*/
			x_weight_rad = choose_gaussian_weight_size (image_normalization_weight_radius * rheight);
			y_weight_rad = choose_gaussian_weight_size (1.0 * rheight);	
			
			x_weight = new double [2 * x_weight_rad + 1];
			y_weight = new double [2 * y_weight_rad + 1];
			
			compute_gaussian_weights (x_weight, x_weight_rad);		
			compute_gaussian_weights (y_weight, y_weight_rad);
		}
		else if (image_normalization_weight.compareTo("Binomial") == 0) { 
			/*Binomial weights*/
			x_weight_rad = y_weight_rad = (int) image_normalization_weight_radius;
			
			x_weight = new double [2 * x_weight_rad + 1];
			y_weight = new double [2 * y_weight_rad + 1];
			
			compute_binomial_weights (x_weight, x_weight_rad);
			compute_binomial_weights (y_weight, y_weight_rad);
		}
		else {
			System.err.printf("Error: choose a valid weight to the image normalization\n");
			System.exit(1);
		}

		/*Convert to grey scale*/
		double[] grey = get_grey_image (image_resized);		
		if (debug){
			/*Writing image norm*/  
			Util.writeDoubletoPGM (grey, rwidth, rheight, 0.0, 1.0, "grey");
		}
		
		/*Image normalization*/
		if (image_normalization) {			 
			if ( (x_weight != null) &&  (y_weight != null) ) {
				grel = normalize_grey_image (grey, rwidth, rheight, x_weight, x_weight_rad, y_weight, y_weight_rad, noise);
			}
			else {
				System.err.printf("Error: choose a valid image_normalization_weight\n");
				System.exit(1);
			}
		}
		else {
			for (int i = 0; i < n; i++) { grel[i] = grey[i]; }

		}		
		if (debug){
			Util.writeDoubletoPGM (grel, rwidth, rheight, 0.0, 1.0, "norm");
		}
		
		/*Convert to log scale*/
		if (image_logscale) {
			convert_to_log_scale(grel, rwidth, rheight, black_level);
		}
		
		if (debug){
			Util.writeDoubletoPGM (grel, rwidth, rheight, 0.0, 1.0, "glog");
		}
			
		/*Computing the approximate baselines {ytop, ybot} to each column.*/
		
		double[][] baselines = null;
		
		if (deformable_weights) {
			/*Computing the variable text-baselines {ytop, ybot} using image normalization.*/
			//int tmp_rwt = choose_gaussian_weight_size (0.75 * rheight); -> Best
			int tmp_rwt = choose_gaussian_weight_size (1.0 * rheight);
			double[] dist_weight = new double[2 * tmp_rwt + 1];
			compute_gaussian_weights (dist_weight, tmp_rwt);
			baselines = text_baselines (rwidth, rheight, dist_weight, tmp_rwt, grel);
		}
		else {
			/*Without variable text-baselines weights*/
			baselines = new double[rwidth][2];
			for (int w = 0; w < rwidth; w++) {
				//baselines[w][0] = rheight;// - safe_margin - 1; /*ybot*/
				//baselines[w][1] = 0;//safe_margin; /*ytop*/ 
				baselines[w][0] = rheight - safe_margin - 1; /*ybot*/
				baselines[w][1] = safe_margin; /*ytop*/ 
			}	
		}

		/*Creating a matrix to hold the cells weights.*/
		double[][] cell_weight_image = new double[number_of_cells][n];
		
		if (settings[0] == 0) {
			get_mag_theta_dalal (image_resized, dnorm, dtheta, rwidth, rheight, debug); 	
		}
		else {
			if (gradient_option.compareTo("Sobel") == 0) {
				safe_margin = 1;
			}
			get_mag_theta (grel, dnorm, dtheta, rwidth, rheight, safe_margin, noise, gradient_option, debug);
		}
		
		float[][][] hist = new float[number_of_cells_x][number_of_cells_y][bins_per_cell];
		for (int indx = 0; indx < number_of_cells_x; indx++) {
			for (int indy = 0; indy < number_of_cells_y; indy++) {
				for (int k = 0; k < bins_per_cell; k++) {
					hist[indx][indy][k] = (float)0.0;
				}
			}
		}
  		
		for (int x = safe_margin; x < (rwidth - safe_margin); x++) {

				for (int y = safe_margin; y < (rheight - safe_margin); y++) {

				int position = y * rwidth + x;
		
				/*Getting baselines.*/
				double xleft = safe_margin;
				
				double xright = rwidth - safe_margin;
				
				double ybot = baselines[x][0];
				
				double ytop = baselines[x][1];

				/*Computing the cells histogram and fuzzy weights*/
				for (int cx = 0; cx < number_of_cells_x; cx++) {

					double wtx = cell_weight (weight_function, number_of_cells_x, cx, x, xright, xleft);
					
					for (int cy = 0; cy < number_of_cells_y; cy++) {
															
						int c_pos = cy * number_of_cells_x + cx;
						
						double wty = cell_weight (weight_function, number_of_cells_y, cy, y, ybot, ytop);
						
						if (weight_function.compareTo("Dalal") == 0) {
							//wd = dalal_weight (x, y, number_of_cells_x, number_of_cells_y, rwidth, rheight);
							wty *= dalal_weight (x, y, 1, 1, rwidth, rheight);
						}
								
						int[] bin = new int[2];
						double[] factor = new double[2];
						
						//double wd = 1.0;
						if (settings[1] == 0) {
							//System.out.printf("%d %d ", x, y);
							get_bin_pos (hist, (float)(dnorm[position] * wtx * wty), x, y, number_of_cells_x, number_of_cells_y, rwidth, rheight, bins_per_cell, (float)(dtheta[position]), bin, factor );
							//System.out.printf(" %.14f %.14f %.14f\n", (float)(dnorm[position]*wd), wd, dnorm[position]);							
						}
						else if (settings[1] == 1) {
							get_bin_pos (bins_per_cell, dtheta[position], bin, factor);
						}
																	
						int bin_pos1 = c_pos * bins_per_cell + bin[0];
						
						int bin_pos2 = c_pos * bins_per_cell + bin[1];	
											
						cells_histogram[bin_pos1] += (dnorm[position] * wtx * wty) * factor[0];
						
						cells_histogram[bin_pos2] += (dnorm[position] * wtx * wty) * factor[1];
						
						//System.out.printf("bin_pos1 %d, bin_pos2 : %d, hist1 : %f, hist2 : %f, dnorm : %f, wd : %f, factor1 : %f factor2 : %f\n", bin_pos1, bin_pos2, cells_histogram[bin_pos1], cells_histogram[bin_pos2], dnorm[position], wtx * wty * wd, factor[0], factor[1]);							
												
						//System.out.printf("value : %f, weight %f, hist : %f\n", dnorm[position]*wd, dnorm[position]*wd*factor[0], cells_histogram[bin_pos1]);
						//System.out.printf("value : %f, weight %f, hist : %f\n", dnorm[position]*wd, dnorm[position]*wd*factor[1], cells_histogram[bin_pos2]);
						
						cell_weight_image[c_pos][position] = wtx * wty;
					}
				}			
			}
		}
		
		/*int index = 0;
		for (int indx = 0; indx < number_of_cells_x; indx++) {
            for (int indy = 0; indy < number_of_cells_y; indy++) {
                for (int k = 0; k < bins_per_cell; k++) {
                	cells_histogram[index] = hist[indx][indy][k];
                	index++;
                  	//System.out.printf(" %.14f ", hist[indx][indy][k]);
                }
            }
		}*/
					
		/*Normalize the histogram of each cell to unit L1 or L2 norm*/
		double sum = 0.0;
		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];
					
					if (histogram_normalization_metric.compareTo("L1") == 0) {
						sum += v; 
					}
					else if (histogram_normalization_metric.compareTo("D1") == 0) {
						sum += v; 
					}
					else if (histogram_normalization_metric.compareTo("D2") == 0) {
						sum += v; 
					}
					else if (histogram_normalization_metric.compareTo("L2") == 0) {
						sum += v * v;
					}
					else {
						System.err.printf("Error: choose a valid normalization option\n");
						System.exit(1);
					}
				}
			}
		}
		
		//System.err.printf("Sum : %.9f\n", sum);
			
			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 cell_norm = 0.0;
				
				if (histogram_normalization_metric.compareTo("L1") == 0) {
					cell_norm = sum + 1.0e-100; 
				}
				else if (histogram_normalization_metric.compareTo("D1") == 0) {
					//cell_norm = sum + 1.0*bins_per_cell;
					cell_norm = sum + 1.0*bins_per_cell*number_of_cells_x*number_of_cells_y;
				}
				else if (histogram_normalization_metric.compareTo("D2") == 0) {
					cell_norm = 1.0;					
				}
				else if (histogram_normalization_metric.compareTo("L2") == 0) {
					cell_norm = Math.sqrt(sum + 1.0e-100);
				}
				else {
					System.err.printf("Error: choose a valid normalization option\n");
					System.exit(1);
				}

				for (int bin = 0; bin < bins_per_cell; bin++) {
					
					int bin_pos = c_pos * bins_per_cell + bin;
					
					double v = cells_histogram [bin_pos];
					
					//System.err.printf("cell : %f\n", v);
										
					cells_histogram [bin_pos] = (float)(v/cell_norm);
				}
			}
		}
				
		if (debug) {
			
			try {
				/*Writing original image.*/
				ImageIO.write(image, "png", new File("original.png"));
				
				/*Writing resized image.*/
				ImageIO.write(image_resized, "png", new File("resized.png"));
				
			} catch (IOException e) {
				e.printStackTrace();
			}

			/*Writing baselines*/			
			double[] tmp = new double[n];

			/*Copying the resized and normalized grey-level image.*/
			for (int i = 0; i < n; i++) { tmp[i] = grel[i]; }

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

				/*Getting the baselines extremes.*/
				int ytop = (int)(baselines[x][0] + 0.5) * rwidth + x;

				int ybot = (int)(baselines[x][1] + 0.5) * rwidth + x;

				/*Writing in black the baselines extremes.*/
				if ( (ytop > 0) && (ytop < (rwidth * rheight))) {
					tmp[ytop] = -2;
				}
				if ( (ybot > 0) && (ybot < (rwidth * rheight))) {
					tmp[ybot] = -2;
				}
			}
			Util.writeDoubletoPGM (tmp, rwidth, rheight, -2.0, 2.0, "image_baselines");			  
			/*End-baselines*/  

			/*Writing image gradient directions*/ 
			double dnorm_max = 0.5;

			Util.writeDoubletoPGM (dnorm, rwidth, rheight, 0.0, dnorm_max, "dnorm");

			Util.writePolartoPNG (dnorm, dtheta, rwidth, rheight, dnorm_max, 0.0, Math.PI, "dpolar");

			Util.writeDoubletoPGM (dtheta, rwidth, rheight, 0, Math.PI, "dtheta");
			
			/*Writing weight functions*/ 
			for (int nc = 0; nc < number_of_cells; nc++) {
				Util.writeDoubletoPGM (cell_weight_image[nc], rwidth, rheight, 0.0, 1.0, "cwt_"+ String.format("%02d", nc));
			}

			/*Writing HOG descriptors as circular images*/ 

			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[] histogram = new double[bins_per_cell];
					
					for (int bin = 0; bin < bins_per_cell; bin++) {
						
						int bin_pos = c_pos * bins_per_cell + bin;
						
						histogram[bin] = cells_histogram [bin_pos];
						
						//System.err.printf("cell : %d,%d bin : %d, sum : %.18f\n", cx, cy, bin, cells_histogram [bin_pos]);
					}
					
					/*To each cell print the HOG histogram.*/
					if ((cy == 0) || (cy == (number_of_cells_y-1))) {
					    print_histogram (image_resized, histogram, number_of_cells_x, number_of_cells_y, bins_per_cell, histogram_normalization_metric, "hog_directions_" + cy + "_" + cx + ".png", 1000);
					    //print_histogram (image_resized, histogram, number_of_cells_x, number_of_cells_y, bins_per_cell, histogram_normalization_metric, "hog_directions_" + cy + "_" + cx + ".png", 600);
					}
					else {
						print_histogram (image_resized, histogram, number_of_cells_x, number_of_cells_y, bins_per_cell, histogram_normalization_metric, "hog_directions_" + cy + "_" + cx + ".png", 300);						
					}
				}
			}			
		}
		return cells_histogram;
	} /*End-HOG*/	
	
	public void get_bin_pos (int bins_per_cell, double dtheta, int[] bin, double[] factor)
	{
		/*Computing the bin according the gradient direction.*/
		//boolean full_circ = false; //[0-30]
		
		boolean full_circ = true;
		//boolean full_circ = false;
		
		double period = (full_circ ? 2 * Math.PI : Math.PI);

		double a = bins_per_cell*(dtheta/period+1);
		int ia = (int)Math.floor(a);
		double frac = a - (double)ia;

		bin[0] = (ia + bins_per_cell) % bins_per_cell;
		bin[1] = (ia + 1) % bins_per_cell;
		factor[0] = (1-frac);
		factor[1] = frac;
		
		assert ( (bin[0] >= 0) && (bin[0] < bins_per_cell));
		assert ( (bin[1] >= 0) && (bin[1] < bins_per_cell));		
	}
	
	public void get_bin_pos (
			float hist[][][],
			float weight,
			int x, 
			int y,
			int number_of_cells_x,
			int number_of_cells_y,
			int rwidth, 
			int rheight, 
			int bins_per_cell, 
			float dtheta, 
			int[] bin, 
			double[] factor)
	{
		boolean full_circ = true;
		
		//double[] extent = { (double)rwidth/number_of_cells_x, (double)rheight/number_of_cells_y};
		
		int number_of_blocks_x = 1;
		
		int number_of_blocks_y = 1;
		
		double[] extent = { (double)rwidth/number_of_blocks_x, (double)rheight/number_of_blocks_y};
		
		float angle = (float) (dtheta * 180.0/Math.PI + 180.0);
		
		if (!full_circ) {
			angle %= 180;
		}
		
		//System.out.printf("theta : %f, angle : %f \n", dtheta, angle);
		//System.out.printf(" %d   ", (int)(Math.floor(angle)));
		
		System.out.printf("*********************\n");
		
		
		/*float[][][] H = new float[2][2][2]; 
		
        for (int indx = 0; indx < number_of_cells_x; indx++) {
                for (int indy = 0; indy < number_of_cells_y; indy++) {
                        for (int k = 0; k < bins_per_cell; k++) {
                                H[indx][indy][k] = (float)0.0;
                        }
                }
        }*/
						
		compute_bin (hist, weight, rwidth, rheight, (double)((x % extent[0]) + 0.5), (double)((y % extent[1])+0.5), (int)(Math.floor(angle)), number_of_cells_x, number_of_cells_y, bins_per_cell, full_circ);

		/*bin[0] = (int)r[0][1];
		bin[1] = (int)r[1][1];
		factor[0] = r[0][0];
		factor[1] = r[1][0];
		
		System.out.printf("-> %d %d %f %f\n", bin[0], bin[1], factor[0], factor[1]);*/
		
	}
	
	/***************************Gradient functions****************************/
	
	public void get_mag_theta (
			double[] grel, 
			double[] dnorm,
			double[] dtheta,
			int rwidth, 
			int rheight, 
			int safe_margin, 
			double noise,
			String gradient_option,
			boolean debug) 
	{
		int n = rwidth * rheight;
		
		/*Image derivative in x direction.*/
		double[] dx = new double[n];

		/*Image derivative in y direction.*/
		double[] dy = new double[n];	
		
		double eps = Math.sqrt(2.0)*noise; /*Assumed deviation of noise in gradient*/
		
		double eps2 = eps * eps;
		
		for (int x = safe_margin; x < (rwidth - safe_margin); x++) {

			for (int y = safe_margin; y < (rheight - safe_margin); y++) {

				int position = y * rwidth + x;

				/*Computing image gradients*/
				double[] grad = new double[2];

				if (gradient_option.compareTo("Sobel") == 0) {
					gradient_sobel (grel, rwidth, rheight, x, y, grad);
				}
				else if (gradient_option.compareTo("Simple") == 0) {
					gradient_simple (grel, rwidth, rheight, x, y, grad);
				}
				else  {
					System.err.printf("Error: choose a valid gradient option\n");
					System.exit(1);
				}

				dx[position] = grad[0];
				dy[position] = grad[1];

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

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

				/*Computing the gradient direction.*/
				dtheta[position] = Math.atan2(dy[position], dx[position]);

				if (dtheta[position] < 0) {
					//dtheta[position] += Math.PI;
					dtheta[position] += 2*Math.PI;
				}	
			}
		}
		
		if (debug) {
			/*Writing image gradients*/  

			Util.writeDoubletoPGM (dx, rwidth, rheight, -1.0, 1.0, "dx");

			Util.writeDoubletoPGM (dy, rwidth, rheight, -1.0, 1.0, "dy");
		}
	}
	
	public void get_mag_theta_dalal (
			BufferedImage image,
			double[] dnorm,
			double[] dtheta,
			int rwidth, 
			int rheight,
			boolean debug) {
		
		int n = rwidth * rheight;
		
		/*Image derivative in x direction.*/
		double[] dxx = new double[n];

		/*Image derivative in y direction.*/
		double[] dyy = new double[n];
		
		int w = image.getWidth();
		int h = image.getHeight();
		
		assert (w == rwidth);
		
		assert (h == rheight);
		
		/*double[][][] rgb = new double[w][h][3];
		double[][][] mag = new double[w][h][3];
		double[][][] theta = new double[w][h][3];
		double[][][] dx = new double[w][h][3];
		double[][][] dy = new double[w][h][3];*/
		
		float[][][] rgb = new float[w][h][3];
		float[][][] mag = new float[w][h][3];
		float[][][] theta = new float[w][h][3];
		float[][][] dx = new float[w][h][3];
		float[][][] dy = new float[w][h][3];

		for (int x = 0; x < w; x++) {
			for (int y = 0; y < h; y++) {				
				int pixel = image.getRGB(x, y);
				rgb[x][y][0] = (pixel >> 16) & 255;
				rgb[x][y][1] = (pixel >> 8) & 255;
				rgb[x][y][2] = (pixel & 255);
				rgb[x][y][0] = (float) Math.sqrt(rgb[x][y][0]);
				rgb[x][y][1] = (float) Math.sqrt(rgb[x][y][1]);
				rgb[x][y][2] = (float) Math.sqrt(rgb[x][y][2]);
			}			
		}

		/*Dy*/
		for (int x = 0; x < w; x++) {
			for (int y = 1; y < h-1; y++) {				
				/*dy[x][y][0] = (rgb[x][y+1][0] - rgb[x][y-1][0])/2.0;
				dy[x][y][1] = (rgb[x][y+1][1] - rgb[x][y-1][1])/2.0;
				dy[x][y][2] = (rgb[x][y+1][2] - rgb[x][y-1][2])/2.0;*/
				dy[x][y][0] = (rgb[x][y+1][0] - rgb[x][y-1][0]);
				dy[x][y][1] = (rgb[x][y+1][1] - rgb[x][y-1][1]);
				dy[x][y][2] = (rgb[x][y+1][2] - rgb[x][y-1][2]);
			}
		}
		for (int x = 0; x < w; x++) {
			for (int k = 0; k < 3; k++) {
				dy[x][0][k] = dy[x][1][k];
				dy[x][h-1][k] = dy[x][h-2][k];
			}
		}
		/*End Dy*/

		/*Dx*/
		for (int x = 1; x < w-1; x++) {
			for (int y = 0; y < h; y++) {				
				/*dx[x][y][0] = (rgb[x+1][y][0] - rgb[x-1][y][0])/2.0;
				dx[x][y][1] = (rgb[x+1][y][1] - rgb[x-1][y][1])/2.0;
				dx[x][y][2] = (rgb[x+1][y][2] - rgb[x-1][y][2])/2.0;*/
				dx[x][y][0] = (rgb[x+1][y][0] - rgb[x-1][y][0]);
				dx[x][y][1] = (rgb[x+1][y][1] - rgb[x-1][y][1]);
				dx[x][y][2] = (rgb[x+1][y][2] - rgb[x-1][y][2]);
			}			
		}
		for (int y = 0; y < h; y++) {
			for (int k = 0; k < 3; k++) { 
				dx[0][y][k] = dx[1][y][k];
				dx[w-1][y][k] = dx[w-2][y][k];
			}
		}
		/*End Dx*/

		/*Mag*/
		for (int x = 0; x < w; x++) {
			for (int y = 0; y < h; y++) {
				for (int k = 0; k < 3; k++) {
					mag[x][y][k] = (float) Math.sqrt((dx[x][y][k]*dx[x][y][k]) + (dy[x][y][k]*dy[x][y][k]));
				}
			}			
		}
		/*End Mag*/

		/*Theta*/
		for (int x = 0; x < w; x++) {
			for (int y = 0; y < h; y++) {
				for (int k = 0; k < 3; k++) {
					theta[x][y][k] = (float) Math.atan2(dy[x][y][k],dx[x][y][k]);				    
				}
			}			
		}
		/*End Mag*/

		for (int x = 0; x < w; x++) {
			for (int y = 0; y < h; y++) {
				int position = y * rwidth + x;
				double imaxMag = -Double.MAX_VALUE;
				double imaxTheta = -Double.MAX_VALUE;
				for (int k = 0; k < 3; k++) {
					if (mag[x][y][k] > imaxMag) {
						imaxMag = mag[x][y][k];
						imaxTheta = theta[x][y][k];
						dxx[position] = dx[x][y][k];
						dyy[position] = dy[x][y][k];
					}
				}
				//System.out.printf("[%.8f %.8f], ", imaxMag, imaxTheta);
				dnorm[position] = imaxMag;
				dtheta[position] = imaxTheta;
			}			
		}	
		
		
		if (debug) {
			/*Writing image gradients*/  

			Util.writeDoubletoPGM (dxx, rwidth, rheight, -1.0, 1.0, "dx");

			Util.writeDoubletoPGM (dyy, rwidth, rheight, -1.0, 1.0, "dy");
		}	
		
	}
	
	/*Sobel*/
	public void gradient_sobel (double[] image, int width, int height, int x, int y, double[] grad) {
		
		int position = y * width + x;
		
		double vmo = image[position - 1];
		double vpo = image[position + 1];
		double vom = image[position - width];
		double vop = image[position + width];

		double vmm = image[position - 1 - width];
		double vmp = image[position - 1 + width];
		double vpm = image[position + 1 - width];
		double vpp = image[position + 1 + width];
		
		grad[0] = (vpm + 2*vpo + vpp - vmm - 2*vmo - vmp)/8.0;
		grad[1] = (vmm + 2*vom + vpm - vmp - 2*vop - vpp)/8.0;
	}
	
	/*Simple*/
	public void gradient_simple (double[] image, int width, int height, int x, int y, double[] grad) {

		int position_x = y * width + x;
		
		int position_y = y * width + x;
		
		if (x == 0) {
			position_x = y * width + (x + 1);
		}
		if (x == (width-1)) {
			position_x = y * width + (x - 1);
		}
		
		if (y == 0) {
			position_y = (y + 1) * width + x;
		}
		
		if (y == (height - 1)) {
			position_y = (y - 1) * width + x;
		}
		
		double kxm = image[position_x - 1];
		double kxp = image[position_x + 1];
		double kym = image[position_y - width];
		double kyp = image[position_y + width];

		grad[0] = (kxp - kxm)/2;
		grad[1] = (kyp - kym)/2;
		/*grad[0] = (kxp - kxm);
		grad[1] = (kyp - kym);*/
	}
	
	/****************************Weight functions*****************************/
	
	/* Computes the cell weight factor for one axis {z} (x or y). Given: number of cells 
	 * {ncz}, cell index {cz}, pixel index {z}, all along that axis. 
	 * The option selects the weight type. Adjusting the interval by {r_bot, r_top}.*/
	public double cell_weight (String weight_function, int ncz, int cz, int z, double zmax, double zmin) {
		
		if (ncz == 1) {
			return 1; 
		}
		
		double z_star = (z - zmin)/(zmax - zmin);

		if (weight_function.compareTo("Step") == 0) {
			return StepFunc (ncz,cz,z_star);
		}
		else if (weight_function.compareTo("Dalal") == 0) {
			//return dalal_weight2 (z, cz, (int)(zmax - zmin));
			return StepFunc (ncz,cz,z_star);			
		}
		else if (weight_function.compareTo("Bernstein") == 0){
			return Bernstein (ncz-1,cz,z_star);
		}
		else if (weight_function.compareTo("Core") == 0) {
			return EdgeCore (ncz,cz,z_star);
		}
		else if (weight_function.compareTo("Exp") == 0){
			return Exp (ncz,cz,z_star);
		}
		else {
			assert(false);
			System.exit(1);
			return 0;
		}
	}
		
	/*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));
		//if (z <= 0) { return (k == 0 ? 0 : 1); }
		//else if (z >= 1) { return (k == n ? 0 : 1); }
		if (z <= 0) { return (k == 0 ? 1 : 0); }
		else if (z >= 1) { return (k == (n - 1) ? 1 : 0); }
		else {
			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));
		if (z <= 0) { return (k == 0 ? 1 : 0); }
		else if (z >= 1) { return (k == n ? 1 : 0); }
		else {
			double zmax = ((double)k)/((double)n);
			return BernsteinPoly(n,k,z)/BernsteinPoly(n,k,zmax);
		}
	}
	
	public double BernsteinPoly (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);
	}

	/*An edge-core weight function. If {n == 1} returns 1, if (n == 2) returns 
	 *weight 1.0 near the edges, or 1.0 in the core region depending on {k}*/
	public double EdgeCore (int n, int k, double z) {
		assert(n == 2);
		assert((k >= 0) && (k < n));
		if ( (z <= 0) || (z >= 1) ) { return (k == 0 ? 1 : 0); }
		else {
			double v = 4 * z * (1 - z);
			v = v*v;
			return (k==0? 1 - v: v);
		}
	}
	
	/*An exponential weight function.*/	
	private double gaussian (double z, double mu, double sigma) {
		return Math.exp(- ((z - mu)*(z - mu))/(2 * sigma * sigma));
	}

	/*An exponential weight function. */
	/*public double Exp (int n, int k, double z) {

		double mu = 0.15;

		double sigma_A = 0.3;

		double sigma_B = 0.3;

		assert((n==1) || (n == 3));
		assert((k >= 0) && (k < n));

		if (k == 0) {
			return gaussian (z, -mu, sigma_A);
		}
		else if (k == 2) {
			return gaussian (z, 1+mu, sigma_A);
		}
		else if (k == 1) {
			return gaussian (z, 0.5, sigma_B);
		}
		else {
			assert(false);
			return 0;
		}
	}*/
	
	/*An exponential weight function. */
	/*public double Exp (int n, int k, double z) {

		double mu = 0.15;

		double sigma = 1.0;
		
		assert((k >= 0) && (k < n));

		double avg = -mu + (1 + 2 * mu)/(double)(n - 1)*k;
		
		double dev = sigma/n;
		
		return gaussian (z, avg, dev);		
	}*/
	
	/*Best*/
	/*public double Exp (int n, int k, double z) {

		double mu = 0.01;

		double sigma = 0.5; 
		
		assert((k >= 0) && (k < n));

		double avg = -mu + (1 + 2 * mu)/(double)(n - 1)*k;
		
		double dev = sigma/n;
		
		return gaussian (z, avg, dev);		
	}*/
	
	public double Exp (int n, int k, double z) {

		double mu = 0.01; //-> Best JOURNAL IEEE
				
		double sigma = 0.5; //-> Best JOURNAL IEEE
		//double sigma = 0.4; //-> Best JOURNAL IEEE
		
		assert((k >= 0) && (k < n));

		double avg = -mu + (1 + 2 * mu)/(double)(n - 1)*k;
		
		double dev = sigma/n;
		
		return gaussian (z, avg, dev);		
	}
		
	/**/
	public int choose_gaussian_weight_size (double dev) {
		double rwt = 2*dev;
		return (int)(Math.ceil(rwt));
	}
	
	/**/
	public void compute_binomial_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 compute_gaussian_weights (double[] weight, int rwt) {
		double eps = 0.01;
		double a = -Math.log(eps);
		int nwt = 2*rwt+1;
		
		for (int i = 0; i < nwt; i++) { 
			double z = (i - rwt)/((double)rwt);
			weight[i] = (1-z*z)*Math.exp(-a*z*z);
		}
	}
	
	/*************************Drawing HOG histograms**************************/
	
	/*Function used to draw the HOG descriptor with a circular shape, 
	 *where each bin is proportional to its gradient norm.*/
	public void print_histogram (BufferedImage image, double[] histogram, int number_of_cells_x, int number_of_cells_y, int bins_per_cell, String histogram_normalization_metric, String outname, int cte) {
		
		/*HOG descriptor image dimensions*/		
		int width = 600;		
		int height = 600;

		/*Factor to magnify each HOG bin*/
		double magnify = -1;
		
		if (histogram_normalization_metric.compareTo("L1") == 0) {
			magnify = Math.sqrt(bins_per_cell) * cte;
		}
		else if (histogram_normalization_metric.compareTo("L2") == 0) {
			magnify = 1 * cte;
		}
		else if (histogram_normalization_metric.compareTo("D1") == 0) {
			magnify = Math.sqrt(bins_per_cell) * cte;			
		}

		//magnify /= 3;
		
		/*Circle radius*/
		double radius = 90;
		
		/*Circle center*/
		double c_x = width/2;		
		double c_y = height/2;
		
		int n = width * height;
				
		BufferedImage temp = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		
		/*Defining the graphics settings.*/
		float[] hsbvals = new float[3];		
		Graphics2D g = temp.createGraphics();		
		g.setStroke(new BasicStroke(1.0f));			
		Color.RGBtoHSB(0, 0, 0, hsbvals);		
		g.setColor(Color.getHSBColor(hsbvals[0], hsbvals[1], hsbvals[2]));
		
		/*Background painting*/		
		int white = 0xFFFFFF;
		
		for (int i = 0; i < n; i++) {
			int x = i % width;
			int y = i / width;
			temp.setRGB(x, y, white);
		}
		
		/*Drawing HOG bins*/		
		for (int bin = 0; bin < bins_per_cell; bin++) {
			
			/*180*/
			//double theta_a = (bin+0.4)*Math.PI/bins_per_cell;
			
			//double theta_b = (bin-0.4)*Math.PI/bins_per_cell;
		
			/*360*/
			double theta_a = (bin+0.4)*2*Math.PI/bins_per_cell;
			
			double theta_b = (bin-0.4)*2*Math.PI/bins_per_cell;
	
			Polygon polygon = null;
			
			for (int k = 0; k < 2; k++) {
			
				/*180*/
				//polygon = draw_Polygon (c_x, c_y, radius, theta_a+k*Math.PI, theta_b+k*Math.PI, histogram[bin], magnify);
				
				/*360*/
				polygon = draw_Polygon (c_x, c_y, radius, theta_a+k*2*Math.PI, theta_b+k*2*Math.PI, histogram[bin], magnify);					
							
				g.fillPolygon(polygon);
			}			
		}
		g.dispose();
		
		try {
			ImageIO.write(temp, "png", new File(outname));
		}
		catch (Exception e) { 
			System.err.printf("cannot write image\n");
		}		
	} 
	
	
	/*Function to draw each HOG bin as a rectangle around a circle.*/
	/*Function used to draw each HOG bin as a rectangle around a circle.*/
	/*Assumes that the Y axis points up.*/
	private Polygon draw_Polygon (double c_x, double c_y, double radius, double theta_a, double theta_b, double value, double magnify) { 
		
		/*Taking each point of the rectangle*/
		double x1 = c_x + radius * Math.cos(theta_a);
		double y1 = c_y + radius * Math.sin(theta_a);
		
		double x2 = c_x + (radius + value*magnify) * Math.cos(theta_a);
		double y2 = c_y + (radius + value*magnify) * Math.sin(theta_a);
		
		double x3 = c_x + radius * Math.cos(theta_b);
		double y3 = c_y + radius * Math.sin(theta_b);
		
		double x4 = c_x + (radius + value*magnify) * Math.cos(theta_b);
		double y4 = c_y + (radius + value*magnify) * Math.sin(theta_b);
		
		/*Getting the polygon*/
		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); 
		
		return polygon;
	}

	/*********************** Text baselines functions ************************/
	
	public double[][] text_baselines (int w, int h, double[] weight, int rwt, double[] grel) {
		
		double[] s = new double[h];
		
		double[][] baselines = new double[w][2]; 
		
		for (int x = 0; x < w; x++) {

			for (int y = 0; y < h; y++) {	
				
				double sum_var = 0.0, sum_avg = 0.0, sum_w = 0.0;
				
				for (int dx = -rwt; dx <= rwt; dx++) {
					int x1 = x + dx;
					int position = y * w + x1;
					if ((x1 >= 0) && (x1 < w)) {
						sum_avg += weight[rwt+dx]*grel[position];
						sum_w += weight[rwt+dx]; 
					}
				}	
				assert(sum_w != 0.0); 
				
				double AVG = (sum_avg/sum_w);
				
				for (int dx = -rwt; dx <= rwt; dx++) {
					int x1 = x + dx;
					int position = y * w + x1;
					if ((x1 >= 0) && (x1 < w)) {
						sum_var += weight[rwt+dx]* ( (grel[position]-AVG)*(grel[position]-AVG) );
					}
				}				
				s[y] = (sum_var/sum_w);
			}
			double[] tmp = get_baselines_by_percentiles (h, s);
			baselines[x][0] = tmp[0];
			baselines[x][1] = tmp[1];
		}
		return baselines;
	}
	
	public double[] get_baselines_by_percentiles (int h, double[] s)		
	{
		//double percentile = 0.1; //-> Best
		double percentile = 0.2; //-> Best
		//double percentile = 0.15;
		
		double[] baselines = new double[2];

		double sum_s = 0.0;

		for (int y = 0; y < h; y++) {
			sum_s += s[y];
		}

		int top = 0;
		
		double sum_top = 0.0;
		
		while ( (top < h) && (sum_top+s[top] <= percentile*sum_s) ) {
			sum_top += s[top];
			top++;
		}
		
		if (top > 0) { top--; }
		
		int bot = h-1;
		
		double sum_bot = 0.0;
		
		while ( (bot >= 0) && (sum_bot+s[bot] <= percentile*sum_s) ) {
			sum_bot += s[bot];
			bot--;
		}
		
		if (bot < h-1) { bot++; }
		
		double y_med = 0.5*(top+bot);
		
		double y_delta_min = 0.2*h;
		
		double y_delta = Math.max(0.5*(bot-top), y_delta_min);

		baselines[0] = Math.min(y_med + y_delta,h-1); //ybot
		baselines[1] = Math.max(y_med - y_delta,0);   //ytop 

		return baselines;
	}
	
	public double[] get_baselines_by_med (int h, double[] s)		
	{
		double[] baselines = new double[2];

		double sum_s = 0.0;

		double sum_sy = 0.0;

		for (int y = 0; y < h; y++) {
			sum_sy += s[y] * y;
			sum_s += s[y];
		}

		double y_med = sum_sy/sum_s;

		double sum_sdy2 = 0.0;

		for (int y = 0; y < h; y++) {
			sum_sdy2 += s[y] * ( (y - y_med)*(y - y_med) );
			sum_s += s[y];
		}

		double y_dev = Math.sqrt(sum_sdy2/sum_s);
		
		double y_delta_min = 0.2*h;
		
		double y_delta = Math.max(3*y_dev, y_delta_min);

		baselines[0] = Math.min(y_med + y_delta,h); //ybot
		baselines[1] = Math.max(y_med - y_delta,0); //ytop 
		
		return baselines;
	}

	
	/************************* Auxiliary functions ***************************/

    /*Get a grey level image from a color image.*/
	public double[] get_grey_image (BufferedImage image) {

		
		int w = image.getWidth(null);
		
		int h = image.getHeight(null);
		
		double[] grey = new double[w*h];
		
		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); 
				
				R /= 255.0;
				G /= 255.0;
				B /= 255.0;
				/*R *= R;
				G *= G;
				B *= B;*/					
				grey[position] = 0.299*R + 0.587*G + 0.114*B;
				
			}
		}
		return grey;
	}
	
	public void convert_to_log_scale (double[] grey, int w, int h, double eps) {
		int n = w * h;
		for (int position = 0; position < n; position++) {
			grey[position] = (Math.log(grey[position] + eps) - Math.log(eps))/(Math.log(1+eps)-Math.log(eps));
		}
	}
	
	/*Get a normalized image from a grey level image.*/
	public double[] normalize_grey_image (double[] grey, int w, int h, double[] x_weight, int x_rwt, double[] y_weight, int y_rwt, double noise) {
		
		double[] grel = new double[w*h];
		
		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, x_weight, x_rwt, y_weight, y_rwt);
				DEV = get_grey_dev (grey, w, h, x, y, x_weight, x_rwt, y_weight, y_rwt, AVG, noise);
				grel[position] = (grey[position] - AVG)/(3*DEV)+0.5;
				//grel[position] = (grey[position] - AVG)/(2*DEV)+0.5;
				//grel[position] = (grey[position] - AVG)/DEV+0.5; //-> Best do Best
				if (grel[position] < 0) { grel[position] = 0.0; }
				else if (grel[position] > 1) { grel[position] = 1.0; }
			}
		}
		return grel;
	}
	
	/*Get the averaged pixel value weighted by normalizing window*/
	public double get_grey_avg (double[] grey, int w, int h, int x, int y, double[] x_weight, int x_rwt, double[] y_weight, int y_rwt) {
		double sum_vwt = 0.0, sum_wt = 0.0;
		for (int dy = -y_rwt; dy <= y_rwt; dy++) {
			for (int dx = -x_rwt; dx <= x_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 = x_weight[x_rwt+dx]*y_weight[y_rwt+dy];
				    sum_vwt += v * wt;
				    sum_wt += wt; 
				}
			}
		}
		return sum_vwt/sum_wt;
	}
	 
	/*Get the deviation of a pixel given a normalizing window*/
	public double get_grey_dev (double[] grey, int w, int h, int x, int y, double[] x_weight, int x_rwt, double[] y_weight, int y_rwt, double AVG, double noise) {
		double sum_v2wt = 0.0, sum_wt = 0.0;
		for (int dy = -y_rwt; dy <= y_rwt; dy++) {
			for (int dx = -x_rwt; dx <= x_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 = x_weight[x_rwt+dx]*y_weight[y_rwt+dy];
				    sum_v2wt += v * v * wt;
				    sum_wt += wt; 
				}

			}
		}
		
		return Math.sqrt(sum_v2wt/sum_wt + noise*noise);
	}
	/*END*/
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	public void compute_bin (float hist[][][], float weight, int w, int h, double x, double y, int angle, int ncx, int ncy, int bins_per_cell, boolean full_circ) {

		int nbx = 1;
		int nby = 1;		
		
		int orientation = 0;
		
		if (full_circ) {
			orientation = 360; /* half circle */
		}
		else {
			orientation = 180; /* half circle */
		}
		int VDim = 3; /* Vector dimension */
		float[] vec = { (float)x, (float)y, (float) angle };
		// System.out.printf("vec : %f, %f, %f\n", vec[0], vec[1], vec[2]);
		float[] pI = toindex(vec, w, h, ncx, ncy, nbx, nby, bins_per_cell,
				orientation, VDim);
		
		//System.out.printf("pI : %.14f, %.14f, %.14f\n", pI[0], pI[1], pI[2]);

		int[] fI = { (int) Math.floor(pI[0]), (int) Math.floor(pI[1]),
				(int) Math.floor(pI[2]) };
		int[] cI = { (int) Math.ceil(pI[0]), (int) Math.ceil(pI[1]),
				(int) Math.ceil(pI[2]) };
		float[] d = { (float)(pI[0] - fI[0]), (float)(pI[1] - fI[1]),(float)(pI[2] - fI[2])};

		//System.out.printf("fI: %d, %d, %d\n", fI[0], fI[1], fI[2]);
		
		//System.out.printf("d: %.14f, %.14f, %.14f\n", d[0], d[1], d[2]);
		
		//int[] tvbin_ = { nbx, nby, bins_per_cell }; -> Por blocos
		
		int[] tvbin_ = { nbx, ncy, bins_per_cell };
		
		//System.out.printf("tvbin: %d, %d, %d\n", tvbin_[0], tvbin_[1], tvbin_[2]);
		System.out.printf("--------------------\n");
		boolean[] warp_ = { false, false, true };

		boolean[] lowervalid = { true, true, true };
		boolean[] uppervalid = { true, true, true };

		float onehalf_ = 1 / 2;

		for (int i = 0; i < VDim; ++i) {
			if (warp_[i]) {
				cI[i] %= tvbin_[i];
				fI[i] %= tvbin_[i];
				if (fI[i] < 0)
					fI[i] += tvbin_[i];
				if (cI[i] < 0)
					cI[i] += tvbin_[i];
			} else {
				if (cI[i] >= tvbin_[i] - onehalf_ || cI[i] < -onehalf_)
					uppervalid[i] = false;
				if (fI[i] < -onehalf_ || fI[i] >= tvbin_[i] - onehalf_)
					lowervalid[i] = false;
			}
		}
		interpol_linear(hist, weight, d, fI, cI, lowervalid, uppervalid);
	}
	
	
	/*public void compute_bin (float hist[][][], double weight, int w, int h, double x, double y, int angle, int ncx, int ncy, int bins_per_cell, boolean full_circ) {

		int nbx = 1;
		int nby = 1;		
		
		int orientation = 0;
		
		if (full_circ) {
			orientation = 360; 
		}
		else {
			orientation = 180; 
		}
		int VDim = 3; 
		double[] vec = { x, y, (double) angle };
		// System.out.printf("vec : %f, %f, %f\n", vec[0], vec[1], vec[2]);
		double[] pI = toindex(vec, w, h, ncx, ncy, nbx, nby, bins_per_cell,
				orientation, VDim);
		
		// System.out.printf("pI : %f, %f, %f\n", pI[0], pI[1], pI[2]);

		int[] fI = { (int) Math.floor(pI[0]), (int) Math.floor(pI[1]),
				(int) Math.floor(pI[2]) };
		int[] cI = { (int) Math.ceil(pI[0]), (int) Math.ceil(pI[1]),
				(int) Math.ceil(pI[2]) };
		double[] d = { pI[0] - fI[0], pI[1] - fI[1], pI[2] - fI[2] };

		//System.out.printf("fI: %d, %d, %d\n", fI[0], fI[1], fI[2]);
		
		//System.out.printf("d: %f, %f, %f\n", d[0], d[1], d[2]);
		
		//int[] tvbin_ = { nbx, nby, bins_per_cell }; -> Por blocos
		
		int[] tvbin_ = { nbx, ncy, bins_per_cell };
		
		//System.out.printf("tvbin: %d, %d, %d\n", tvbin_[0], tvbin_[1], tvbin_[2]);
		boolean[] warp_ = { false, false, true };

		boolean[] lowervalid = { true, true, true };
		boolean[] uppervalid = { true, true, true };

		double onehalf_ = 1 / 2;

		for (int i = 0; i < VDim; ++i) {
			if (warp_[i]) {
				cI[i] %= tvbin_[i];
				fI[i] %= tvbin_[i];
				if (fI[i] < 0)
					fI[i] += tvbin_[i];
				if (cI[i] < 0)
					cI[i] += tvbin_[i];
			} else {
				if (cI[i] >= tvbin_[i] - onehalf_ || cI[i] < -onehalf_)
					uppervalid[i] = false;
				if (fI[i] < -onehalf_ || fI[i] >= tvbin_[i] - onehalf_)
					lowervalid[i] = false;
			}
		}
		interpol_linear(hist, weight, d, fI, cI, lowervalid, uppervalid);
	} */
	
	/*float[] toindex (float[] value, int w, int h, int ncx, int ncy, int nbx, int nby, 
			int bins_per_cell, int orientation, int VDim) {
		float[] r = new float[VDim];
		float onehalf_ = (float) 0.5;
		//double[] half_bin = {0.5*nbx, 0.5*nby, 0.5*bins_per_cell}; -> Block
		float[] half_bin = {(float) (0.5*nbx), (float) (0.5*ncy), (float) (0.5*bins_per_cell)};
		float[] bandwidth = {(float)w/(float)ncx, (float)h/(float)ncy, (float)orientation/(float)bins_per_cell};
		//double[] cenBound = {(double)w/(double)ncx/2.0, (double)h/(double)ncy/2.0, (double)orientation/2.0};-> Block
		float[] cenBound = {(float)w/(float)ncx/(float)2.0, (float)h/(float)nby/(float)2.0, (float)orientation/(float)2.0};
		
		//int[] cenBound = {w/ncx/2, h/nby/2, orientation/2};
		
		
		System.out.printf("half_bin: %.14f, %.14f, %.14f\n", half_bin[0], half_bin[1], half_bin[2]);
		System.out.printf("v: %.14f, %.14f, %.14f\n", value[0], value[1], value[2]);
		System.out.printf("cenBound: %.14f, %.14f, %.14f\n", cenBound[0], cenBound[1], cenBound[2]);
		System.out.printf("bandwidth: %.14f, %.14f, %.14f\n", bandwidth[0], bandwidth[1], bandwidth[2]);
		System.out.printf("onehalf : %.14f\n", onehalf_);
		
		
		for (int i = 0; i < 3; i++) {
			r[i] = (half_bin[i] - onehalf_ + (float)(value[i] - cenBound[i])/(float)bandwidth[i]);
		}
    	return r;
	}*/
	
	float[] toindex (float[] value, int w, int h, int ncx, int ncy, int nbx, int nby, 
			int bins_per_cell, int orientation, int VDim) {
		float[] r = new float[VDim];
		double onehalf_ = 0.5;
		//double[] half_bin = {0.5*nbx, 0.5*nby, 0.5*bins_per_cell}; -> Block
		double[] half_bin = {0.5*nbx, 0.5*ncy, 0.5*bins_per_cell};
		double[] bandwidth = {(double)w/(double)ncx, (double)h/(double)ncy, (double)orientation/(double)bins_per_cell};
		//double[] cenBound = {(double)w/(double)ncx/2.0, (double)h/(double)ncy/2.0, (double)orientation/2.0};-> Block
		double[] cenBound = {(double)w/(double)ncx/2.0, (double)h/(double)nby/2.0, (double)orientation/2.0};

		/*System.out.printf("half_bin: %.14f, %.14f, %.14f\n", half_bin[0], half_bin[1], half_bin[2]);
		System.out.printf("v: %.14f, %.14f, %.14f\n", value[0], value[1], value[2]);
		System.out.printf("cenBound: %.14f, %.14f, %.14f\n", cenBound[0], cenBound[1], cenBound[2]);
		System.out.printf("bandwidth: %.14f, %.14f, %.14f\n", bandwidth[0], bandwidth[1], bandwidth[2]);
		System.out.printf("onehalf : %.14f\n", onehalf_);*/
		
		for (int i = 0; i < 3; i++) {
			r[i] = (float)(half_bin[i] - onehalf_ + (value[i] - (double)cenBound[i])/(double)bandwidth[i]);
		}
		return r;
	}
	
	
	/*double[] toindex (double[] value, int w, int h, int ncx, int ncy, int nbx, int nby, 
			int bins_per_cell, int orientation, int VDim) {
		double[] r = new double[VDim];
		double onehalf_ = 0.5;
		//double[] half_bin = {0.5*nbx, 0.5*nby, 0.5*bins_per_cell}; -> Block
		double[] half_bin = {0.5*nbx, 0.5*ncy, 0.5*bins_per_cell};
		double[] bandwidth = {(double)w/(double)ncx, (double)h/(double)ncy, (double)orientation/(double)bins_per_cell};
		//double[] cenBound = {(double)w/(double)ncx/2.0, (double)h/(double)ncy/2.0, (double)orientation/2.0};-> Block
		double[] cenBound = {(double)w/(double)ncx/2.0, (double)h/(double)nby/2.0, (double)orientation/2.0};
		
		for (int i = 0; i < 3; i++) {
			r[i] = half_bin[i] - onehalf_ + (value[i] - (double)cenBound[i])/(double)bandwidth[i];
		}
    	return r;
	}*/
	
	public void interpol_linear (float[][][] hist, float weight, float[] d, int[] lowerI, int[] upperI, boolean[] lowerValid, boolean[] upperValid) {

		//double[][] r = new double[2][2];
		
		//float[] c = {(float)(1-d[0]), (float)(1-d[1]), (float)(1-d[2])};
		
		double[] c = {(1-d[0]), (1-d[1]), (1-d[2])};
		
		//System.out.printf("c: %.14f, %.14f, %.14f\n", c[0], c[1], c[2]);

		//System.err.printf("%f, %f, %f, %f \n", d[0], d[1], d[2], weight);

		for (int i = 0; i < 2; ++i) {
			if ((i==0 && !lowerValid[0]) || (i != 0 && !upperValid[0]))
				continue;
			float iwt = (float)(i != 0 ? d[0] : c[0]);
			//System.out.printf("iwt : %.14f\n", iwt);
			int ipos = (i != 0 ? upperI[0] : lowerI[0]);

			for (int j = 0; j < 2; ++j) {
				if ((j==0 && !lowerValid[1]) || (j !=0 && !upperValid[1]))
					continue;
				
				double jwt = iwt*(j != 0 ? d[1] : c[1]);
				
				//System.out.printf("jwt : %.14f\n", jwt); 
				int jpos = (j != 0 ? upperI[1] : lowerI[1]);

				for (int k = 0; k < 2; ++k) {
					if ((k==0 && !lowerValid[2]) || (k != 0 && !upperValid[2]))
						continue;
					double kwt = (float)(jwt)*(k != 0 ? d[2] : c[2]);
					//double kwt = (jwt)*(k != 0 ? d[2] : c[2]);
					int kpos = (k != 0 ? upperI[2] : lowerI[2]);
					{
						//r[k][0] = kwt;
						//r[k][1] = kpos;
						hist[ipos][jpos][kpos] += (float)(weight*kwt);
						System.out.printf("ipos %d, jpos %d, kpos %d, value : %.14f, kwt %.14f, weight : %.14f, hist : %.14f\n", ipos, jpos, kpos, weight, kwt, weight*kwt, hist[ipos][jpos][kpos]);
					}
				}
			}
		}
		//return r;
	}
	
	/*public void interpol_linear (float[][][] hist, double weight, double[] d, int[] lowerI, int[] upperI, boolean[] lowerValid, boolean[] upperValid) {

		//double[][] r = new double[2][2];
		
		double[] c = {(1-d[0]), (1-d[1]), (1-d[2])};

		//System.err.printf("%f, %f, %f, %f \n", d[0], d[1], d[2], weight);

		for (int i = 0; i < 2; ++i) {
			if ((i==0 && !lowerValid[0]) || (i != 0 && !upperValid[0]))
				continue;
			double iwt = (i != 0 ? d[0] : c[0]);
			int ipos = (i != 0 ? upperI[0] : lowerI[0]);

			for (int j = 0; j < 2; ++j) {
				if ((j==0 && !lowerValid[1]) || (j !=0 && !upperValid[1]))
					continue;
				double jwt = iwt*(j != 0 ? d[1] : c[1]);
				int jpos = (j != 0 ? upperI[1] : lowerI[1]);

				for (int k = 0; k < 2; ++k) {
					if ((k==0 && !lowerValid[2]) || (k != 0 && !upperValid[2]))
						continue;
					double kwt = jwt*(k != 0 ? d[2] : c[2]);
					int kpos = (k != 0 ? upperI[2] : lowerI[2]);
					{
						//r[k][0] = kwt;
						//r[k][1] = kpos;
						hist[ipos][jpos][kpos] += weight*kwt;
						//System.out.printf("ipos %d, jpos %d, kpos %d, value : %f, kwt %f, weight : %f, hist : %f\n", ipos, jpos, kpos, weight, kwt, weight*kwt, hist[ipos][jpos][kpos]);
					}
				}
			}
		}
		//return r;
	}*/
	
    public double dalal_weight2 (int z, int ncz, int size) {
    	
    	assert((z >= 0) && (z <= size));
		if (z <= 0) { return (z == 0 ? 1 : 0); }
		else if (z >= 1) { return (z == size ? 1 : 0); }
		else {
		
		float wtscale = 1;
		
		int nb = 1;

		float cellsize = (float)size/(float)ncz;
		
		float extent = (float)size/ncz;
		
		float center = (float)extent/(float)2.0;
		
		float numcell = (float)nb;

		float var2 = (cellsize * numcell)/(2*wtscale);

		z %= extent;		
		float sum = (float)(z - center);
		sum *= sum / var2;
		return Math.exp(-sum);
		}
	}
	
	
	public float dalal_weight (int x, int y, int ncx, int ncy, int w, int h) {
		
		float wtscale = 1;
		
		int nbx = 1, nby = 1;

		float[] var2 = new float[2];

		float[] cellsize = { (float)w/(float)ncx, (float)h/(float)ncy};
		
		float[] extent = { (float)w/ncx, (float)h/ncy};
		
		float[] center = { (float)extent[0]/(float)2.0, (float)extent[1]/(float)2.0};
		
		float[] numcell = { (float)nbx, (float)nby};

		for (int i = 0; i < 2; i++) { 
			var2[i] = (cellsize[i] * numcell[i])/(2*wtscale);
			var2[i] *= var2[i]*2;
		}

		x %= extent[0];
		y %= extent[1];			
		
		float[] t = new float[2];
		for (int k = 0; k < 2; k++) {
			if (k == 0) {			    
				t[k] = x - center[k];
			}
			else {
				t[k] = y - center[k];
			}
			t[k] *= t[k] / var2[k];
		}
		float sum = (float)0.0;
		for (int k = 0; k < 2; k++) { sum += t[k]; }
		return (float)Math.exp(-sum);
	}
	
	/*public float dalal_weight (int x, int y, int ncx, int ncy, int w, int h) {
		
		double wtscale = 1;
		
		int nbx = 1, nby = 1;

		double[] var2 = new double[2];

		double[] cellsize = { (double)w/(double)ncx, (double)h/(double)ncy};
		
		double[] extent = { (double)w/ncx, (double)h/ncy};
		
		double[] center = { extent[0]/2.0, extent[1]/2.0};
		
		double[] numcell = { (double)nbx, (double)nby};

		for (int i = 0; i < 2; i++) { 
			var2[i] = (cellsize[i] * numcell[i])/(2*wtscale);
			var2[i] *= var2[i]*2;
		}

		x %= extent[0];
		y %= extent[1];			
		
		double[] t = new double[2];
		for (int k = 0; k < 2; k++) {
			if (k == 0) {			    
				t[k] = x - center[k];
			}
			else {
				t[k] = y - center[k];
			}
			t[k] *= t[k] / var2[k];
		}
		double sum = 0.0;
		for (int k = 0; k < 2; k++) { sum += t[k]; }
		return (float)Math.exp(-sum);
	}*/
}