EssentialMatrix.java

/*
 * Copyright (C) 2015 Alberto Irurueta Carro (alberto@irurueta.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.irurueta.ar.epipolar;

import com.irurueta.algebra.AlgebraException;
import com.irurueta.algebra.Matrix;
import com.irurueta.algebra.SingularValueDecomposer;
import com.irurueta.algebra.Utils;
import com.irurueta.geometry.*;

import java.io.Serializable;

/**
 * The essential matrix defines the relation between two views in a similar way
 * that the fundamental matrix does, but taking into account the intrinsic
 * parameters of the cameras associated to both views. That ways the relation
 * between their extrinsic parameters (rotation and translation) can be precisely
 * obtained.
 */
public class EssentialMatrix extends FundamentalMatrix implements Serializable {

    /**
     * Default threshold to determine that the two non-zero singular values are
     * equal.
     */
    public static final double DEFAULT_SINGULAR_VALUES_THRESHOLD = 1e-8;

    private Rotation3D rotation1;
    private Rotation3D rotation2;

    private Point2D translation1;
    private Point2D translation2;

    private boolean possibleRotationsAndTranslationsAvailable;

    /**
     * Constructor.
     */
    public EssentialMatrix() {
        super();
    }

    /**
     * Constructor.
     *
     * @param internalMatrix          matrix to be set internally.
     * @param singularValuesThreshold threshold to determine that both singular
     *                                values are equal.
     * @throws InvalidEssentialMatrixException if provided matrix is not 3x3,
     *                                         does not have rank 2 or its two non-zero singular values
     *                                         are not equal up to provided threshold.
     * @throws IllegalArgumentException        if provided threshold is negative.
     */
    public EssentialMatrix(final Matrix internalMatrix, final double singularValuesThreshold)
            throws InvalidEssentialMatrixException {
        super();
        setInternalMatrix(internalMatrix, singularValuesThreshold);
    }

    /**
     * Constructor.
     *
     * @param internalMatrix matrix to be set internally.
     * @throws InvalidEssentialMatrixException if provided matrix is not 3x3,
     *                                         does not have rank 2 or its two non-zero singular values
     *                                         are not equal.
     */
    public EssentialMatrix(final Matrix internalMatrix) throws InvalidEssentialMatrixException {
        this(internalMatrix, DEFAULT_SINGULAR_VALUES_THRESHOLD);
    }

    /**
     * Constructor from a pair of cameras.
     *
     * @param leftCamera              camera corresponding to left view.
     * @param rightCamera             camera corresponding to right view.
     * @param singularValuesThreshold threshold to determine that both singular
     *                                values of generated essential matrix are equal.
     * @throws InvalidPairOfCamerasException if provided cameras do not span a
     *                                       valid epipolar geometry (i.e. they are set in a degenerate
     *                                       configuration).
     * @throws IllegalArgumentException      if provided threshold is negative.
     */
    public EssentialMatrix(
            final PinholeCamera leftCamera, final PinholeCamera rightCamera, final double singularValuesThreshold)
            throws InvalidPairOfCamerasException {
        super();
        setFromPairOfCameras(leftCamera, rightCamera, singularValuesThreshold);
    }

    /**
     * Constructor from a pair of cameras.
     *
     * @param leftCamera  camera corresponding to left view.
     * @param rightCamera camera corresponding to right view.
     * @throws InvalidPairOfCamerasException if provided cameras do not span a
     *                                       valid epipolar geometry (i.e. they are set in a degenerate
     *                                       configuration).
     */
    public EssentialMatrix(
            final PinholeCamera leftCamera, final PinholeCamera rightCamera) throws InvalidPairOfCamerasException {
        this(leftCamera, rightCamera, DEFAULT_SINGULAR_VALUES_THRESHOLD);
    }

    /**
     * Constructor from rotation and translation of the image of world origin
     * relative to left view camera, which is assumed to be located at origin
     * of coordinates with no rotation.
     *
     * @param rotation                rotation of right camera relative to left camera.
     * @param translation             translation of the image of world origin on right
     *                                camera relative to left camera.
     * @param singularValuesThreshold threshold to determine that both singular
     *                                values of generated essential matrix are equal.
     * @throws InvalidRotationAndTranslationException if provided rotation and
     *                                                translation yield a degenerate epipolar geometry.
     * @throws IllegalArgumentException               if provided threshold is negative.
     */
    public EssentialMatrix(
            final Rotation3D rotation, final Point2D translation, final double singularValuesThreshold)
            throws InvalidRotationAndTranslationException {
        super();
        setFromRotationAndTranslation(rotation, translation, singularValuesThreshold);
    }

    /**
     * Constructor from rotation and translation of the image of world origin
     * relative to left view camera, which is assumed to be located at origin
     * of coordinates with no rotation.
     *
     * @param rotation    rotation of right camera relative to left camera.
     * @param translation translation of the image of world origin on right
     *                    camera relative to left camera.
     * @throws InvalidRotationAndTranslationException if provided rotation and
     *                                                translation yield a degenerate epipolar geometry.
     */
    public EssentialMatrix(
            final Rotation3D rotation, final Point2D translation) throws InvalidRotationAndTranslationException {
        this(rotation, translation, DEFAULT_SINGULAR_VALUES_THRESHOLD);
    }

    /**
     * Constructor from rotation and translation of the camera center relative
     * to left view camera, which is assumed to be located at origin of
     * coordinates with no rotation.
     *
     * @param rotation                rotation of right camera relative to left camera.
     * @param cameraCenter            camera center of right camera relative to left camera.
     * @param singularValuesThreshold threshold to determine that both singular
     *                                values of generated essential matrix are equal.
     * @throws InvalidRotationAndTranslationException if provided rotation and
     *                                                translation yield a degenerate epipolar geometry.
     * @throws IllegalArgumentException               if provided threshold is negative.
     */
    public EssentialMatrix(
            final Rotation3D rotation, final Point3D cameraCenter, final double singularValuesThreshold)
            throws InvalidRotationAndTranslationException {
        setFromRotationAndCameraCenter(rotation, cameraCenter, singularValuesThreshold);
    }

    /**
     * Constructor from rotation and translation of the camera center relative
     * to left view camera, which is assumed to be located at origin of
     * coordinates with no rotation.
     *
     * @param rotation     rotation of right camera relative to left camera.
     * @param cameraCenter camera center of right camera relative to left camera.
     * @throws InvalidRotationAndTranslationException if provided rotation and
     *                                                translation yield a degenerate epipolar geometry.
     */
    public EssentialMatrix(
            final Rotation3D rotation, final Point3D cameraCenter) throws InvalidRotationAndTranslationException {
        this(rotation, cameraCenter, DEFAULT_SINGULAR_VALUES_THRESHOLD);
    }

    /**
     * Constructor from fundamental matrix and intrinsic camera parameters.
     *
     * @param fundamentalMatrix        a fundamental matrix.
     * @param leftIntrinsicParameters  intrinsic camera parameters of left view.
     * @param rightIntrinsicParameters intrinsic camera parameters of right view.
     * @throws InvalidPairOfIntrinsicParametersException if provided intrinsic
     *                                                   parameters generate an invalid essential matrix.
     */
    public EssentialMatrix(
            final FundamentalMatrix fundamentalMatrix,
            final PinholeCameraIntrinsicParameters leftIntrinsicParameters,
            final PinholeCameraIntrinsicParameters rightIntrinsicParameters)
            throws InvalidPairOfIntrinsicParametersException {
        setFromFundamentalMatrixAndIntrinsics(fundamentalMatrix, leftIntrinsicParameters, rightIntrinsicParameters);
    }

    /**
     * Sets internal matrix associated to this instance.
     * This method makes a copy of provided matrix.
     *
     * @param internalMatrix matrix to be assigned to this instance.
     * @throws InvalidEssentialMatrixException if provided matrix is not 3x3,
     *                                         does not have rank 2 or its two non-zero singular values
     *                                         are not equal.
     */
    @Override
    public final void setInternalMatrix(final Matrix internalMatrix) throws InvalidEssentialMatrixException {
        setInternalMatrix(internalMatrix, DEFAULT_SINGULAR_VALUES_THRESHOLD);
    }

    /**
     * Sets internal matrix associated to this instance.
     * This method makes a copy of provided matrix.
     *
     * @param internalMatrix          matrix to be assigned to this instance.
     * @param singularValuesThreshold threshold to determine that both
     *                                singular values are equal.
     * @throws IllegalArgumentException        if provided threshold is negative.
     * @throws InvalidEssentialMatrixException if provided matrix is not 3x3,
     *                                         does not have rank 2 or its two non-zero singular values
     *                                         are not equal up to provided threshold.
     */
    public final void setInternalMatrix(
            final Matrix internalMatrix, final double singularValuesThreshold) throws InvalidEssentialMatrixException {
        if (!isValidInternalMatrix(internalMatrix, singularValuesThreshold)) {
            throw new InvalidEssentialMatrixException();
        }

        // because provided matrix is valid, we proceed to setting it
        this.internalMatrix = new Matrix(internalMatrix);
        normalized = false;
        leftEpipole = rightEpipole = null;
    }

    /**
     * Returns a boolean indicating whether provided matrix is a valid essential
     * matrix (i.e. has size 3x3, rank 2 and two non-zero and equal singular
     * values).
     *
     * @param internalMatrix matrix to be checked.
     * @return true if provided matrix is a valid essential matrix, false
     * otherwise.
     */
    public static boolean isValidInternalMatrix(final Matrix internalMatrix) {
        return isValidInternalMatrix(internalMatrix, DEFAULT_SINGULAR_VALUES_THRESHOLD);
    }

    /**
     * Returns a boolean indicating whether provided matrix is a valid
     * essential matrix (i.e. has size 3x3, rank 2 and his two non-zero singular
     * values are equal up to provided threshold).
     *
     * @param internalMatrix          matrix to be checked.
     * @param singularValuesThreshold threshold to determine that both singular
     *                                values are equal.
     * @return true if provided matrix is a valid essential matrix, false
     * otherwise.
     * @throws IllegalArgumentException if provided threshold is negative.
     */
    public static boolean isValidInternalMatrix(final Matrix internalMatrix, final double singularValuesThreshold) {
        if (singularValuesThreshold < 0) {
            throw new IllegalArgumentException();
        }

        if (internalMatrix.getColumns() != FUNDAMENTAL_MATRIX_COLS
                || internalMatrix.getRows() != FUNDAMENTAL_MATRIX_ROWS) {
            return false;
        }

        try {
            final var decomposer = new SingularValueDecomposer(internalMatrix);

            decomposer.decompose();

            final var rankEssential = decomposer.getRank();

            if (rankEssential != FUNDAMENTAL_MATRIX_RANK) {
                return false;
            }

            final var singularValues = decomposer.getSingularValues();

            return (Math.abs(singularValues[0] - singularValues[1]) <= singularValuesThreshold);
        } catch (final AlgebraException e) {
            return false;
        }
    }

    /**
     * Sets essential matrix from provided a pair of cameras.
     *
     * @param leftCamera  camera corresponding to left view.
     * @param rightCamera camera corresponding to right view.
     * @throws InvalidPairOfCamerasException if provided cameras do not span a
     *                                       valid epipolar geometry (i.e. they are set in a degenerate
     *                                       configuration).
     */
    @Override
    public void setFromPairOfCameras(final PinholeCamera leftCamera, final PinholeCamera rightCamera)
            throws InvalidPairOfCamerasException {
        setFromPairOfCameras(leftCamera, rightCamera, DEFAULT_SINGULAR_VALUES_THRESHOLD);
    }

    /**
     * Sets essential matrix from provided a pair of cameras.
     *
     * @param leftCamera              camera corresponding to left view.
     * @param rightCamera             camera corresponding to right view.
     * @param singularValuesThreshold threshold to determine that both singular
     *                                values of generated essential matrix are equal.
     * @throws InvalidPairOfCamerasException if provided cameras do not span a
     *                                       valid epipolar geometry (i.e. they are set in a degenerate
     *                                       configuration).
     * @throws IllegalArgumentException      if provided threshold is negative.
     */
    public final void setFromPairOfCameras(
            final PinholeCamera leftCamera, final PinholeCamera rightCamera, final double singularValuesThreshold)
            throws InvalidPairOfCamerasException {

        if (singularValuesThreshold < 0) {
            throw new IllegalArgumentException();
        }

        try {
            // normalize cameras to increase accuracy of results and fix their signs
            // if needed
            leftCamera.normalize();
            rightCamera.normalize();

            if (!leftCamera.isCameraSignFixed()) {
                leftCamera.fixCameraSign();
            }
            if (!rightCamera.isCameraSignFixed()) {
                rightCamera.fixCameraSign();
            }

            // Obtain intrinsic parameters of cameras to obtain normalized pinhole
            // cameras where intrinsic parameters have been removed P1' = inv(K) * P1
            if (!leftCamera.areIntrinsicParametersAvailable()) {
                leftCamera.decompose(true, false);
            }
            if (!rightCamera.areIntrinsicParametersAvailable()) {
                rightCamera.decompose(true, false);
            }

            final var leftIntrinsics = leftCamera.getIntrinsicParameters();
            final var rightIntrinsics = rightCamera.getIntrinsicParameters();

            final var leftIntrinsicsMatrix = leftIntrinsics.getInternalMatrix();
            final var rightIntrinsicsMatrix = rightIntrinsics.getInternalMatrix();

            // get left and right internal matrices of cameras
            final var leftCameraInternalMatrix = leftCamera.getInternalMatrix();
            final var rightCameraInternalMatrix = rightCamera.getInternalMatrix();

            // normalize internal camera matrices using inverse intrinsic matrices
            final var invLeftIntrinsicsMatrix = Utils.inverse(leftIntrinsicsMatrix);
            final var invRightIntrinsicsMatrix = Utils.inverse(rightIntrinsicsMatrix);

            // normalize cameras
            // P1' = inv(K1) * P1
            invLeftIntrinsicsMatrix.multiply(leftCameraInternalMatrix);

            // P2' = inv(K2) * P2
            invRightIntrinsicsMatrix.multiply(rightCameraInternalMatrix);

            // instantiate normalized left camera to project right camera center
            // and obtain left eipole
            final var normLeftCamera = new PinholeCamera(invLeftIntrinsicsMatrix);

            // instantiate normalized right camera to decompose it and obtain its
            // center
            final var normRightCamera = new PinholeCamera(invRightIntrinsicsMatrix);

            normRightCamera.decompose(false, true);

            final var rightCameraCenter = normRightCamera.getCameraCenter();
            final var normLeftEpipole = normLeftCamera.project(rightCameraCenter);
            // to increase accuracy
            normLeftEpipole.normalize();

            // compute skew matrix of left epipole
            final var skewNormLeftEpipoleMatrix = Utils.skewMatrix(new double[]{
                    normLeftEpipole.getHomX(), normLeftEpipole.getHomY(), normLeftEpipole.getHomW()});

            // compute transposed of internal normalized left pinhole camera
            final var transNormLeftCameraMatrix = invLeftIntrinsicsMatrix.transposeAndReturnNew();

            // compute transposed of internal normalized right pinhole camera
            final var transNormRightCameraMatrix = invRightIntrinsicsMatrix.transposeAndReturnNew();

            // compute pseudo-inverse of transposed normalized right pinhole camera
            final var pseudoTransNormRightCameraMatrix = Utils.pseudoInverse(transNormRightCameraMatrix);

            // obtain essential matrix as: inv(P2norm') * P1norm' * skew(e1)
            transNormLeftCameraMatrix.multiply(skewNormLeftEpipoleMatrix);
            pseudoTransNormRightCameraMatrix.multiply(transNormLeftCameraMatrix);

            setInternalMatrix(pseudoTransNormRightCameraMatrix, singularValuesThreshold);
        } catch (final GeometryException | AlgebraException e) {
            throw new InvalidPairOfCamerasException(e);
        }
    }

    /**
     * Sets essential matrix from provided rotation and translation of the image
     * of world origin relative to left view camera, which is assumed to be
     * located at origin of coordinates with no rotation.
     *
     * @param rotation    rotation of right camera relative to left camera.
     * @param translation translation of the image of world origin on right
     *                    camera relative to left camera.
     * @throws InvalidRotationAndTranslationException if provided rotation and
     *                                                translation yield a degenerate epipolar geometry.
     */
    public void setFromRotationAndTranslation(
            final Rotation3D rotation, final Point2D translation) throws InvalidRotationAndTranslationException {
        setFromRotationAndTranslation(rotation, translation, DEFAULT_SINGULAR_VALUES_THRESHOLD);
    }

    /**
     * Sets essential matrix from provided rotation and translation of the image
     * of world origin relative to left view camera, which is assumed to be
     * located at origin of coordinates with no rotation.
     *
     * @param rotation                rotation of right camera relative to left camera.
     * @param translation             translation of the image of world origin on right
     *                                camera relative to left camera.
     * @param singularValuesThreshold threshold to determine that both singular
     *                                values of generated essential matrix are equal.
     * @throws InvalidRotationAndTranslationException if provided rotation and
     *                                                translation yield a degenerate epipolar geometry.
     * @throws IllegalArgumentException               if provided threshold is negative.
     */
    public final void setFromRotationAndTranslation(
            final Rotation3D rotation, final Point2D translation, final double singularValuesThreshold)
            throws InvalidRotationAndTranslationException {

        if (singularValuesThreshold < 0) {
            throw new IllegalArgumentException();
        }

        try {
            // to increase accuracy
            translation.normalize();
            final var translationArray = new double[]{
                    translation.getHomX(), translation.getHomY(), translation.getHomW()
            };

            final var skewTranslationMatrix = Utils.skewMatrix(translationArray);

            final var rotationMatrix = rotation.asInhomogeneousMatrix();

            // obtain essential matrix as: skew(translation) * rotation
            skewTranslationMatrix.multiply(rotationMatrix);

            setInternalMatrix(skewTranslationMatrix, singularValuesThreshold);
        } catch (final AlgebraException | InvalidEssentialMatrixException e) {
            throw new InvalidRotationAndTranslationException(e);
        }
    }

    /**
     * Sets essential matrix from provided rotation and translation of the
     * camera center relative to left view camera, which is assumed to be
     * located at origin of coordinates with no rotation.
     *
     * @param rotation     rotation of right camera relative to left camera.
     * @param cameraCenter camera center of right camera relative to left camera.
     * @throws InvalidRotationAndTranslationException if provided rotation and
     *                                                camera center yield a degenerate epipolar geometry.
     */
    public void setFromRotationAndCameraCenter(
            final Rotation3D rotation, final Point3D cameraCenter) throws InvalidRotationAndTranslationException {
        setFromRotationAndCameraCenter(rotation, cameraCenter, DEFAULT_SINGULAR_VALUES_THRESHOLD);
    }

    /**
     * Sets essential matrix from provided rotation and translation of the
     * camera center relative to left view camera, which is assumed to be
     * located at origin of coordinates with no rotation.
     *
     * @param rotation                rotation of right camera relative to left camera.
     * @param cameraCenter            camera center of right camera relative to left camera.
     * @param singularValuesThreshold threshold to determine that both singular
     *                                values of generated essential matrix are equal.
     * @throws InvalidRotationAndTranslationException if provided rotation and
     *                                                camera center yield a degenerate epipolar geometry.
     * @throws IllegalArgumentException               if provided threshold is negative.
     */
    public final void setFromRotationAndCameraCenter(
            final Rotation3D rotation, final Point3D cameraCenter, final double singularValuesThreshold)
            throws InvalidRotationAndTranslationException {

        if (singularValuesThreshold < 0) {
            throw new IllegalArgumentException();
        }

        try {
            var rotationMatrix = rotation.asInhomogeneousMatrix();

            // to increase accuracy
            cameraCenter.normalize();
            final var inhomCenterMatrix = new Matrix(Point3D.POINT3D_INHOMOGENEOUS_COORDINATES_LENGTH, 1);
            inhomCenterMatrix.setElementAtIndex(0, cameraCenter.getInhomX());
            inhomCenterMatrix.setElementAtIndex(1, cameraCenter.getInhomY());
            inhomCenterMatrix.setElementAtIndex(2, cameraCenter.getInhomZ());

            // translationMatrix = -rotationMatrix * inhomCenterMatrix
            rotationMatrix.multiplyByScalar(-1.0);
            rotationMatrix.multiply(inhomCenterMatrix);
            final var translationMatrix = rotationMatrix;

            // essentialMatrix = skew(translationMatrix) * rotationMatrix
            final var skewTranslationMatrix = Utils.skewMatrix(translationMatrix);
            rotationMatrix = rotation.asInhomogeneousMatrix();
            skewTranslationMatrix.multiply(rotationMatrix);

            setInternalMatrix(skewTranslationMatrix, singularValuesThreshold);

        } catch (final AlgebraException | InvalidEssentialMatrixException e) {
            throw new InvalidRotationAndTranslationException(e);
        }
    }

    /**
     * Sets essential matrix from provided fundamental matrix and intrinsic
     * camera parameters.
     *
     * @param fundamentalMatrix        a fundamental matrix.
     * @param leftIntrinsicParameters  intrinsic camera parameters of left view.
     * @param rightIntrinsicParameters intrinsic camera parameters of right view.
     * @throws InvalidPairOfIntrinsicParametersException if provided intrinsic
     *                                                   parameters generate an invalid essential matrix.
     */
    public final void setFromFundamentalMatrixAndIntrinsics(
            final FundamentalMatrix fundamentalMatrix, final PinholeCameraIntrinsicParameters leftIntrinsicParameters,
            final PinholeCameraIntrinsicParameters rightIntrinsicParameters)
            throws InvalidPairOfIntrinsicParametersException {

        try {
            final var k1 = leftIntrinsicParameters.getInternalMatrix();
            final var normK1 = Utils.normF(k1);
            k1.multiplyByScalar(1.0 / normK1);

            final var k2 = rightIntrinsicParameters.getInternalMatrix();
            final var normK2 = Utils.normF(k2);
            k2.multiplyByScalar(1.0 / normK2);

            // to increase accuracy
            fundamentalMatrix.normalize();
            final var fundMatrix = fundamentalMatrix.getInternalMatrix();

            k2.transpose();

            // E = K2' * F * K1
            fundMatrix.multiply(k1);
            k2.multiply(fundMatrix);

            final var normEssential = Utils.normF(k2);
            k2.multiplyByScalar(1.0 / normEssential);

            internalMatrix = k2;
            normalized = false;
            leftEpipole = rightEpipole = null;
        } catch (final AlgebraException | GeometryException e) {
            throw new InvalidPairOfIntrinsicParametersException(e);
        }
    }

    /**
     * Converts this essential matrix into a fundamental matrix by applying
     * provided intrinsic parameters on left and right views.
     * The essential matrix only contains information about rotation and
     * translation relating two views, while fundamental matrix also contains
     * information about the intrinsic parameters in both views.
     * NOTE: although essential matrix is a subclass of fundamental matrix, it
     * does not behave like a fundamental matrix.
     *
     * @param leftIntrinsicParameters  intrinsic parameters in left view.
     * @param rightIntrinsicParameters intrinsic parameters in right view.
     * @return a fundamental matrix.
     * @throws EpipolarException if something fails.
     */
    public FundamentalMatrix toFundamentalMatrix(
            final PinholeCameraIntrinsicParameters leftIntrinsicParameters,
            final PinholeCameraIntrinsicParameters rightIntrinsicParameters) throws EpipolarException {
        try {
            normalize();

            final var essentialMatrix = getInternalMatrix();

            final var k1 = leftIntrinsicParameters.getInternalMatrix();
            final var invK1 = Utils.inverse(k1);
            final var normInvK1 = Utils.normF(invK1);
            invK1.multiplyByScalar(1.0 / normInvK1);

            final var k2 = rightIntrinsicParameters.getInternalMatrix();
            final var invK2 = Utils.inverse(k2);
            final var normInvK2 = Utils.normF(invK2);
            invK2.multiplyByScalar(1.0 / normInvK2);
            invK2.transpose();

            essentialMatrix.multiply(invK1);
            invK2.multiply(essentialMatrix);

            return new FundamentalMatrix(invK2);

        } catch (final AlgebraException | GeometryException e) {
            throw new EpipolarException(e);
        }
    }

    /**
     * Computes all possible camera rotations and translations that can generate
     * this essential matrix.
     *
     * @throws InvalidEssentialMatrixException if essential matrix contains
     *                                         numerically unstable values.
     */
    public void computePossibleRotationAndTranslations() throws InvalidEssentialMatrixException {
        try {
            final var decomposer = new SingularValueDecomposer(internalMatrix);

            decomposer.decompose();

            final var u = decomposer.getU();
            final var v = decomposer.getV();

            v.transpose();

            translation1 = new HomogeneousPoint2D(u.getElementAt(0, 2),
                    u.getElementAt(1, 2), u.getElementAt(2, 2));
            translation2 = new HomogeneousPoint2D(-u.getElementAt(0, 2),
                    -u.getElementAt(1, 2), -u.getElementAt(2, 2));

            // W is a skew-symmetric matrix that can be used to obtain two possible
            // rotations
            final var w = new Matrix(FUNDAMENTAL_MATRIX_ROWS, FUNDAMENTAL_MATRIX_COLS);
            w.setElementAt(0, 1, -1.0);
            w.setElementAt(1, 0, 1.0);
            w.setElementAt(2, 2, 1.0);

            final var transW = w.transposeAndReturnNew();

            // R1 = U * W * V'
            w.multiply(v);
            final var rotationMatrix1 = u.multiplyAndReturnNew(w);

            // R2 = U * W' * V'
            transW.multiply(v);
            final var rotationMatrix2 = u.multiplyAndReturnNew(transW);

            rotation1 = new MatrixRotation3D(rotationMatrix1);
            rotation2 = new MatrixRotation3D(rotationMatrix2);

            possibleRotationsAndTranslationsAvailable = true;
        } catch (final AlgebraException | InvalidRotationMatrixException e) {
            throw new InvalidEssentialMatrixException(e);
        }
    }

    /**
     * Indicates whether possible camera rotations and translations that can
     * generate this essential matrix have already been computed or not.
     *
     * @return true if possible camera rotations and translations have been
     * computed, false otherwise.
     */
    public boolean arePossibleRotationsAndTranslationsAvailable() {
        return possibleRotationsAndTranslationsAvailable;
    }

    /**
     * Gets first possible rotation that can generate this essential matrix.
     *
     * @return first possible rotation.
     * @throws NotAvailableException if possible rotation has not yet been
     *                               computed.
     */
    public Rotation3D getFirstPossibleRotation() throws NotAvailableException {
        if (!arePossibleRotationsAndTranslationsAvailable()) {
            throw new NotAvailableException();
        }

        return rotation1;
    }

    /**
     * Gets second possible rotation that can generate this essential matrix.
     *
     * @return second possible rotation.
     * @throws NotAvailableException if possible rotation has not yet been
     *                               computed.
     */
    public Rotation3D getSecondPossibleRotation() throws NotAvailableException {
        if (!arePossibleRotationsAndTranslationsAvailable()) {
            throw new NotAvailableException();
        }

        return rotation2;
    }

    /**
     * Gets first possible translation that can generate this essential matrix.
     *
     * @return first possible translation.
     * @throws NotAvailableException if possible translation has not yet been
     *                               computed.
     */
    public Point2D getFirstPossibleTranslation() throws NotAvailableException {
        if (!arePossibleRotationsAndTranslationsAvailable()) {
            throw new NotAvailableException();
        }

        return translation1;
    }

    /**
     * Gets second possible translation that can generate this essential matrix.
     *
     * @return second possible translation.
     * @throws NotAvailableException if possible translation has not yet been
     *                               computed.
     */
    public Point2D getSecondPossibleTranslation() throws NotAvailableException {
        if (!arePossibleRotationsAndTranslationsAvailable()) {
            throw new NotAvailableException();
        }

        return translation2;
    }
}