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 }