content: /*
   * Copyright (C) 2010-2014 Chris Schwemmer
   * CONRAD is developed as an Open Source project under the GNU General Public License (GPL).
  */
  package edu.stanford.rsl.conrad.phantom;
  
  import java.util.ArrayList;
  
  import edu.stanford.rsl.conrad.geometry.motion.RotationMotionField;
  import edu.stanford.rsl.conrad.geometry.motion.timewarp.HarmonicTimeWarper;
  import edu.stanford.rsl.conrad.geometry.motion.timewarp.IdentityTimeWarper;
  import edu.stanford.rsl.conrad.geometry.motion.timewarp.PeriodicTimeWarper;
  import edu.stanford.rsl.conrad.geometry.motion.timewarp.RestPhaseTimeWarper;
  import edu.stanford.rsl.conrad.geometry.motion.timewarp.SigmoidTimeWarper;
  import edu.stanford.rsl.conrad.geometry.shapes.simple.PointND;
  import edu.stanford.rsl.conrad.geometry.shapes.simple.Sphere;
  import edu.stanford.rsl.conrad.numerics.SimpleVector;
  import edu.stanford.rsl.conrad.physics.PhysicalObject;
  import edu.stanford.rsl.conrad.physics.materials.database.MaterialsDB;
  import edu.stanford.rsl.conrad.rendering.PrioritizableScene;
  import edu.stanford.rsl.conrad.utils.CONRAD;
  import edu.stanford.rsl.conrad.utils.Configuration;
  import edu.stanford.rsl.conrad.utils.DoubleArrayUtil;
  import edu.stanford.rsl.conrad.utils.RegKeys;
  import edu.stanford.rsl.jpop.utils.UserUtil;
  
  /**
   * A simple 4D phantom similar to a swinging pendulum - spherical version.
   * 
   * @author Chris Schwemmer
   *
   */
  public class MovingBallPhantom extends AnalyticPhantom4D {
  
  	private static final long serialVersionUID = 8308889276453752657L;
  	
  	private RotationMotionField mf;
  	private boolean dynamic;
  	private PointND refPosition;
  	
  	private PeriodicTimeWarper periWarper;
  	private HarmonicTimeWarper harmoWarper;
  	private SigmoidTimeWarper sigWarper;
  	private RestPhaseTimeWarper restWarper;
  	
  	private double radius, angle;
  	private PointND initialCentre;
  
  	public MovingBallPhantom(){
  	}
  	
  	public void configure() throws Exception {
  		radius = UserUtil.queryDouble("Radius of sphere", 10);
  		angle = UserUtil.queryDouble("Angle of deflection (degrees)", 20) * Math.PI / 180.0;
  		double accFac = UserUtil.queryDouble("Acceleration factor", 4.0);
  		double rp = UserUtil.queryDouble("Rest phase (%)", 20);
  		double heartCycles = UserUtil.queryDouble("Number of \"heart beats\" in Scene (acquisition time * heart rate):", 4.0);
  		
  		Configuration config = Configuration.getGlobalConfiguration();
  		int dimz = config.getGeometry().getProjectionStackSize() * config.getNumSweeps();
  		double [] heartStates = new double [dimz];
  		for (int i = 0; i < dimz; i++){
  			heartStates[i]= (i*heartCycles) / (dimz);
  		}
  		config.getRegistry().put(RegKeys.HEART_PHASES, DoubleArrayUtil.toString(heartStates));
  		
  		dynamic = UserUtil.queryBoolean("Create dynamic scene?");
  		
  		mf = new RotationMotionField(new PointND(0, 0, 0), new SimpleVector(0, 1, 0), 2.0 * angle);
  		
  		harmoWarper = new HarmonicTimeWarper(heartCycles);
  		sigWarper = new SigmoidTimeWarper();
  		periWarper = new PeriodicTimeWarper();
  		restWarper = new RestPhaseTimeWarper();
  		this.warper = new IdentityTimeWarper();
  		
  		sigWarper.setAcc(accFac);
  		restWarper.setRestPhase(rp / 100);
  		
  		PointND range = mf.getPosition(new PointND(0, 0, 5.0 * radius), 0, 0.5);
  		
  		initialCentre = new PointND(-range.get(0), range.get(1), range.get(2));
  		PointND endPos = new PointND(range.get(0), range.get(1), range.get(2));
  		
  		double maxExt = Math.max(endPos.get(0), endPos.get(2));
  		
  		this.max = new PointND(1.7 * maxExt, 1.7 * maxExt, 1.7 * maxExt);
  		this.min = new PointND(-1.7 * maxExt, -1.7 * maxExt, -1.7 * maxExt);
  		
  		if (!dynamic) {
  			double refHeartPhase = UserUtil.queryDouble("Reference heart phase", 0.75);
  			refPosition = mf.getPosition(initialCentre, 0, sigWarper.warpTime(periWarper.warpTime(restWarper.warpTime(refHeartPhase))));
  		}
  	}
  	
  	@Override
  	public PointND getPosition(PointND initialPosition, double initialTime,
  			double time) {
  		double nT = sigWarper.warpTime(periWarper.warpTime(restWarper.warpTime(harmoWarper.warpTime(time))));
  		System.out.print(nT);
  		System.out.print(", ");
  		return mf.getPosition(initialPosition, initialTime, nT);
  	}
  
  	@Override
  	public ArrayList<PointND> getPositions(PointND initialPosition,
  			double initialTime, double... times) {
  		ArrayList<PointND> list = new ArrayList<PointND>();
  		for (int i=0; i< times.length; i++){
  			list.add(getPosition(initialPosition, initialTime, times[i]));
  		}
  		return list;
  	}
  
  	@Override
  	public String getBibtexCitation() {
  		return CONRAD.CONRADBibtex;
  	}
  
  	@Override
  	public String getMedlineCitation() {
  		return CONRAD.CONRADMedline;
  	}
  
  	@Override
  	public PrioritizableScene getScene(double time) {
  		PrioritizableScene scene = new PrioritizableScene();
  		PhysicalObject obj = new PhysicalObject();
  		obj.setMaterial(MaterialsDB.getMaterial("I"));
  		obj.setNameString("Contrast Bolus");
  		
  		System.out.print(time);
  		System.out.print(", ");
  		PointND centre;
  		if (dynamic)
  			centre = getPosition(initialCentre, 0, time);
  		else
  			centre = refPosition.clone();
  		System.out.println(centre.toString());
  		
  		scene.setMin(min);
  		scene.setMax(max);
  		
  		Sphere sp = new Sphere(radius, centre);
  		sp.setName("Moving Ball");
  		obj.setShape(sp);
  		scene.add(obj);
  		return scene;
  	}
  
  	@Override
  	public String getName() {
  		return "Moving Ball Phantom";
  	}
  
  }
  