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 }