package edu.stanford.rsl.conrad.phantom; import edu.stanford.rsl.conrad.calibration.CalibrationBead; import edu.stanford.rsl.conrad.geometry.shapes.simple.Cylinder; import edu.stanford.rsl.conrad.geometry.shapes.simple.PointND; import edu.stanford.rsl.conrad.geometry.shapes.simple.Sphere; import edu.stanford.rsl.conrad.physics.PhysicalObject; import edu.stanford.rsl.conrad.physics.materials.database.MaterialsDB; import edu.stanford.rsl.conrad.utils.UserUtil; /** * * @author Philipp Roser * */ public class MathematicalPhantom extends AbstractCalibrationPhantom { /** * */ private static final long serialVersionUID = -1192434187777290739L; /** * array that stores information about bead locations, internal */ private double[] phi; /** * indicates which function is wrapped around the phantom */ private String function = ""; private final String[] FUNCTIONS = new String[] { "Sine", "Cosine", "Sinc", "Tangent", "Linear", "Parabolical", "Hyperbolical" }; @Override public String getBibtexCitation() { // TODO Auto-generated method stub return null; } @Override public String getMedlineCitation() { // TODO Auto-generated method stub return null; } @Override public String getName() { return "Mathematical Phantom"; } /** * method to query function * @see function * @param message * @param messageTitle * @param functions * @return a single string of the input parameter functions * @throws Exception */ public static String queryFunction(String message, String messageTitle, String[] functions) throws Exception { return (String) UserUtil.chooseObject(message, messageTitle, functions, functions[0]); } /** * method to query all parameters that are necessary to determine the phantoms geometry */ private void query() { try { cylRadius = UserUtil.queryDouble("Cylinder radius", cylRadius); cylHeight = UserUtil.queryDouble("Cylinder height", cylHeight); beadRadius = UserUtil.queryDouble("Bead radius", beadRadius); numberOfBeads = UserUtil.queryInt("Number of beads", numberOfBeads); matBead = UserUtil.queryString("Bead material", matBead); matCyl = UserUtil.queryString("Cylinder material", matCyl); function = queryFunction("Evaluation function", "", FUNCTIONS); } catch (Exception e) { // TODO: handle exception } } @Override public void configure() { try { query(); buildPhantom(); } catch (Exception e) { // TODO: handle exception } } /** * sinc function * @param x * @return sinc of x */ private double sinc(double x) { return x == 0 ? 1.0 : Math.sin(x) / x; } private double sin(double x) { return Math.sin(x); } private double cos(double x) { return Math.cos(x); } private double tan(double x) { return Math.tan(x); } /** * evaluates the function in the case it is of trigonometric origin * @param func * @return */ private boolean evaluateTrigonometric(String func) { boolean ret = false; phi = new double[numberOfBeads]; for (int i = 0; i < numberOfBeads; i++) { double shifted = (i - numberOfBeads / 2) / 2.0; // phi[i] = (2.0 * Math.PI / standardDeviation) // * Math.exp(-0.5 // * Math.pow(((double) (shifted - mean)) // / standardDeviation, 2)); if (func.equals("Sine")) { phi[i] = 2.0 * Math.PI * sin(shifted); ret = true; } else if (func.equals("Cosine")) { phi[i] = 2.0 * Math.PI * cos(shifted); ret = true; } else if (func.equals("Sinc")) { phi[i] = 2.0 * Math.PI * sinc(shifted); ret = true; } else if (func.equals("Tangent")) { phi[i] = 2.0 * Math.PI * tan(shifted); ret = true; } } for (int i = 1; i <= numberOfBeads; i++) { double x = cylRadius * Math.sin(phi[i - 1]); double y = cylRadius * Math.cos(phi[i - 1]); double z = cylHeight / 2.0 - cylHeight * ((double) i - 0.5) / numberOfBeads; beadX[i - 1] = x; beadY[i - 1] = y; beadZ[i - 1] = z; } return ret; } /** * evaluates the function */ private void evaluate() { if (evaluateTrigonometric(function)) { return; } else { evaluatePolynomial(function); } } /** * evaluates the function in the case it is of polynomial origin * @param func */ private void evaluatePolynomial(String func) { int n = 1; if (func.equals("Linear")) { n = 1; } else if (func.equals("Parabolical")) { n = 2; } else if (func.equals("Hyperbolical")) { n = 3; } for (int i = 1; i <= numberOfBeads; i++) { double z = cylHeight / 2.0 - cylHeight * ((double) i - 0.5) / numberOfBeads; double phi = Math.pow(z, 1.0 / n); double x = cylRadius * Math.sin(phi); double y = cylRadius * Math.cos(phi); beadX[i - 1] = x; beadY[i - 1] = y; beadZ[i - 1] = z; } } private void buildPhantom() { Cylinder shape = new Cylinder(cylRadius, cylRadius, cylHeight); PhysicalObject po = new PhysicalObject(); po.setMaterial(MaterialsDB.getMaterialWithName(matCyl)); po.setShape(shape); add(po); shape = new Cylinder(0.8 * cylRadius, 0.8 * cylRadius, cylHeight); po = new PhysicalObject(); po.setMaterial(MaterialsDB.getMaterialWithName("vacuum")); po.setShape(shape); add(po); beadX = new double[numberOfBeads]; beadY = new double[numberOfBeads]; beadZ = new double[numberOfBeads]; evaluate(); for (int i = 1; i <= numberOfBeads; i++) { double x = beadX[i - 1]; double y = beadY[i - 1]; double z = beadZ[i - 1]; System.out.println("(" + x + ", " + y + ", " + z + ")"); Sphere sp = new Sphere(beadRadius, new PointND(x, y, z)); po = new PhysicalObject(); po.setMaterial(MaterialsDB.getMaterialWithName(matBead)); po.setShape(sp); add(po); } } public MathematicalPhantom() { } /** * initializes the bead positions for better access in the calibration * @see GeometricCalibration */ public void init() { query(); beadX = new double[numberOfBeads]; beadY = new double[numberOfBeads]; beadZ = new double[numberOfBeads]; evaluate(); } public double getX(int id) { return beadX[id]; } public double getY(int id) { return beadY[id]; } public double getZ(int id) { return beadZ[id]; } }