View Javadoc

1   /**
2    * FaceTracker Licence
3    * -------------------
4    * (Academic, non-commercial, not-for-profit licence)
5    *
6    * Copyright (c) 2010 Jason Mora Saragih
7    * All rights reserved.
8    *
9    * Redistribution and use in source and binary forms, with or without
10   * modification, are permitted provided that the following conditions are met:
11   *
12   *     * The software is provided under the terms of this licence stricly for
13   *       academic, non-commercial, not-for-profit purposes.
14   *     * Redistributions of source code must retain the above copyright notice,
15   *       this list of conditions (licence) and the following disclaimer.
16   *     * Redistributions in binary form must reproduce the above copyright
17   *       notice, this list of conditions (licence) and the following disclaimer
18   *       in the documentation and/or other materials provided with the
19   *       distribution.
20   *     * The name of the author may not be used to endorse or promote products
21   *       derived from this software without specific prior written permission.
22   *     * As this software depends on other libraries, the user must adhere to and
23   *       keep in place any licencing terms of those libraries.
24   *     * Any publications arising from the use of this software, including but
25   *       not limited to academic journal and conference publications, technical
26   *       reports and manuals, must cite the following work:
27   *
28   *       J. M. Saragih, S. Lucey, and J. F. Cohn. Face Alignment through Subspace
29   *       Constrained Mean-Shifts. International Journal of Computer Vision
30   *       (ICCV), September, 2009.
31   *
32   * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED
33   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
34   * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
35   * EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
36   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
37   * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
38   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
39   * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
40   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
41   * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42   */
43  package com.jsaragih;
44  
45  import java.io.BufferedReader;
46  import java.io.BufferedWriter;
47  import java.io.FileNotFoundException;
48  import java.io.FileReader;
49  import java.io.FileWriter;
50  import java.io.IOException;
51  import java.util.Scanner;
52  
53  import org.openimaj.math.matrix.MatrixUtils;
54  
55  import com.jsaragih.CLM.SimTData;
56  
57  import Jama.Matrix;
58  import Jama.SingularValueDecomposition;
59  
60  /**
61   * 3D Point Distribution Model
62   *
63   * @author Jason Mora Saragih
64   * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
65   */
66  public class PDM {
67  	static { Tracker.init(); }
68  	
69  	/** basis of variation */
70  	public Matrix _V;
71  
72  	/** vector of eigenvalues (row vector) */
73  	public Matrix _E;
74  
75  	/** mean 3D shape vector [x1,..,xn,y1,...yn] */
76  	public Matrix _M;
77  
78  	private Matrix S_, R_, P_, Px_, Py_, Pz_, R1_, R2_, R3_;
79  
80  	/**
81  	 * Returns a copy of this PDM.
82  	 * 
83  	 * @return A copy of this PDM.
84  	 */
85  	public PDM copy() {
86  		PDM p = new PDM();
87  		p._V = _V.copy();
88  		p._E = _E.copy();
89  		p._M = _M.copy();
90  		p.S_ = S_.copy();
91  		p.R_ = R_.copy();
92  		p.P_ = P_.copy();
93  		p.Px_ = Px_.copy();
94  		p.Py_ = Py_.copy();
95  		p.Pz_ = Pz_.copy();
96  		p.R1_ = R1_.copy();
97  		p.R2_ = R2_.copy();
98  		p.R3_ = R3_.copy();
99  		return p;
100 	}
101 
102 	void addOrthRow(Matrix R) {
103 		assert ((R.getRowDimension() == 3) && (R.getColumnDimension() == 3));
104 
105 		R.set(2, 0, R.get(0, 1) * R.get(1, 2) - R.get(0, 2) * R.get(1, 1));
106 		R.set(2, 1, R.get(0, 2) * R.get(1, 0) - R.get(0, 0) * R.get(1, 2));
107 		R.set(2, 2, R.get(0, 0) * R.get(1, 1) - R.get(0, 1) * R.get(1, 0));
108 	}
109 
110 	void metricUpgrade(Matrix R) {
111 		assert ((R.getRowDimension() == 3) && (R.getColumnDimension() == 3));
112 		SingularValueDecomposition svd = R.svd();
113 
114 		Matrix X = svd.getU().times(svd.getV().transpose());
115 		Matrix W = Matrix.identity(3, 3);
116 		W.set(2, 2, X.det());
117 
118 		R.setMatrix(0, 3 - 1, 0, 3 - 1,
119 				svd.getU().times(W).times(svd.getV().transpose()));
120 	}
121 
122 	Matrix euler2Rot(final double pitch, final double yaw, final double roll) {
123 		return euler2Rot(pitch, yaw, roll, true);
124 	}
125 
126 	Matrix euler2Rot(final double pitch, final double yaw, final double roll,
127 			boolean full) {
128 		Matrix R;
129 		if (full) {
130 			R = new Matrix(3, 3);
131 		} else {
132 			R = new Matrix(2, 3);
133 		}
134 
135 		double sina = Math.sin(pitch), sinb = Math.sin(yaw), sinc = Math
136 				.sin(roll);
137 		double cosa = Math.cos(pitch), cosb = Math.cos(yaw), cosc = Math
138 				.cos(roll);
139 		R.set(0, 0, cosb * cosc);
140 		R.set(0, 1, -cosb * sinc);
141 		R.set(0, 2, sinb);
142 		R.set(1, 0, cosa * sinc + sina * sinb * cosc);
143 		R.set(1, 1, cosa * cosc - sina * sinb * sinc);
144 		R.set(1, 2, -sina * cosb);
145 
146 		if (full)
147 			addOrthRow(R);
148 
149 		return R;
150 	}
151 
152 	Matrix euler2Rot(Matrix p) {
153 		return euler2Rot(p, true);
154 	}
155 
156 	Matrix euler2Rot(Matrix p, boolean full) {
157 		assert ((p.getRowDimension() == 6) && (p.getColumnDimension() == 1));
158 		return euler2Rot(p.get(1, 0), p.get(2, 0), p.get(3, 0), full);
159 	}
160 
161 	double[] rot2Euler(Matrix R) {
162 		assert ((R.getRowDimension() == 3) && (R.getColumnDimension() == 3));
163 		double[] q = new double[4];
164 		q[0] = Math.sqrt(1 + R.get(0, 0) + R.get(1, 1) + R.get(2, 2)) / 2;
165 		q[1] = (R.get(2, 1) - R.get(1, 2)) / (4 * q[0]);
166 		q[2] = (R.get(0, 2) - R.get(2, 0)) / (4 * q[0]);
167 		q[3] = (R.get(1, 0) - R.get(0, 1)) / (4 * q[0]);
168 		double yaw = Math.asin(2 * (q[0] * q[2] + q[1] * q[3]));
169 		double pitch = Math.atan2(2 * (q[0] * q[1] - q[2] * q[3]), q[0] * q[0]
170 				- q[1] * q[1] - q[2] * q[2] + q[3] * q[3]);
171 		double roll = Math.atan2(2 * (q[0] * q[3] - q[1] * q[2]), q[0] * q[0]
172 				+ q[1] * q[1] - q[2] * q[2] - q[3] * q[3]);
173 		return new double[] { pitch, roll, yaw };
174 	}
175 
176 	void rot2Euler(Matrix R, Matrix p) {
177 		assert ((p.getRowDimension() == 6) && (p.getColumnDimension() == 1));
178 		double[] pry = rot2Euler(R);
179 
180 		p.set(1, 0, pry[0]);
181 		p.set(2, 0, pry[2]);
182 		p.set(3, 0, pry[1]);
183 	}
184 
185 	class AlignmentParams {
186 		double scale;
187 		double pitch;
188 		double yaw;
189 		double roll;
190 		double x;
191 		double y;
192 	}
193 
194 	void align3Dto2DShapes(AlignmentParams ap, Matrix s2D, Matrix s3D) {
195 		assert ((s2D.getColumnDimension() == 1)
196 				&& (s3D.getRowDimension() == 3 * (s2D.getRowDimension() / 2)) && (s3D
197 				.getColumnDimension() == 1));
198 
199 		final int n = s2D.getRowDimension() / 2;
200 		double[] t2 = new double[2];
201 		double[] t3 = new double[3];
202 
203 		Matrix X = MatrixUtils.reshape(s2D, 2).transpose();
204 		Matrix S = MatrixUtils.reshape(s3D, 3).transpose();
205 
206 		for (int i = 0; i < 2; i++) {
207 			t2[i] = MatrixUtils.sumColumn(X, i) / n;
208 			MatrixUtils.incrColumn(X, i, -t2[i]);
209 		}
210 
211 		for (int i = 0; i < 3; i++) {
212 			t3[i] = MatrixUtils.sumColumn(S, i) / n;
213 			MatrixUtils.incrColumn(S, i, -t3[i]);
214 		}
215 
216 		Matrix M = ((S.transpose().times(S)).inverse()).times(S.transpose())
217 				.times(X);
218 
219 		Matrix MtM = M.transpose().times(M);
220 
221 		SingularValueDecomposition svd = MtM.svd();
222 		Matrix svals = svd.getS();
223 		svals.set(0, 0, 1.0 / Math.sqrt(svals.get(0, 0)));
224 		svals.set(1, 1, 1.0 / Math.sqrt(svals.get(1, 1)));
225 
226 		Matrix T = new Matrix(3, 3);
227 		T.setMatrix(
228 				0,
229 				2 - 1,
230 				0,
231 				3 - 1,
232 				svd.getU().times(svals).times(svd.getV().transpose())
233 						.times(M.transpose()));
234 
235 		ap.scale = 0;
236 		for (int r = 0; r < 2; r++)
237 			for (int c = 0; c < 3; c++)
238 				ap.scale += T.get(r, c) * M.get(c, r);
239 		ap.scale *= 0.5;
240 
241 		addOrthRow(T);
242 
243 		double[] pyr = rot2Euler(T);
244 		ap.pitch = pyr[0];
245 		ap.roll = pyr[1];
246 		ap.yaw = pyr[2];
247 
248 		T = T.times(ap.scale);
249 
250 		ap.x = t2[0]
251 				- (T.get(0, 0) * t3[0] + T.get(0, 1) * t3[1] + T.get(0, 2)
252 						* t3[2]);
253 		ap.y = t2[1]
254 				- (T.get(1, 0) * t3[0] + T.get(1, 1) * t3[1] + T.get(1, 2)
255 						* t3[2]);
256 	}
257 
258 	void clamp(Matrix p, double c) {
259 		assert ((p.getRowDimension() == _E.getColumnDimension()) && (p
260 				.getColumnDimension() == 1));
261 
262 		for (int i = 0; i < p.getRowDimension(); i++) {
263 			double v = c * Math.sqrt(_E.get(0, i));
264 			double p1 = p.get(i, 0);
265 
266 			if (Math.abs(p1) > v) {
267 				if (p1 > 0.0) {
268 					p1 = v;
269 				} else {
270 					p1 = -v;
271 				}
272 			}
273 		}
274 	}
275 
276 	Matrix calcShape3D(Matrix plocal) {
277 		assert ((plocal.getRowDimension() == _E.getColumnDimension()) && (plocal
278 				.getColumnDimension() == 1));
279 
280 		Matrix s = _M.plus(_V.times(plocal));
281 
282 		return s;
283 	}
284 
285 	/**
286 	 * Calculate Shape 2D
287 	 * 
288 	 * @param s
289 	 * @param plocal
290 	 * @param pglobl
291 	 */
292 	public void calcShape2D(Matrix s, Matrix plocal, Matrix pglobl) {
293 		assert ((plocal.getRowDimension() == _E.getColumnDimension()) && (plocal
294 				.getColumnDimension() == 1));
295 		assert ((pglobl.getRowDimension() == 6) && (pglobl.getColumnDimension() == 1));
296 
297 		int n = _M.getRowDimension() / 3;
298 		double a = pglobl.get(0, 0);
299 		double x = pglobl.get(4, 0);
300 		double y = pglobl.get(5, 0);
301 
302 		R_ = euler2Rot(pglobl);
303 
304 		S_ = _M.plus(_V.times(plocal));
305 
306 		for (int i = 0; i < n; i++) {
307 			s.set(i,
308 					0,
309 					a
310 							* (R_.get(0, 0) * S_.get(i, 0) + R_.get(0, 1)
311 									* S_.get(i + n, 0) + R_.get(0, 2)
312 									* S_.get(i + n * 2, 0)) + x);
313 			s.set(i + n,
314 					0,
315 					a
316 							* (R_.get(1, 0) * S_.get(i, 0) + R_.get(1, 1)
317 									* S_.get(i + n, 0) + R_.get(1, 2)
318 									* S_.get(i + n * 2, 0)) + y);
319 		}
320 	}
321 
322 	/**
323 	 * Calculate the PDM parameters
324 	 * 
325 	 * @param s
326 	 * @param plocal
327 	 * @param pglobl
328 	 */
329 	public void calcParams(Matrix s, Matrix plocal, Matrix pglobl) {
330 		assert ((s.getRowDimension() == 2 * (_M.getRowDimension() / 3)) && (s
331 				.getColumnDimension() == 1));
332 
333 		int n = _M.getRowDimension() / 3;
334 
335 		Matrix R = new Matrix(3, 3);
336 		Matrix t = new Matrix(3, 1);
337 		Matrix p = new Matrix(_V.getColumnDimension(), 1);
338 
339 		MatrixUtils.zero(plocal);
340 
341 		AlignmentParams ap = new AlignmentParams();
342 
343 		for (int iter = 0; iter < 100; iter++) {
344 			S_ = calcShape3D(plocal);
345 
346 			align3Dto2DShapes(ap, s, S_);
347 
348 			R = euler2Rot(ap.pitch, ap.yaw, ap.roll);
349 
350 			Matrix r = new Matrix(new double[][] { R.getArray()[2] });
351 
352 			Matrix S = MatrixUtils.reshape(S_, 3).transpose();
353 
354 			Matrix z = (S.times(r.transpose())).times(ap.scale);
355 			double si = 1.0 / ap.scale;
356 
357 			double Tx = -si * (R.get(0, 0) * ap.x + R.get(1, 0) * ap.y);
358 			double Ty = -si * (R.get(0, 1) * ap.x + R.get(1, 1) * ap.y);
359 			double Tz = -si * (R.get(0, 2) * ap.x + R.get(1, 2) * ap.y);
360 
361 			for (int j = 0; j < n; j++) {
362 				t.set(0, 0, s.get(j, 0));
363 				t.set(1, 0, s.get(j + n, 0));
364 				t.set(2, 0, z.get(j, 0));
365 
366 				S_.set(j, 0, si * dotCol(t, R, 0) + Tx);
367 				S_.set(j + n, 0, si * dotCol(t, R, 1) + Ty);
368 				S_.set(j + n * 2, 0, si * dotCol(t, R, 2) + Tz);
369 			}
370 
371 			plocal.setMatrix(0, p.getRowDimension() - 1, 0, 1 - 1, _V
372 					.transpose().times(S_.minus(_M)));
373 
374 			if (iter > 0) {
375 				double norm = 0;
376 				for (int i = 0; i < plocal.getRowDimension(); i++) {
377 					double diff = plocal.get(i, 0) - p.get(i, 0);
378 					norm += Math.abs(diff * diff);
379 				}
380 				norm = Math.sqrt(norm);
381 
382 				if (norm < 1.0e-5)
383 					break;
384 			}
385 
386 			p.setMatrix(0, p.getRowDimension() - 1, 0, 1 - 1, plocal);
387 		}
388 
389 		pglobl.set(0, 0, ap.scale);
390 		pglobl.set(1, 0, ap.pitch);
391 		pglobl.set(2, 0, ap.yaw);
392 		pglobl.set(3, 0, ap.roll);
393 		pglobl.set(4, 0, ap.x);
394 		pglobl.set(5, 0, ap.y);
395 
396 		return;
397 	}
398 
399 	private double dotCol(Matrix colvec, Matrix m, int col) {
400 		final int rows = colvec.getRowDimension();
401 
402 		final double[][] colvec_arr = colvec.getArray();
403 		final double[][] m_arr = m.getArray();
404 
405 		double dp = 0;
406 		for (int i = 0; i < rows; i++)
407 			dp += colvec_arr[i][0] * m_arr[i][col];
408 
409 		return dp;
410 	}
411 
412 	/**
413 	 * Initialise the identify face parameters
414 	 * @param plocal
415 	 * @param pglobl
416 	 */
417 	public void identity(Matrix plocal, Matrix pglobl) {
418 		MatrixUtils.zero(plocal);
419 
420 		MatrixUtils.zero(pglobl);
421 		pglobl.set(0, 0, 1);
422 	}
423 
424 	void calcRigidJacob(Matrix plocal, Matrix pglobl, Matrix Jacob) {
425 		final int n = _M.getRowDimension() / 3;
426 		final int m = _V.getColumnDimension();
427 
428 		assert ((plocal.getRowDimension() == m)
429 				&& (plocal.getColumnDimension() == 1)
430 				&& (pglobl.getRowDimension() == 6)
431 				&& (pglobl.getColumnDimension() == 1)
432 				&& (Jacob.getRowDimension() == 2 * n) && (Jacob
433 				.getColumnDimension() == 6));
434 
435 		Matrix Rx = new Matrix(new double[][] { { 0, 0, 0 }, { 0, 0, -1 },
436 				{ 0, 1, 0 } });
437 		Matrix Ry = new Matrix(new double[][] { { 0, 0, 1 }, { 0, 0, 0 },
438 				{ -1, 0, 0 } });
439 		Matrix Rz = new Matrix(new double[][] { { 0, -1, 0 }, { 1, 0, 0 },
440 				{ 0, 0, 0 } });
441 
442 		double s = pglobl.get(0, 0);
443 
444 		S_ = calcShape3D(plocal);
445 
446 		R_ = euler2Rot(pglobl);
447 
448 		P_ = R_.getMatrix(0, 2 - 1, 0, 3 - 1).times(s);
449 		Px_ = P_.times(Rx);
450 		Py_ = P_.times(Ry);
451 		Pz_ = P_.times(Rz);
452 
453 		final double[][] px = Px_.getArray();
454 		final double[][] py = Py_.getArray();
455 		final double[][] pz = Pz_.getArray();
456 		final double[][] r = R_.getArray();
457 		final double[][] J = Jacob.getArray();
458 
459 		for (int i = 0; i < n; i++) {
460 			double X = S_.get(i, 0);
461 			double Y = S_.get(i + n, 0);
462 			double Z = S_.get(i + n * 2, 0);
463 			J[i][0] = r[0][0] * X + r[0][1] * Y + r[0][2] * Z;
464 			J[i + n][0] = r[1][0] * X + r[1][1] * Y + r[1][2] * Z;
465 			J[i][1] = px[0][0] * X + px[0][1] * Y + px[0][2] * Z;
466 			J[i + n][1] = px[1][0] * X + px[1][1] * Y + px[1][2] * Z;
467 			J[i][2] = py[0][0] * X + py[0][1] * Y + py[0][2] * Z;
468 			J[i + n][2] = py[1][0] * X + py[1][1] * Y + py[1][2] * Z;
469 			J[i][3] = pz[0][0] * X + pz[0][1] * Y + pz[0][2] * Z;
470 			J[i + n][3] = pz[1][0] * X + pz[1][1] * Y + pz[1][2] * Z;
471 			J[i][4] = 1.0;
472 			J[i + n][4] = 0.0;
473 			J[i][5] = 0.0;
474 			J[i + n][5] = 1.0;
475 		}
476 	}
477 
478 	void calcJacob(Matrix plocal, Matrix pglobl, Matrix Jacob) {
479 		final int n = _M.getRowDimension() / 3;
480 		final int m = _V.getColumnDimension();
481 
482 		assert ((plocal.getRowDimension() == m)
483 				&& (plocal.getColumnDimension() == 1)
484 				&& (pglobl.getRowDimension() == 6)
485 				&& (pglobl.getColumnDimension() == 1)
486 				&& (Jacob.getRowDimension() == 2 * n) && (Jacob
487 				.getColumnDimension() == 6 + m));
488 		double s = pglobl.get(0, 0);
489 
490 		Matrix Rx = new Matrix(new double[][] { { 0, 0, 0 }, { 0, 0, -1 },
491 				{ 0, 1, 0 } });
492 		Matrix Ry = new Matrix(new double[][] { { 0, 0, 1 }, { 0, 0, 0 },
493 				{ -1, 0, 0 } });
494 		Matrix Rz = new Matrix(new double[][] { { 0, -1, 0 }, { 1, 0, 0 },
495 				{ 0, 0, 0 } });
496 
497 		S_ = calcShape3D(plocal);
498 
499 		R_ = euler2Rot(pglobl);
500 
501 		P_ = R_.getMatrix(0, 2 - 1, 0, 3 - 1).times(s);
502 		Px_ = P_.times(Rx);
503 		Py_ = P_.times(Ry);
504 		Pz_ = P_.times(Rz);
505 
506 		final double[][] px = Px_.getArray();
507 		final double[][] py = Py_.getArray();
508 		final double[][] pz = Pz_.getArray();
509 		final double[][] p = P_.getArray();
510 		final double[][] r = R_.getArray();
511 
512 		final double[][] V = _V.getArray();
513 
514 		final double[][] J = Jacob.getArray();
515 
516 		for (int i = 0; i < n; i++) {
517 			double X = S_.get(i, 0);
518 			double Y = S_.get(i + n, 0);
519 			double Z = S_.get(i + n * 2, 0);
520 
521 			J[i][0] = r[0][0] * X + r[0][1] * Y + r[0][2] * Z;
522 			J[i + n][0] = r[1][0] * X + r[1][1] * Y + r[1][2] * Z;
523 			J[i][1] = px[0][0] * X + px[0][1] * Y + px[0][2] * Z;
524 			J[i + n][1] = px[1][0] * X + px[1][1] * Y + px[1][2] * Z;
525 			J[i][2] = py[0][0] * X + py[0][1] * Y + py[0][2] * Z;
526 			J[i + n][2] = py[1][0] * X + py[1][1] * Y + py[1][2] * Z;
527 			J[i][3] = pz[0][0] * X + pz[0][1] * Y + pz[0][2] * Z;
528 			J[i + n][3] = pz[1][0] * X + pz[1][1] * Y + pz[1][2] * Z;
529 			J[i][4] = 1.0;
530 			J[i + n][4] = 0.0;
531 			J[i][5] = 0.0;
532 			J[i + n][5] = 1.0;
533 
534 			for (int j = 0; j < m; j++) {
535 				J[i][6 + j] = p[0][0] * V[i][j] + p[0][1] * V[i + n][j]
536 						+ p[0][2] * V[i + 2 * n][j];
537 				J[i + n][6 + j] = p[1][0] * V[i][j] + p[1][1] * V[i + n][j]
538 						+ p[1][2] * V[i + 2 * n][j];
539 			}
540 		}
541 	}
542 
543 	void calcReferenceUpdate(Matrix dp, Matrix plocal, Matrix pglobl) {
544 		assert ((dp.getRowDimension() == 6 + _V.getColumnDimension()) && (dp
545 				.getColumnDimension() == 1));
546 
547 		plocal.setMatrix(0, plocal.getRowDimension() - 1, 0, plocal
548 				.getColumnDimension() - 1, plocal.plus(dp.getMatrix(6,
549 				6 + _V.getColumnDimension() - 1, 0, 1 - 1)));
550 
551 		pglobl.set(0, 0, pglobl.get(0, 0) + dp.get(0, 0));
552 		pglobl.set(4, 0, pglobl.get(4, 0) + dp.get(4, 0));
553 		pglobl.set(5, 0, pglobl.get(5, 0) + dp.get(5, 0));
554 
555 		R1_ = euler2Rot(pglobl);
556 
557 		R2_ = Matrix.identity(3, 3);
558 		R2_.set(2, 1, dp.get(1, 0));
559 		R2_.set(1, 2, -R2_.get(2, 1));
560 
561 		R2_.set(0, 2, dp.get(2, 0));
562 		R2_.set(2, 0, -R2_.get(0, 2));
563 
564 		R2_.set(1, 0, dp.get(3, 0));
565 		R2_.set(0, 1, -R2_.get(1, 0));
566 
567 		metricUpgrade(R2_);
568 		R3_ = R1_.times(R2_);
569 		rot2Euler(R3_, pglobl);
570 	}
571 
572 	void applySimT(SimTData data, Matrix pglobl) {
573 		assert ((pglobl.getRowDimension() == 6) && (pglobl.getColumnDimension() == 1));
574 
575 		double angle = Math.atan2(data.b, data.a);
576 		double scale = data.a / Math.cos(angle);
577 		double ca = Math.cos(angle);
578 		double sa = Math.sin(angle);
579 		double xc = pglobl.get(4, 0);
580 		double yc = pglobl.get(5, 0);
581 
582 		MatrixUtils.zero(R1_);
583 		R1_.set(2, 2, 1.0);
584 		R1_.set(0, 0, ca);
585 		R1_.set(0, 1, -sa);
586 		R1_.set(1, 0, sa);
587 		R1_.set(1, 1, ca);
588 
589 		R2_ = euler2Rot(pglobl);
590 		R3_ = R1_.times(R2_);
591 
592 		pglobl.set(0, 0, pglobl.get(0, 0) * scale);
593 		rot2Euler(R3_, pglobl);
594 
595 		pglobl.set(4, 0, data.a * xc - data.b * yc + data.tx);
596 		pglobl.set(5, 0, data.b * xc + data.a * yc + data.ty);
597 	}
598 
599 	static PDM read(Scanner s, boolean readType) {
600 		if (readType) {
601 			int type = s.nextInt();
602 			assert (type == IO.Types.PDM.ordinal());
603 		}
604 
605 		PDM pdm = new PDM();
606 
607 		pdm._V = IO.readMat(s);
608 		pdm._E = IO.readMat(s);
609 		pdm._M = IO.readMat(s);
610 
611 		pdm.S_ = new Matrix(pdm._M.getRowDimension(), 1);
612 		pdm.R_ = new Matrix(3, 3);
613 		pdm.P_ = new Matrix(2, 3);
614 		pdm.Px_ = new Matrix(2, 3);
615 		pdm.Py_ = new Matrix(2, 3);
616 		pdm.Pz_ = new Matrix(2, 3);
617 		pdm.R1_ = new Matrix(3, 3);
618 		pdm.R2_ = new Matrix(3, 3);
619 		pdm.R3_ = new Matrix(3, 3);
620 
621 		return pdm;
622 	}
623 
624 	void write(BufferedWriter s) throws IOException {
625 		s.write(IO.Types.PDM.ordinal() + " ");
626 
627 		IO.writeMat(s, _V);
628 		IO.writeMat(s, _E);
629 		IO.writeMat(s, _M);
630 	}
631 
632 	static PDM load(final String fname) throws FileNotFoundException {
633 		BufferedReader br = null;
634 		try {
635 			br = new BufferedReader(new FileReader(fname));
636 			Scanner sc = new Scanner(br);
637 			return read(sc, true);
638 		} finally {
639 			try {
640 				br.close();
641 			} catch (IOException e) {
642 			}
643 		}
644 	}
645 
646 	void save(final String fname) throws IOException {
647 		BufferedWriter bw = null;
648 		try {
649 			bw = new BufferedWriter(new FileWriter(fname));
650 
651 			write(bw);
652 		} finally {
653 			try {
654 				if (bw != null)
655 					bw.close();
656 			} catch (IOException e) {
657 			}
658 		}
659 	}
660 
661 	/**
662 	 * @return the number of points in the face model
663 	 */
664 	public final int nPoints() {
665 		return _M.getRowDimension() / 3;
666 	}
667 
668 	int nModes() {
669 		return _V.getColumnDimension();
670 	}
671 
672 	double var(int i) {
673 		assert (i < _E.getColumnDimension());
674 
675 		return _E.get(0, i);
676 	}
677 }