PinholeCameraEstimator.java

/*
 * Copyright (C) 2013 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.geometry.estimators;

import com.irurueta.geometry.InhomogeneousPoint2D;
import com.irurueta.geometry.InhomogeneousPoint3D;
import com.irurueta.geometry.PinholeCamera;
import com.irurueta.geometry.Quaternion;

/**
 * This class defines the interface for an estimator for pinhole cameras.
 */
public abstract class PinholeCameraEstimator {

    /**
     * Default estimator type.
     */
    public static final PinholeCameraEstimatorType DEFAULT_ESTIMATOR_TYPE =
            PinholeCameraEstimatorType.DLT_POINT_PINHOLE_CAMERA_ESTIMATOR;

    /**
     * Default value indicating whether skewness value is suggested or not.
     * By default, this is disabled.
     */
    public static final boolean DEFAULT_SUGGEST_SKEWNESS_VALUE_ENABLED = false;

    /**
     * Default value of skewness to be suggested when suggestion is enabled.
     * By default suggested skewness is zero.
     */
    public static final double DEFAULT_SUGGESTED_SKEWNESS_VALUE = 0.0;

    /**
     * Default value indicating whether horizontal focal length value is
     * suggested or not. By default, this is disabled.
     */
    public static final boolean DEFAULT_SUGGEST_HORIZONTAL_FOCAL_LENGTH_ENABLED = false;

    /**
     * Default value indicating whether vertical focal length value is suggested
     * or not. By default, this is disabled.
     */
    public static final boolean DEFAULT_SUGGEST_VERTICAL_FOCAL_LENGTH_ENABLED = false;

    /**
     * Default value indicating whether aspect ratio is suggested or not. By
     * default, this is disabled.
     */
    public static final boolean DEFAULT_SUGGEST_ASPECT_RATIO_ENABLED = false;

    /**
     * Default value of aspect ratio to be suggested when suggestion is enabled.
     * By default, suggested aspect ratio is 1.0, although also -1.0 is a typical
     * value when vertical coordinates increase downwards.
     */
    public static final double DEFAULT_SUGGESTED_ASPECT_RATIO_VALUE = 1.0;

    /**
     * Default value indicating whether principal point is suggested or not. By
     * default, this is disabled.
     */
    public static final boolean DEFAULT_SUGGEST_PRINCIPAL_POINT_ENABLED = false;

    /**
     * Default value indicating whether rotation is suggested or not. By default,
     * this is disabled.
     */
    public static final boolean DEFAULT_SUGGEST_ROTATION_ENABLED = false;

    /**
     * Default value indicating whether center is suggested or not. By default,
     * this is disabled.
     */
    public static final boolean DEFAULT_SUGGEST_CENTER_ENABLED = false;

    /**
     * Default value for minimum suggestion weight. This weight is used to
     * slowly draw original camera parameters into desired suggested values.
     * Suggestion weight slowly increases each time Levenberg-Marquardt is used
     * to find a solution so that the algorithm can converge into desired value.
     * The faster the weights are increased the less likely that suggested
     * values can be converged if they differ too much from the original ones.
     */
    public static final double DEFAULT_MIN_SUGGESTION_WEIGHT = 0.1;

    /**
     * Default value for maximum suggestion weight. This weight is used to
     * slowly draw original camera parameters into desired suggested values.
     * Suggestion weight slowly increases each time Levenberg-Marquardt is used
     * to find a solution so that the algorithm can converge into desired value.
     * The faster the weights are increased the less likely that suggested
     * values can be converged if they differ too much from the original ones.
     */
    public static final double DEFAULT_MAX_SUGGESTION_WEIGHT = 2.0;

    /**
     * Default value for the step to increase suggestion weight. This weight is
     * used to slowly draw original camera parameters into desired suggested
     * values. Suggestion weight slowly increases each time Levenberg-Marquardt
     * is used to find a solution so that the algorithm can converge into
     * desired value. The faster the weights are increased the less likely that
     * suggested values can be converged if they differ too much from the
     * original ones.
     */
    public static final double DEFAULT_SUGGESTION_WEIGHT_STEP = 0.475;

    /**
     * True when an estimator is estimating a camera.
     */
    protected boolean locked;

    /**
     * Listener to be notified of events such as when estimation starts, ends
     * or estimation progress changes.
     */
    protected PinholeCameraEstimatorListener listener;

    /**
     * Indicates whether skewness value is suggested or not. When enabled, the
     * estimator will attempt to enforce suggested value in an iterative manner
     * starting from an initially estimated camera.
     * Even when suggestion is enabled, the iterative algorithm might not reach
     * suggested value if the initial value largely differs from the suggested
     * value.
     */
    protected boolean suggestSkewnessValueEnabled = DEFAULT_SUGGEST_SKEWNESS_VALUE_ENABLED;

    /**
     * Suggested skewness value to be reached when suggestion is enabled.
     * Suggested value should be close to the initially estimated value
     * otherwise the iterative refinement might not converge to provided
     * value.
     */
    protected double suggestedSkewnessValue = DEFAULT_SUGGESTED_SKEWNESS_VALUE;

    /**
     * Indicates whether horizontal focal length is suggested or not. When
     * enabled, the estimator will attempt to enforce suggested value in an
     * iterative manner starting from an initially estimated camera.
     * Even when suggestion is enabled, the iterative algorithm might not reach
     * suggested value if the initial value largely differs from the suggested
     * value.
     */
    protected boolean suggestHorizontalFocalLengthEnabled = DEFAULT_SUGGEST_HORIZONTAL_FOCAL_LENGTH_ENABLED;

    /**
     * Suggested horizontal focal length value to be reached when suggestion is
     * enabled.
     * Suggested value should be close to the initially estimated value
     * otherwise the iterative refinement might not converge to provided value.
     */
    protected double suggestedHorizontalFocalLengthValue;

    /**
     * Indicates whether vertical focal length is suggested or not. When
     * enabled, the estimator will attempt to enforce suggested value in an
     * iterative manner starting from an initially estimated camera.
     * Even when suggestion is enabled, the iterative algorithm might not reach
     * suggested value if the initial value largely differs from the suggested
     * value.
     */
    protected boolean suggestVerticalFocalLengthEnabled = DEFAULT_SUGGEST_VERTICAL_FOCAL_LENGTH_ENABLED;

    /**
     * Suggested vertical focal length value to be reached when suggestion is
     * enabled.
     * Suggested value should be close to the initially estimated value
     * otherwise the iterative refinement might not converge to provided value.
     */
    protected double suggestedVerticalFocalLengthValue;

    /**
     * Indicates whether aspect ratio is suggested or not. When enabled, the
     * estimator will attempt to enforce suggested value in an iterative manner
     * starting from an initially estimated camera.
     * Even when suggestion is enabled, the iterative algorithm might not reach
     * suggested value if the initial value largely differs from the suggested
     * value.
     */
    protected boolean suggestAspectRatioEnabled = DEFAULT_SUGGEST_ASPECT_RATIO_ENABLED;

    /**
     * Suggested aspect ratio value to be reached when suggestion is enabled.
     * Suggested value should be close to the initially estimated value
     * otherwise the iterative refinement might not converge to provided value.
     */
    protected double suggestedAspectRatioValue = DEFAULT_SUGGESTED_ASPECT_RATIO_VALUE;

    /**
     * Indicates whether principal point is suggested or not. When enabled, the
     * estimator will attempt to enforce suggested value in an iterative manner
     * starting from an initially estimated camera.
     * Even when suggestion is enabled, the iterative algorithm might not reach
     * suggested value if the initial value largely differs from the suggested
     * value.
     */
    protected boolean suggestPrincipalPointEnabled = DEFAULT_SUGGEST_PRINCIPAL_POINT_ENABLED;

    /**
     * Suggested principal point value to be reached when suggestion is enabled.
     * Suggested value should be close to the initially estimated value
     * otherwise the iterative refinement might not converge to provided value.
     */
    protected InhomogeneousPoint2D suggestedPrincipalPointValue;

    /**
     * Indicates whether camera rotation is suggested or not. When enabled, the
     * estimator will attempt to enforce suggested value in an iterative manner
     * starting from an initially estimated camera.
     * Even when suggestion is enabled, the iterative algorithm might not reach
     * suggested value if the initial value largely differs from the suggested
     * value.
     */
    protected boolean suggestRotationEnabled = DEFAULT_SUGGEST_ROTATION_ENABLED;

    /**
     * Suggested rotation to be reached when suggestion is enabled.
     * Suggested value should be close to the initially estimated value
     * otherwise the iterative refinement might not converge to provided value.
     */
    protected Quaternion suggestedRotationValue;

    /**
     * Indicates whether camera center is suggested or not. When enabled, the
     * estimator will attempt to enforce suggested value in an iterative manner
     * starting from an initially estimated camera.
     * Even when suggestion is enabled, the iterative algorithm might not reach
     * suggested value if the initial value largely differs from the suggested
     * value.
     */
    protected boolean suggestCenterEnabled;

    /**
     * Suggested center to be reached when suggestion is enabled.
     * Suggested value should be close to the initially estimated value
     * otherwise the iterative refinement might not converge to provided value.
     */
    protected InhomogeneousPoint3D suggestedCenterValue;

    /**
     * Minimum suggestion weight. This weight is used to slowly draw original
     * camera parameters into desired suggested values.
     * Suggestion weight slowly increases each time Levenberg-Marquardt is used
     * to find a solution so that the algorithm can converge into desired value.
     * The faster the weights are increased the less likely that suggested
     * values can be converged if they differ too much from the original ones.
     */
    protected double minSuggestionWeight = DEFAULT_MIN_SUGGESTION_WEIGHT;

    /**
     * Maximum suggestion weight. This weight is used to slowly draw original
     * camera parameters into desired suggested values.
     * Suggestion weight slowly increases each time Levenberg-Marquardt is used
     * to find a solution so that the algorithm can converge into desired value.
     * The faster the weights are increased the less likely that suggested
     * values can be converged if they differ too much from the original ones.
     */
    protected double maxSuggestionWeight = DEFAULT_MAX_SUGGESTION_WEIGHT;

    /**
     * Step to increase suggestion weight. This weight is used to slowly draw
     * original camera parameters into desired suggested values. Suggestion
     * weight slowly increases each time Levenberg-Marquardt is used to find a
     * solution so that the algorithm can converge into desired value. The
     * faster the weights are increased the less likely that suggested values
     * can be converged if they differ too much from the original ones.
     */
    protected double suggestionWeightStep = DEFAULT_SUGGESTION_WEIGHT_STEP;

    /**
     * Constructor.
     */
    protected PinholeCameraEstimator() {
        locked = false;
        listener = null;
    }

    /**
     * Constructor with listener.
     *
     * @param listener listener to be notified of events such as when estimation
     *                 starts, ends or estimation progress changes.
     */
    protected PinholeCameraEstimator(final PinholeCameraEstimatorListener listener) {
        locked = false;
        this.listener = listener;
    }

    /**
     * Returns listener to be notified of events such as when estimation starts,
     * ends or estimation progress changes.
     *
     * @return listener to be notified of events.
     */
    public PinholeCameraEstimatorListener getListener() {
        return listener;
    }

    /**
     * Sets listener to be notified of events such as when estimation starts,
     * ends or estimation progress changes.
     *
     * @param listener listener to be notified of events.
     * @throws LockedException if estimator is locked.
     */
    public void setListener(final PinholeCameraEstimatorListener listener) throws LockedException {
        if (isLocked()) {
            throw new LockedException();
        }
        this.listener = listener;
    }

    /**
     * Indicates whether skewness value is suggested or not. When enabled, the
     * estimator will attempt to enforce suggested value in an iterative manner
     * starting from an initially estimated camera.
     * Even when suggestion is enabled, the iterative algorithm might not reach
     * suggested value if the initial value largely differs from the suggested
     * value.
     *
     * @return true if skewness value is suggested, false otherwise.
     */
    public boolean isSuggestSkewnessValueEnabled() {
        return suggestSkewnessValueEnabled;
    }

    /**
     * Specifies whether skewness value is suggested or not. When enabled, the
     * estimator will attempt to enforce suggested value in an iterative manner
     * starting from an initially estimated camera.
     * Even when suggestion is enabled, the iterative algorithm might not reach
     * suggested value if the initial value largely differs from the suggested
     * value.
     *
     * @param suggestSkewnessValueEnabled true if skewness value is suggested,
     *                                    false otherwise.
     * @throws LockedException if estimator is locked.
     */
    public void setSuggestSkewnessValueEnabled(final boolean suggestSkewnessValueEnabled) throws LockedException {
        if (isLocked()) {
            throw new LockedException();
        }
        this.suggestSkewnessValueEnabled = suggestSkewnessValueEnabled;
    }

    /**
     * Gets suggested skewness value to be reached when suggestion is enabled.
     * Suggested value should be close to the initially estimated value
     * otherwise the iterative refinement might not converge to provided value.
     *
     * @return suggested skewness value.
     */
    public double getSuggestedSkewnessValue() {
        return suggestedSkewnessValue;
    }

    /**
     * Sets suggested skewness value to be reached when suggestion is enabled.
     * Suggested value should be close to the initially estimated value
     * otherwise the iterative refinement might not converge to provided value.
     *
     * @param suggestedSkewnessValue suggested skewness value.
     * @throws LockedException if estimator is locked.
     */
    public void setSuggestedSkewnessValue(final double suggestedSkewnessValue) throws LockedException {
        if (isLocked()) {
            throw new LockedException();
        }
        this.suggestedSkewnessValue = suggestedSkewnessValue;
    }

    /**
     * Indicates whether horizontal focal length is suggested or not. When
     * enabled, the estimator will attempt to enforce suggested value in an
     * iterative manner starting from an initially estimated camera.
     * Even when suggestion is enabled, the iterative algorithm might not reach
     * suggested value if the initial value largely differs from the suggested
     * value.
     *
     * @return true if horizontal focal length is suggested, false otherwise.
     */
    public boolean isSuggestHorizontalFocalLengthEnabled() {
        return suggestHorizontalFocalLengthEnabled;
    }

    /**
     * Specifies whether horizontal focal length is suggested or not. When
     * enabled, the estimator will attempt to enforce suggested value in an
     * iterative manner starting from an initially estimated camera.
     * Even when suggestion is enabled, the iterative algorithm might not reach
     * suggested value if the initial value largely differs from the suggested
     * value.
     *
     * @param suggestHorizontalFocalLengthEnabled true if horizontal focal
     *                                            length is suggested, false otherwise.
     * @throws LockedException if estimator is locked.
     */
    public void setSuggestHorizontalFocalLengthEnabled(final boolean suggestHorizontalFocalLengthEnabled)
            throws LockedException {
        if (isLocked()) {
            throw new LockedException();
        }
        this.suggestHorizontalFocalLengthEnabled =
                suggestHorizontalFocalLengthEnabled;
    }

    /**
     * Gets suggested horizontal focal length value to be reached when
     * suggestion is enabled.
     * Suggested value should be close to the initially estimated value
     * otherwise the iterative refinement might not converge to provided value.
     *
     * @return suggested horizontal focal length value.
     */
    public double getSuggestedHorizontalFocalLengthValue() {
        return suggestedHorizontalFocalLengthValue;
    }

    /**
     * Sets suggested horizontal focal length value to be reached when
     * suggestion is enabled.
     * Suggested value should be close to the initially estimated value
     * otherwise the iterative refinement might not converge to provided value.
     *
     * @param suggestedHorizontalFocalLengthValue suggested horizontal focal
     *                                            length value.
     * @throws LockedException if estimator is locked.
     */
    public void setSuggestedHorizontalFocalLengthValue(final double suggestedHorizontalFocalLengthValue)
            throws LockedException {
        if (isLocked()) {
            throw new LockedException();
        }
        this.suggestedHorizontalFocalLengthValue = suggestedHorizontalFocalLengthValue;
    }

    /**
     * Indicates whether vertical focal length is suggested or not. When
     * enabled, the estimator will attempt to enforce suggested value in an
     * iterative manner starting from an initially estimated camera.
     * Even when suggestion is enabled, the iterative algorithm might not reach
     * suggested value if the initial value largely differs from the suggested
     * value.
     *
     * @return true if vertical focal length is suggested, false otherwise.
     */
    public boolean isSuggestVerticalFocalLengthEnabled() {
        return suggestVerticalFocalLengthEnabled;
    }

    /**
     * Specifies whether vertical focal length is suggested or not. When
     * enabled, the estimator will attempt to enforce suggested value in an
     * iterative manner starting from an initially estimated camera.
     * Even when suggestion is enabled, the iterative algorithm might not reach
     * suggested value if the initial value largely differs from the suggested
     * value.
     *
     * @param suggestVerticalFocalLengthEnabled true if vertical focal length is
     *                                          suggested, false otherwise.
     * @throws LockedException if estimator is locked.
     */
    public void setSuggestVerticalFocalLengthEnabled(final boolean suggestVerticalFocalLengthEnabled)
            throws LockedException {
        if (isLocked()) {
            throw new LockedException();
        }
        this.suggestVerticalFocalLengthEnabled = suggestVerticalFocalLengthEnabled;
    }

    /**
     * Gets suggested vertical focal length value to be reached when suggestion
     * is enabled.
     * Suggested value should be close to the initially estimated value
     * otherwise the iterative refinement might not converge to provided value.
     *
     * @return suggested vertical focal length.
     */
    public double getSuggestedVerticalFocalLengthValue() {
        return suggestedVerticalFocalLengthValue;
    }

    /**
     * Sets suggested vertical focal length value to be reached when suggestion
     * is enabled.
     * Suggested value should be close to the initially estimated value
     * otherwise the iterative refinement might not converge to provided value.
     *
     * @param suggestedVerticalFocalLengthValue suggested vertical focal length.
     * @throws LockedException if estimator is locked.
     */
    public void setSuggestedVerticalFocalLengthValue(final double suggestedVerticalFocalLengthValue)
            throws LockedException {
        if (isLocked()) {
            throw new LockedException();
        }
        this.suggestedVerticalFocalLengthValue = suggestedVerticalFocalLengthValue;
    }

    /**
     * Indicates whether aspect ratio is suggested or not. When enabled, the
     * estimator will attempt to enforce suggested value in an iterative manner
     * starting from an initially estimated camera.
     * Even when suggestion is enabled, the iterative algorithm might not reach
     * suggested value if the initial value largely differs from the suggested
     * value.
     *
     * @return true if aspect ratio is suggested, false otherwise.
     */
    public boolean isSuggestAspectRatioEnabled() {
        return suggestAspectRatioEnabled;
    }

    /**
     * Specifies whether aspect ratio is suggested or not. When enabled, the
     * estimator will attempt to enforce suggested value in an iterative manner
     * starting from an initially estimated camera.
     * Even when suggestion is enabled, the iterative algorithm might not reach
     * suggested value if the initial value largely differs from the suggested
     * value.
     *
     * @param suggestAspectRatioEnabled true if aspect ratio is suggested, false
     *                                  otherwise.
     * @throws LockedException if estimator is locked.
     */
    public void setSuggestAspectRatioEnabled(final boolean suggestAspectRatioEnabled) throws LockedException {
        if (isLocked()) {
            throw new LockedException();
        }
        this.suggestAspectRatioEnabled = suggestAspectRatioEnabled;
    }

    /**
     * Gets suggested aspect ratio value to be reached when suggestion is
     * enabled. Suggested value should be close to the initially estimated value
     * otherwise the iterative refinement might not converge to provided value.
     *
     * @return suggested aspect ratio value.
     */
    public double getSuggestedAspectRatioValue() {
        return suggestedAspectRatioValue;
    }

    /**
     * Sets suggested aspect ratio value to be reached when suggestion is
     * enabled. Suggested value should be close to the initially estimated value
     * otherwise the iterative refinement might not converge to provided value.
     *
     * @param suggestedAspectRatioValue suggested aspect ratio value.
     * @throws LockedException if estimator is locked.
     */
    public void setSuggestedAspectRatioValue(final double suggestedAspectRatioValue) throws LockedException {
        if (isLocked()) {
            throw new LockedException();
        }
        this.suggestedAspectRatioValue = suggestedAspectRatioValue;
    }

    /**
     * Indicates whether principal point is suggested or not. When enabled, the
     * estimator will attempt to enforce suggested value in an iterative manner
     * starting from an initially estimated camera.
     * Even when suggestion is enabled, the iterative algorithm might not reach
     * suggested value if the initial value largely differs from the suggested
     * value.
     *
     * @return true if principal point is suggested, false otherwise.
     */
    public boolean isSuggestPrincipalPointEnabled() {
        return suggestPrincipalPointEnabled;
    }

    /**
     * Specifies whether principal point is suggested or not. When enabled, the
     * estimator will attempt to enforce suggested value in an iterative manner
     * starting from an initially estimated camera.
     * Even when suggestion is enabled, the iterative algorithm might not reach
     * suggested value if the initial value largely differs from the suggested
     * value.
     *
     * @param suggestPrincipalPointEnabled true if principal point is suggested,
     *                                     false otherwise.
     * @throws LockedException if estimator is locked.
     */
    public void setSuggestPrincipalPointEnabled(final boolean suggestPrincipalPointEnabled) throws LockedException {
        if (isLocked()) {
            throw new LockedException();
        }
        this.suggestPrincipalPointEnabled = suggestPrincipalPointEnabled;
        if (suggestPrincipalPointEnabled && suggestedPrincipalPointValue == null) {
            suggestedPrincipalPointValue = new InhomogeneousPoint2D();
        }
    }

    /**
     * Gets suggested principal point value to be reached when suggestion is
     * enabled. Suggested value should be close to the initially estimated value
     * otherwise the iterative refinement might not converge to provided value.
     *
     * @return suggested principal point value to be reached when suggestion is
     * enabled.
     */
    public InhomogeneousPoint2D getSuggestedPrincipalPointValue() {
        return suggestedPrincipalPointValue;
    }

    /**
     * Sets suggested principal point value to be reached when suggestion is
     * enabled. Suggested value should be close to the initially estimated value
     * otherwise the iterative refinement might not converge to provided value.
     *
     * @param suggestedPrincipalPointValue suggested principal point value to be
     *                                     reached when suggestion is enabled.
     * @throws LockedException if estimator is locked.
     */
    public void setSuggestedPrincipalPointValue(final InhomogeneousPoint2D suggestedPrincipalPointValue)
            throws LockedException {
        if (isLocked()) {
            throw new LockedException();
        }
        this.suggestedPrincipalPointValue = suggestedPrincipalPointValue;
    }

    /**
     * Indicates whether camera rotation is suggested or not. When enabled, the
     * estimator will attempt to enforce suggested value in an iterative manner
     * starting from an initially estimated camera.
     * Even when suggestion is enabled, the iterative algorithm might not reach
     * suggested value if the initial value largely differs from the suggested
     * value.
     *
     * @return true if camera rotation is suggested, false otherwise.
     */
    public boolean isSuggestRotationEnabled() {
        return suggestRotationEnabled;
    }

    /**
     * Specifies whether camera rotation is suggested or not. When enabled, the
     * estimator will attempt to enforce suggested value in an iterative manner
     * starting from an initially estimated camera.
     * Even when suggestion is enabled, the iterative algorithm might not reach
     * suggested value if the initial value largely differs from the suggested
     * value.
     *
     * @param suggestRotationEnabled true if camera rotation is suggested, false
     *                               otherwise.
     * @throws LockedException if estimator is locked.
     */
    public void setSuggestRotationEnabled(final boolean suggestRotationEnabled) throws LockedException {
        if (isLocked()) {
            throw new LockedException();
        }
        this.suggestRotationEnabled = suggestRotationEnabled;
        if (suggestRotationEnabled && suggestedRotationValue == null) {
            suggestedRotationValue = new Quaternion();
        }
    }

    /**
     * Gets suggested rotation to be reached when suggestion is enabled.
     * Suggested value should be close to the initially estimated value
     * otherwise the iterative refinement might not converge to provided value.
     *
     * @return suggested rotation to be reached when suggestion is enabled.
     */
    public Quaternion getSuggestedRotationValue() {
        return suggestedRotationValue;
    }

    /**
     * Sets suggested rotation to be reached when suggestion is enabled.
     * Suggested value should be close to the initially estimated value
     * otherwise the iterative refinement might not converge to provided value.
     *
     * @param suggestedRotationValue suggested rotation to be reached when
     *                               suggestion is enabled.
     * @throws LockedException if estimator is locked.
     */
    public void setSuggestedRotationValue(final Quaternion suggestedRotationValue) throws LockedException {
        if (isLocked()) {
            throw new LockedException();
        }
        this.suggestedRotationValue = suggestedRotationValue;
    }

    /**
     * Indicates whether camera center is suggested or not. When enabled, the
     * estimator will attempt to enforce suggested value in an iterative manner
     * starting from an initially estimated camera.
     * Even when suggestion is enabled, the iterative algorithm might not reach
     * suggested value if the initial value largely differs from the suggested
     * value.
     *
     * @return true if camera center is suggested, false otherwise.
     */
    public boolean isSuggestCenterEnabled() {
        return suggestCenterEnabled;
    }

    /**
     * Specifies whether camera center is suggested or not. When enabled, the
     * estimator will attempt to enforce suggested value in an iterative manner
     * starting from an initially estimated camera.
     * Even when suggestion is enabled, the iterative algorithm might not reach
     * suggested value if the initial value largely differs from the suggested
     * value.
     *
     * @param suggestCenterEnabled true if camera is suggested, false otherwise.
     * @throws LockedException if estimator is locked.
     */
    public void setSuggestCenterEnabled(final boolean suggestCenterEnabled) throws LockedException {
        if (isLocked()) {
            throw new LockedException();
        }
        this.suggestCenterEnabled = suggestCenterEnabled;
        if (suggestCenterEnabled && suggestedCenterValue == null) {
            suggestedCenterValue = new InhomogeneousPoint3D();
        }
    }

    /**
     * Gets suggested center to be reached when suggestion is enabled.
     * Suggested value should be close to the initially estimated value
     * otherwise the iterative refinement might not converge to provided value.
     *
     * @return suggested center to be reached when suggestion is enabled.
     */
    public InhomogeneousPoint3D getSuggestedCenterValue() {
        return suggestedCenterValue;
    }

    /**
     * Sets suggested center to be reached when suggestion is enabled.
     * Suggested value should be close to the initially estimated value
     * otherwise the iterative refinement might not converge to provided value.
     *
     * @param suggestedCenterValue suggested center to be reached when
     *                             suggestion is enabled.
     * @throws LockedException if estimator is locked.
     */
    public void setSuggestedCenterValue(final InhomogeneousPoint3D suggestedCenterValue) throws LockedException {
        if (isLocked()) {
            throw new LockedException();
        }
        this.suggestedCenterValue = suggestedCenterValue;
    }

    /**
     * Gets minimum suggestion weight. This weight is used to slowly draw
     * original camera parameters into desired suggested values.
     * Suggestion weight slowly increases each time Levenberg-Marquardt is used
     * to find a solution so that the algorithm can converge into desired value.
     * The faster the weights are increased the less likely that suggested
     * values can be converged if they differ too much from the original ones.
     *
     * @return minimum suggestion weight.
     */
    public double getMinSuggestionWeight() {
        return minSuggestionWeight;
    }

    /**
     * Sets minimum suggestion weight. This weight is used to slowly draw
     * original camera parameters into desired suggested values.
     * Suggestion weight slowly increases each time Levenberg-Marquardt is used
     * to find a solution so that the algorithm can converge into desired value.
     * The faster the weights are increased the less likely that suggested
     * values can be converged if they differ too much from the original ones.
     *
     * @param minSuggestionWeight minimum suggestion weight.
     * @throws LockedException if estimator is locked.
     */
    public void setMinSuggestionWeight(final double minSuggestionWeight) throws LockedException {
        if (isLocked()) {
            throw new LockedException();
        }
        this.minSuggestionWeight = minSuggestionWeight;
    }

    /**
     * Gets maximum suggestion weight. This weight is used to slowly draw
     * original camera parameters into desired suggested values.
     * Suggestion weight slowly increases each time Levenberg-Marquardt is used
     * to find a solution so that the algorithm can converge into desired value.
     * The faster the weights are increased the less likely that suggested
     * values can be converged if they differ too much from the original ones.
     *
     * @return maximum suggestion weight.
     */
    public double getMaxSuggestionWeight() {
        return maxSuggestionWeight;
    }

    /**
     * Sets maximum suggestion weight. This weight is used to slowly draw
     * original camera parameters into desired suggested values.
     * Suggestion weight slowly increases each time Levenberg-Marquardt is used
     * to find a solution so that the algorithm can converge into desired value.
     * The faster the weights are increased the less likely that suggested
     * values can be converged if they differ too much from the original ones.
     *
     * @param maxSuggestionWeight maximum suggestion weight.
     * @throws LockedException if estimator is locked.
     */
    public void setMaxSuggestionWeight(final double maxSuggestionWeight) throws LockedException {
        if (isLocked()) {
            throw new LockedException();
        }
        this.maxSuggestionWeight = maxSuggestionWeight;
    }

    /**
     * Sets minimum and maximum suggestion weights. Suggestion weight is used to
     * slowly draw original camera parameters into desired suggested values.
     * Suggestion weight slowly increases each time Levenberg-Marquardt is used
     * to find a solution so that the algorithm can converge into desired value.
     * The faster the weights are increased the less likely that suggested
     * values can be converged if they differ too much from the original ones.
     *
     * @param minSuggestionWeight minimum suggestion weight.
     * @param maxSuggestionWeight maximum suggestion weight.
     * @throws LockedException          if estimator is locked.
     * @throws IllegalArgumentException if minimum suggestion weight is greater
     *                                  or equal than maximum value.
     */
    public void setMinMaxSuggestionWeight(final double minSuggestionWeight, final double maxSuggestionWeight)
            throws LockedException {
        if (isLocked()) {
            throw new LockedException();
        }
        if (minSuggestionWeight >= maxSuggestionWeight) {
            throw new IllegalArgumentException();
        }

        this.minSuggestionWeight = minSuggestionWeight;
        this.maxSuggestionWeight = maxSuggestionWeight;
    }

    /**
     * Gets step to increase suggestion weight. This weight is used to slowly
     * draw original camera parameters into desired suggested values. Suggestion
     * weight slowly increases each time Levenberg-Marquardt is used to find a
     * solution so that the algorithm can converge into desired value. The
     * faster the weights are increased the less likely that suggested values
     * can be converged if they differ too much from the original ones.
     *
     * @return step to increase suggestion weight.
     */
    public double getSuggestionWeightStep() {
        return suggestionWeightStep;
    }

    /**
     * Sets step to increase suggestion weight. This weight is used to slowly
     * draw original camera parameters into desired suggested values. Suggestion
     * weight slowly increases each time Levenberg-Marquardt is used to find a
     * solution so that the algorithm can converge into desired value. The
     * faster the weights are increased the less likely that suggested values
     * can be converged if they differ too much from the original ones.
     *
     * @param suggestionWeightStep step to increase suggestion weight.
     * @throws LockedException          if estimator is locked.
     * @throws IllegalArgumentException if provided step is negative or zero.
     */
    public void setSuggestionWeightStep(final double suggestionWeightStep) throws LockedException {
        if (isLocked()) {
            throw new LockedException();
        }
        if (suggestionWeightStep <= 0.0) {
            throw new IllegalArgumentException();
        }

        this.suggestionWeightStep = suggestionWeightStep;
    }

    /**
     * Indicates whether this instance is locked.
     *
     * @return true if this estimator is busy estimating a camera, false
     * otherwise.
     */
    public boolean isLocked() {
        return locked;
    }

    /**
     * Indicates if this estimator is ready to start the estimation.
     *
     * @return true if estimator is ready, false otherwise.
     */
    public abstract boolean isReady();

    /**
     * Estimates a pinhole camera.
     *
     * @return estimated pinhole camera.
     * @throws LockedException                 if estimator is locked.
     * @throws NotReadyException               if input has not yet been provided.
     * @throws PinholeCameraEstimatorException if an error occurs during
     *                                         estimation, usually because input data is not valid.
     */
    public abstract PinholeCamera estimate() throws LockedException, NotReadyException, PinholeCameraEstimatorException;

    /**
     * Returns type of pinhole camera estimator.
     *
     * @return type of pinhole camera estimator.
     */
    public abstract PinholeCameraEstimatorType getType();

    /**
     * Creates an instance of a pinhole camera estimator using default type.
     *
     * @return an instance of a pinhole camera estimator.
     */
    public static PinholeCameraEstimator create() {
        return create(DEFAULT_ESTIMATOR_TYPE);
    }

    /**
     * Creates an instance of a pinhole camera estimator using provided type.
     *
     * @param type type of pinhole camera estimator.
     * @return an instance of a pinhole camera estimator.
     */
    public static PinholeCameraEstimator create(final PinholeCameraEstimatorType type) {
        return switch (type) {
            case DLT_LINE_PLANE_PINHOLE_CAMERA_ESTIMATOR -> new DLTLinePlaneCorrespondencePinholeCameraEstimator();
            case WEIGHTED_LINE_PLANE_PINHOLE_CAMERA_ESTIMATOR ->
                    new WeightedLinePlaneCorrespondencePinholeCameraEstimator();
            case WEIGHTED_POINT_PINHOLE_CAMERA_ESTIMATOR -> new WeightedPointCorrespondencePinholeCameraEstimator();
            default -> new DLTPointCorrespondencePinholeCameraEstimator();
        };
    }

    /**
     * Attempts to refine provided camera using requested suggestions.
     * If no suggestions are requested or if refinement fails, provided
     * camera is returned instead.
     *
     * @param pinholeCamera camera to be refined.
     * @return refined camera.
     */
    protected abstract PinholeCamera attemptRefine(final PinholeCamera pinholeCamera);

    /**
     * Indicates whether obtained solution requires refinement to apply provided
     * suggestions.
     *
     * @return true if solution requires refinement to apply provided
     * suggestions, false otherwise.
     */
    protected boolean hasSuggestions() {
        return hasIntrinsicSuggestions() || hasExtrinsicSuggestions();
    }

    /**
     * Indicates whether suggestions for any intrinsic parameter are required
     * or not.
     *
     * @return true if suggestions for any intrinsic parameters are required,
     * false otherwise.
     */
    private boolean hasIntrinsicSuggestions() {
        return suggestSkewnessValueEnabled || suggestHorizontalFocalLengthEnabled || suggestVerticalFocalLengthEnabled
                || suggestAspectRatioEnabled;
    }

    /**
     * Indicates whether suggestions for any extrinsic parameter are required
     * or not.
     *
     * @return true if suggestions for any extrinsic parameter are required,
     * false otherwise.
     */
    private boolean hasExtrinsicSuggestions() {
        return suggestPrincipalPointEnabled || suggestRotationEnabled || suggestCenterEnabled;
    }
}