public static Matrix averageColumnVector(Matrix... matrices) {
    int columns = matrices[0].getColumnDimension();
    if (matrices[0].getRowDimension() != 1) throw new IllegalArgumentException();
    // double[] avg = new double[columns];
    Matrix avg = new Matrix(1, columns);

    for (Matrix m : matrices) {
      avg.plusEquals(m);
    }
    avg.timesEquals(1.0 / matrices.length);
    return avg;
  }
  /**
   * Update the covariance Matrix of the weight posterior distribution (SIGMA) along with its
   * cholesky factor:
   *
   * <p>SIGMA = (A + beta * PHI^t * PHI)^{-1}
   *
   * <p>SIGMA_chol with SIGMA_chol * SIGMA_chol^t = SIGMA
   */
  protected void updateSIGMA() {

    Matrix SIGMA_inv = PHI_t.times(PHI_t.transpose());
    SIGMA_inv.timesEquals(beta);
    SIGMA_inv.plusEquals(A);

    /** Update the factor ... */
    SECholeskyDecomposition CD = new SECholeskyDecomposition(SIGMA_inv.getArray());
    Matrix U = CD.getPTR().times(CD.getL());
    SIGMA_chol = U.inverse();

    /** Update SIGMA */
    SIGMA = (SIGMA_chol.transpose()).times(SIGMA_chol);
  }
 /**
  * Update the mean of the weight posterior distribution (mu):
  *
  * <p>mu = beta * SIGMA * PHI^t * t
  */
 protected void updateMu() {
   mu = SIGMA.times(PHI_t.times(new Matrix(t)));
   mu.timesEquals(beta);
 }
  @Override
  public final void process(final Spot spot) {

    if (img.numDimensions() == 3) {

      // 3D case
      final SpotNeighborhood<T> neighborhood = new SpotNeighborhood<T>(spot, img);
      final SpotNeighborhoodCursor<T> cursor = neighborhood.cursor();

      double x, y, z;
      double x2, y2, z2;
      double mass, totalmass = 0;
      double Ixx = 0, Iyy = 0, Izz = 0, Ixy = 0, Ixz = 0, Iyz = 0;
      final double[] position = new double[img.numDimensions()];

      while (cursor.hasNext()) {
        cursor.fwd();
        mass = cursor.get().getRealDouble();
        cursor.getRelativePosition(position);
        x = position[0];
        y = position[1];
        z = position[2];
        totalmass += mass;
        x2 = x * x;
        y2 = y * y;
        z2 = z * z;
        Ixx += mass * (y2 + z2);
        Iyy += mass * (x2 + z2);
        Izz += mass * (x2 + y2);
        Ixy -= mass * x * y;
        Ixz -= mass * x * z;
        Iyz -= mass * y * z;
      }

      final Matrix mat =
          new Matrix(new double[][] {{Ixx, Ixy, Ixz}, {Ixy, Iyy, Iyz}, {Ixz, Iyz, Izz}});
      mat.timesEquals(1 / totalmass);
      final EigenvalueDecomposition eigdec = mat.eig();
      final double[] eigenvalues = eigdec.getRealEigenvalues();
      final Matrix eigenvectors = eigdec.getV();

      final double I1 = eigenvalues[0];
      final double I2 = eigenvalues[1];
      final double I3 = eigenvalues[2];
      final double a = Math.sqrt(2.5 * (I2 + I3 - I1));
      final double b = Math.sqrt(2.5 * (I3 + I1 - I2));
      final double c = Math.sqrt(2.5 * (I1 + I2 - I3));
      final double[] semiaxes = new double[] {a, b, c};

      // Sort semi-axes by ascendent order and get the sorting index
      final double[] semiaxes_ordered = semiaxes.clone();
      Arrays.sort(semiaxes_ordered);
      final int[] order = new int[3];
      for (int i = 0; i < semiaxes_ordered.length; i++)
        for (int j = 0; j < semiaxes.length; j++)
          if (semiaxes_ordered[i] == semiaxes[j]) order[i] = j;

      // Get the sorted eigenvalues
      final double[][] uvectors = new double[3][3];
      for (int i = 0; i < eigenvalues.length; i++) {
        uvectors[i][0] = eigenvectors.get(0, order[i]);
        uvectors[i][1] = eigenvectors.get(1, order[i]);
        uvectors[i][2] = eigenvectors.get(2, order[i]);
      }

      // Store in the Spot object
      double theta, phi;
      for (int i = 0; i < uvectors.length; i++) {
        theta =
            Math.acos(
                uvectors[i][2]
                    / Math.sqrt(
                        uvectors[i][0] * uvectors[i][0]
                            + uvectors[i][1] * uvectors[i][1]
                            + uvectors[i][2] * uvectors[i][2]));
        phi = Math.atan2(uvectors[i][1], uvectors[i][0]);
        if (phi < -Math.PI / 2) phi += Math.PI; // For an ellipsoid we care only for the
        // angles in [-pi/2 , pi/2]
        if (phi > Math.PI / 2) phi -= Math.PI;

        // Store in descending order
        spot.putFeature(featurelist_sa[i], semiaxes_ordered[i]);
        spot.putFeature(featurelist_phi[i], phi);
        spot.putFeature(featurelist_theta[i], theta);
      }

      // Store the Spot morphology (needs to be outside the above loop)
      spot.putFeature(MORPHOLOGY, estimateMorphology(semiaxes_ordered));

    } else if (img.numDimensions() == 2) {

      // 2D case
      final SpotNeighborhood<T> neighborhood = new SpotNeighborhood<T>(spot, img);
      final SpotNeighborhoodCursor<T> cursor = neighborhood.cursor();
      double x, y;
      double x2, y2;
      double mass, totalmass = 0;
      double Ixx = 0, Iyy = 0, Ixy = 0;
      final double[] position = new double[img.numDimensions()];

      while (cursor.hasNext()) {
        cursor.fwd();
        mass = cursor.get().getRealDouble();
        cursor.getRelativePosition(position);
        x = position[0];
        y = position[1];
        totalmass += mass;
        x2 = x * x;
        y2 = y * y;
        Ixx += mass * (y2);
        Iyy += mass * (x2);
        Ixy -= mass * x * y;
      }

      final Matrix mat = new Matrix(new double[][] {{Ixx, Ixy}, {Ixy, Iyy}});
      mat.timesEquals(1 / totalmass);
      final EigenvalueDecomposition eigdec = mat.eig();
      final double[] eigenvalues = eigdec.getRealEigenvalues();
      final Matrix eigenvectors = eigdec.getV();

      final double I1 = eigenvalues[0];
      final double I2 = eigenvalues[1];
      final double a = Math.sqrt(4 * I1);
      final double b = Math.sqrt(4 * I2);
      final double[] semiaxes = new double[] {a, b};

      // Sort semi-axes by ascendent order and get the sorting index
      final double[] semiaxes_ordered = semiaxes.clone();
      Arrays.sort(semiaxes_ordered);
      final int[] order = new int[2];
      for (int i = 0; i < semiaxes_ordered.length; i++)
        for (int j = 0; j < semiaxes.length; j++)
          if (semiaxes_ordered[i] == semiaxes[j]) order[i] = j;

      // Get the sorted eigenvalues
      final double[][] uvectors = new double[2][2];
      for (int i = 0; i < eigenvalues.length; i++) {
        uvectors[i][0] = eigenvectors.get(0, order[i]);
        uvectors[i][1] = eigenvectors.get(1, order[i]);
      }

      // Store in the Spot object
      double theta, phi;
      for (int i = 0; i < uvectors.length; i++) {
        theta = 0;
        phi = Math.atan2(uvectors[i][1], uvectors[i][0]);
        if (phi < -Math.PI / 2) phi += Math.PI; // For an ellipsoid we care only for the
        // angles in [-pi/2 , pi/2]
        if (phi > Math.PI / 2) phi -= Math.PI;

        // Store in descending order
        spot.putFeature(featurelist_sa[i], semiaxes_ordered[i]);
        spot.putFeature(featurelist_phi[i], phi);
        spot.putFeature(featurelist_theta[i], theta);
      }
      spot.putFeature(featurelist_sa[2], Double.valueOf(0));
      spot.putFeature(featurelist_phi[2], Double.valueOf(0));
      spot.putFeature(featurelist_theta[2], Double.valueOf(0));

      // Store the Spot morphology (needs to be outside the above loop)
      spot.putFeature(MORPHOLOGY, estimateMorphology(semiaxes_ordered));
    }
  }
  /**
   * Get mean covariance matrix C2 for given pixel.
   *
   * @param x X coordinate of the given pixel.
   * @param y Y coordinate of the given pixel.
   * @param halfWindowSizeX The sliding window width /2
   * @param halfWindowSizeY The sliding window height /2
   * @param sourceImageWidth Source image width.
   * @param sourceImageHeight Source image height.
   * @param sourceProductType The source product type.
   * @param sourceTiles The source tiles for all bands.
   * @param dataBuffers Source tile data buffers.
   * @param Cr The real part of the mean covariance matrix.
   * @param Ci The imaginary part of the mean covariance matrix.
   */
  public static void getMeanCovarianceMatrixC2(
      final int x,
      final int y,
      final int halfWindowSizeX,
      final int halfWindowSizeY,
      final int sourceImageWidth,
      final int sourceImageHeight,
      final PolBandUtils.MATRIX sourceProductType,
      final Tile[] sourceTiles,
      final ProductData[] dataBuffers,
      final double[][] Cr,
      final double[][] Ci) {

    final double[][] tempCr = new double[2][2];
    final double[][] tempCi = new double[2][2];

    final int xSt = Math.max(x - halfWindowSizeX, 0);
    final int xEd = Math.min(x + halfWindowSizeX, sourceImageWidth - 1);
    final int ySt = Math.max(y - halfWindowSizeY, 0);
    final int yEd = Math.min(y + halfWindowSizeY, sourceImageHeight - 1);
    final int num = (yEd - ySt + 1) * (xEd - xSt + 1);

    final TileIndex srcIndex = new TileIndex(sourceTiles[0]);

    final Matrix CrMat = new Matrix(2, 2);
    final Matrix CiMat = new Matrix(2, 2);

    if (sourceProductType == PolBandUtils.MATRIX.C2) {

      for (int yy = ySt; yy <= yEd; ++yy) {
        srcIndex.calculateStride(yy);
        for (int xx = xSt; xx <= xEd; ++xx) {
          getCovarianceMatrixC2(srcIndex.getIndex(xx), dataBuffers, tempCr, tempCi);
          CrMat.plusEquals(new Matrix(tempCr));
          CiMat.plusEquals(new Matrix(tempCi));
        }
      }

    } else if (sourceProductType == PolBandUtils.MATRIX.LCHCP
        || sourceProductType == PolBandUtils.MATRIX.RCHCP
        || sourceProductType == PolBandUtils.MATRIX.DUAL_HH_HV
        || sourceProductType == PolBandUtils.MATRIX.DUAL_VH_VV
        || sourceProductType == PolBandUtils.MATRIX.DUAL_HH_VV) {

      final double[] tempKr = new double[2];
      final double[] tempKi = new double[2];

      for (int yy = ySt; yy <= yEd; ++yy) {
        srcIndex.calculateStride(yy);
        for (int xx = xSt; xx <= xEd; ++xx) {
          getScatterVector(srcIndex.getIndex(xx), dataBuffers, tempKr, tempKi);
          computeCovarianceMatrixC2(tempKr, tempKi, tempCr, tempCi);
          CrMat.plusEquals(new Matrix(tempCr));
          CiMat.plusEquals(new Matrix(tempCi));
        }
      }

    } else {
      throw new OperatorException("getMeanCovarianceMatrixC2 not implemented for raw dual pol");
    }

    CrMat.timesEquals(1.0 / num);
    CiMat.timesEquals(1.0 / num);
    for (int i = 0; i < 2; i++) {
      Cr[i][0] = CrMat.get(i, 0);
      Ci[i][0] = CiMat.get(i, 0);

      Cr[i][1] = CrMat.get(i, 1);
      Ci[i][1] = CiMat.get(i, 1);
    }
  }