View Javadoc
1   /*
2    * Copyright (C) 2016 Alberto Irurueta Carro (alberto@irurueta.com)
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.irurueta.ar.calibration.estimators;
17  
18  import com.irurueta.algebra.AlgebraException;
19  import com.irurueta.algebra.Matrix;
20  import com.irurueta.algebra.SingularValueDecomposer;
21  import com.irurueta.algebra.Utils;
22  import com.irurueta.ar.calibration.DualAbsoluteQuadric;
23  import com.irurueta.geometry.PinholeCamera;
24  import com.irurueta.geometry.estimators.LockedException;
25  import com.irurueta.geometry.estimators.NotReadyException;
26  import com.irurueta.numerical.NumericalException;
27  import com.irurueta.numerical.polynomials.Polynomial;
28  
29  import java.util.List;
30  
31  /**
32   * This class defines the interface for an estimator of the Dual Absolute
33   * Quadric (DAQ) assuming equal vertical and horizontal focal length, no
34   * skewness and principal point at the center of the image.
35   * The projection of the Dual Absolute Quadric through a given
36   * pinhole camera P, becomes the Dual Image of Absolute Conic (DIAC).
37   * Since the DIAC is directly related to the intrinsic parameters K of the
38   * camera P used for projection, then by using a pair of arbitrary cameras,
39   * it is possible to solve their corresponding focal length, assuming that:
40   * - cameras are arbitrary (usually the initial camera is the identity and must
41   * be discarded) as it creates a numerical degeneracy.
42   * - all provided cameras have the same intrinsic parameters.
43   * - it is assumed that skewness is zero, the principal point is at the center
44   * of the image plane (zero), and both horizontal and vertical focal planes are
45   * equal.
46   */
47  @SuppressWarnings("DuplicatedCode")
48  public abstract class DualAbsoluteQuadricEstimator {
49  
50      /**
51       * Constant defining whether zero skewness is assumed.
52       */
53      public static final boolean DEFAULT_ZERO_SKEWNESS = true;
54  
55      /**
56       * Constant defining whether principal point is assumed to be at origin of
57       * coordinates.
58       */
59      public static final boolean DEFAULT_PRINCIPAL_POINT_AT_ORIGIN = true;
60  
61      /**
62       * Constant defining whether aspect ratio of focal distance (i.e. vertical
63       * focal distance divided by horizontal focal distance) is known or not.
64       * Notice that focal distance aspect ratio is not related to image size
65       * aspect ratio. Typically, LCD sensor cells are square and hence aspect
66       * ratio of focal distances is known and equal to 1.
67       */
68      public static final boolean DEFAULT_FOCAL_DISTANCE_ASPECT_RATIO_KNOWN = true;
69  
70      /**
71       * Constant defining default aspect ratio of focal distances. This constant
72       * takes into account that typically LCD sensor cells are square and hence
73       * aspect ratio of focal distances is known and equal to 1.
74       */
75      public static final double DEFAULT_FOCAL_DISTANCE_ASPECT_RATIO = 1.0;
76  
77      /**
78       * Constant defining whether rank 3 must be enforced in estimated Dual
79       * Absolute Quadric (DAQ), thus making it singular.
80       * DAQ is always singular in any arbitrary projective space, however, due to
81       * noise in samples, estimation might not be singular.
82       * By enforcing singularity an additional constraint is imposed, which might
83       * result in less cameras needed for DAQ estimation.
84       */
85      public static final boolean DEFAULT_ENFORCE_SINGULARITY = true;
86  
87      /**
88       * Indicates whether enforced singularity will be validated by checking that
89       * determinant of estimated Dual Absolute Quadric (DAQ) is below a certain
90       * threshold.
91       */
92      public static final boolean DEFAULT_VALIDATE_ENFORCED_SINGULARITY = true;
93  
94      /**
95       * Default threshold to determine whether estimated Dual Absolute Quadric
96       * (DAQ) has rank 3 or not.
97       */
98      public static final double DEFAULT_DETERMINANT_THRESHOLD = 1e-6;
99  
100     /**
101      * Minimum absolute value allowed for aspect ratio of focal distances.
102      */
103     public static final double MIN_ABS_FOCAL_DISTANCE_ASPECT_RATIO = 1e-6;
104 
105     /**
106      * Minimum number of required equations to solve DAQ.
107      */
108     protected static final int MIN_REQUIRED_EQUATIONS = 9;
109 
110     /**
111      * Default type for a DAQ estimator.
112      */
113     public static final DualAbsoluteQuadricEstimatorType DEFAULT_ESTIMATOR_TYPE =
114             DualAbsoluteQuadricEstimatorType.LMSE_DUAL_ABSOLUTE_QUADRIC_ESTIMATOR;
115 
116     /**
117      * Indicates whether camera skewness is assumed to be zero or not.
118      */
119     protected boolean zeroSkewness;
120 
121     /**
122      * Indicates whether principal point is assumed to be at origin of
123      * coordinates or not.
124      * If false, the principal point will be estimated, otherwise it will be
125      * assumed to be at image center (i.e. origin of coordinates).
126      */
127     protected boolean principalPointAtOrigin;
128 
129     /**
130      * Indicates whether aspect ratio of focal distances (i.e. vertical focal
131      * distance divided by horizontal focal distance) is known or not.
132      * Notice that focal distance aspect ratio is not related to image size
133      * aspect ratio. Typically, LCD sensor cells are square and hence aspect
134      * ratio of focal distances is known and equal to 1.
135      * This value is only taken into account if skewness is assumed to be zero,
136      * otherwise it is ignored.
137      */
138     protected boolean focalDistanceAspectRatioKnown;
139 
140     /**
141      * Contains aspect ratio of focal distances (i.e. vertical focal distance
142      * divided by horizontal focal distance).
143      * This value is only taken into account if skewness is assumed to be zero
144      * and focal distance aspect ratio is marked as known, otherwise it is
145      * ignored.
146      * By default, this is 1.0, since it is taken into account that typically
147      * LCD sensor cells are square and hence aspect ratio focal distances is
148      * known and equal to 1.
149      * Notice that focal distance aspect ratio is not related to image size
150      * aspect ratio.
151      */
152     protected double focalDistanceAspectRatio;
153 
154     /**
155      * Indicates whether a singular DAQ is enforced or not.
156      * Dual Absolute Quadric is singular (has rank 3) in any projective space,
157      * however, due to noise in samples, estimated DAQ might not be fully
158      * singular.
159      */
160     protected boolean singularityEnforced;
161 
162     /**
163      * Indicates whether enforced singularity will be validated by checking that
164      * determinant of estimated Dual Absolute Quadric (DAQ) is below a certain
165      * threshold.
166      */
167     protected boolean validateEnforcedSingularity;
168 
169     /**
170      * Threshold to determine whether estimated Dual Absolute Quadric (DAQ)
171      * has rank 3 or not when validation is enabled.
172      */
173     protected double determinantThreshold;
174 
175     /**
176      * True when an estimator is estimating the Dual Absolute Quadric (DAQ).
177      */
178     protected boolean locked;
179 
180     /**
181      * Listener to be notified of events such as when estimation starts, ends or
182      * estimation progress changes.
183      */
184     protected DualAbsoluteQuadricEstimatorListener listener;
185 
186     /**
187      * List of cameras used to estimate the Dual Absolute Quadric (DAQ).
188      */
189     protected List<PinholeCamera> cameras;
190 
191     /**
192      * Constructor.
193      */
194     protected DualAbsoluteQuadricEstimator() {
195         zeroSkewness = DEFAULT_ZERO_SKEWNESS;
196         principalPointAtOrigin = DEFAULT_PRINCIPAL_POINT_AT_ORIGIN;
197         focalDistanceAspectRatioKnown = DEFAULT_FOCAL_DISTANCE_ASPECT_RATIO_KNOWN;
198         focalDistanceAspectRatio = DEFAULT_FOCAL_DISTANCE_ASPECT_RATIO;
199         singularityEnforced = DEFAULT_ENFORCE_SINGULARITY;
200         validateEnforcedSingularity = DEFAULT_VALIDATE_ENFORCED_SINGULARITY;
201         determinantThreshold = DEFAULT_DETERMINANT_THRESHOLD;
202 
203         locked = false;
204         listener = null;
205         cameras = null;
206     }
207 
208     /**
209      * Constructor with listener.
210      *
211      * @param listener listener to be notified of events such as when estimation
212      *                 starts, ends or estimation progress changes.
213      */
214     protected DualAbsoluteQuadricEstimator(final DualAbsoluteQuadricEstimatorListener listener) {
215         this();
216         this.listener = listener;
217     }
218 
219     /**
220      * Constructor.
221      *
222      * @param cameras list of cameras used to estimate the Dual Absolute Quadric
223      *                (DAQ).
224      * @throws IllegalArgumentException if list of cameras is null or invalid
225      *                                  for default constraints.
226      */
227     protected DualAbsoluteQuadricEstimator(final List<PinholeCamera> cameras) {
228         this();
229         try {
230             setCameras(cameras);
231         } catch (final LockedException ignore) {
232             // never thrown
233         }
234     }
235 
236     /**
237      * Constructor.
238      *
239      * @param cameras  list of cameras used to estimate the Dual Absolute Quadric
240      *                 (DAQ).
241      * @param listener listener to be notified of events such as when estimation
242      *                 starts, ends or estimation progress changes.
243      * @throws IllegalArgumentException if list of cameras is null or invalid
244      *                                  for default constraints.
245      */
246     protected DualAbsoluteQuadricEstimator(final List<PinholeCamera> cameras,
247                                            final DualAbsoluteQuadricEstimatorListener listener) {
248         this(cameras);
249         this.listener = listener;
250     }
251 
252     /**
253      * Returns boolean indicating whether camera skewness is assumed to be zero
254      * or not.
255      * Skewness determines whether LCD sensor cells are properly aligned or not,
256      * where zero indicates perfect alignment.
257      * Typically, skewness is a value equal or very close to zero.
258      *
259      * @return true if camera skewness is assumed to be zero, otherwise camera
260      * skewness is estimated
261      */
262     public boolean isZeroSkewness() {
263         return zeroSkewness;
264     }
265 
266     /**
267      * Sets boolean indicating whether camera skewness is assumed to be zero or
268      * not.
269      * Skewness determines whether LCD sensor cells are properly aligned or not,
270      * where zero indicates perfect alignment.
271      * Typically, skewness is a value equal or very close to zero.
272      *
273      * @param zeroSkewness true if camera skewness is assumed to be zero,
274      *                     otherwise camera skewness is estimated
275      * @throws LockedException if estimator is locked
276      */
277     public void setZeroSkewness(final boolean zeroSkewness) throws LockedException {
278         if (isLocked()) {
279             throw new LockedException();
280         }
281 
282         this.zeroSkewness = zeroSkewness;
283     }
284 
285     /**
286      * Returns boolean indicating whether principal point is assumed to be at
287      * origin of coordinates or not.
288      * Typically principal point is located at image center (origin of
289      * coordinates), and usually matches the center of radial distortion if
290      * it is taken into account.
291      *
292      * @return true if principal point is assumed to be at origin of
293      * coordinates, false if principal point must be estimated
294      */
295     public boolean isPrincipalPointAtOrigin() {
296         return principalPointAtOrigin;
297     }
298 
299     /**
300      * Sets boolean indicating whether principal point is assumed to be at
301      * origin of coordinates or not.
302      * Typically principal point is located at image center (origin of
303      * coordinates), and usually matches the center of radial distortion if it
304      * is taken into account.
305      *
306      * @param principalPointAtOrigin true if principal point is assumed to be at
307      *                               origin of coordinates, false if principal point must be estimated
308      * @throws LockedException if estimator is locked
309      */
310     public void setPrincipalPointAtOrigin(final boolean principalPointAtOrigin) throws LockedException {
311         if (isLocked()) {
312             throw new LockedException();
313         }
314 
315         this.principalPointAtOrigin = principalPointAtOrigin;
316     }
317 
318     /**
319      * Returns boolean indicating whether aspect ratio of focal distances (i.e.
320      * vertical focal distance divided by horizontal focal distance) is known or
321      * not.
322      * Notice that focal distance aspect ratio is not related to image size
323      * aspect ratio. Typically, LCD sensor cells are square and hence aspect
324      * ratio of focal distances is known and equal to 1.
325      * This value is only taken into account if skewness is assumed to be zero,
326      * otherwise it is ignored.
327      *
328      * @return true if focal distance aspect ratio is known, false otherwise
329      */
330     public boolean isFocalDistanceAspectRatioKnown() {
331         return focalDistanceAspectRatioKnown;
332     }
333 
334     /**
335      * Sets value indicating whether aspect ratio of focal distances (i.e.
336      * vertical focal distance divided by horizontal focal distance) is known or
337      * not.
338      * Notice that focal distance aspect ratio is not related to image size
339      * aspect ratio. Typically, LCD sensor cells are square and hence aspect
340      * ratio of focal distances is known and equal to 1.
341      * This value is only taken into account if skewness is assumed to be zero,
342      * otherwise it is ignored.
343      *
344      * @param focalDistanceAspectRatioKnown true if focal distance aspect ratio
345      *                                      is known, false otherwise.
346      * @throws LockedException if estimator is locked.
347      */
348     public void setFocalDistanceAspectRatioKnown(final boolean focalDistanceAspectRatioKnown) throws LockedException {
349         if (isLocked()) {
350             throw new LockedException();
351         }
352 
353         this.focalDistanceAspectRatioKnown = focalDistanceAspectRatioKnown;
354     }
355 
356     /**
357      * Returns aspect ratio of focal distances (i.e. vertical focal distance
358      * divided by horizontal focal distance).
359      * This value is only taken into account if skewness is assumed to be zero
360      * and focal distance aspect ratio is marked as known, otherwise it is
361      * ignored.
362      * By default, this is 1.0, since it is taken into account that typically
363      * LCD sensor cells are square and hence aspect ratio focal distances is
364      * known and equal to 1.
365      * Notice that focal distance aspect ratio is not related to image size
366      * aspect ratio.
367      * Notice that a negative aspect ratio indicates that vertical axis is
368      * reversed. This can be useful in some situations where image vertical
369      * coordinates are reversed respect to the physical world (i.e. in computer
370      * graphics typically image vertical coordinates go downwards, while in
371      * physical world they go upwards).
372      *
373      * @return aspect ratio of focal distances.
374      */
375     public double getFocalDistanceAspectRatio() {
376         return focalDistanceAspectRatio;
377     }
378 
379     /**
380      * Sets aspect ratio of focal distances (i.e. vertical focal distance
381      * divided by horizontal focal distance).
382      * This value is only taken into account if skewness is assumed to be zero
383      * and focal distance aspect ratio is marked as known, otherwise it is
384      * ignored.
385      * By default, this is 1.0, since it is taken into account that typically
386      * LCD sensor cells are square and hence aspect ratio focal distances is
387      * known and equal to 1.
388      * Notice that focal distance aspect ratio is not related to image size
389      * aspect ratio.
390      * Notice that a negative aspect ratio indicates that vertical axis is
391      * reversed. This can be useful in some situations where image vertical
392      * coordinates are reversed respect to the physical world (i.e. in computer
393      * graphics typically image vertical coordinates go downwards, while in
394      * physical world they go upwards).
395      *
396      * @param focalDistanceAspectRatio aspect ratio of focal distances to be set.
397      * @throws LockedException          if estimator is locked.
398      * @throws IllegalArgumentException if focal distance aspect ratio is too
399      *                                  close to zero, as it might produce numerical instabilities.
400      */
401     public void setFocalDistanceAspectRatio(final double focalDistanceAspectRatio) throws LockedException {
402         if (isLocked()) {
403             throw new LockedException();
404         }
405         if (Math.abs(focalDistanceAspectRatio) < MIN_ABS_FOCAL_DISTANCE_ASPECT_RATIO) {
406             throw new IllegalArgumentException();
407         }
408 
409         this.focalDistanceAspectRatio = focalDistanceAspectRatio;
410     }
411 
412     /**
413      * Indicates whether a singular DAQ is enforced or not.
414      * Dual Absolute Quadric is singular (has rank 3) in any projective space,
415      * however, due to noise in samples, estimated DAQ might not be fully
416      * singular.
417      *
418      * @return true when singular DAQ is enforced, false otherwise.
419      */
420     public boolean isSingularityEnforced() {
421         return singularityEnforced;
422     }
423 
424     /**
425      * Specifies whether a singular DAQ is enforced or not.
426      * Dual Absolute Quadric is singular (has rank 3) in any projective space,
427      * however, due to noise in samples, estimated DAQ might not be fully
428      * singular.
429      *
430      * @param singularityEnforced true when singular DAQ is enforced, false
431      *                            otherwise.
432      * @throws LockedException if estimator is locked.
433      */
434     public void setSingularityEnforced(final boolean singularityEnforced) throws LockedException {
435         if (isLocked()) {
436             throw new LockedException();
437         }
438         this.singularityEnforced = singularityEnforced;
439     }
440 
441     /**
442      * Indicates whether enforced singularity will be validated by checking that
443      * determinant of estimated Dual Absolute Quadric (DAQ) is below a certain
444      * threshold.
445      *
446      * @return true if enforced singularity is validated, false otherwise.
447      */
448     public boolean isEnforcedSingularityValidated() {
449         return validateEnforcedSingularity;
450     }
451 
452     /**
453      * Specifies whether enforced singularity will be validated by checking that
454      * determinant of estimated Dual Absolute Quadric (DAQ) is below a certain
455      * threshold.
456      *
457      * @param validateEnforcedSingularity true if enforced singularity is
458      *                                    validated, false otherwise.
459      * @throws LockedException if estimator is locked.
460      */
461     public void setEnforcedSingularityValidated(final boolean validateEnforcedSingularity) throws LockedException {
462         if (isLocked()) {
463             throw new LockedException();
464         }
465         this.validateEnforcedSingularity = validateEnforcedSingularity;
466     }
467 
468     /**
469      * Returns threshold to determine whether estimated Dual Absolute Quadric
470      * (DAQ) has rank 3 or not when validation is enabled.
471      *
472      * @return threshold to determine whether estimated DAQ has rank 3 or not.
473      */
474     public double getDeterminantThreshold() {
475         return determinantThreshold;
476     }
477 
478     /**
479      * Sets threshold to determine whether estimated Dual Absolute Quadric (DAQ)
480      * has rank 3 or not when validation is enabled.
481      *
482      * @param determinantThreshold threshold to determine whether estimated DAQ
483      *                             has rank 3 or not.
484      * @throws IllegalArgumentException if provided value is zero or negative.
485      * @throws LockedException          if estimator is locked.
486      */
487     public void setDeterminantThreshold(final double determinantThreshold) throws LockedException {
488         if (isLocked()) {
489             throw new LockedException();
490         }
491         if (determinantThreshold < 0.0) {
492             throw new IllegalArgumentException("threshold must be positive and greater than zero");
493         }
494         this.determinantThreshold = determinantThreshold;
495     }
496 
497     /**
498      * Indicates whether this instance is locked.
499      *
500      * @return true if this estimator is busy estimating the Dual Absolute
501      * Quadric, false otherwise.
502      */
503     public boolean isLocked() {
504         return locked;
505     }
506 
507     /**
508      * Obtains listener to be notified of events such as when estimation starts,
509      * ends or estimation progress changes.
510      *
511      * @return listener to be notified of events.
512      */
513     public DualAbsoluteQuadricEstimatorListener getListener() {
514         return listener;
515     }
516 
517     /**
518      * Sets listener to be notified of events such as when estimation starts,
519      * ends or estimation progress changes.
520      *
521      * @param listener listener to be notified of events.
522      */
523     public void setListener(final DualAbsoluteQuadricEstimatorListener listener) {
524         this.listener = listener;
525     }
526 
527     /**
528      * Obtains the list of cameras used to estimate the Dual Absolute Quadric
529      * (DAQ).
530      *
531      * @return list of cameras to estimate the DAQ.
532      */
533     public List<PinholeCamera> getCameras() {
534         return cameras;
535     }
536 
537     /**
538      * Sets the list of cameras used to estimate the Dual Absolute Quadric
539      * (DAQ).
540      *
541      * @param cameras list of cameras used to estimate the DAQ.
542      * @throws IllegalArgumentException if list is null.
543      * @throws LockedException          if estimator is locked.
544      */
545     public final void setCameras(final List<PinholeCamera> cameras) throws LockedException {
546         if (isLocked()) {
547             throw new LockedException();
548         }
549         if (cameras == null || cameras.size() < getMinNumberOfRequiredCameras()) {
550             throw new IllegalArgumentException();
551         }
552         this.cameras = cameras;
553     }
554 
555     /**
556      * Returns minimum number of required cameras needed to estimate the
557      * Dual Absolute Quadric (DAQ).
558      * At least 8 equations are needed to solve the DAQ
559      * For each imposed constraint, one less equation is required.
560      * Depending on the number of constraints more or less cameras will be
561      * required.
562      * If zero skewness is enforced, a solution is available with 8 cameras.
563      * If zero skewness and focal distance aspect ratio is known, then a
564      * solution is available with 4 cameras.
565      * If principal point is located at origin, then a solution is available
566      * with 4 cameras.
567      * If zero skewness and principal point at origin are enforced, then a
568      * solution is available with 3 cameras.
569      * If zero skewness is enforced, focal distance aspect ratio is known and
570      * principal point is at origin, then a solution is available with 2
571      * cameras.
572      * NOTE: minimum number of cameras considers only the cameras providing
573      * additional information. If a camera is equivalent to another one or does
574      * not provide additional information (such as a camera at the origin with
575      * no rotation), then more cameras will be needed.
576      *
577      * @return minimum number of required cameras needed to estimate the Dual
578      * Absolute Quadric (DAQ) or -1 if constraints configurations is not valid.
579      */
580     public int getMinNumberOfRequiredCameras() {
581         var numEquations = 0;
582         if (principalPointAtOrigin) {
583             numEquations += 2;
584             if (zeroSkewness) {
585                 numEquations++;
586 
587                 // only a linear solution exists for known aspect ratio if
588                 // skewness is zero
589                 if (focalDistanceAspectRatioKnown) {
590                     numEquations++;
591                 }
592             }
593         }
594 
595         if (numEquations == 0) {
596             return -1;
597         }
598 
599         var minRequiredEquations = MIN_REQUIRED_EQUATIONS;
600         if (singularityEnforced) {
601             minRequiredEquations--;
602         }
603         return (minRequiredEquations / numEquations) + ((minRequiredEquations % numEquations != 0) ? 1 : 0);
604     }
605 
606     /**
607      * Indicates whether current constraints are enough to start the estimation.
608      * In order to obtain a linear solution for the DAQ estimation, we need at
609      * least the principal point at origin constraint.
610      *
611      * @return true if constraints are valid, false otherwise.
612      */
613     public boolean areValidConstraints() {
614         return principalPointAtOrigin && (focalDistanceAspectRatioKnown || !singularityEnforced);
615     }
616 
617 
618     /**
619      * Indicates if this estimator is ready to start the estimation.
620      *
621      * @return true if estimator is ready, false otherwise.
622      */
623     public boolean isReady() {
624         return cameras != null && cameras.size() >= getMinNumberOfRequiredCameras() && areValidConstraints();
625     }
626 
627 
628     /**
629      * Estimates the Dual Absolute Quadric using provided cameras.
630      *
631      * @return estimated Dual Absolute Quadric (DAQ).
632      * @throws LockedException                       if estimator is locked.
633      * @throws NotReadyException                     if no valid input data has already been
634      *                                               provided.
635      * @throws DualAbsoluteQuadricEstimatorException if an error occurs during
636      *                                               estimation, usually because input data is not valid
637      *                                               or numerically unstable.
638      */
639     public DualAbsoluteQuadric estimate() throws LockedException, NotReadyException,
640             DualAbsoluteQuadricEstimatorException {
641         final var result = new DualAbsoluteQuadric();
642         estimate(result);
643         return result;
644     }
645 
646     /**
647      * Estimates the Dual Absolute Quadric using provided cameras.
648      *
649      * @param result instance where estimated Dual Absolute Quadric (DAQ) will
650      *               be stored.
651      * @throws LockedException                       if estimator is locked.
652      * @throws NotReadyException                     if no valid input data has already been
653      *                                               provided.
654      * @throws DualAbsoluteQuadricEstimatorException if an error occurs during
655      *                                               estimation, usually because input data is not valid
656      *                                               or numerically unstable.
657      */
658     public abstract void estimate(final DualAbsoluteQuadric result) throws LockedException, NotReadyException,
659         DualAbsoluteQuadricEstimatorException;
660 
661 
662     /**
663      * Returns type of Dual Absolute Quadric estimator.
664      *
665      * @return type of DAQ estimator.
666      */
667     public abstract DualAbsoluteQuadricEstimatorType getType();
668 
669     /**
670      * Creates an instance of a DAQ estimator using default type.
671      *
672      * @return an instance of a DAQ estimator.
673      */
674     public static DualAbsoluteQuadricEstimator create() {
675         return create(DEFAULT_ESTIMATOR_TYPE);
676     }
677 
678     /**
679      * Creates an instance of Dual Absolute Quadric estimator using provided
680      * listener and default type.
681      *
682      * @param listener listener to be notified of events such as when estimation
683      *                 starts, ends or estimation progress changes.
684      * @return an instance of a DAQ estimator.
685      */
686     public static DualAbsoluteQuadricEstimator create(final DualAbsoluteQuadricEstimatorListener listener) {
687         return create(listener, DEFAULT_ESTIMATOR_TYPE);
688     }
689 
690     /**
691      * Creates an instance of Dual Absolute Quadric estimator using provided
692      * cameras.
693      *
694      * @param cameras list of cameras used to estimate the Dual Absolute Quadric
695      *                (DAQ).
696      * @return an instance of a DAQ estimator.
697      * @throws IllegalArgumentException if list of cameras is null or invalid
698      *                                  for default constraints.
699      */
700     public static DualAbsoluteQuadricEstimator create(final List<PinholeCamera> cameras) {
701         return create(cameras, DEFAULT_ESTIMATOR_TYPE);
702     }
703 
704     /**
705      * Creates an instance of Dual Absolute Quadric estimator using provided
706      * cameras and listener.
707      *
708      * @param cameras  list of cameras used to estimate the Dual Absolute Quadric
709      *                 (DAQ).
710      * @param listener listener to be notified of events such as when estimation
711      *                 starts, ends or estimation progress changes.
712      * @return an instance of a DAQ estimator.
713      * @throws IllegalArgumentException if list of cameras is null or invalid
714      *                                  for default constraints.
715      */
716     public static DualAbsoluteQuadricEstimator create(
717             final List<PinholeCamera> cameras, final DualAbsoluteQuadricEstimatorListener listener) {
718 
719         return create(cameras, listener, DEFAULT_ESTIMATOR_TYPE);
720     }
721 
722     /**
723      * Creates an instance of a DAQ estimator using provided type.
724      *
725      * @param type type of DAQ estimator.
726      * @return an instance of a DAQ estimator.
727      */
728     public static DualAbsoluteQuadricEstimator create(final DualAbsoluteQuadricEstimatorType type) {
729         return type == DualAbsoluteQuadricEstimatorType.WEIGHTED_DUAL_ABSOLUTE_QUADRIC_ESTIMATOR
730                 ? new WeightedDualAbsoluteQuadricEstimator() : new LMSEDualAbsoluteQuadricEstimator();
731     }
732 
733     /**
734      * Creates an instance of a DAQ estimator using provided listener and type.
735      *
736      * @param listener listener to be notified of events such as when estimation
737      *                 starts, ends or estimation progress changes.
738      * @param type     type of DAQ estimator.
739      * @return an instance of a DAQ estimator.
740      */
741     public static DualAbsoluteQuadricEstimator create(
742             final DualAbsoluteQuadricEstimatorListener listener, final DualAbsoluteQuadricEstimatorType type) {
743         return type == DualAbsoluteQuadricEstimatorType.WEIGHTED_DUAL_ABSOLUTE_QUADRIC_ESTIMATOR
744                 ? new WeightedDualAbsoluteQuadricEstimator(listener) : new LMSEDualAbsoluteQuadricEstimator(listener);
745     }
746 
747     /**
748      * Creates an instance of a DAQ estimator using provided cameras and type.
749      *
750      * @param cameras list of cameras used to estimate the Dual Absolute Quadric
751      *                (DAQ).
752      * @param type    type of DAQ estimator.
753      * @return an instance of a DAQ estimator.
754      * @throws IllegalArgumentException if list of cameras is null or invalid
755      *                                  for default constraints.
756      */
757     public static DualAbsoluteQuadricEstimator create(
758             final List<PinholeCamera> cameras, final DualAbsoluteQuadricEstimatorType type) {
759         return type == DualAbsoluteQuadricEstimatorType.WEIGHTED_DUAL_ABSOLUTE_QUADRIC_ESTIMATOR
760                 ? new WeightedDualAbsoluteQuadricEstimator(cameras) : new LMSEDualAbsoluteQuadricEstimator(cameras);
761     }
762 
763     /**
764      * Creates an instance of a DAQ estimator using provided cameras, listener
765      * and type.
766      *
767      * @param cameras  list of cameras used to estimate the Dual Absolute Quadric
768      *                 (DAQ).
769      * @param listener listener to be notified of events such as when estimation
770      *                 starts, ends or estimation progress changes.
771      * @param type     type of DAQ estimator.
772      * @return an instance of DAQ estimator.
773      * @throws IllegalArgumentException if list of cameras is null or invalid
774      *                                  for default constraints.
775      */
776     public static DualAbsoluteQuadricEstimator create(
777             final List<PinholeCamera> cameras, final DualAbsoluteQuadricEstimatorListener listener,
778             final DualAbsoluteQuadricEstimatorType type) {
779 
780         return type == DualAbsoluteQuadricEstimatorType.WEIGHTED_DUAL_ABSOLUTE_QUADRIC_ESTIMATOR
781                 ? new WeightedDualAbsoluteQuadricEstimator(cameras, listener)
782                 : new LMSEDualAbsoluteQuadricEstimator(cameras, listener);
783     }
784 
785     /**
786      * Normalizes i-th row of provided matrix "a".
787      *
788      * @param a matrix whose row must be normalized.
789      * @param i row to be normalized.
790      */
791     protected void normalizeRow(final Matrix a, final int i) {
792         var rowNorm = 0.0;
793         final var cols = a.getColumns();
794         for (var j = 0; j < cols; j++) {
795             rowNorm += Math.pow(a.getElementAt(i, j), 2.0);
796         }
797 
798         rowNorm = Math.sqrt(rowNorm);
799 
800         for (var j = 0; j < cols; j++) {
801             a.setElementAt(i, j, a.getElementAt(i, j) / rowNorm);
802         }
803     }
804 
805     /**
806      * Fills equation p3^t*Q*p1 in provided row of matrix "a".
807      *
808      * @param p11 element (1,1) of camera matrix.
809      * @param p31 element (3,1) of camera matrix.
810      * @param p12 element (1,2) of camera matrix.
811      * @param p32 element (3,2) of camera matrix.
812      * @param p13 element (1,3) of camera matrix.
813      * @param p33 element (3,3) of camera matrix.
814      * @param p14 element (1,4) of camera matrix.
815      * @param p34 element (3,4) of camera matrix.
816      * @param a   matrix where data is stored.
817      * @param row row of matrix a where data is stored.
818      */
819     protected void fill3rdRowAnd1stRowEquation(
820         final double p11, final double p31, final double p12, final double p32, final double p13, final double p33,
821         final double p14, final double p34, final Matrix a, final int row) {
822 
823         // a
824         a.setElementAt(row, 0, p31 * p11);
825         // b
826         a.setElementAt(row, 1, p32 * p12);
827         // c
828         a.setElementAt(row, 2, p33 * p13);
829         // d
830         a.setElementAt(row, 3, p32 * p11 + p31 * p12);
831         // e
832         a.setElementAt(row, 4, p33 * p12 + p32 * p13);
833         // f
834         a.setElementAt(row, 5, p33 * p11 + p31 * p13);
835         // g
836         a.setElementAt(row, 6, p34 * p11 + p31 * p14);
837         // h
838         a.setElementAt(row, 7, p34 * p12 + p32 * p14);
839         // i
840         a.setElementAt(row, 8, p34 * p13 + p33 * p14);
841         // j
842         a.setElementAt(row, 9, p34 * p14);
843 
844         normalizeRow(a, row);
845     }
846 
847     /**
848      * Fills equation p3^t*Q*p2 in provided row of matrix "a".
849      *
850      * @param p21 element (2,1) of camera matrix.
851      * @param p31 element (3,1) of camera matrix.
852      * @param p22 element (2,2) of camera matrix.
853      * @param p32 element (3,2) of camera matrix.
854      * @param p23 element (2,3) of camera matrix.
855      * @param p33 element (3,3) of camera matrix.
856      * @param p24 element (2,4) of camera matrix.
857      * @param p34 element (3,4) of camera matrix.
858      * @param a   matrix where data is stored.
859      * @param row row of matrix a where data is stored.
860      */
861     protected void fill3rdRowAnd2ndRowEquation(
862         final double p21, final double p31, final double p22, final double p32, final double p23, final double p33,
863         final double p24, final double p34, final Matrix a, final int row) {
864 
865         // a
866         a.setElementAt(row, 0, p31 * p21);
867         // b
868         a.setElementAt(row, 1, p32 * p22);
869         // c
870         a.setElementAt(row, 2, p33 * p23);
871         // d
872         a.setElementAt(row, 3, p32 * p21 + p31 * p22);
873         // e
874         a.setElementAt(row, 4, p33 * p22 + p32 * p23);
875         // f
876         a.setElementAt(row, 5, p33 * p21 + p31 * p23);
877         // g
878         a.setElementAt(row, 6, p34 * p21 + p31 * p24);
879         // h
880         a.setElementAt(row, 7, p34 * p22 + p32 * p24);
881         // i
882         a.setElementAt(row, 8, p34 * p23 + p33 * p24);
883         // j
884         a.setElementAt(row, 9, p34 * p24);
885 
886         normalizeRow(a, row);
887     }
888 
889     /**
890      * Fills equation p2^t*Q*p1 in provided row of matrix "a".
891      *
892      * @param p11 element (1,1) of camera matrix.
893      * @param p21 element (2,1) of camera matrix.
894      * @param p12 element (1,2) of camera matrix.
895      * @param p22 element (2,2) of camera matrix.
896      * @param p13 element (1,3) of camera matrix.
897      * @param p23 element (2,3) of camera matrix.
898      * @param p14 element (1,4) of camera matrix.
899      * @param p24 element (2,4) of camera matrix.
900      * @param a   matrix where data is stored.
901      * @param row row of matrix a where data is stored.
902      */
903     protected void fill2ndRowAnd1stRowEquation(
904         final double p11, final double p21, final double p12, final double p22, final double p13, final double p23,
905         final double p14, final double p24, final Matrix a, final int row) {
906 
907         // a
908         a.setElementAt(row, 0, p21 * p11);
909         // b
910         a.setElementAt(row, 1, p22 * p12);
911         // c
912         a.setElementAt(row, 2, p23 * p13);
913         // d
914         a.setElementAt(row, 3, p22 * p11 + p21 * p12);
915         // e
916         a.setElementAt(row, 4, p23 * p12 + p22 * p13);
917         // f
918         a.setElementAt(row, 5, p23 * p11 + p21 * p13);
919         // g
920         a.setElementAt(row, 6, p24 * p11 + p21 * p14);
921         // h
922         a.setElementAt(row, 7, p24 * p12 + p22 * p14);
923         // i
924         a.setElementAt(row, 8, p24 * p13 + p23 * p14);
925         // j
926         a.setElementAt(row, 9, p24 * p14);
927 
928         normalizeRow(a, row);
929     }
930 
931     /**
932      * Fills equation p1^t*Q*p1 = r^2*p2^t*Q*p2
933      *
934      * @param p11 element (1,1) of camera matrix.
935      * @param p21 element (2,1) of camera matrix.
936      * @param p12 element (1,2) of camera matrix.
937      * @param p22 element (2,2) of camera matrix.
938      * @param p13 element (1,3) of camera matrix.
939      * @param p23 element (2,3) of camera matrix.
940      * @param p14 element (1,4) of camera matrix.
941      * @param p24 element (2,4) of camera matrix.
942      * @param a   matrix where data is stored.
943      * @param row row of matrix a where data is stored.
944      */
945     protected void fill1stRowEqualTo2ndRowEquation(
946         final double p11, final double p21, final double p12, final double p22, final double p13, final double p23,
947         final double p14, final double p24, final Matrix a, final int row) {
948 
949         final var r2 = focalDistanceAspectRatio * focalDistanceAspectRatio;
950 
951         // a
952         a.setElementAt(row, 0, p11 * p11 * r2 - p21 * p21);
953         // b
954         a.setElementAt(row, 1, p12 * p12 * r2 - p22 * p22);
955         // c
956         a.setElementAt(row, 2, p13 * p13 * r2 - p23 * p23);
957         // d
958         a.setElementAt(row, 3, 2.0 * (p12 * p11 * r2 - p22 * p21));
959         // e
960         a.setElementAt(row, 4, 2.0 * (p13 * p12 * r2 - p23 * p22));
961         // f
962         a.setElementAt(row, 5, 2.0 * (p13 * p11 * r2 - p23 * p21));
963         // g
964         a.setElementAt(row, 6, 2.0 * (p14 * p11 * r2 - p24 * p21));
965         // h
966         a.setElementAt(row, 7, 2.0 * (p14 * p12 * r2 - p24 * p22));
967         // i
968         a.setElementAt(row, 8, 2.0 * (p14 * p13 * r2 - p24 * p23));
969         // j
970         a.setElementAt(row, 9, p14 * p14 * r2 - p24 * p24);
971 
972         normalizeRow(a, row);
973     }
974 
975     /**
976      * Enforces (if needed) rank 3 of estimated quadric by building a polynomial
977      * out of the last columns of the singular value vector matrix to obtain a
978      * linear combination solution.
979      *
980      * @param decomposer decomposer containing possible solutions after
981      *                   decomposition.
982      * @param result     instance where estimated Dual Absolute Quadrics (DAQs) with
983      *                   rank 3 enforced will be stored.
984      * @throws AlgebraException                      if there are numerical instabilities
985      * @throws NumericalException                    if a solution to the polynomial enforcing
986      *                                               rank 3 cannot be found.
987      * @throws DualAbsoluteQuadricEstimatorException if something else fails.
988      */
989     protected void enforceRank3IfNeeded(final SingularValueDecomposer decomposer, final DualAbsoluteQuadric result)
990         throws AlgebraException, NumericalException, DualAbsoluteQuadricEstimatorException {
991 
992         decomposer.decompose();
993 
994         if (singularityEnforced) {
995             if (decomposer.getNullity() > 2) {
996                 // provided cameras are degenerate and there is not a single
997                 // solution for the DAQ (up to scale)
998                 throw new DualAbsoluteQuadricEstimatorException();
999             }
1000 
1001             final var v = decomposer.getV();
1002 
1003             // last 2 columns of v contains parameters a, b, c, d, e,
1004             // f, g, h, i  that can be obtained as a linear combination of
1005             // those last columns (i.e. a = a1 + alpha*a2).
1006             // By enforcing determinant zero, we can obtain a 4th degree
1007             // polynomial to determine alpha to find the unique linear
1008             // combination of last 2 columns that solve both DAQ equation and
1009             // generates rank 3 DAQ.
1010             final var a1 = v.getElementAt(0, 8);
1011             final var b1 = v.getElementAt(1, 8);
1012             final var c1 = v.getElementAt(2, 8);
1013             final var d1 = v.getElementAt(3, 8);
1014             final var e1 = v.getElementAt(4, 8);
1015             final var f1 = v.getElementAt(5, 8);
1016             final var g1 = v.getElementAt(6, 8);
1017             final var h1 = v.getElementAt(7, 8);
1018             final var i1 = v.getElementAt(8, 8);
1019             final var j1 = v.getElementAt(9, 8);
1020 
1021             final var a2 = v.getElementAt(0, 9);
1022             final var b2 = v.getElementAt(1, 9);
1023             final var c2 = v.getElementAt(2, 9);
1024             final var d2 = v.getElementAt(3, 9);
1025             final var e2 = v.getElementAt(4, 9);
1026             final var f2 = v.getElementAt(5, 9);
1027             final var g2 = v.getElementAt(6, 9);
1028             final var h2 = v.getElementAt(7, 9);
1029             final var i2 = v.getElementAt(8, 9);
1030             final var j2 = v.getElementAt(9, 9);
1031 
1032             final var poly = buildPolynomialToEnforceRank3(a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, a2, b2, c2, d2, e2,
1033                     f2, g2, h2, i2, j2);
1034 
1035             final var roots = poly.getRoots();
1036 
1037             if (roots != null) {
1038                 // pick the best solution (closest real root) = evaluation closest to
1039                 // zero
1040                 var bestPolyEval = Double.MAX_VALUE;
1041                 for (final var root : roots) {
1042                     final var real = root.getReal();
1043                     final var polyEval = Math.abs(poly.evaluate(real));
1044                     if (polyEval < bestPolyEval) {
1045                         bestPolyEval = polyEval;
1046                         final var a = a1 + real * a2;
1047                         final var b = b1 + real * b2;
1048                         final var c = c1 + real * c2;
1049                         final var d = d1 + real * d2;
1050                         final var e = e1 + real * e2;
1051                         final var f = f1 + real * f2;
1052                         final var g = g1 + real * g2;
1053                         final var h = h1 + real * h2;
1054                         final var i = i1 + real * i2;
1055                         final var j = j1 + real * j2;
1056                         result.setParameters(a, b, c, d, e, f, g, h, i, j);
1057                     }
1058                 }
1059             } else {
1060                 // if no roots could be found, it might be due to numerical
1061                 // inaccuracies, so we find minimum or maximum of polynomial
1062                 // which evaluates closest to zero
1063                 final var extrema = poly.getExtrema();
1064                 if (extrema == null) {
1065                     // polynomial has no extrema, which means it is degenerate
1066                     throw new DualAbsoluteQuadricEstimatorException();
1067                 }
1068 
1069                 var bestPolyEval = Double.MAX_VALUE;
1070                 for (final var extremum : extrema) {
1071                     final var polyEval = Math.abs(poly.evaluate(extremum));
1072                     if (polyEval < bestPolyEval) {
1073                         bestPolyEval = polyEval;
1074                         final var a = a1 + extremum * a2;
1075                         final var b = b1 + extremum * b2;
1076                         final var c = c1 + extremum * c2;
1077                         final var d = d1 + extremum * d2;
1078                         final var e = e1 + extremum * e2;
1079                         final var f = f1 + extremum * f2;
1080                         final var g = g1 + extremum * g2;
1081                         final var h = h1 + extremum * h2;
1082                         final var i = i1 + extremum * i2;
1083                         final var j = j1 + extremum * j2;
1084                         result.setParameters(a, b, c, d, e, f, g, h, i, j);
1085                     }
1086                 }
1087 
1088                 if (validateEnforcedSingularity) {
1089                     // check that determinant of estimated DAQ is below allowed
1090                     // threshold
1091                     final var absDet = Math.abs(Utils.det(result.asMatrix()));
1092                     if (absDet > determinantThreshold) {
1093                         // DAQ does not have rank 3
1094                         throw new DualAbsoluteQuadricEstimatorException();
1095                     }
1096                 }
1097             }
1098 
1099         } else {
1100             if (decomposer.getNullity() > 1) {
1101                 // provided cameras are degenerate and there is not a single
1102                 // solution for the DAQ (up to scale)
1103                 throw new DualAbsoluteQuadricEstimatorException();
1104             }
1105 
1106             final var v = decomposer.getV();
1107 
1108             // last column of v contains parameters a, b, c, d, e, f, g, h, i
1109             // defining the Dual Absolute Quadric (DAQ) as:
1110             final var a = v.getElementAt(0, 9);
1111             final var b = v.getElementAt(1, 9);
1112             final var c = v.getElementAt(2, 9);
1113             final var d = v.getElementAt(3, 9);
1114             final var e = v.getElementAt(4, 9);
1115             final var f = v.getElementAt(5, 9);
1116             final var g = v.getElementAt(6, 9);
1117             final var h = v.getElementAt(7, 9);
1118             final var i = v.getElementAt(8, 9);
1119             final var j = v.getElementAt(9, 9);
1120 
1121             result.setParameters(a, b, c, d, e, f, g, h, i, j);
1122         }
1123     }
1124 
1125     /**
1126      * Builds polynomial to enforce rank 3 (zero determinant).
1127      *
1128      * @param a1 a of 1st DAQ.
1129      * @param b1 b of 1st DAQ.
1130      * @param c1 c of 1st DAQ.
1131      * @param d1 d of 1st DAQ.
1132      * @param e1 e of 1st DAQ.
1133      * @param f1 f of 1st DAQ.
1134      * @param g1 g of 1st DAQ.
1135      * @param h1 h of 1st DAQ.
1136      * @param i1 i of 1st DAQ.
1137      * @param j1 j of 1st DAQ.
1138      * @param a2 a of 2nd DAQ.
1139      * @param b2 b of 2nd DAQ.
1140      * @param c2 c of 2nd DAQ.
1141      * @param d2 d of 2nd DAQ.
1142      * @param e2 e of 2nd DAQ.
1143      * @param f2 f of 2nd DAQ.
1144      * @param g2 g of 2nd DAQ.
1145      * @param h2 h of 2nd DAQ.
1146      * @param i2 i of 2nd DAQ.
1147      * @param j2 j of 2nd DAQ.
1148      * @return polynomial.
1149      */
1150     private Polynomial buildPolynomialToEnforceRank3(
1151             final double a1, final double b1, final double c1, final double d1, final double e1,
1152             final double f1, final double g1, final double h1, final double i1, final double j1,
1153             final double a2, final double b2, final double c2, final double d2, final double e2,
1154             final double f2, final double g2, final double h2, final double i2, final double j2) {
1155 
1156         final var polyA = new Polynomial(a1, a2);
1157         final var polyB = new Polynomial(b1, b2);
1158         final var polyC = new Polynomial(c1, c2);
1159         final var polyD = new Polynomial(d1, d2);
1160         final var polyE = new Polynomial(e1, e2);
1161         final var polyF = new Polynomial(f1, f2);
1162         final var polyG = new Polynomial(g1, g2);
1163         final var polyH = new Polynomial(h1, h2);
1164         final var polyI = new Polynomial(i1, i2);
1165         final var polyJ = new Polynomial(j1, j2);
1166 
1167         final var result = new Polynomial(5);
1168 
1169         // (a1 + x*a2) * (b1 + x*b2) * (c1 + x*c2) * (j1 + x*j2)
1170         var tmp = polyA.multiplyAndReturnNew(polyB);
1171         tmp.multiply(polyC);
1172         tmp.multiply(polyJ);
1173 
1174         result.add(tmp);
1175 
1176         // 2 * (a1 + x*a2) * (e1 + x*e2) * (h1 + x*h2) * (i1 + x*i2)
1177         tmp = polyA.multiplyAndReturnNew(polyE);
1178         tmp.multiply(polyH);
1179         tmp.multiply(polyI);
1180         tmp.multiplyByScalar(2.0);
1181 
1182         result.add(tmp);
1183 
1184         // -1 * (a1 + x*a2) * (c1 + x*c2) * (h1 + x*h2)^2
1185         tmp = polyA.multiplyAndReturnNew(polyC);
1186         tmp.multiply(polyH);
1187         tmp.multiply(polyH);
1188         tmp.multiplyByScalar(-1.0);
1189 
1190         result.add(tmp);
1191 
1192         // -1 * (a1 + x*a2) * (b1 + x*b2) * (i1 + x*i2)^2
1193         tmp = polyA.multiplyAndReturnNew(polyB);
1194         tmp.multiply(polyI);
1195         tmp.multiply(polyI);
1196         tmp.multiplyByScalar(-1.0);
1197 
1198         result.add(tmp);
1199 
1200         // -1 * (a1 + x*a2) * (e1 + x*e2)^2 * (j1 + x*j2)
1201         tmp = polyA.multiplyAndReturnNew(polyE);
1202         tmp.multiply(polyE);
1203         tmp.multiply(polyJ);
1204         tmp.multiplyByScalar(-1.0);
1205 
1206         result.add(tmp);
1207 
1208         // -1 * (c1 + x*c2) * (d1 + x*d2)^2 * (j1 + x*j2)
1209         tmp = polyC.multiplyAndReturnNew(polyD);
1210         tmp.multiply(polyD);
1211         tmp.multiply(polyJ);
1212         tmp.multiplyByScalar(-1.0);
1213 
1214         result.add(tmp);
1215 
1216         // -2 * (d1 + x*d2) * (f1 + x*f2) * (h1 + x*h2) * (i1 + x*i2)
1217         tmp = polyD.multiplyAndReturnNew(polyF);
1218         tmp.multiply(polyH);
1219         tmp.multiply(polyI);
1220         tmp.multiplyByScalar(-2.0);
1221 
1222         result.add(tmp);
1223 
1224         // -2 * (d1 + x*d2) * (e1 + x*e2) * (g1 + x*g2) * (i1 + x*i2)
1225         tmp = polyD.multiplyAndReturnNew(polyE);
1226         tmp.multiply(polyG);
1227         tmp.multiply(polyI);
1228         tmp.multiplyByScalar(-2.0);
1229 
1230         result.add(tmp);
1231 
1232         // 2 * (c1 + x*c2) * (d1 + x*d2) * (g1 + x*g2) * (h1 + x*h2)
1233         tmp = polyC.multiplyAndReturnNew(polyD);
1234         tmp.multiply(polyG);
1235         tmp.multiply(polyH);
1236         tmp.multiplyByScalar(2.0);
1237 
1238         result.add(tmp);
1239 
1240         // (d1 + x*d2)^2 * (i1 + x*i2)^2
1241         tmp = polyD.multiplyAndReturnNew(polyD);
1242         tmp.multiply(polyI);
1243         tmp.multiply(polyI);
1244 
1245         result.add(tmp);
1246 
1247         // 2 * (d1 + x*d2) * (e1 + x*e2) * (f1 + x*f2) * (j1 + x*j2)
1248         tmp = polyD.multiplyAndReturnNew(polyE);
1249         tmp.multiply(polyF);
1250         tmp.multiply(polyJ);
1251         tmp.multiplyByScalar(2.0);
1252 
1253         result.add(tmp);
1254 
1255         // (f1 + x*f2)^2 * (h1 + x*h2)^2
1256         tmp = polyF.multiplyAndReturnNew(polyF);
1257         tmp.multiply(polyH);
1258         tmp.multiply(polyH);
1259 
1260         result.add(tmp);
1261 
1262         // 2 * (b1 + x*b2) * (f1 + x*f2) * (g1 + x*g2) * (i1 + x*i2)
1263         tmp = polyB.multiplyAndReturnNew(polyF);
1264         tmp.multiply(polyG);
1265         tmp.multiply(polyI);
1266         tmp.multiplyByScalar(2.0);
1267 
1268         result.add(tmp);
1269 
1270         // -2 * (e1 + x*e2) * (f1 + x*r2) * (g1 + x*g2) * (h1 + x*h2)
1271         tmp = polyE.multiplyAndReturnNew(polyF);
1272         tmp.multiply(polyG);
1273         tmp.multiply(polyH);
1274         tmp.multiplyByScalar(-2.0);
1275 
1276         result.add(tmp);
1277 
1278         // -1 * (b1 + x*b2) * (f1 + x*f2)^2 * (j1 + x*j2)
1279         tmp = polyB.multiplyAndReturnNew(polyF);
1280         tmp.multiply(polyF);
1281         tmp.multiply(polyJ);
1282         tmp.multiplyByScalar(-1.0);
1283 
1284         result.add(tmp);
1285 
1286         // -1 * (b1 + x*b2) * (c1 + x*c2) * (g1 + x*g2)^2
1287         tmp = polyB.multiplyAndReturnNew(polyC);
1288         tmp.multiply(polyG);
1289         tmp.multiply(polyG);
1290         tmp.multiplyByScalar(-1.0);
1291 
1292         result.add(tmp);
1293 
1294         // (e1 + x*e2)^2 * (g1 + x*g2)^2
1295         tmp = polyE.multiplyAndReturnNew(polyE);
1296         tmp.multiply(polyG);
1297         tmp.multiply(polyG);
1298 
1299         result.add(tmp);
1300 
1301         return result;
1302     }
1303 }