/**
   * Get standard deviation of the image
   *
   * @param img input image
   * @return sigma
   */
  protected double getStdDev(MTBImage img) {

    int sizeStack = img.getSizeStack();
    int sizeX = img.getSizeX();
    int sizeY = img.getSizeY();

    double val;
    double mu = 0.0;
    double mu2 = 0.0;
    double N = 0.0;
    double sigma;

    for (int i = 0; i < sizeStack; i++) {
      img.setCurrentSliceIndex(i);

      for (int y = 0; y < sizeY; y++) {
        for (int x = 0; x < sizeX; x++) {
          val = img.getValueDouble(x, y);
          mu += val;
          mu2 += val * val;
          N++;
        }
      }
    }

    mu /= N;
    mu2 /= N;
    sigma = Math.sqrt(mu2 - mu * mu);

    return sigma;
  }
  /**
   * @param inImg
   * @param trajectories
   */
  public TrackVisualizer(MTBImage inImg, Vector<Trajectory2D> trajectories) {
    this.inImg = inImg;
    this.trajectories = trajectories;

    this.sizeX = inImg.getSizeX();
    this.sizeY = inImg.getSizeY();
    this.sizeT = inImg.getSizeT();
  }
  /**
   * Denoise wavelet coefficients using Jeffrey's noninformative prior for a given sigma of noise
   *
   * @param img input image
   * @param sigma sigma of noise
   */
  protected void denoise(MTBImage img, double sigma) {
    int sizeStack = img.getSizeStack();
    int sizeX = img.getSizeX();
    int sizeY = img.getSizeY();

    double s2 = 3.0 * sigma * sigma;
    double val, val2;
    for (int i = 0; i < sizeStack; i++) {
      img.setCurrentSliceIndex(i);

      for (int y = 0; y < sizeY; y++) {
        for (int x = 0; x < sizeX; x++) {
          val = img.getValueDouble(x, y);

          val2 = (val * val - s2);

          if (val2 < 0.0) val2 = 0.0;

          if (val != 0.0) img.putValueDouble(x, y, val2 / val);
          else img.putValueDouble(x, y, 0.0);
        }
      }
    }
    img.setCurrentSliceIndex(0);
  }
  /* (non-Javadoc)
   * @see de.unihalle.informatik.Alida.operator.ALDOperator#operate()
   */
  @Override
  protected void operate() {
    this.width = this.inputImage.getSizeX();
    this.height = this.inputImage.getSizeY();
    this.precursorMap = new Point2D.Double[this.height][this.width];
    this.closestObjectPixelMap = new Point2D.Double[this.height][this.width];

    // initially each points is its own precursor and closest
    // object pixel
    for (int y = 0; y < this.height; y++) {
      for (int x = 0; x < this.width; x++) {
        this.precursorMap[y][x] = new Point2D.Double(x, y);
        this.closestObjectPixelMap[y][x] = new Point2D.Double(x, y);
      }
    }

    // calculate distance map
    this.calcDM();
    // create distance image
    this.distanceImg =
        MTBImage.createMTBImage(this.width, this.height, 1, 1, 1, MTBImageType.MTB_DOUBLE);
    this.distanceImg.setTitle("DistanceTransformation-Result");
    for (int y = 0; y < this.height; y++) {
      for (int x = 0; x < this.width; x++) {
        this.distanceImg.putValueDouble(x, y, this.distanceMap[y][x]);
      }
    }
  }
  public MTBImage createProgressionImage(boolean pseudocolor) throws ALDOperatorException {
    MTBImage trajectoryImg;

    if (pseudocolor) {
      trajectoryImg =
          MTBImage.createMTBImage(sizeX, sizeY, 1, sizeT, 1, MTBImage.MTBImageType.MTB_RGB);
    } else {
      trajectoryImg =
          MTBImage.createMTBImage(sizeX, sizeY, 1, sizeT, 1, MTBImage.MTBImageType.MTB_BYTE);
    }

    trajectoryImg.fillBlack();
    trajectoryImg.setTitle("trajectories");

    Random r = new Random();

    for (int i = 0; i < trajectories.size(); i++) {
      Trajectory2D trajectory = trajectories.elementAt(i);

      int color = trajectory.getID();

      if (pseudocolor) {
        color = r.nextInt((int) Math.pow(2, 32));
      }

      Vector<Point2D.Double> points = trajectory.getPoints();
      int startFrame = trajectory.getStartFrame();

      for (int j = 0; j < points.size(); j++) {
        MTBImage currSlice = trajectoryImg.getSlice(0, startFrame + j, 0);

        // TODO: draw point in starting frame

        for (int k = 1; k <= j; k++) {
          Point2D.Double start = points.elementAt(k - 1);
          Point2D.Double end = points.elementAt(k);

          currSlice.drawLine2D((int) start.x, (int) start.y, (int) end.x, (int) end.y, color);
        }

        trajectoryImg.setCurrentSliceIndex(startFrame + j);
        trajectoryImg.setCurrentSlice(currSlice);
      }
    }

    return trajectoryImg;
  }
  /**
   * Constructor with default Gaussian kernel [1/16, 1/4, 3/8, 1/4, 1/16] for dimension x, y and z.
   */
  public UndecimatedWaveletTransform() throws ALDOperatorException {
    this.m_statusListeners = new Vector<StatusListener>(1);

    double[] kernel = {1.0 / 16.0, 1.0 / 4.0, 3.0 / 8.0, 1.0 / 4.0, 1.0 / 16.0};
    MTBImage[] kernels;

    kernels = new MTBImage[3];
    kernels[0] = MTBImage.createMTBImage(5, 1, 1, 1, 1, MTBImageType.MTB_DOUBLE);
    kernels[1] = MTBImage.createMTBImage(1, 5, 1, 1, 1, MTBImageType.MTB_DOUBLE);
    kernels[2] = MTBImage.createMTBImage(1, 1, 5, 1, 1, MTBImageType.MTB_DOUBLE);

    for (int i = 0; i < kernel.length; i++) {
      kernels[0].putValueDouble(i, 0, 0, 0, 0, kernel[i]);
      kernels[1].putValueDouble(0, i, 0, 0, 0, kernel[i]);
      kernels[2].putValueDouble(0, 0, i, 0, 0, kernel[i]);
    }

    this.setKernels(kernels);
    this.operatorExecStatus = OperatorExecutionStatus.OP_EXEC_INIT;
  }
  /**
   * @param pseudocolor
   * @param center
   * @return
   * @throws ALDOperatorException
   */
  public MTBImage create2DTrajectoryImage(boolean pseudocolor, boolean center)
      throws ALDOperatorException {
    MTBImage trajectoryImg;

    if (pseudocolor) {
      trajectoryImg = MTBImage.createMTBImage(sizeX, sizeY, 1, 1, 1, MTBImage.MTBImageType.MTB_RGB);
    } else {
      trajectoryImg =
          MTBImage.createMTBImage(sizeX, sizeY, 1, 1, 1, MTBImage.MTBImageType.MTB_BYTE);
    }

    trajectoryImg.fillBlack();
    trajectoryImg.setTitle("trajectories");

    int centerX = sizeX / 2;
    int centerY = sizeY / 2;
    int tx = 0;
    int ty = 0;

    Random r = new Random();

    for (int i = 0; i < trajectories.size(); i++) {
      Trajectory2D trajectory = trajectories.elementAt(i);

      int color = trajectory.getID();

      if (pseudocolor) {
        color = r.nextInt((int) Math.pow(2, 32));
      }

      Vector<Point2D.Double> points = trajectory.getPoints();

      for (int j = 1; j < points.size(); j++) {
        Point2D.Double start = points.elementAt(j - 1);
        Point2D.Double end = points.elementAt(j);

        if (center) {
          if (j == 1) // calculate translation vector
          {
            tx = centerX - (int) start.x;
            ty = centerY - (int) start.y;
          }

          trajectoryImg.drawLine2D(
              (int) start.x + tx, (int) start.y + ty, (int) end.x + tx, (int) end.y + ty, color);
        } else {
          trajectoryImg.drawLine2D((int) start.x, (int) start.y, (int) end.x, (int) end.y, color);
        }
      }
    }

    return trajectoryImg;
  }
  @Override
  public int setup(String arg, ImagePlus imP) {

    this.img = null;
    this.imp = null;

    try {
      this.img = MTBImage.createMTBImage(imP);
    } catch (IllegalArgumentException e) {
      this.imp = imP;
    } catch (NullPointerException e) {
      // do nothing, ImageJ will handle the case of no input image
    }

    return DOES_ALL + NO_CHANGES;
  }
  /**
   * Create an image with Gaussian noise
   *
   * @param mean
   * @param sigma
   * @param clippingFactor
   * @param bins
   * @param sizeX
   * @param sizeY
   * @param sizeZ
   * @param sizeT
   * @param sizeC
   * @return
   */
  protected MTBImage createGaussianNoiseImage(
      double mean,
      double sigma,
      double clippingFactor,
      int bins,
      int sizeX,
      int sizeY,
      int sizeZ,
      int sizeT,
      int sizeC) {
    MTBImage gImg =
        MTBImage.createMTBImage(sizeX, sizeY, sizeZ, sizeT, sizeC, MTBImageType.MTB_DOUBLE);
    int sizeStack = gImg.getSizeStack();

    double[] dist = new double[bins];
    double cs = -clippingFactor * sigma;
    double gFactor = 1.0 / (Math.sqrt(2.0 * Math.PI) * sigma);

    double lastVal = 0.0;

    double X;

    // cumulative distribution
    for (int i = 0; i < bins; i++) {
      X = ((double) i / (double) (bins - 1)) * 2.0 * cs - cs;

      dist[i] = lastVal + gFactor * Math.exp(-0.5 * (X * X) / (sigma * sigma));
      lastVal = dist[i];
    }

    // normalization
    //		for (int i = 0; i < bins; i++) {
    //			dist[i] /= dist[bins-1];
    //		}
    double sample;
    for (int i = 0; i < sizeStack; i++) {
      gImg.setCurrentSliceIndex(i);

      for (int y = 0; y < sizeY; y++) {
        for (int x = 0; x < sizeX; x++) {
          sample = getSample(dist);
          sample = sample * 2.0 * cs - cs - mean;
          gImg.putValueDouble(x, y, sample);
        }
      }
    }
    gImg.setCurrentSliceIndex(0);

    return gImg;
  }
  /**
   * Constructor with default Gaussian kernel [1/16, 1/4, 3/8, 1/4, 1/16] for at most dimension x, y
   * (and z if present).
   *
   * @param img input image
   * @param Jmax maximum scale (2^Jmax - 1)
   * @param denoise reduction of gaussian noise
   * @throws ALDOperatorException
   */
  public UndecimatedWaveletTransform(MTBImage _img, int _Jmax, boolean _denoise)
      throws ALDOperatorException {
    this.m_statusListeners = new Vector<StatusListener>(1);

    this.setImg(_img);
    this.setJmax(_Jmax);
    this.setForwardTransform();
    this.setDenoise(_denoise);

    double[] kernel = {1.0 / 16.0, 1.0 / 4.0, 3.0 / 8.0, 1.0 / 4.0, 1.0 / 16.0};
    MTBImage[] kernels;

    if (_img.getSizeZ() > 1) {
      kernels = new MTBImage[3];
      kernels[0] = MTBImage.createMTBImage(5, 1, 1, 1, 1, MTBImageType.MTB_DOUBLE);
      kernels[1] = MTBImage.createMTBImage(1, 5, 1, 1, 1, MTBImageType.MTB_DOUBLE);
      kernels[2] = MTBImage.createMTBImage(1, 1, 5, 1, 1, MTBImageType.MTB_DOUBLE);

      for (int i = 0; i < kernel.length; i++) {
        kernels[0].putValueDouble(i, 0, 0, 0, 0, kernel[i]);
        kernels[1].putValueDouble(0, i, 0, 0, 0, kernel[i]);
        kernels[2].putValueDouble(0, 0, i, 0, 0, kernel[i]);
      }

    } else {
      kernels = new MTBImage[2];
      kernels[0] = MTBImage.createMTBImage(5, 1, 1, 1, 1, MTBImageType.MTB_DOUBLE);
      kernels[1] = MTBImage.createMTBImage(1, 5, 1, 1, 1, MTBImageType.MTB_DOUBLE);

      for (int i = 0; i < kernel.length; i++) {
        kernels[0].putValueDouble(i, 0, 0, 0, 0, kernel[i]);
        kernels[1].putValueDouble(0, i, 0, 0, 0, kernel[i]);
      }
    }

    this.setKernels(kernels);
    this.operatorExecStatus = OperatorExecutionStatus.OP_EXEC_INIT;
  }
  /**
   * @param pseudocolor
   * @return
   * @throws ALDOperatorException
   */
  public MTBImage create3DTrajectoryImage(boolean pseudocolor) throws ALDOperatorException {
    MTBImage trajectoryImg;

    if (pseudocolor) {
      trajectoryImg =
          MTBImage.createMTBImage(sizeX, sizeY, sizeT, 1, 1, MTBImage.MTBImageType.MTB_RGB);
    } else {
      trajectoryImg =
          MTBImage.createMTBImage(sizeX, sizeY, sizeT, 1, 1, MTBImage.MTBImageType.MTB_BYTE);
    }

    trajectoryImg.fillBlack();
    trajectoryImg.setTitle("trajectories");

    Random r = new Random();

    for (int i = 0; i < trajectories.size(); i++) {
      Trajectory2D trajectory = trajectories.elementAt(i);

      int color = trajectory.getID();

      if (pseudocolor) {
        color = r.nextInt((int) Math.pow(2, 32));
      }

      Vector<Point2D.Double> points = trajectory.getPoints();
      int startFrame = trajectory.getStartFrame();

      for (int j = 0; j < points.size(); j++) {
        Point2D.Double p = points.elementAt(j);

        trajectoryImg.putValueInt((int) p.x, (int) p.y, startFrame + j, color);
      }
    }

    return trajectoryImg;
  }
  /**
   * Creates a <code>MTBImage</code> with three channels, the same size as the original one and
   * converts each rgb pixel to a <i>hsx</i> pixel, where <i>x</i> stands for Inentsity or
   * Brightness or Value depending on the mode set.
   *
   * <p>
   *
   * @throws ALDOperatorException
   * @throws ALDProcessingDAGException
   */
  @Override
  protected void operate() throws ALDOperatorException, ALDProcessingDAGException {
    final MTBImageRGB input = this.getInputMTBImgRGB();
    // if input is null -> throw an error
    if (input == null) {
      throw new ALDOperatorException(
          ALDOperatorException.OperatorExceptionType.INSTANTIATION_ERROR,
          "The input image is null!");
    }
    if (mode == null) {
      throw new ALDOperatorException(
          ALDOperatorException.OperatorExceptionType.INSTANTIATION_ERROR,
          "The operation mode is null!");
    }
    final int width = input.getSizeX();
    final int height = input.getSizeY();

    // creates a 3-channel image
    MTBImage.MTBImageType imageType;
    if (createFloatImage) {
      imageType = MTBImage.MTBImageType.MTB_FLOAT;
    } else {
      imageType = MTBImage.MTBImageType.MTB_BYTE;
    }
    resultMTBImg =
        MTBImage.createMTBImage(width, height, 1, 1, THREE_COMPONENTS_COLOR_SPACE, imageType);

    // Array of size 3 - containing the r/g/b-values of the actual pixel
    final int[] rgbColorValues = new int[THREE_COMPONENTS_COLOR_SPACE];

    // Array of size 3 - containing the h/s/v-values of the actual pixel
    float[] hsxColorValues = null;

    float hueByte, satByte, xByte;

    // For each Pixel of the input image...
    for (int y = 0; y < height; y++) {
      for (int x = 0; x < width; x++) {

        // rgb-Color : actual Pixel
        int rgbColorPack = inputMTBImageRGB.getValueInt(x, y);
        // deflate the color components
        rgbColorValues[RED_COMPONENT_INDEX] = (rgbColorPack >> 16) & 0xFF;
        rgbColorValues[GREEN_COMPONENT_INDEX] = (rgbColorPack >> 8) & 0xFF;
        rgbColorValues[BLUE_COMPONENT_INDEX] = (rgbColorPack) & 0xFF;

        // conversion from rgb to hsv
        switch (mode) {
          case RGB_HSI_SONKA:
            hsxColorValues = rgbToHSI_Sonka(rgbColorValues);
            break;
          case RGB_HSB_JRE:
            hsxColorValues = rgbToHSB_JRE(rgbColorValues);
            break;
          case RGB_HSV_EASY_RGB:
            hsxColorValues = rgbToHSV_EasyRGB(rgbColorValues);
            break;

          default:
            throw new IllegalArgumentException("The Algorithm : " + mode + " is not supported!");
        }

        // mapping from [0,1] to [0,255]
        hueByte = hsxColorValues[HUE_COMPONENT_INDEX] * 255F;
        satByte = hsxColorValues[SATURATION_COMPONENT_INDEX] * 255F;
        xByte = hsxColorValues[X_COMPONENT_INDEX] * 255F;

        // set each channel
        if (createFloatImage) {
          resultMTBImg.putValueDouble(
              x, y, 0, 0, HUE_COMPONENT_INDEX, hsxColorValues[HUE_COMPONENT_INDEX]);
          resultMTBImg.putValueDouble(
              x, y, 0, 0, SATURATION_COMPONENT_INDEX, hsxColorValues[SATURATION_COMPONENT_INDEX]);
          resultMTBImg.putValueDouble(
              x, y, 0, 0, X_COMPONENT_INDEX, hsxColorValues[X_COMPONENT_INDEX]);
        } else {
          resultMTBImg.putValueInt(
              x, y, 0, 0, HUE_COMPONENT_INDEX, Float.valueOf(hueByte).intValue());
          resultMTBImg.putValueInt(
              x, y, 0, 0, SATURATION_COMPONENT_INDEX, Float.valueOf(satByte).intValue());
          resultMTBImg.putValueInt(x, y, 0, 0, X_COMPONENT_INDEX, Float.valueOf(xByte).intValue());
        }
      }
    }
    // the hue channel is shown by default
    resultMTBImg.setTitle("HSX image");
    // make a copy of each channel
    final MTBImage tempHueImage = resultMTBImg.getSlice(0, 0, HUE_COMPONENT_INDEX);
    final MTBImage tempSatImage = resultMTBImg.getSlice(0, 0, SATURATION_COMPONENT_INDEX);
    final MTBImage tempXImage = resultMTBImg.getSlice(0, 0, X_COMPONENT_INDEX);
    // label it
    tempHueImage.setTitle("Hue image");
    tempSatImage.setTitle("Saturation image");

    final String xChannelName =
        mode.equals(RGBToHSXConverter.Mode.RGB_HSI_SONKA)
            ? "Intensity "
            : mode.equals(RGBToHSXConverter.Mode.RGB_HSB_JRE) ? "Brightness" : "Value ";

    tempXImage.setTitle(xChannelName + "image");
    // set the channel images
    setHueMTBImg(tempHueImage);
    setSatMTBImg(tempSatImage);
    setXMTBImg(tempXImage);
  }
  /**
   * Convolve input image with an 'a trous' kernel (zeros inserted) given the original kernel and
   * scale j
   *
   * @param img input image
   * @param scaleOneKernels original kernel (without inserted zeros)
   * @param j scale parameter
   * @return filtered image
   * @throws ALDProcessingDAGException
   * @throws ALDOperatorException
   */
  protected MTBImage conv(MTBImage img, MTBImage[] scaleOneKernels, int j)
      throws ALDOperatorException, ALDProcessingDAGException {

    MTBImage tImg = img;

    for (int k = 0; k < scaleOneKernels.length; k++) {

      if (scaleOneKernels[k] != null) {

        // compute size of inflated kernel (depends on scale)
        int kSizeX =
            scaleOneKernels[k].getSizeX()
                + (scaleOneKernels[k].getSizeX() - 1) * ((int) Math.pow(2.0, j - 1) - 1);
        int kSizeY =
            scaleOneKernels[k].getSizeY()
                + (scaleOneKernels[k].getSizeY() - 1) * ((int) Math.pow(2.0, j - 1) - 1);
        int kSizeZ =
            scaleOneKernels[k].getSizeZ()
                + (scaleOneKernels[k].getSizeZ() - 1) * ((int) Math.pow(2.0, j - 1) - 1);
        int kSizeT =
            scaleOneKernels[k].getSizeT()
                + (scaleOneKernels[k].getSizeT() - 1) * ((int) Math.pow(2.0, j - 1) - 1);
        int kSizeC =
            scaleOneKernels[k].getSizeC()
                + (scaleOneKernels[k].getSizeC() - 1) * ((int) Math.pow(2.0, j - 1) - 1);

        // create kernel image
        MTBImage kernel =
            MTBImage.createMTBImage(
                kSizeX, kSizeY, kSizeZ, kSizeT, kSizeC, MTBImageType.MTB_DOUBLE);

        // take values from scale one kernel to put into actual scale's kernel at the right position
        // (all other values are zero)
        for (int c = 0; c < kSizeC; c++) {
          if (c % (int) Math.pow(2.0, j - 1) == 0) {

            for (int t = 0; t < kSizeT; t++) {
              if (t % (int) Math.pow(2.0, j - 1) == 0) {

                for (int z = 0; z < kSizeZ; z++) {
                  if (z % (int) Math.pow(2.0, j - 1) == 0) {

                    for (int y = 0; y < kSizeY; y++) {
                      if (y % (int) Math.pow(2.0, j - 1) == 0) {

                        for (int x = 0; x < kSizeX; x++) {
                          if (x % (int) Math.pow(2.0, j - 1) == 0) {
                            kernel.putValueDouble(
                                x,
                                y,
                                z,
                                t,
                                c,
                                scaleOneKernels[k].getValueDouble(
                                    x / (int) Math.pow(2.0, j - 1),
                                    y / (int) Math.pow(2.0, j - 1),
                                    z / (int) Math.pow(2.0, j - 1),
                                    t / (int) Math.pow(2.0, j - 1),
                                    c / (int) Math.pow(2.0, j - 1)));
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }

        // compute anchor element of the kernel
        int[] kAnchor = {kSizeX / 2, kSizeY / 2, kSizeZ / 2, kSizeT / 2, kSizeC / 2};

        // linear filter
        LinearFilter lf =
            new LinearFilter(tImg, kernel, kAnchor, true, BoundaryPadding.PADDING_BORDER);
        for (int i = 0; i < this.m_statusListeners.size(); i++) {
          lf.addStatusListener(this.m_statusListeners.get(i));
        }

        // do linear filtering
        lf.runOp(false);
        tImg = lf.getResultImg();
      }
    }

    return tImg;
  }