@Override
  public void configure() throws Exception {
    Configuration config = Configuration.getGlobalConfiguration();
    setConfiguration(config);
    //		double [] minmax = null;
    checkDelta();
    setNumberOfProjections(config.getGeometry().getPrimaryAngles().length);
    double maxRange = (Math.PI + (2 * delta));
    if (getPrimaryAngles() != null) {
      // minmax = DoubleArrayUtil.minAndMaxOfArray(primaryAngles);
      double factor = 15 - (deltaX / Math.PI * 180);
      if (factor < 1) factor = 1;
      double range =
          (computeScanRange() + (factor * config.getGeometry().getAverageAngularIncrement()))
              * Math.PI
              / 180.0;
      offset = ((maxRange - range) / 2);
      offset = factor * config.getGeometry().getAverageAngularIncrement() / 180.0 * Math.PI / 2;
      deltaX = range - Math.PI;
      if (debug) System.out.println("delta: " + delta * 180 / Math.PI);
      if (debug) System.out.println("Angular Offset: " + offset + " " + maxRange + " " + range);
      if (debug) System.out.println("deltaX: " + deltaX);
    }

    if (this.numberOfProjections == 0) {
      throw new Exception("Number of projections not known");
    }
    setConfigured(true);
  }
  @Override
  public void workOnSlice(int sliceNumber) {
    PrioritizableScene phantomScene = phantom;
    if (phantom instanceof AnalyticPhantom4D) {
      AnalyticPhantom4D scene4D = (AnalyticPhantom4D) phantom;
      phantomScene = scene4D.getScene(((double) sliceNumber) / trajectory.getProjectionStackSize());

      String disableAutoCenterBoolean =
          Configuration.getGlobalConfiguration()
              .getRegistryEntry(RegKeys.DISABLE_CENTERING_4DPHANTOM_PROJECTION_RENDERING);
      boolean disableAutoCenter = false;
      if (disableAutoCenterBoolean != null) {
        disableAutoCenter = Boolean.parseBoolean(disableAutoCenterBoolean);
      }

      Translation centerTranslation = new Translation(new SimpleVector(0, 0, 0));
      if (!disableAutoCenter) {
        SimpleVector center =
            SimpleOperators.add(
                    phantom.getMax().getAbstractVector(), phantom.getMin().getAbstractVector())
                .dividedBy(2);
        centerTranslation = new Translation(center.negated());
      }

      for (PhysicalObject o : phantomScene) {
        o.getShape().applyTransform(centerTranslation);
        // System.out.println(o.getShape().getMax() + " " + o.getShape().getMin());

        // Translate a part of XCAT to the center of source & detector for 2D projection (e.g. knee
        // at the center of the 2d projection)
        String translationString =
            Configuration.getGlobalConfiguration()
                .getRegistryEntry(RegKeys.GLOBAL_TRANSLATION_4DPHANTOM_PROJECTION_RENDERING);
        if (translationString != null) {
          // Center b/w RKJC & LKJC: -292.6426  211.7856  440.7783 (subj 5, static60),-401.1700
          // 165.9885  478.5600 (subj 2, static60)
          // XCAT Center by min & max: -177.73999504606988, 179.8512744259873, 312.19713254613583
          // translationVector = (XCAT Center by min & max) - (Center b/w RKJC & LKJC)=>
          // 114.9026, -31.9343, -128.5811 (subj5),  120, 3, -110(subj2) Try 114.0568    2.4778
          // -106.2550
          String[] values = translationString.split(", ");
          SimpleVector translationVector =
              new SimpleVector(
                  Double.parseDouble(values[0]),
                  Double.parseDouble(values[1]),
                  Double.parseDouble(values[2]));
          Translation translationToRotationCenter = new Translation(translationVector);
          o.getShape().applyTransform(translationToRotationCenter);
        }
      }
      // System.out.println(phantomScene.getMax() + " " + phantomScene.getMin());
    }
    Grid2D slice = raytraceScene(phantomScene, trajectory.getProjectionMatrix(sliceNumber));
    this.imageBuffer.add(slice, sliceNumber);
  }
 /**
  * feeds the filter from a projection Source.
  *
  * @param source the source
  * @param showStatus displays whether the status should be displayed using ImageJ
  * @throws Exception may happen.
  */
 public void feedFilter(ProjectionSource source, boolean showStatus) throws Exception {
   // Stream data into filter
   int stackSize = Configuration.getGlobalConfiguration().getGeometry().getProjectionStackSize();
   for (int i = 0; i < stackSize; i++) {
     process(source.getNextProjection(), i);
   }
 }
 public void reconstructOffline(ImagePlus imagePlus) throws Exception {
   ImagePlusDataSink sink = new ImagePlusDataSink();
   configure();
   init();
   for (int i = 0; i < imagePlus.getStackSize(); i++) {
     backproject(ImageUtil.wrapImageProcessor(imagePlus.getStack().getProcessor(i + 1)), i);
   }
   waitForResult();
   if (Configuration.getGlobalConfiguration().getUseHounsfieldScaling()) applyHounsfieldScaling();
   int[] size = projectionVolume.getSize();
   System.out.println(size[0] + " " + size[1] + " " + size[2]);
   for (int k = 0; k < projectionVolume.getSize()[2]; k++) {
     FloatProcessor fl =
         new FloatProcessor(projectionVolume.getSize()[0], projectionVolume.getSize()[1]);
     for (int j = 0; j < projectionVolume.getSize()[1]; j++) {
       for (int i = 0; i < projectionVolume.getSize()[0]; i++) {
         fl.putPixelValue(i, j, projectionVolume.getAtIndex(i, j, k));
       }
     }
     sink.process(projectionVolume.getSubGrid(k), k);
   }
   sink.close();
   ImagePlus revan =
       ImageUtil.wrapGrid3D(sink.getResult(), "Reconstruction of " + imagePlus.getTitle());
   revan.setTitle(imagePlus.getTitle() + " reconstructed");
   revan.show();
   reset();
 }
 @Test
 public void testCreateVolume() {
   Configuration.loadConfiguration();
   Volume3D vol = op.createVolume(size, dim, 1);
   CUDAVolume3D cudaVol = (CUDAVolume3D) cuop.createVolume(size, dim, 1);
   cudaVol.fetch();
   assertVolumeEquality(vol, cudaVol);
   cudaVol.destroy();
   vol.destroy();
 }
 @Test
 public void testfilt_gauss() {
   Configuration.loadConfiguration();
   cuop.initCUDA();
   Volume3D one = op.createGaussLowPassFilter(3, size, dim, 2);
   CUDAVolume3D cudaVol = (CUDAVolume3D) cuop.createGaussLowPassFilter(3, size, dim, 2);
   cudaVol.fetch();
   assertVolumeEquality(one, cudaVol);
   cudaVol.destroy();
   one.destroy();
 }
  protected Grid3D reconstructCL(double[] motionfield) {
    init();
    int n = inputQueue.size();
    for (int i = 0; i < n; i++) {
      backproject(inputQueue.get(i), i);
    }
    waitForResult(motionfield);
    if (Configuration.getGlobalConfiguration().getUseHounsfieldScaling()) applyHounsfieldScaling();

    // projectionVolume.show();
    return projectionVolume;
  }
 @Test
 public void testMin() {
   Configuration.loadConfiguration();
   Volume3D one = createRandomVolume();
   cuop.initCUDA();
   CUDAVolume3D cudaVol = createCUDACopy(one);
   float cpu = op.min(one);
   float gpu = cuop.min(cudaVol);
   cudaVol.fetch();
   org.junit.Assert.assertTrue(cpu == gpu);
   cudaVol.destroy();
   one.destroy();
 }
 @Test
 public void testDivideScalar() {
   Configuration.loadConfiguration();
   Volume3D one = createRandomVolume();
   cuop.initCUDA();
   CUDAVolume3D cudaVol = createCUDACopy(one);
   op.divideScalar(one, 3.0f, 0.0f);
   cuop.divideScalar(cudaVol, 3.0f, 0.0f);
   cudaVol.fetch();
   assertVolumeEquality(one, cudaVol);
   cudaVol.destroy();
   one.destroy();
 }
 @Test
 public void testAbs() {
   Configuration.loadConfiguration();
   Volume3D one = createRandomVolume();
   cuop.initCUDA();
   CUDAVolume3D cudaVol = createCUDACopy(one);
   op.abs(one);
   cuop.abs(cudaVol);
   cudaVol.fetch();
   assertVolumeEquality(one, cudaVol);
   cudaVol.destroy();
   one.destroy();
 }
 public static void main(String[] args) {
   CONRAD.setup();
   Configuration config = Configuration.getGlobalConfiguration();
   CircularTrajectory traj = new CircularTrajectory(config.getGeometry());
   double[] startAngles = new double[] {0, 20, 40};
   for (int j = 0; j < startAngles.length; j++) {
     traj.setTrajectory(
         2,
         600,
         90,
         0,
         0,
         CameraAxisDirection.ROTATIONAXIS_PLUS,
         CameraAxisDirection.DETECTORMOTION_PLUS,
         new SimpleVector(0, 0, 1),
         new PointND(0, 0, 0),
         startAngles[j]);
     for (int i = 0; i < traj.getNumProjectionMatrices(); i++) {
       System.out.println("Matrix: " + traj.getProjectionMatrix(i).toString());
     }
     System.out.println(" ");
   }
 }
 @Test
 public void testMean() {
   Configuration.loadConfiguration();
   Volume3D one = createRandomVolume();
   cuop.initCUDA();
   CUDAVolume3D cudaVol = createCUDACopy(one);
   float cpu = op.mean(one);
   float gpu = cuop.mean(cudaVol);
   cudaVol.fetch();
   // System.out.println(cpu + " " + gpu);
   org.junit.Assert.assertTrue(Math.abs(cpu - gpu) < 0.00001);
   cudaVol.destroy();
   one.destroy();
 }
  @Override
  protected void reconstruct() throws Exception {
    init();
    for (int i = 0; i < nImages; i++) {
      backproject(inputQueue.get(i), i);
    }
    waitForResult();
    if (Configuration.getGlobalConfiguration().getUseHounsfieldScaling()) applyHounsfieldScaling();
    int[] size = projectionVolume.getSize();

    for (int k = 0; k < size[2]; k++) {
      sink.process(projectionVolume.getSubGrid(k), k);
    }
    sink.close();
  }
 @Test
 public void testfilt_cos2_quad() {
   Configuration.loadConfiguration();
   cuop.initCUDA();
   float[][] dirs = new float[6][3];
   AnisotropicFilterFunction.filt_get_filt_dirs(3, dirs);
   Volume3D one =
       op.createDirectionalWeights(3, size, dim, dirs[0], 1, VolumeOperator.FILTER_TYPE.QUADRATIC);
   CUDAVolume3D cudaVol =
       (CUDAVolume3D)
           cuop.createDirectionalWeights(
               3, size, dim, dirs[0], 1, VolumeOperator.FILTER_TYPE.QUADRATIC);
   cudaVol.fetch();
   assertVolumeEquality(one, cudaVol);
   cudaVol.destroy();
   one.destroy();
 }
 @Test
 public void testAddVolumeWeight() {
   Configuration.loadConfiguration();
   Volume3D one = createRandomVolume();
   Volume3D two = createRandomVolume();
   cuop.initCUDA();
   CUDAVolume3D cudaVol = createCUDACopy(one);
   CUDAVolume3D cudaVol2 = createCUDACopy(two);
   op.addVolume(one, two, 2.0);
   cuop.addVolume(cudaVol, cudaVol2, 2.0);
   cudaVol.fetch();
   assertVolumeEquality(one, cudaVol);
   cudaVol.destroy();
   cudaVol2.destroy();
   one.destroy();
   two.destroy();
 }
 @Test
 public void testfilt_cos2r() {
   Configuration.loadConfiguration();
   cuop.initCUDA();
   float[][] dirs = new float[6][3];
   AnisotropicFilterFunction.filt_get_filt_dirs(3, dirs);
   Volume3D one =
       op.createExponentialDirectionalHighPassFilter(
           3, size, dim, dirs[0], 1, 2.0f, 1.5f, VolumeOperator.FILTER_TYPE.NORMAL);
   CUDAVolume3D cudaVol =
       (CUDAVolume3D)
           cuop.createExponentialDirectionalHighPassFilter(
               3, size, dim, dirs[0], 1, 2.0f, 1.5f, VolumeOperator.FILTER_TYPE.NORMAL);
   cudaVol.fetch();
   assertVolumeEquality(one, cudaVol);
   cudaVol.destroy();
   one.destroy();
 }
 @Test
 public void testDivideByVolume() {
   Configuration.loadConfiguration();
   Volume3D one = createRandomVolume();
   Volume3D two = createRandomVolume();
   op.multiplyScalar(one, 1000, 0);
   op.addScalar(two, 10, 0);
   cuop.initCUDA();
   CUDAVolume3D cudaVol = createCUDACopy(one);
   CUDAVolume3D cudaVol2 = createCUDACopy(two);
   op.divideByVolume(one, two);
   cuop.divideByVolume(cudaVol, cudaVol2);
   cudaVol.fetch();
   assertWeakVolumeEquality(one, cudaVol);
   cudaVol.destroy();
   cudaVol2.destroy();
   one.destroy();
   two.destroy();
 }
  public DefrisePhantom() {

    Trajectory trajectory = Configuration.getGlobalConfiguration().getGeometry();

    double sourceAxisDistance = trajectory.getSourceToAxisDistance();
    double sourceDetectorDistance = trajectory.getSourceToDetectorDistance();
    double detectorYAxis = trajectory.getDetectorHeight() * trajectory.getPixelDimensionY();
    double detectorXAxis = trajectory.getDetectorWidth() * trajectory.getPixelDimensionX();

    double fovRadius = detectorXAxis * sourceAxisDistance / sourceDetectorDistance / 2 * 0.95;
    double diskRadius = 4.0 / 5.0 * fovRadius;
    double fovHeight = detectorYAxis * sourceAxisDistance / sourceDetectorDistance;
    double diskHeight = fovHeight / 15.0;
    double diskSpacing = fovHeight / 15.0;
    int numDisks = 5;
    double diskStart =
        -(fovHeight / 2.0)
            + ((fovHeight - (numDisks * diskHeight + diskSpacing * (numDisks - 1))) / 2.0)
            + diskHeight / 2.0;

    // Water Body
    Cylinder cyl = new Cylinder(fovRadius, fovRadius, fovHeight);
    cyl.setName("Water-like body of the phantom");
    PhysicalObject po = new PhysicalObject();
    po.setMaterial(MaterialsDB.getMaterialWithName("Water")); // D = 1.0
    po.setShape(cyl);
    add(po);

    // Disk Inserts
    for (int i = 0; i < numDisks; i++) {
      cyl = new Cylinder(diskRadius, diskRadius, diskHeight);
      cyl.setName("Disk");
      cyl.applyTransform(
          new Translation(new SimpleVector(0, 0, diskStart + (diskSpacing + diskHeight) * i)));
      po = new PhysicalObject();
      po.setMaterial(MaterialsDB.getMaterialWithName("Plexiglass")); // D = 1.95
      po.setShape(cyl);
      add(po);
    }
  }
  @Override
  public void configure() throws Exception {
    // We will now read this from the Configuration. The detector needs to be preconfigured!
    // detector = (XRayDetector) UserUtil.queryObject("Select Detector:", "Detector Selection",
    // XRayDetector.class);
    // detector.configure();
    detector = Configuration.getGlobalConfiguration().getDetector();
    detector.init();
    phantom = UserUtil.queryPhantom("Select Phantom", "Select Phantom");
    Material mat = null;

    do {
      String materialstr = UserUtil.queryString("Enter Background Medium:", "vacuum");
      mat = MaterialsDB.getMaterialWithName(materialstr);
    } while (mat == null);

    phantom.setBackground(mat);
    phantom.configure();

    rayTracer.setScene((PrioritizableScene) phantom);
    environment.setMaterial(phantom.getBackgroundMaterial());
    super.configure();
  }
 @Override
 public void configure() throws Exception {
   slope = UserUtil.queryDouble("Enter slope of correction transformatoin", slope);
   // offset = UserUtil.queryDouble("Enter offset of correction transformation", offset);
   measureHard = UserUtil.queryDouble("Measurement of hard material [HU]", measureHard);
   measureSoft = UserUtil.queryDouble("Measurement of soft material [HU]", measureSoft);
   projectionXShift = UserUtil.queryDouble("Enter projection x shift", projectionXShift);
   projectionYShift = UserUtil.queryDouble("Enter projection y shift", projectionYShift);
   lambda0 = VolumeAttenuationFactorCorrectionTool.getLambda0(measureHard, measureSoft);
   lut = Configuration.getGlobalConfiguration().getBeamHardeningLookupTable();
   ImagePlus[] images = ImageUtil.getAvailableImagePlusAsArray();
   hardMaterial =
       ImageUtil.wrapImagePlus(
           (ImagePlus)
               JOptionPane.showInputDialog(
                   null,
                   "Select projections with hard material: ",
                   "Select source",
                   JOptionPane.PLAIN_MESSAGE,
                   null,
                   images,
                   images[0]));
   configured = true;
 }
  @Test
  public void testfilt_solve_max_eigenValue() {
    Configuration.loadConfiguration();
    cuop.initCUDA();
    int[] size = {30, 30, 30};
    this.size = size;
    float[][] dirs = new float[6][3];
    AnisotropicFilterFunction.filt_get_filt_dirs(3, dirs);

    Volume3D a11 =
        op.createExponentialDirectionalHighPassFilter(
            3, size, dim, dirs[0], 1, 2.0f, 1.5f, VolumeOperator.FILTER_TYPE.QUADRATIC);
    Volume3D a12 =
        op.createExponentialDirectionalHighPassFilter(
            3, size, dim, dirs[1], 1, 2.0f, 1.5f, VolumeOperator.FILTER_TYPE.QUADRATIC);
    Volume3D a13 =
        op.createExponentialDirectionalHighPassFilter(
            3, size, dim, dirs[2], 1, 2.0f, 1.5f, VolumeOperator.FILTER_TYPE.QUADRATIC);
    Volume3D a22 =
        op.createExponentialDirectionalHighPassFilter(
            3, size, dim, dirs[3], 1, 2.0f, 1.5f, VolumeOperator.FILTER_TYPE.QUADRATIC);
    Volume3D a23 =
        op.createExponentialDirectionalHighPassFilter(
            3, size, dim, dirs[4], 1, 2.0f, 1.5f, VolumeOperator.FILTER_TYPE.QUADRATIC);
    Volume3D a33 =
        op.createExponentialDirectionalHighPassFilter(
            3, size, dim, dirs[5], 1, 2.0f, 1.5f, VolumeOperator.FILTER_TYPE.QUADRATIC);
    op.real(a11);
    op.real(a12);
    op.real(a13);
    op.real(a22);
    op.real(a23);
    op.real(a33);
    Volume3D[][] st = new Volume3D[3][3];
    st[0][0] = a11;
    st[0][1] = a12;
    st[0][2] = a13;
    st[1][0] = a12;
    st[1][1] = a22;
    st[1][2] = a23;
    st[2][0] = a13;
    st[2][1] = a23;
    st[2][2] = a33;
    CUDAVolume3D ca11 =
        (CUDAVolume3D)
            cuop.createExponentialDirectionalHighPassFilter(
                3, size, dim, dirs[0], 1, 2.0f, 1.5f, VolumeOperator.FILTER_TYPE.QUADRATIC);
    CUDAVolume3D ca12 =
        (CUDAVolume3D)
            cuop.createExponentialDirectionalHighPassFilter(
                3, size, dim, dirs[1], 1, 2.0f, 1.5f, VolumeOperator.FILTER_TYPE.QUADRATIC);
    CUDAVolume3D ca13 =
        (CUDAVolume3D)
            cuop.createExponentialDirectionalHighPassFilter(
                3, size, dim, dirs[2], 1, 2.0f, 1.5f, VolumeOperator.FILTER_TYPE.QUADRATIC);
    CUDAVolume3D ca22 =
        (CUDAVolume3D)
            cuop.createExponentialDirectionalHighPassFilter(
                3, size, dim, dirs[3], 1, 2.0f, 1.5f, VolumeOperator.FILTER_TYPE.QUADRATIC);
    CUDAVolume3D ca23 =
        (CUDAVolume3D)
            cuop.createExponentialDirectionalHighPassFilter(
                3, size, dim, dirs[4], 1, 2.0f, 1.5f, VolumeOperator.FILTER_TYPE.QUADRATIC);
    CUDAVolume3D ca33 =
        (CUDAVolume3D)
            cuop.createExponentialDirectionalHighPassFilter(
                3, size, dim, dirs[5], 1, 2.0f, 1.5f, VolumeOperator.FILTER_TYPE.QUADRATIC);
    cuop.real(ca11);
    cuop.real(ca12);
    cuop.real(ca13);
    cuop.real(ca22);
    cuop.real(ca23);
    cuop.real(ca33);
    CUDAVolume3D[][] cst = new CUDAVolume3D[3][3];
    cst[0][0] = ca11;
    cst[0][1] = ca12;
    cst[0][2] = ca13;
    cst[1][1] = ca22;
    cst[1][2] = ca23;
    cst[2][2] = ca33;

    // new ImageJ();
    Volume3D one = op.solveMaximumEigenvalue(st);
    // one.getImagePlus("CPU Result").show();
    CUDAVolume3D cudaVol = (CUDAVolume3D) cuop.solveMaximumEigenvalue(cst);
    cudaVol.fetch();
    // cudaVol.getImagePlus("CUDA Result").show();
    try {
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    assertWeakVolumeEquality(one, cudaVol);
    a11.destroy();
    a12.destroy();
    a13.destroy();
    a22.destroy();
    a23.destroy();
    a33.destroy();
    ca11.destroy();
    ca12.destroy();
    ca13.destroy();
    ca22.destroy();
    ca23.destroy();
    ca33.destroy();
    cudaVol.destroy();
    one.destroy();
  }
  protected void init() {
    if (!initialized) {
      largeVolumeMode = false;

      int reconDimensionX = getGeometry().getReconDimensionX();
      int reconDimensionY = getGeometry().getReconDimensionY();
      int reconDimensionZ = getGeometry().getReconDimensionZ();
      projectionsAvailable = new ArrayList<Integer>();
      projectionsDone = new ArrayList<Integer>();

      // Initialize JOCL.
      context = OpenCLUtil.createContext();

      try {
        // get the fastest device
        device = context.getMaxFlopsDevice();
        // create the command queue
        commandQueue = device.createCommandQueue();

        // initialize the program
        if (program == null || !program.getContext().equals(this.context)) {
          program =
              context
                  .createProgram(
                      OpenCLCompensatedBackProjector.class.getResourceAsStream(
                          "compensatedBackprojectCL.cl"))
                  .build();
        }

      } catch (Exception e) {
        if (commandQueue != null) commandQueue.release();
        if (kernelFunction != null) kernelFunction.release();
        if (program != null) program.release();
        // destory context
        if (context != null) context.release();
        // TODO: handle exception
        e.printStackTrace();
      }

      // check space on device:
      long memory = device.getMaxMemAllocSize();
      long availableMemory = (memory);
      long requiredMemory =
          (long)
              (((((double) reconDimensionX) * reconDimensionY * ((double) reconDimensionZ) * 4)
                  + (((double)
                          Configuration.getGlobalConfiguration().getGeometry().getDetectorHeight())
                      * Configuration.getGlobalConfiguration().getGeometry().getDetectorWidth()
                      * 4)));
      if (debug) {
        System.out.println("Total available Memory on OpenCL card:" + availableMemory);
        System.out.println("Required Memory on OpenCL card:" + requiredMemory);
      }
      if (requiredMemory > availableMemory) {
        nSteps = (int) OpenCLUtil.iDivUp(requiredMemory, availableMemory);
        if (debug) System.out.println("Switching to large volume mode with nSteps = " + nSteps);
        largeVolumeMode = true;
      }
      if (debug) {
        // TODO replace
        /*
        CUdevprop prop = new CUdevprop();
        JCudaDriver.cuDeviceGetProperties(prop, dev);
        System.out.println(prop.toFormattedString());
        */
      }

      // create the computing kernel
      kernelFunction = program.createCLKernel("backprojectKernel");

      // create the reconstruction volume;
      int memorysize = reconDimensionX * reconDimensionY * reconDimensionZ * 4;
      if (largeVolumeMode) {
        subVolumeZ = OpenCLUtil.iDivUp(reconDimensionZ, nSteps);
        if (debug) System.out.println("SubVolumeZ: " + subVolumeZ);
        h_volume = new float[reconDimensionX * reconDimensionY * subVolumeZ];
        memorysize = reconDimensionX * reconDimensionY * subVolumeZ * 4;
        if (debug) System.out.println("Memory: " + memorysize);
      } else {
        h_volume = new float[reconDimensionX * reconDimensionY * reconDimensionZ];
      }

      // compute adapted volume size
      //    volume size in x = multiple of bpBlockSize[0]
      //    volume size in y = multiple of bpBlockSize[1]

      int adaptedVolSize[] = new int[3];
      if ((reconDimensionX % bpBlockSize[0]) == 0) {
        adaptedVolSize[0] = reconDimensionX;
      } else {
        adaptedVolSize[0] = ((reconDimensionX / bpBlockSize[0]) + 1) * bpBlockSize[0];
      }
      if ((reconDimensionY % bpBlockSize[1]) == 0) {
        adaptedVolSize[1] = reconDimensionY;
      } else {
        adaptedVolSize[1] = ((reconDimensionY / bpBlockSize[1]) + 1) * bpBlockSize[1];
      }
      adaptedVolSize[2] = reconDimensionZ;
      int volStrideHost[] = new int[2];
      // compute volstride and copy it to constant memory
      volStrideHost[0] = adaptedVolSize[0];
      volStrideHost[1] = adaptedVolSize[0] * adaptedVolSize[1];

      // copy volume to device
      volumePointer = context.createFloatBuffer(h_volume.length, Mem.WRITE_ONLY);
      volumePointer.getBuffer().put(h_volume);
      volumePointer.getBuffer().rewind();

      // copy volume stride to device
      volStride = context.createIntBuffer(volStrideHost.length, Mem.READ_ONLY);
      volStride.getBuffer().put(volStrideHost);
      volStride.getBuffer().rewind();

      commandQueue.putWriteBuffer(volumePointer, true).putWriteBuffer(volStride, true).finish();

      initialized = true;
    }
  }
/**
 * Projects arbitrarily defined phantoms to a detector using ray casting.<br>
 * The pixel value on the detector is determined by the absorption model.<br>
 * <br>
 * <b>If you change anything in this class, notify the conrad-dev mailing list.</b>
 *
 * @author Rotimi X Ojo
 * @author Andreas Maier
 */
public class AnalyticPhantomProjectorWorker extends SliceWorker {

  protected AnalyticPhantom phantom;
  protected Trajectory trajectory = Configuration.getGlobalConfiguration().getGeometry();
  private XRayDetector detector;
  private PriorityRayTracer rayTracer = new PriorityRayTracer();
  private static boolean accurate = false;

  // First rule of optimization is: Don't optimize!
  // Start for speed up
  // private StraightLine castLine = new StraightLine(new PointND(0,0,0),new SimpleVector(0,0,0));
  // private SimpleVector pixel = new SimpleVector(0, 0);
  // private ArrayList<PhysicalObject> segmentsBuff = new ArrayList<PhysicalObject> (1);
  private PhysicalObject environment = new PhysicalObject();
  // private PointND startPoint = new PointND(0,0);
  // private PointND endPoint = new PointND(0,0);
  // private Edge environmentEdge = new Edge(startPoint, endPoint);
  // private PointND raySource = new PointND(0,0,0);
  // End for speed up

  @Override
  public void workOnSlice(int sliceNumber) {
    PrioritizableScene phantomScene = phantom;
    if (phantom instanceof AnalyticPhantom4D) {
      AnalyticPhantom4D scene4D = (AnalyticPhantom4D) phantom;
      phantomScene = scene4D.getScene(((double) sliceNumber) / trajectory.getProjectionStackSize());

      String disableAutoCenterBoolean =
          Configuration.getGlobalConfiguration()
              .getRegistryEntry(RegKeys.DISABLE_CENTERING_4DPHANTOM_PROJECTION_RENDERING);
      boolean disableAutoCenter = false;
      if (disableAutoCenterBoolean != null) {
        disableAutoCenter = Boolean.parseBoolean(disableAutoCenterBoolean);
      }

      Translation centerTranslation = new Translation(new SimpleVector(0, 0, 0));
      if (!disableAutoCenter) {
        SimpleVector center =
            SimpleOperators.add(
                    phantom.getMax().getAbstractVector(), phantom.getMin().getAbstractVector())
                .dividedBy(2);
        centerTranslation = new Translation(center.negated());
      }

      for (PhysicalObject o : phantomScene) {
        o.getShape().applyTransform(centerTranslation);
        // System.out.println(o.getShape().getMax() + " " + o.getShape().getMin());

        // Translate a part of XCAT to the center of source & detector for 2D projection (e.g. knee
        // at the center of the 2d projection)
        String translationString =
            Configuration.getGlobalConfiguration()
                .getRegistryEntry(RegKeys.GLOBAL_TRANSLATION_4DPHANTOM_PROJECTION_RENDERING);
        if (translationString != null) {
          // Center b/w RKJC & LKJC: -292.6426  211.7856  440.7783 (subj 5, static60),-401.1700
          // 165.9885  478.5600 (subj 2, static60)
          // XCAT Center by min & max: -177.73999504606988, 179.8512744259873, 312.19713254613583
          // translationVector = (XCAT Center by min & max) - (Center b/w RKJC & LKJC)=>
          // 114.9026, -31.9343, -128.5811 (subj5),  120, 3, -110(subj2) Try 114.0568    2.4778
          // -106.2550
          String[] values = translationString.split(", ");
          SimpleVector translationVector =
              new SimpleVector(
                  Double.parseDouble(values[0]),
                  Double.parseDouble(values[1]),
                  Double.parseDouble(values[2]));
          Translation translationToRotationCenter = new Translation(translationVector);
          o.getShape().applyTransform(translationToRotationCenter);
        }
      }
      // System.out.println(phantomScene.getMax() + " " + phantomScene.getMin());
    }
    Grid2D slice = raytraceScene(phantomScene, trajectory.getProjectionMatrix(sliceNumber));
    this.imageBuffer.add(slice, sliceNumber);
  }

  public Grid2D raytraceScene(PrioritizableScene phantomScene, Projection projection) {
    Trajectory geom = Configuration.getGlobalConfiguration().getGeometry();
    // Grid2D slice = new Grid2D(geom.getDetectorWidth(), geom.getDetectorHeight());
    Grid2D slice =
        detector.createDetectorGrid(geom.getDetectorWidth(), geom.getDetectorHeight(), projection);
    rayTracer.setScene(phantomScene);
    // Second rule of optimization is: Optimize later.
    PointND raySource = new PointND(0, 0, 0);
    raySource.setCoordinates(projection.computeCameraCenter());
    StraightLine castLine = new StraightLine(raySource, new SimpleVector(0, 0, 0));

    SimpleVector centerPixDir = null;
    if (accurate) {
      centerPixDir = projection.computePrincipalAxis();
    }
    // SimpleVector prinpoint = trajectory.getProjectionMatrix(sliceNumber).getPrincipalPoint();

    double xcorr = 0; // trajectory.getDetectorWidth()/2 - prinpoint.getElement(0);
    double ycorr = 0; // trajectory.getDetectorHeight()/2 - prinpoint.getElement(1);

    double length = trajectory.getSourceToDetectorDistance();
    Edge environmentEdge = new Edge(new PointND(0), new PointND(length));

    ArrayList<PhysicalObject> fallBackBackground = new ArrayList<PhysicalObject>(1);
    SimpleVector pixel = new SimpleVector(0, 0);
    boolean negate = false;
    for (int y = 0; y < trajectory.getDetectorHeight(); y++) {
      for (int x = 0; x < trajectory.getDetectorWidth(); x++) {
        pixel.setElementValue(0, x - xcorr);
        pixel.setElementValue(1, y - ycorr);
        SimpleVector dir = projection.computeRayDirection(pixel);
        if ((y == 0) && (x == 0)) {
          // Check that ray direction is towards origin.
          double max = 0;
          int index = 0;
          for (int i = 0; i < 3; i++) {
            if (Math.abs(dir.getElement(i)) > max) {
              max = Math.abs(dir.getElement(i));
              index = i;
            }
          }
          double t = -raySource.get(index) / dir.getElement(index);
          if (t < 0) negate = true;
        }
        if (negate) dir.negate();
        castLine.setDirection(dir);

        ArrayList<PhysicalObject> segments = rayTracer.castRay(castLine);
        if (accurate) {
          double dirCosine = SimpleOperators.multiplyInnerProd(centerPixDir, dir);
          length = trajectory.getSourceToDetectorDistance() / dirCosine;
        }

        if (segments == null) {
          fallBackBackground.clear();
          segments = fallBackBackground;
        } else {
          if (accurate) {
            environmentEdge =
                new Edge(new PointND(0), new PointND(length - getTotalSegmentsLength(segments)));
          }
        }
        environment.setShape(environmentEdge);
        segments.add(environment);
        /* old code:
        double integral = absorptionModel.evaluateLineIntegral(segments);

        slice.putPixelValue(x, y, integral);
         */
        detector.writeToDetector(slice, x, y, segments);
      }
    }
    return slice;
  }

  private double getTotalSegmentsLength(ArrayList<PhysicalObject> segments) {
    double sum = 0;
    Iterator<PhysicalObject> it = segments.iterator();
    while (it.hasNext()) {
      // sum+=((Edge) it.next().getShape()).getLength();
    }
    return sum;
  }

  public SliceWorker clone() {
    AnalyticPhantomProjectorWorker newRend = new AnalyticPhantomProjectorWorker();
    newRend.phantom = phantom;
    newRend.detector = detector;
    newRend.environment.setMaterial(phantom.getBackgroundMaterial());
    newRend.rayTracer.setScene((PrioritizableScene) phantom);
    return newRend;
  }

  @Override
  public void configure() throws Exception {
    // We will now read this from the Configuration. The detector needs to be preconfigured!
    // detector = (XRayDetector) UserUtil.queryObject("Select Detector:", "Detector Selection",
    // XRayDetector.class);
    // detector.configure();
    detector = Configuration.getGlobalConfiguration().getDetector();
    detector.init();
    phantom = UserUtil.queryPhantom("Select Phantom", "Select Phantom");
    Material mat = null;

    do {
      String materialstr = UserUtil.queryString("Enter Background Medium:", "vacuum");
      mat = MaterialsDB.getMaterialWithName(materialstr);
    } while (mat == null);

    phantom.setBackground(mat);
    phantom.configure();

    rayTracer.setScene((PrioritizableScene) phantom);
    environment.setMaterial(phantom.getBackgroundMaterial());
    super.configure();
  }

  public void configure(AnalyticPhantom phan, XRayDetector detector) throws Exception {
    this.detector = detector;
    this.detector.init();
    phantom = phan;
    if (phantom.getBackgroundMaterial() == null) {
      Material mat = null;
      String materialstr = "vacuum";
      mat = MaterialsDB.getMaterialWithName(materialstr);
      phantom.setBackground(mat);
    }
    rayTracer.setScene((PrioritizableScene) phantom);
    environment.setMaterial(phantom.getBackgroundMaterial());
    super.configure();
  }

  @Override
  public String getProcessName() {
    return "Generic Phantom Projector";
  }

  @Override
  public String getBibtexCitation() {
    return CONRAD.CONRADBibtex;
  }

  @Override
  public String getMedlineCitation() {
    return CONRAD.CONRADMedline;
  }

  /** @return the absorptionModel */
  public XRayDetector getDetector() {
    return detector;
  }

  /** @param absorptionModel the absorptionModel to set */
  public void setDetector(XRayDetector detector) {
    this.detector = detector;
  }
}
  public Grid2D raytraceScene(PrioritizableScene phantomScene, Projection projection) {
    Trajectory geom = Configuration.getGlobalConfiguration().getGeometry();
    // Grid2D slice = new Grid2D(geom.getDetectorWidth(), geom.getDetectorHeight());
    Grid2D slice =
        detector.createDetectorGrid(geom.getDetectorWidth(), geom.getDetectorHeight(), projection);
    rayTracer.setScene(phantomScene);
    // Second rule of optimization is: Optimize later.
    PointND raySource = new PointND(0, 0, 0);
    raySource.setCoordinates(projection.computeCameraCenter());
    StraightLine castLine = new StraightLine(raySource, new SimpleVector(0, 0, 0));

    SimpleVector centerPixDir = null;
    if (accurate) {
      centerPixDir = projection.computePrincipalAxis();
    }
    // SimpleVector prinpoint = trajectory.getProjectionMatrix(sliceNumber).getPrincipalPoint();

    double xcorr = 0; // trajectory.getDetectorWidth()/2 - prinpoint.getElement(0);
    double ycorr = 0; // trajectory.getDetectorHeight()/2 - prinpoint.getElement(1);

    double length = trajectory.getSourceToDetectorDistance();
    Edge environmentEdge = new Edge(new PointND(0), new PointND(length));

    ArrayList<PhysicalObject> fallBackBackground = new ArrayList<PhysicalObject>(1);
    SimpleVector pixel = new SimpleVector(0, 0);
    boolean negate = false;
    for (int y = 0; y < trajectory.getDetectorHeight(); y++) {
      for (int x = 0; x < trajectory.getDetectorWidth(); x++) {
        pixel.setElementValue(0, x - xcorr);
        pixel.setElementValue(1, y - ycorr);
        SimpleVector dir = projection.computeRayDirection(pixel);
        if ((y == 0) && (x == 0)) {
          // Check that ray direction is towards origin.
          double max = 0;
          int index = 0;
          for (int i = 0; i < 3; i++) {
            if (Math.abs(dir.getElement(i)) > max) {
              max = Math.abs(dir.getElement(i));
              index = i;
            }
          }
          double t = -raySource.get(index) / dir.getElement(index);
          if (t < 0) negate = true;
        }
        if (negate) dir.negate();
        castLine.setDirection(dir);

        ArrayList<PhysicalObject> segments = rayTracer.castRay(castLine);
        if (accurate) {
          double dirCosine = SimpleOperators.multiplyInnerProd(centerPixDir, dir);
          length = trajectory.getSourceToDetectorDistance() / dirCosine;
        }

        if (segments == null) {
          fallBackBackground.clear();
          segments = fallBackBackground;
        } else {
          if (accurate) {
            environmentEdge =
                new Edge(new PointND(0), new PointND(length - getTotalSegmentsLength(segments)));
          }
        }
        environment.setShape(environmentEdge);
        segments.add(environment);
        /* old code:
        double integral = absorptionModel.evaluateLineIntegral(segments);

        slice.putPixelValue(x, y, integral);
         */
        detector.writeToDetector(slice, x, y, segments);
      }
    }
    return slice;
  }
  @Override
  public Grid2D applyToolToImage(Grid2D imageProcessor) {
    FloatProcessor imp = new FloatProcessor(imageProcessor.getWidth(), imageProcessor.getHeight());
    imp.setPixels(imageProcessor.getBuffer());

    if (!initBead) initializeBead();
    ImageProcessor imp1 = imp.duplicate(); // original

    double[][] beadMean3D = config.getBeadMeanPosition3D(); // [beadNo][x,y,z]
    double[] uv = new double[1];

    SimpleMatrix pMatrix = config.getGeometry().getProjectionMatrix(imageIndex).computeP();
    // [projection #][bead #][u, v, state[0: initial, 1: registered, 2: updated by hough searching]]
    double[][][] beadPosition2D = config.getBeadPosition2D();

    int noBeadRegistered = 0;

    double[][] xy1 = new double[WeightBearingBeadPositionBuilder.beadNo][2]; // original
    double[][] xy2 =
        new double[WeightBearingBeadPositionBuilder.beadNo]
            [2]; // warped	(mapped to the mean), control points, reference

    double[][] xy1_hat = new double[WeightBearingBeadPositionBuilder.beadNo][2]; // original
    double[][] xy2_hat = new double[WeightBearingBeadPositionBuilder.beadNo][2]; // original

    // double distanceReferenceToCurrentBead = 0;

    for (int i = WeightBearingBeadPositionBuilder.currentBeadNo; i >= 0; i--) {

      if (beadMean3D[i][0] != 0
          || beadMean3D[i][1] != 0
          || beadMean3D[i][2] != 0) { // assume bead 3d is registered.

        uv = compute2DCoordinates(beadMean3D[i], pMatrix);

        // find bead location if registered by txt: state 1
        if (beadPosition2D[imageIndex][i][2] == 1) {

          noBeadRegistered++;

          if (isDisplay) {
            imp1.setValue(2);
            imp1.drawLine(
                (int) Math.round(beadPosition2D[imageIndex][i][0] - 10),
                (int) Math.round(beadPosition2D[imageIndex][i][1] - 10),
                (int) Math.round(beadPosition2D[imageIndex][i][0] + 10),
                (int) Math.round(beadPosition2D[imageIndex][i][1] + 10));
            imp1.drawLine(
                (int) Math.round(beadPosition2D[imageIndex][i][0] - 10),
                (int) Math.round(beadPosition2D[imageIndex][i][1] + 10),
                (int) Math.round(beadPosition2D[imageIndex][i][0] + 10),
                (int) Math.round(beadPosition2D[imageIndex][i][1] - 10));
            imp1.drawString(
                "Bead " + i + " (state:" + (int) beadPosition2D[imageIndex][i][2] + ")",
                (int) beadPosition2D[imageIndex][i][0],
                (int) beadPosition2D[imageIndex][i][1] - 10);
          }

          xy1[noBeadRegistered - 1][0] = beadPosition2D[imageIndex][i][0];
          xy1[noBeadRegistered - 1][1] = beadPosition2D[imageIndex][i][1];

          xy2[noBeadRegistered - 1][0] = uv[0];
          xy2[noBeadRegistered - 1][1] = uv[1];

        } else if (imageIndex != 0
            && imageIndex != config.getGeometry().getNumProjectionMatrices() - 1) {

          if (beadPosition2D[imageIndex - 1][i][2] == 1
              && beadPosition2D[imageIndex + 1][i][2] == 1) {

            noBeadRegistered++;

            double xMean =
                (beadPosition2D[imageIndex - 1][i][0] + beadPosition2D[imageIndex - 1][i][0]) / 2;
            double yMean =
                (beadPosition2D[imageIndex + 1][i][1] + beadPosition2D[imageIndex + 1][i][1]) / 2;

            if (isDisplay) {
              imp1.setValue(2);
              imp1.drawLine(
                  (int) Math.round(xMean - 10),
                  (int) Math.round(yMean - 10),
                  (int) Math.round(xMean + 10),
                  (int) Math.round(yMean + 10));
              imp1.drawLine(
                  (int) Math.round(xMean - 10),
                  (int) Math.round(yMean + 10),
                  (int) Math.round(xMean + 10),
                  (int) Math.round(yMean - 10));
              imp1.drawString("Bead " + i + " (state:" + "M)", (int) xMean, (int) yMean - 10);
            }

            xy1[noBeadRegistered - 1][0] = xMean;
            xy1[noBeadRegistered - 1][1] = yMean;

            xy2[noBeadRegistered - 1][0] = uv[0];
            xy2[noBeadRegistered - 1][1] = uv[1];
          }
        }

        // mean projected bead
        //				imp1.drawLine((int) Math.round(uv[0]-10), (int) Math.round(uv[1]), (int)
        // Math.round(uv[0]+10), (int) Math.round(uv[1]));
        //				imp1.drawLine((int) Math.round(uv[0]), (int) Math.round(uv[1]-10), (int)
        // Math.round(uv[0]), (int) Math.round(uv[1]+10));
      }
    }

    if (isDisplay) {
      for (int x = 0; x < config.getGeometry().getDetectorWidth(); x += 50)
        imp1.drawLine(x, 0, x, config.getGeometry().getDetectorHeight());
      for (int y = 0; y < config.getGeometry().getDetectorHeight(); y += 50)
        imp1.drawLine(0, y, config.getGeometry().getDetectorWidth(), y);
    }

    if (isCornerIncluded) {
      xy1[noBeadRegistered + 0][0] = 0;
      xy1[noBeadRegistered + 0][1] = 0;
      xy2[noBeadRegistered + 0][0] = 0;
      xy2[noBeadRegistered + 0][1] = 0;

      xy1[noBeadRegistered + 1][0] = 0;
      xy1[noBeadRegistered + 1][1] = config.getGeometry().getDetectorHeight();
      xy2[noBeadRegistered + 1][0] = 0;
      xy2[noBeadRegistered + 1][1] = config.getGeometry().getDetectorHeight();

      xy1[noBeadRegistered + 2][0] = config.getGeometry().getDetectorWidth();
      xy1[noBeadRegistered + 2][1] = 0;
      xy2[noBeadRegistered + 2][0] = config.getGeometry().getDetectorWidth();
      xy2[noBeadRegistered + 2][1] = 0;

      xy1[noBeadRegistered + 3][0] = config.getGeometry().getDetectorWidth();
      xy1[noBeadRegistered + 3][1] = config.getGeometry().getDetectorHeight();
      xy2[noBeadRegistered + 3][0] = config.getGeometry().getDetectorWidth();
      xy2[noBeadRegistered + 3][1] = config.getGeometry().getDetectorHeight();

      noBeadRegistered = noBeadRegistered + 4;
    }

    boolean fScaling = true;

    double minX = Double.MAX_VALUE;
    double maxX = 0;
    double minY = Double.MAX_VALUE;
    double maxY = 0;
    double c = 0;
    if (fScaling) { // ----- scaling to reduce condition # of A matrix
      for (int i = 0; i < noBeadRegistered; i++) {
        minX = Math.min(minX, xy1[i][0]);
        maxX = Math.max(maxX, xy1[i][0]);
        minY = Math.min(minY, xy1[i][1]);
        maxY = Math.max(maxY, xy1[i][1]);
      }
      c = Math.max(maxX - minX, maxY - minY);

      for (int i = 0; i < noBeadRegistered; i++) {
        xy1_hat[i][0] = (xy1[i][0] - minX) / c;
        xy1_hat[i][1] = (xy1[i][1] - minY) / c;

        xy2_hat[i][0] = (xy2[i][0] - minX) / c;
        xy2_hat[i][1] = (xy2[i][1] - minY) / c;
      }
    } else {
      xy1_hat = xy1;
      xy2_hat = xy2;
    }

    ImageProcessor imp2 = imp1.duplicate(); // warped

    /*
     * A*x = b
     * Matrix A = (n + 3) * (n + 3);
     * n (noBeadRegistered + 4): # of control points + 4 corner points (assume corner points are static)
     */

    int n = noBeadRegistered + 3;

    SimpleMatrix A = new SimpleMatrix(n, n);
    SimpleVector x_x = new SimpleVector(n);
    SimpleVector x_y = new SimpleVector(n);
    SimpleVector b_x = new SimpleVector(n);
    SimpleVector b_y = new SimpleVector(n);

    double rij = 0;
    double valA = 0;
    double valb_x = 0;
    double valb_y = 0;

    // Matrix L formation
    // alpha: mean of distances between control points' xy-projections) is a constant only present
    // on the diagonal of K
    // lambda: TPS smoothing regularization coefficient

    double alpha = 0.0;
    double lambda = 1.6; // 1.6
    for (int i = 0; i < noBeadRegistered; i++) { // i= # of row
      for (int j = i; j < noBeadRegistered; j++) { // j= # of column
        alpha +=
            Math.sqrt(
                Math.pow(xy2_hat[i][0] - xy2_hat[j][0], 2)
                    + Math.pow(xy2_hat[i][1] - xy2_hat[j][1], 2));
      }
    }
    alpha = alpha / Math.pow(noBeadRegistered, 2);

    for (int i = 0; i < n; i++) { // i= # of row
      for (int j = i; j < n; j++) { // j= # of column
        if (i < 3 && j < 3) valA = 0;
        else if (i >= 3 && j >= 3 && i == j) {
          valA = Math.pow(alpha, 2) * lambda;
          // valA = lambda;
          if (imageIndex < 10)
            System.out.println("Regularization = " + valA + ", lambda= " + lambda);
        } else if (i == 0 && j >= 0) valA = 1;
        else if (i == 1 && j >= 3) valA = xy1_hat[j - 3][0];
        else if (i == 2 && j >= 3) valA = xy1_hat[j - 3][1];
        else {
          rij =
              Math.pow(xy1_hat[j - 3][0] - xy1_hat[i - 3][0], 2)
                  + Math.pow(xy1_hat[j - 3][1] - xy1_hat[i - 3][1], 2);
          if (rij == 0) valA = 0;
          else valA = rij * Math.log(rij);
        }

        A.setElementValue(i, j, valA);
        A.setElementValue(j, i, valA);
      }

      if (i < 3) {
        valb_x = 0;
        valb_y = 0;
      } else {
        //				valb_x = xy2_hat[i-3][0]-xy1_hat[i-3][0];
        //				valb_y = xy2_hat[i-3][1]-xy1_hat[i-3][1];
        valb_x = xy2[i - 3][0] - xy1[i - 3][0];
        valb_y = xy2[i - 3][1] - xy1[i - 3][1];
        //				if (imageIndex > 150 && imageIndex < 170)
        //					System.out.println("Idx" + imageIndex + ",Elevation" + (i-3) + ": " + valb_x + "---"
        // + valb_y);
      }

      b_x.setElementValue(i, valb_x);
      b_y.setElementValue(i, valb_y);
    }

    // System.out.println("A condition number=" + A.conditionNumber(MatrixNormType.MAT_NORM_L1));
    // System.out.println("A condition number=" + A.conditionNumber(MatrixNormType.MAT_NORM_LINF));

    x_x = Solvers.solveLinearSysytemOfEquations(A, b_x);
    x_y = Solvers.solveLinearSysytemOfEquations(A, b_y);

    if (fScaling) {
      // ----- pixel space coefficients a, b scaling back
      double tmpA0 =
          x_x.getElement(0) - x_x.getElement(1) * (minX / c) - x_x.getElement(2) * (minY / c);
      for (int j = 0; j < noBeadRegistered; j++) {
        tmpA0 -=
            Math.log(c)
                * 2
                * x_x.getElement(j + 3)
                * (Math.pow(xy1_hat[j][0], 2) + Math.pow(xy1_hat[j][1], 2));
      }
      x_x.setElementValue(0, tmpA0);
      tmpA0 = x_y.getElement(0) - x_y.getElement(1) * (minX / c) - x_y.getElement(2) * (minY / c);
      for (int j = 0; j < noBeadRegistered; j++) {
        tmpA0 -=
            Math.log(c)
                * 2
                * x_y.getElement(j + 3)
                * (Math.pow(xy1_hat[j][0], 2) + Math.pow(xy1_hat[j][1], 2));
      }
      x_y.setElementValue(0, tmpA0);

      x_x.setElementValue(1, x_x.getElement(1) / c);
      x_y.setElementValue(1, x_y.getElement(1) / c);
      x_x.setElementValue(2, x_x.getElement(2) / c);
      x_y.setElementValue(2, x_y.getElement(2) / c);

      for (int i = 3; i < n; i++) {
        x_x.setElementValue(i, x_x.getElement(i) / Math.pow(c, 2));
        x_y.setElementValue(i, x_y.getElement(i) / Math.pow(c, 2));
      }
      // ----- pixel space coefficients a, b scaling back end
    }

    double devU = 0;
    double devV = 0;
    // Do warping
    // if (imageIndex == 0) {
    for (int y = 0; y < config.getGeometry().getDetectorHeight(); y++) {
      // for (int y=252; y<253; y++) {
      for (int x = 0; x < config.getGeometry().getDetectorWidth(); x++) {
        // for (int x=606; x<607; x++) {
        devU = x_x.getElement(0) + x_x.getElement(1) * x + x_x.getElement(2) * y;
        devV = x_y.getElement(0) + x_y.getElement(1) * x + x_y.getElement(2) * y;
        for (int i = 0; i < noBeadRegistered; i++) {
          rij = Math.pow(xy1[i][0] - x, 2) + Math.pow(xy1[i][1] - y, 2);
          if (rij > 0) {
            devU += x_x.getElement(i + 3) * rij * Math.log(rij);
            devV += x_y.getElement(i + 3) * rij * Math.log(rij);
          }
        }

        //					devU = 0;
        //					devV = 0;

        imp2.setf(x, y, (float) imp1.getInterpolatedValue(x - devU, y - devV));

        // System.out.println("x, y=" + x + ", " + y + "\t" + devU + ", " + devV);
        // maxDevU = Math.max(maxDevU, devU);
        // maxDevV = Math.max(maxDevV, devV);
      }
    }

    // Error estimate after transformation
    //			for (int i=0; i<= WeightBearingBeadPositionBuilder.currentBeadNo; i++){
    //
    //				if (beadMean3D[i][0] != 0 || beadMean3D[i][1] != 0 || beadMean3D[i][2] != 0){ // assume
    // bead 3d is registered.
    //
    //					// find bead location if registered by txt: state 1
    //					if (beadPosition2D[imageIndex][i][2] == 1){
    //
    //						// Projected Reference
    //						uv = compute2DCoordinates(beadMean3D[i], pMatrix);
    //						double x = uv[0];
    //						double y = uv[1];
    //						// bead detected position in 2d
    //						// Transform to 2D coordinates, time variant position
    //						//beadPosition2D[imageIndex][i][0];
    //						//beadPosition2D[imageIndex][i][1];
    //
    //						devU = x_x.getElement(0) + x_x.getElement(1)*x + x_x.getElement(2)*y;
    //						devV = x_y.getElement(0) + x_y.getElement(1)*x + x_y.getElement(2)*y;
    //						for (int j=0; j<noBeadRegistered; j++){
    //							rij = Math.pow(xy1[j][0]-x, 2) + Math.pow(xy1[j][1]-y, 2);
    //							if (rij > 0) {
    //								devU += x_x.getElement(j+3)*rij*Math.log(rij);
    //								devV += x_y.getElement(j+3)*rij*Math.log(rij);
    //							}
    //						}
    //
    //						distanceReferenceToCurrentBead +=
    // Math.sqrt(Math.pow(uv[0]-(beadPosition2D[imageIndex][i][0]+devU),
    // 2)+Math.pow(uv[1]-(beadPosition2D[imageIndex][i][1]+devV), 2));
    //
    //					}
    //				}
    //			}
    //			System.out.println("Euclidean distance\t" + imageIndex + "\t" +
    // distanceReferenceToCurrentBead/noBeadRegistered);

    // }

    if (isDisplay) {
      for (int i = WeightBearingBeadPositionBuilder.currentBeadNo; i >= 0; i--) {

        if (beadMean3D[i][0] != 0
            || beadMean3D[i][1] != 0
            || beadMean3D[i][2] != 0) { // assume bead 3d is registered.

          uv = compute2DCoordinates(beadMean3D[i], pMatrix);

          imp2.setValue(2);
          // mean projected bead
          imp2.drawLine(
              (int) Math.round(uv[0] - 10),
              (int) Math.round(uv[1]),
              (int) Math.round(uv[0] + 10),
              (int) Math.round(uv[1]));
          imp2.drawLine(
              (int) Math.round(uv[0]),
              (int) Math.round(uv[1] - 10),
              (int) Math.round(uv[0]),
              (int) Math.round(uv[1] + 10));
        }
      }
    }
    Grid2D result = new Grid2D((float[]) imp2.getPixels(), imp2.getWidth(), imp2.getHeight());
    return result;
  }
public class ThinPlateSplinesBasedProjectionWarpingTool extends IndividualImageFilteringTool {

  /**
   * Warping projections using thin plate splines for marker based motion correction
   *
   * <p>http://elonen.iki.fi/code/tpsdemo/index.html, Based mostly on "Approximation Methods for
   * Thin Plate Spline Mappings and Principal Warps" by Gianluca Donato and Serge Belongie, 2002.
   * (http://cseweb.ucsd.edu/~sjb/pami_tps.pdf)
   *
   * @author Jang-Hwan Choi
   */
  private static final long serialVersionUID = -3377571138470875648L;

  WeightBearingBeadPositionBuilder beadBuilder;
  Configuration config = Configuration.getGlobalConfiguration();

  private boolean initBead = false;
  // display bead indication, horizontal & vertical lines
  private boolean isDisplay = false;
  private boolean isCornerIncluded = true;

  public ThinPlateSplinesBasedProjectionWarpingTool() {
    configured = true;
  }

  protected void initializeBead() {
    if (!initBead) {
      System.out.println("Read in initial bead positions.");

      beadBuilder = new WeightBearingBeadPositionBuilder();
      beadBuilder.readInitialBeadPositionFromFile();
      beadBuilder.estimateBeadMeanPositionIn3D();
      initBead = true;
    }
  }

  @Override
  public Grid2D applyToolToImage(Grid2D imageProcessor) {
    FloatProcessor imp = new FloatProcessor(imageProcessor.getWidth(), imageProcessor.getHeight());
    imp.setPixels(imageProcessor.getBuffer());

    if (!initBead) initializeBead();
    ImageProcessor imp1 = imp.duplicate(); // original

    double[][] beadMean3D = config.getBeadMeanPosition3D(); // [beadNo][x,y,z]
    double[] uv = new double[1];

    SimpleMatrix pMatrix = config.getGeometry().getProjectionMatrix(imageIndex).computeP();
    // [projection #][bead #][u, v, state[0: initial, 1: registered, 2: updated by hough searching]]
    double[][][] beadPosition2D = config.getBeadPosition2D();

    int noBeadRegistered = 0;

    double[][] xy1 = new double[WeightBearingBeadPositionBuilder.beadNo][2]; // original
    double[][] xy2 =
        new double[WeightBearingBeadPositionBuilder.beadNo]
            [2]; // warped	(mapped to the mean), control points, reference

    double[][] xy1_hat = new double[WeightBearingBeadPositionBuilder.beadNo][2]; // original
    double[][] xy2_hat = new double[WeightBearingBeadPositionBuilder.beadNo][2]; // original

    // double distanceReferenceToCurrentBead = 0;

    for (int i = WeightBearingBeadPositionBuilder.currentBeadNo; i >= 0; i--) {

      if (beadMean3D[i][0] != 0
          || beadMean3D[i][1] != 0
          || beadMean3D[i][2] != 0) { // assume bead 3d is registered.

        uv = compute2DCoordinates(beadMean3D[i], pMatrix);

        // find bead location if registered by txt: state 1
        if (beadPosition2D[imageIndex][i][2] == 1) {

          noBeadRegistered++;

          if (isDisplay) {
            imp1.setValue(2);
            imp1.drawLine(
                (int) Math.round(beadPosition2D[imageIndex][i][0] - 10),
                (int) Math.round(beadPosition2D[imageIndex][i][1] - 10),
                (int) Math.round(beadPosition2D[imageIndex][i][0] + 10),
                (int) Math.round(beadPosition2D[imageIndex][i][1] + 10));
            imp1.drawLine(
                (int) Math.round(beadPosition2D[imageIndex][i][0] - 10),
                (int) Math.round(beadPosition2D[imageIndex][i][1] + 10),
                (int) Math.round(beadPosition2D[imageIndex][i][0] + 10),
                (int) Math.round(beadPosition2D[imageIndex][i][1] - 10));
            imp1.drawString(
                "Bead " + i + " (state:" + (int) beadPosition2D[imageIndex][i][2] + ")",
                (int) beadPosition2D[imageIndex][i][0],
                (int) beadPosition2D[imageIndex][i][1] - 10);
          }

          xy1[noBeadRegistered - 1][0] = beadPosition2D[imageIndex][i][0];
          xy1[noBeadRegistered - 1][1] = beadPosition2D[imageIndex][i][1];

          xy2[noBeadRegistered - 1][0] = uv[0];
          xy2[noBeadRegistered - 1][1] = uv[1];

        } else if (imageIndex != 0
            && imageIndex != config.getGeometry().getNumProjectionMatrices() - 1) {

          if (beadPosition2D[imageIndex - 1][i][2] == 1
              && beadPosition2D[imageIndex + 1][i][2] == 1) {

            noBeadRegistered++;

            double xMean =
                (beadPosition2D[imageIndex - 1][i][0] + beadPosition2D[imageIndex - 1][i][0]) / 2;
            double yMean =
                (beadPosition2D[imageIndex + 1][i][1] + beadPosition2D[imageIndex + 1][i][1]) / 2;

            if (isDisplay) {
              imp1.setValue(2);
              imp1.drawLine(
                  (int) Math.round(xMean - 10),
                  (int) Math.round(yMean - 10),
                  (int) Math.round(xMean + 10),
                  (int) Math.round(yMean + 10));
              imp1.drawLine(
                  (int) Math.round(xMean - 10),
                  (int) Math.round(yMean + 10),
                  (int) Math.round(xMean + 10),
                  (int) Math.round(yMean - 10));
              imp1.drawString("Bead " + i + " (state:" + "M)", (int) xMean, (int) yMean - 10);
            }

            xy1[noBeadRegistered - 1][0] = xMean;
            xy1[noBeadRegistered - 1][1] = yMean;

            xy2[noBeadRegistered - 1][0] = uv[0];
            xy2[noBeadRegistered - 1][1] = uv[1];
          }
        }

        // mean projected bead
        //				imp1.drawLine((int) Math.round(uv[0]-10), (int) Math.round(uv[1]), (int)
        // Math.round(uv[0]+10), (int) Math.round(uv[1]));
        //				imp1.drawLine((int) Math.round(uv[0]), (int) Math.round(uv[1]-10), (int)
        // Math.round(uv[0]), (int) Math.round(uv[1]+10));
      }
    }

    if (isDisplay) {
      for (int x = 0; x < config.getGeometry().getDetectorWidth(); x += 50)
        imp1.drawLine(x, 0, x, config.getGeometry().getDetectorHeight());
      for (int y = 0; y < config.getGeometry().getDetectorHeight(); y += 50)
        imp1.drawLine(0, y, config.getGeometry().getDetectorWidth(), y);
    }

    if (isCornerIncluded) {
      xy1[noBeadRegistered + 0][0] = 0;
      xy1[noBeadRegistered + 0][1] = 0;
      xy2[noBeadRegistered + 0][0] = 0;
      xy2[noBeadRegistered + 0][1] = 0;

      xy1[noBeadRegistered + 1][0] = 0;
      xy1[noBeadRegistered + 1][1] = config.getGeometry().getDetectorHeight();
      xy2[noBeadRegistered + 1][0] = 0;
      xy2[noBeadRegistered + 1][1] = config.getGeometry().getDetectorHeight();

      xy1[noBeadRegistered + 2][0] = config.getGeometry().getDetectorWidth();
      xy1[noBeadRegistered + 2][1] = 0;
      xy2[noBeadRegistered + 2][0] = config.getGeometry().getDetectorWidth();
      xy2[noBeadRegistered + 2][1] = 0;

      xy1[noBeadRegistered + 3][0] = config.getGeometry().getDetectorWidth();
      xy1[noBeadRegistered + 3][1] = config.getGeometry().getDetectorHeight();
      xy2[noBeadRegistered + 3][0] = config.getGeometry().getDetectorWidth();
      xy2[noBeadRegistered + 3][1] = config.getGeometry().getDetectorHeight();

      noBeadRegistered = noBeadRegistered + 4;
    }

    boolean fScaling = true;

    double minX = Double.MAX_VALUE;
    double maxX = 0;
    double minY = Double.MAX_VALUE;
    double maxY = 0;
    double c = 0;
    if (fScaling) { // ----- scaling to reduce condition # of A matrix
      for (int i = 0; i < noBeadRegistered; i++) {
        minX = Math.min(minX, xy1[i][0]);
        maxX = Math.max(maxX, xy1[i][0]);
        minY = Math.min(minY, xy1[i][1]);
        maxY = Math.max(maxY, xy1[i][1]);
      }
      c = Math.max(maxX - minX, maxY - minY);

      for (int i = 0; i < noBeadRegistered; i++) {
        xy1_hat[i][0] = (xy1[i][0] - minX) / c;
        xy1_hat[i][1] = (xy1[i][1] - minY) / c;

        xy2_hat[i][0] = (xy2[i][0] - minX) / c;
        xy2_hat[i][1] = (xy2[i][1] - minY) / c;
      }
    } else {
      xy1_hat = xy1;
      xy2_hat = xy2;
    }

    ImageProcessor imp2 = imp1.duplicate(); // warped

    /*
     * A*x = b
     * Matrix A = (n + 3) * (n + 3);
     * n (noBeadRegistered + 4): # of control points + 4 corner points (assume corner points are static)
     */

    int n = noBeadRegistered + 3;

    SimpleMatrix A = new SimpleMatrix(n, n);
    SimpleVector x_x = new SimpleVector(n);
    SimpleVector x_y = new SimpleVector(n);
    SimpleVector b_x = new SimpleVector(n);
    SimpleVector b_y = new SimpleVector(n);

    double rij = 0;
    double valA = 0;
    double valb_x = 0;
    double valb_y = 0;

    // Matrix L formation
    // alpha: mean of distances between control points' xy-projections) is a constant only present
    // on the diagonal of K
    // lambda: TPS smoothing regularization coefficient

    double alpha = 0.0;
    double lambda = 1.6; // 1.6
    for (int i = 0; i < noBeadRegistered; i++) { // i= # of row
      for (int j = i; j < noBeadRegistered; j++) { // j= # of column
        alpha +=
            Math.sqrt(
                Math.pow(xy2_hat[i][0] - xy2_hat[j][0], 2)
                    + Math.pow(xy2_hat[i][1] - xy2_hat[j][1], 2));
      }
    }
    alpha = alpha / Math.pow(noBeadRegistered, 2);

    for (int i = 0; i < n; i++) { // i= # of row
      for (int j = i; j < n; j++) { // j= # of column
        if (i < 3 && j < 3) valA = 0;
        else if (i >= 3 && j >= 3 && i == j) {
          valA = Math.pow(alpha, 2) * lambda;
          // valA = lambda;
          if (imageIndex < 10)
            System.out.println("Regularization = " + valA + ", lambda= " + lambda);
        } else if (i == 0 && j >= 0) valA = 1;
        else if (i == 1 && j >= 3) valA = xy1_hat[j - 3][0];
        else if (i == 2 && j >= 3) valA = xy1_hat[j - 3][1];
        else {
          rij =
              Math.pow(xy1_hat[j - 3][0] - xy1_hat[i - 3][0], 2)
                  + Math.pow(xy1_hat[j - 3][1] - xy1_hat[i - 3][1], 2);
          if (rij == 0) valA = 0;
          else valA = rij * Math.log(rij);
        }

        A.setElementValue(i, j, valA);
        A.setElementValue(j, i, valA);
      }

      if (i < 3) {
        valb_x = 0;
        valb_y = 0;
      } else {
        //				valb_x = xy2_hat[i-3][0]-xy1_hat[i-3][0];
        //				valb_y = xy2_hat[i-3][1]-xy1_hat[i-3][1];
        valb_x = xy2[i - 3][0] - xy1[i - 3][0];
        valb_y = xy2[i - 3][1] - xy1[i - 3][1];
        //				if (imageIndex > 150 && imageIndex < 170)
        //					System.out.println("Idx" + imageIndex + ",Elevation" + (i-3) + ": " + valb_x + "---"
        // + valb_y);
      }

      b_x.setElementValue(i, valb_x);
      b_y.setElementValue(i, valb_y);
    }

    // System.out.println("A condition number=" + A.conditionNumber(MatrixNormType.MAT_NORM_L1));
    // System.out.println("A condition number=" + A.conditionNumber(MatrixNormType.MAT_NORM_LINF));

    x_x = Solvers.solveLinearSysytemOfEquations(A, b_x);
    x_y = Solvers.solveLinearSysytemOfEquations(A, b_y);

    if (fScaling) {
      // ----- pixel space coefficients a, b scaling back
      double tmpA0 =
          x_x.getElement(0) - x_x.getElement(1) * (minX / c) - x_x.getElement(2) * (minY / c);
      for (int j = 0; j < noBeadRegistered; j++) {
        tmpA0 -=
            Math.log(c)
                * 2
                * x_x.getElement(j + 3)
                * (Math.pow(xy1_hat[j][0], 2) + Math.pow(xy1_hat[j][1], 2));
      }
      x_x.setElementValue(0, tmpA0);
      tmpA0 = x_y.getElement(0) - x_y.getElement(1) * (minX / c) - x_y.getElement(2) * (minY / c);
      for (int j = 0; j < noBeadRegistered; j++) {
        tmpA0 -=
            Math.log(c)
                * 2
                * x_y.getElement(j + 3)
                * (Math.pow(xy1_hat[j][0], 2) + Math.pow(xy1_hat[j][1], 2));
      }
      x_y.setElementValue(0, tmpA0);

      x_x.setElementValue(1, x_x.getElement(1) / c);
      x_y.setElementValue(1, x_y.getElement(1) / c);
      x_x.setElementValue(2, x_x.getElement(2) / c);
      x_y.setElementValue(2, x_y.getElement(2) / c);

      for (int i = 3; i < n; i++) {
        x_x.setElementValue(i, x_x.getElement(i) / Math.pow(c, 2));
        x_y.setElementValue(i, x_y.getElement(i) / Math.pow(c, 2));
      }
      // ----- pixel space coefficients a, b scaling back end
    }

    double devU = 0;
    double devV = 0;
    // Do warping
    // if (imageIndex == 0) {
    for (int y = 0; y < config.getGeometry().getDetectorHeight(); y++) {
      // for (int y=252; y<253; y++) {
      for (int x = 0; x < config.getGeometry().getDetectorWidth(); x++) {
        // for (int x=606; x<607; x++) {
        devU = x_x.getElement(0) + x_x.getElement(1) * x + x_x.getElement(2) * y;
        devV = x_y.getElement(0) + x_y.getElement(1) * x + x_y.getElement(2) * y;
        for (int i = 0; i < noBeadRegistered; i++) {
          rij = Math.pow(xy1[i][0] - x, 2) + Math.pow(xy1[i][1] - y, 2);
          if (rij > 0) {
            devU += x_x.getElement(i + 3) * rij * Math.log(rij);
            devV += x_y.getElement(i + 3) * rij * Math.log(rij);
          }
        }

        //					devU = 0;
        //					devV = 0;

        imp2.setf(x, y, (float) imp1.getInterpolatedValue(x - devU, y - devV));

        // System.out.println("x, y=" + x + ", " + y + "\t" + devU + ", " + devV);
        // maxDevU = Math.max(maxDevU, devU);
        // maxDevV = Math.max(maxDevV, devV);
      }
    }

    // Error estimate after transformation
    //			for (int i=0; i<= WeightBearingBeadPositionBuilder.currentBeadNo; i++){
    //
    //				if (beadMean3D[i][0] != 0 || beadMean3D[i][1] != 0 || beadMean3D[i][2] != 0){ // assume
    // bead 3d is registered.
    //
    //					// find bead location if registered by txt: state 1
    //					if (beadPosition2D[imageIndex][i][2] == 1){
    //
    //						// Projected Reference
    //						uv = compute2DCoordinates(beadMean3D[i], pMatrix);
    //						double x = uv[0];
    //						double y = uv[1];
    //						// bead detected position in 2d
    //						// Transform to 2D coordinates, time variant position
    //						//beadPosition2D[imageIndex][i][0];
    //						//beadPosition2D[imageIndex][i][1];
    //
    //						devU = x_x.getElement(0) + x_x.getElement(1)*x + x_x.getElement(2)*y;
    //						devV = x_y.getElement(0) + x_y.getElement(1)*x + x_y.getElement(2)*y;
    //						for (int j=0; j<noBeadRegistered; j++){
    //							rij = Math.pow(xy1[j][0]-x, 2) + Math.pow(xy1[j][1]-y, 2);
    //							if (rij > 0) {
    //								devU += x_x.getElement(j+3)*rij*Math.log(rij);
    //								devV += x_y.getElement(j+3)*rij*Math.log(rij);
    //							}
    //						}
    //
    //						distanceReferenceToCurrentBead +=
    // Math.sqrt(Math.pow(uv[0]-(beadPosition2D[imageIndex][i][0]+devU),
    // 2)+Math.pow(uv[1]-(beadPosition2D[imageIndex][i][1]+devV), 2));
    //
    //					}
    //				}
    //			}
    //			System.out.println("Euclidean distance\t" + imageIndex + "\t" +
    // distanceReferenceToCurrentBead/noBeadRegistered);

    // }

    if (isDisplay) {
      for (int i = WeightBearingBeadPositionBuilder.currentBeadNo; i >= 0; i--) {

        if (beadMean3D[i][0] != 0
            || beadMean3D[i][1] != 0
            || beadMean3D[i][2] != 0) { // assume bead 3d is registered.

          uv = compute2DCoordinates(beadMean3D[i], pMatrix);

          imp2.setValue(2);
          // mean projected bead
          imp2.drawLine(
              (int) Math.round(uv[0] - 10),
              (int) Math.round(uv[1]),
              (int) Math.round(uv[0] + 10),
              (int) Math.round(uv[1]));
          imp2.drawLine(
              (int) Math.round(uv[0]),
              (int) Math.round(uv[1] - 10),
              (int) Math.round(uv[0]),
              (int) Math.round(uv[1] + 10));
        }
      }
    }
    Grid2D result = new Grid2D((float[]) imp2.getPixels(), imp2.getWidth(), imp2.getHeight());
    return result;
  }

  private double[] compute2DCoordinates(double[] point3D, SimpleMatrix pMatrix) {

    // Compute coordinates in projection data.
    SimpleVector homogeneousPoint =
        SimpleOperators.multiply(pMatrix, new SimpleVector(point3D[0], point3D[1], point3D[2], 1));
    // Transform to 2D coordinates
    double coordU = homogeneousPoint.getElement(0) / homogeneousPoint.getElement(2);
    double coordV = homogeneousPoint.getElement(1) / homogeneousPoint.getElement(2);

    // double pxlSize = config.getGeometry().getPixelDimensionX();

    return new double[] {coordU, coordV};
  }

  @Override
  public IndividualImageFilteringTool clone() {
    IndividualImageFilteringTool clone = new ThinPlateSplinesBasedProjectionWarpingTool();
    clone.configured = configured;
    return clone;
  }

  @Override
  public String getToolName() {
    return "Projection Warping Using Thin Plate Splines";
  }

  @Override
  public void configure() throws Exception {
    setConfigured(true);
  }

  @Override
  public boolean isDeviceDependent() {
    return true;
  }

  @Override
  public String getBibtexCitation() {
    return "@article{Bookstein89-PWT,\n"
        + "  author={Bookstein FL},\n"
        + "  title={Principal warps: Thin-plate splines and the decomposition of deformations},\n"
        + "  journal={IEEE Transactions on Pattern Analysis and Machine Intelligence},\n"
        + "  volume={11},\n"
        + "  number={6},\n"
        + "  pages={568-585},\n"
        + "  year={1989}\n"
        + "}";
  }

  @Override
  public String getMedlineCitation() {
    return "Bookstein FL. Principal warps: Thin-plate splines and the decomposition of deformations. IEEE Transactions on Pattern Analysis and Machine Intelligence 11(6):568-585, 1989.";
  }
}
  protected void init() {
    if (!initialized) {
      largeVolumeMode = false;

      int reconDimensionX = getGeometry().getReconDimensionX();
      int reconDimensionY = getGeometry().getReconDimensionY();
      int reconDimensionZ = getGeometry().getReconDimensionZ();
      projections = new ImageGridBuffer();
      projectionsAvailable = new ArrayList<Integer>();
      projectionsDone = new ArrayList<Integer>();
      // Initialize the JCudaDriver. Note that this has to be done from
      // the same thread that will later use the JCudaDriver API.
      JCudaDriver.setExceptionsEnabled(true);
      JCudaDriver.cuInit(0);
      CUdevice dev = CUDAUtil.getBestDevice();
      cuCtx = new CUcontext();
      JCudaDriver.cuCtxCreate(cuCtx, 0, dev);
      // check space on device:
      int[] memory = new int[1];
      int[] total = new int[1];
      JCudaDriver.cuDeviceTotalMem(memory, dev);
      JCudaDriver.cuMemGetInfo(memory, total);
      int availableMemory = (int) (CUDAUtil.correctMemoryValue(memory[0]) / ((long) 1024 * 1024));
      int requiredMemory =
          (int)
              (((((double) reconDimensionX)
                          * reconDimensionY
                          * ((double) reconDimensionZ)
                          * Sizeof.FLOAT)
                      + (((double)
                              Configuration.getGlobalConfiguration()
                                  .getGeometry()
                                  .getDetectorHeight())
                          * Configuration.getGlobalConfiguration().getGeometry().getDetectorWidth()
                          * Sizeof.FLOAT))
                  / (1024.0 * 1024));
      if (debug) {
        System.out.println("Total available Memory on CUDA card:" + availableMemory);
        System.out.println("Required Memory on CUDA card:" + requiredMemory);
      }
      if (requiredMemory > availableMemory) {
        nSteps = CUDAUtil.iDivUp(requiredMemory, (int) (availableMemory));
        if (debug) System.out.println("Switching to large volume mode with nSteps = " + nSteps);
        largeVolumeMode = true;
      }
      if (debug) {
        CUdevprop prop = new CUdevprop();
        JCudaDriver.cuDeviceGetProperties(prop, dev);
        System.out.println(prop.toFormattedString());
      }

      // Load the CUBIN file containing the kernel
      module = new CUmodule();
      JCudaDriver.cuModuleLoad(module, "backprojectWithCuda.ptx");

      // Obtain a function pointer to the kernel function. This function
      // will later be called.
      //
      function = new CUfunction();
      JCudaDriver.cuModuleGetFunction(function, module, "_Z17backprojectKernelPfiiffffff");
      // create the reconstruction volume;
      int memorysize = reconDimensionX * reconDimensionY * reconDimensionZ * Sizeof.FLOAT;
      if (largeVolumeMode) {
        subVolumeZ = CUDAUtil.iDivUp(reconDimensionZ, nSteps);
        if (debug) System.out.println("SubVolumeZ: " + subVolumeZ);
        h_volume = new float[reconDimensionX * reconDimensionY * subVolumeZ];
        memorysize = reconDimensionX * reconDimensionY * subVolumeZ * Sizeof.FLOAT;
        if (debug) System.out.println("Memory: " + memorysize);
      } else {
        h_volume = new float[reconDimensionX * reconDimensionY * reconDimensionZ];
      }
      // copy volume to device
      volumePointer = new CUdeviceptr();
      JCudaDriver.cuMemAlloc(volumePointer, memorysize);
      JCudaDriver.cuMemcpyHtoD(volumePointer, Pointer.to(h_volume), memorysize);

      // compute adapted volume size
      //    volume size in x = multiple of bpBlockSize[0]
      //    volume size in y = multiple of bpBlockSize[1]

      int adaptedVolSize[] = new int[3];
      if ((reconDimensionX % bpBlockSize[0]) == 0) {
        adaptedVolSize[0] = reconDimensionX;
      } else {
        adaptedVolSize[0] = ((reconDimensionX / bpBlockSize[0]) + 1) * bpBlockSize[0];
      }
      if ((reconDimensionY % bpBlockSize[1]) == 0) {
        adaptedVolSize[1] = reconDimensionY;
      } else {
        adaptedVolSize[1] = ((reconDimensionY / bpBlockSize[1]) + 1) * bpBlockSize[1];
      }
      adaptedVolSize[2] = reconDimensionZ;
      int volStrideHost[] = new int[2];
      // compute volstride and copy it to constant memory
      volStrideHost[0] = adaptedVolSize[0];
      volStrideHost[1] = adaptedVolSize[0] * adaptedVolSize[1];

      volStride = new CUdeviceptr();
      JCudaDriver.cuModuleGetGlobal(volStride, new int[1], module, "gVolStride");
      JCudaDriver.cuMemcpyHtoD(volStride, Pointer.to(volStrideHost), Sizeof.INT * 2);

      // Calculate new grid size
      gridSize =
          new dim3(
              CUDAUtil.iDivUp(adaptedVolSize[0], bpBlockSize[0]),
              CUDAUtil.iDivUp(adaptedVolSize[1], bpBlockSize[1]),
              adaptedVolSize[2]);

      // Obtain the global pointer to the view matrix from
      // the module
      projectionMatrix = new CUdeviceptr();
      JCudaDriver.cuModuleGetGlobal(projectionMatrix, new int[1], module, "gProjMatrix");

      initialized = true;
    }
  }