content: package edu.stanford.rsl.tutorial.fan.redundancy;
  
  import ij.ImageJ;
  import edu.stanford.rsl.conrad.data.numeric.Grid2D;
  import edu.stanford.rsl.conrad.data.numeric.Grid3D;
  import edu.stanford.rsl.conrad.data.numeric.NumericPointwiseOperators;
  import edu.stanford.rsl.conrad.utils.DoubleArrayUtil;
  
  
  public class CompensationWeights extends Grid2D {
  
  	private final double deltax;
  	private final int maxTIndex, maxLambdaIndex;
  
  
  
  	public double EtaFunction(double delta, double deltax, double lambda)
  	{
  		double val = Math.sin((Math.PI/2) * (Math.PI + deltax - lambda) / (deltax - 2 * delta));
  		val = val * val;
  		return val;
  	}
  
  
  	public double ZetaFunction(double delta, double deltax, double lambda)
  	{
  		double val = Math.sin((Math.PI/2) * (lambda) / (deltax + 2 * delta));
  		val = val * val;
  		return val;
  	}
  
  
  	public CompensationWeights(final double focalLength, final double maxT,
  			final double deltaT, double maxLambda, double dLambda) {
  		// Call constructor from superclass
  		super((int) Math.round(maxT / deltaT), (int)(Math.round(maxLambda / dLambda)) + 1);
  		
  		// Initialize parameters
  		this.maxLambdaIndex = (int)(Math.round(maxLambda / dLambda)) + 1;
  		this.maxTIndex = (int) Math.round(maxT / deltaT);
  		//double gammaM = (double) (Math.atan((maxT / 2.d)/ focalLength) );
  		this.deltax = maxLambda - Math.PI; 
  		double lambda, delta;
  
  
  		// iterate over the detector elements
  		for (int t = 0; t < maxTIndex; ++t) {
  			// compute delta of the current ray (detector element)
  			delta = Math.atan((t * deltaT - maxT / 2.d + 0.5*deltaT) / focalLength);
  
  			// iterate over the projection angles
  			for (int b = 0; b < maxLambdaIndex; ++b) {
  				// compute the current lambda angle
  				lambda = b * dLambda;
  
  				// Default weight is 1
  				this.setAtIndex(maxTIndex - t -1, b, 1.0f);
  				
  				// Check if zeta and eta are evaluable, otherwise assign a 1 to the weights
  				if (deltax + 2 * delta == 0)
  					continue;				
  				if (deltax - 2 * delta == 0)
  					continue;
  				
  				// implement the conditions as described in the paper
  				// First case: Handles weights for redundancies at the end of the scan
  				if (lambda >= (Math.PI + 2*delta) && lambda <= (Math.PI + deltax) + 1e-12)
  				{
  					this.setAtIndex(maxTIndex - t -1, b, (float) (this.EtaFunction(delta, deltax, lambda)) );
  				}
  				// Second case: Handles weights for missing data at the end of the scan
  				if (lambda >= (Math.PI + 2*deltax - 2*delta) && lambda <= (Math.PI + deltax) + 1e-12)
  				{
  					this.setAtIndex(maxTIndex - t -1, b, (float) (2 - this.EtaFunction(delta, deltax, lambda)) );
  				}
  				// Third case: Handles weights for redundancies at the beginning of the scan
  				if (lambda >= 0 && lambda <= (2*delta + deltax) + 1e-12) 
  				{
  					this.setAtIndex(maxTIndex - t -1, b, (float) this.ZetaFunction(delta, deltax, lambda));
  				}
  				// Fourth case: Handles weights for missing data at the beginning of the scan
  				if (lambda >= 0 && lambda <= (-1.0 * (2*delta + deltax)) + 1e-12)
  				{
  					this.setAtIndex(maxTIndex - t -1, b, (float) (2 - this.ZetaFunction(delta, deltax, lambda)) );
  				}
  
  			}
  		}
  
  		
  		// Correct for scaling due to varying angle lambda
  		NumericPointwiseOperators.multiplyBy(this, (float)( maxLambda / (Math.PI)));
  		
  		
  		// Low pass filtering of first and last 15 columns to avoid step functions
  		// Sigma of gaussian filter decreases towards the middle
  		int columnsToFilter = 15;
  		double maxSigma = 20;
  		double minSigma = 0.1;
  		double sigma = maxSigma;
  		// Low pass filtering of the first 5 rows + the last 5 rows
  		for (int b=0; b<maxLambdaIndex; ++b)
  		{			
  			// decrease sigma the further we come to the center of the object
  			if ( b < columnsToFilter)
  				sigma = maxSigma - ((double)b)*(maxSigma-minSigma)/((double)columnsToFilter-1.0);
  			if (b >= maxLambdaIndex-columnsToFilter)
  				sigma = minSigma + ((double)(b-maxLambdaIndex+columnsToFilter))*(maxSigma-minSigma)/((double)columnsToFilter-1.0);
  			
  			if ( b < columnsToFilter || b >= maxLambdaIndex-columnsToFilter)
  			{
  			double column[] = new double[maxTIndex];
  			for(int t=0; t < maxTIndex; ++t)
  				column[t]=this.getAtIndex(t, b);
  			
  			column = DoubleArrayUtil.gaussianFilter(column, sigma);
  			
  			for(int t=0; t < maxTIndex; ++t)
  				this.setAtIndex(t, b, (float)column[t]);
  			}
  		}
  		
  		
  	}
  
  	
  	public void applyToGrid(Grid2D sino) {
  		NumericPointwiseOperators.multiplyBy(sino, this);
  	}
  
  	
  	public static void main (String [] args){
  		//fan beam bp parameters
  		
  		double maxT = 400;
  		double deltaT = 1.d;
  		// set focal length according to the fan angle
  		double focalLength = (maxT/2.0-0.5)/Math.tan(10.0*Math.PI/180.0);
  		//double gammaM =Math.atan2(maxT / 2.f - 0.5, focalLength) * 2.0;	
  		new ImageJ();
  		
  		
  		Grid3D g  = new Grid3D((int)maxT, 181, 360, false);
  		
  		for (int i = 0; i < 360; ++i)
  		{
  			double maxBeta =  (double)(i+1) * Math.PI * 2.0 / 360.0;
  			double deltaBeta = maxBeta / 180;
  			
  			CompensationWeights p = new CompensationWeights(focalLength, maxT, deltaT, maxBeta, deltaBeta);
  			g.setSubGrid(i, p);
  			//g.setSliceLabel("MaxBeta: " + Double.toString(maxBeta*180/Math.PI), i+1);
  		}
  		
  		g.show();
  	}
  
  }
  /*
   * Copyright (C) 2010-2014 Martin Berger
   * CONRAD is developed as an Open Source project under the GNU General Public License (GPL).
  */