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