View Javadoc
1   /*
2    * Copyright (C) 2015 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.geometry.estimators;
17  
18  import com.irurueta.algebra.Matrix;
19  import com.irurueta.geometry.Point2D;
20  import com.irurueta.geometry.ProjectiveTransformation2D;
21  
22  import java.util.List;
23  
24  /**
25   * This class takes a collection of points and computes its average
26   * inhomogeneous coordinates and their scale so that a metric transformation is
27   * computed to transform points and normalize them.
28   * Normalized points are useful in many algorithms because due to the nature
29   * of floating point quantization, accuracy of the computations increase for
30   * normalized values between -1.0 and 1.0.
31   * This implementation uses point inhomogeneous coordinates, hence, it should
32   * not be used for points located at or close to infinity (i.e. very large
33   * inhomogeneous values).
34   */
35  public class Point2DNormalizer {
36  
37      /**
38       * Minimum amount of points required to perform normalization.
39       */
40      public static final int MIN_POINTS = 2;
41  
42      /**
43       * Collection of points used to compute normalization.
44       */
45      private List<Point2D> points;
46  
47      /**
48       * Flag indicating that this instance is locked because computation is
49       * in progress.
50       */
51      private boolean locked;
52  
53      /**
54       * Minimum x inhomogeneous coordinate found in provided points.
55       */
56      private double minInhomX;
57  
58      /**
59       * Minimum y inhomogeneous coordinate found in provided points.
60       */
61      private double minInhomY;
62  
63      /**
64       * Maximum x inhomogeneous coordinate found in provided points.
65       */
66      private double maxInhomX;
67  
68      /**
69       * Maximum y inhomogeneous coordinate found in provided points.
70       */
71      private double maxInhomY;
72  
73      /**
74       * Computed scale on x coordinates to normalize points.
75       */
76      private double scaleX;
77  
78      /**
79       * Computed scale on y coordinates to normalize points.
80       */
81      private double scaleY;
82  
83      /**
84       * Computed x coordinate of centroid of points.
85       */
86      private double centroidX;
87  
88      /**
89       * Computed y coordinate of centroid of points.
90       */
91      private double centroidY;
92  
93      /**
94       * Transformation to normalize points.
95       */
96      private ProjectiveTransformation2D transformation;
97  
98      /**
99       * Transformation to denormalize points, which corresponds to the inverse
100      * transformation.
101      */
102     private ProjectiveTransformation2D inverseTransformation;
103 
104     /**
105      * Constructor.
106      *
107      * @param points collection of points to be used to compute normalization.
108      * @throws IllegalArgumentException if provided collection of points does
109      *                                  not contain enough points, which is MIN_POINTS.
110      */
111     public Point2DNormalizer(final List<Point2D> points) {
112         internalSetPoints(points);
113         reset();
114     }
115 
116     /**
117      * Returns collection of points used to compute normalization.
118      *
119      * @return collection of points used to compute normalization.
120      */
121     public List<Point2D> getPoints() {
122         return points;
123     }
124 
125     /**
126      * Sets collection of points used to compute normalization.
127      *
128      * @param points collection of points used to compute normalization.
129      * @throws LockedException          if instance is locked because another computation
130      *                                  is already in progress.
131      * @throws IllegalArgumentException if provided collection of points does
132      *                                  not contain enough points, which is MIN_POINTS.
133      */
134     public void setPoints(final List<Point2D> points) throws LockedException {
135         if (isLocked()) {
136             throw new LockedException();
137         }
138         internalSetPoints(points);
139         reset();
140     }
141 
142     /**
143      * Indicates whether this instance is ready (i.e. has enough data) to
144      * start the computation.
145      *
146      * @return true if this instance is ready, false otherwise.
147      */
148     public boolean isReady() {
149         return points != null && points.size() >= MIN_POINTS;
150     }
151 
152     /**
153      * Indicates whether this instance is locked because computation is
154      * in progress.
155      * While an instance is in progress, no parameter can be modified and
156      * no further computations can be done until instance becomes unlocked.
157      *
158      * @return true if instance is locked, false otherwise.
159      */
160     public boolean isLocked() {
161         return locked;
162     }
163 
164     /**
165      * Returns minimum x inhomogeneous coordinate found in provided points.
166      *
167      * @return minimum x inhomogeneous coordinate found in provided points.
168      */
169     public double getMinInhomX() {
170         return minInhomX;
171     }
172 
173     /**
174      * Returns minimum y inhomogeneous coordinate found in provided points.
175      *
176      * @return minimum y inhomogeneous coordinate found in provided points.
177      */
178     public double getMinInhomY() {
179         return minInhomY;
180     }
181 
182     /**
183      * Returns maximum x inhomogeneous coordinate found in provided points.
184      *
185      * @return maximum x inhomogeneous coordinate found in provided points.
186      */
187     public double getMaxInhomX() {
188         return maxInhomX;
189     }
190 
191     /**
192      * Returns maximum y inhomogeneous coordinate found in provided points.
193      *
194      * @return maximum y inhomogeneous coordinate found in provided points.
195      */
196     public double getMaxInhomY() {
197         return maxInhomY;
198     }
199 
200     /**
201      * Returns computed scale to normalize points on x coordinate.
202      *
203      * @return computed scale to normalize points on x coordinate.
204      */
205     public double getScaleX() {
206         return scaleX;
207     }
208 
209     /**
210      * Returns computed scale to normalize points on y coordinate.
211      *
212      * @return computed scale to normalize points on y coordinate.
213      */
214     public double getScaleY() {
215         return scaleY;
216     }
217 
218     /**
219      * Returns computed x coordinate of centroid of points.
220      *
221      * @return computed x coordinate of centroid of points.
222      */
223     public double getCentroidX() {
224         return centroidX;
225     }
226 
227     /**
228      * Returns computed y coordinate of centroid of points.
229      *
230      * @return computed y coordinate of centroid of points.
231      */
232     public double getCentroidY() {
233         return centroidY;
234     }
235 
236     /**
237      * Returns transformation to normalize points.
238      *
239      * @return transformation to normalize points.
240      */
241     public ProjectiveTransformation2D getTransformation() {
242         return transformation;
243     }
244 
245     /**
246      * Returns transformation to denormalize points, which corresponds to the
247      * inverse transformation.
248      *
249      * @return transformation to denormalize points.
250      */
251     public ProjectiveTransformation2D getInverseTransformation() {
252         return inverseTransformation;
253     }
254 
255     /**
256      * Indicates whether result (i.e. transformation and inverse transformation)
257      * are available or not.
258      *
259      * @return true if result is available, false otherwise.
260      */
261     public boolean isResultAvailable() {
262         return transformation != null;
263     }
264 
265     /**
266      * Computes normalization and de-normalization transformations.
267      *
268      * @throws NotReadyException   if not enough data has been provided to
269      *                             compute normalization.
270      * @throws LockedException     if instance is locked because another computation
271      *                             is already in progress.
272      * @throws NormalizerException if normalization failed due to numerical
273      *                             degeneracy. This usually happens when all provided points are located too
274      *                             close to each other, which results in a singularity when computing proper
275      *                             normalization scale.
276      */
277     public void compute() throws NotReadyException, LockedException, NormalizerException {
278         if (!isReady()) {
279             throw new NotReadyException();
280         }
281         if (isLocked()) {
282             throw new LockedException();
283         }
284         try {
285             locked = true;
286 
287             reset();
288             computeLimits();
289 
290             // compute scale and centroids
291             final var width = maxInhomX - minInhomX;
292             final var height = maxInhomY - minInhomY;
293 
294             if (width < Double.MIN_VALUE || height < Double.MIN_VALUE) {
295                 // numerical degeneracy
296                 throw new NormalizerException();
297             }
298 
299             scaleX = 1.0 / width;
300             scaleY = 1.0 / height;
301 
302             // centroids of points
303             centroidX = (minInhomX + maxInhomX) / 2.0;
304             centroidY = (minInhomY + maxInhomY) / 2.0;
305 
306             // transformation to normalize points
307             final var t = new Matrix(ProjectiveTransformation2D.HOM_COORDS, ProjectiveTransformation2D.HOM_COORDS);
308 
309             // X' = s * X + s * t -->
310             // s * X = X' - s * t -->
311             // X = 1/s*X' - t
312             t.setElementAt(0, 0, scaleX);
313             t.setElementAt(1, 1, scaleY);
314             t.setElementAt(0, 2, -scaleX * centroidX);
315             t.setElementAt(1, 2, -scaleY * centroidY);
316             t.setElementAt(2, 2, 1.0);
317 
318             transformation = new ProjectiveTransformation2D(t);
319             transformation.normalize();
320 
321             // transformation to denormalize points
322             final var invT = new Matrix(ProjectiveTransformation2D.HOM_COORDS, ProjectiveTransformation2D.HOM_COORDS);
323 
324             invT.setElementAt(0, 0, width);
325             invT.setElementAt(1, 1, height);
326             invT.setElementAt(0, 2, centroidX);
327             invT.setElementAt(1, 2, centroidY);
328             invT.setElementAt(2, 2, 1.0);
329 
330             inverseTransformation = new ProjectiveTransformation2D(invT);
331             inverseTransformation.normalize();
332         } catch (final Exception e) {
333             throw new NormalizerException(e);
334         } finally {
335             locked = false;
336         }
337     }
338 
339     /**
340      * Computes minimum and maximum inhomogeneous point coordinates from the
341      * list of provided 2D points.
342      */
343     @SuppressWarnings("DuplicatedCode")
344     private void computeLimits() {
345         for (final var point : points) {
346             final var inhomX = point.getInhomX();
347             final var inhomY = point.getInhomY();
348             if (inhomX < minInhomX) {
349                 minInhomX = inhomX;
350             }
351             if (inhomY < minInhomY) {
352                 minInhomY = inhomY;
353             }
354 
355             if (inhomX > maxInhomX) {
356                 maxInhomX = inhomX;
357             }
358             if (inhomY > maxInhomY) {
359                 maxInhomY = inhomY;
360             }
361         }
362     }
363 
364     /**
365      * Sets list of points.
366      *
367      * @param points list of points to be set.
368      * @throws IllegalArgumentException if not enough points are provided, which
369      *                                  is MIN_POINTS.
370      */
371     private void internalSetPoints(final List<Point2D> points) {
372         if (points.size() < MIN_POINTS) {
373             throw new IllegalArgumentException();
374         }
375         this.points = points;
376     }
377 
378     /**
379      * Resets internal values.
380      */
381     private void reset() {
382         // reset result
383         transformation = inverseTransformation = null;
384         // reset limits
385         minInhomX = minInhomY = Double.MAX_VALUE;
386         maxInhomX = maxInhomY = -Double.MAX_VALUE;
387         scaleX = scaleY = 1.0;
388         centroidX = centroidY = 0.0;
389     }
390 }