00001 package edu.stanford.hci.r3.pen.gesture;
00002
00003 import java.util.ArrayList;
00004
00005 import cern.colt.matrix.DoubleMatrix2D;
00006 import cern.colt.matrix.impl.DenseDoubleMatrix2D;
00007 import cern.colt.matrix.linalg.Algebra;
00008 import cern.colt.matrix.linalg.SingularValueDecomposition;
00009
00020 public class ShapeHistogram {
00021 static public class Pair {
00022 public int column;
00023
00024 public int row;
00025
00026 public Pair(int row, int column) {
00027 this.row = row;
00028 this.column = column;
00029 }
00030 }
00031
00032 public static double costWeighting = .3;
00033
00034 static Pair Z0;
00035
00036
00037 static public DoubleMatrix2D bookstein(int N, double[][] X, double[][] X2, double beta_k,
00038 double[] E) {
00039
00040
00041
00042 double[][] r2 = new double[N + 3][N + 3];
00043 for (int i = 0; i < N; i++)
00044 for (int j = i; j < N; j++)
00045 r2[i][j] = r2[j][i] = Math.pow(X[i][0] - X[j][0], 2)
00046 + Math.pow(X[i][1] - X[j][1], 2);
00047 for (int i = 0; i < N; i++)
00048 r2[i][i] *= Math.log(r2[i][i] + 1);
00049 for (int i = 0; i < N; i++)
00050 for (int j = i + 1; j < N; j++)
00051 r2[i][j] = r2[j][i] = r2[j][i] * Math.log(r2[j][i]);
00052 DoubleMatrix2D K = new DenseDoubleMatrix2D(r2);
00053 for (int i = 0; i < N; i++) {
00054 r2[i][i] += beta_k;
00055 r2[i][N] = r2[N][i] = 1;
00056 r2[i][N + 1] = r2[N + 1][i] = X[i][0];
00057 r2[i][N + 2] = r2[N + 2][i] = X[i][1];
00058 }
00059
00060
00061 Algebra algebra = new Algebra();
00062 DoubleMatrix2D L = new DenseDoubleMatrix2D(r2);
00063 DoubleMatrix2D invL = algebra.inverse(L);
00064 double[][] v = new double[N + 3][2];
00065 for (int i = 0; i < N; i++) {
00066 v[i][0] = X2[i][0];
00067 v[i][1] = X2[i][1];
00068 }
00069 DoubleMatrix2D V = new DenseDoubleMatrix2D(v);
00070 DoubleMatrix2D c = algebra.mult(invL, V);
00071 DoubleMatrix2D Qpart = algebra.mult(K, c);
00072 DoubleMatrix2D Q = algebra.mult(algebra.transpose(c), Qpart);
00073
00074 E[0] = algebra.trace(Q) / N;
00075
00076 double[][] a = new double[2][2];
00077 a[0][0] = c.getQuick(N + 1, 0);
00078 a[0][1] = c.getQuick(N + 1, 1);
00079 a[1][0] = c.getQuick(N + 2, 0);
00080 a[1][1] = c.getQuick(N + 2, 1);
00081 if (a[0][0] == Double.NaN)
00082 System.exit(-1);
00083 SingularValueDecomposition s = new SingularValueDecomposition(new DenseDoubleMatrix2D(a));
00084 E[1] = Math.log(s.cond());
00085 return c;
00086 }
00087
00088 static public void clear_covers(int n, double[][] C, int[][] M, int[] R_cov, int[] C_cov) {
00089 for (int i = 0; i < n; i++) {
00090 R_cov[i] = 0;
00091 C_cov[i] = 0;
00092 }
00093 }
00094
00095 static public double[][] computeCostMatrix(ArrayList<ShapeHistogram> first,
00096 ArrayList<ShapeHistogram> second, int size1, int size2, double dummy_epsilon) {
00097 assert (first.size() == second.size());
00098 int points = first.size();
00099 double[][] cost = new double[points][points];
00100 for (int i = 0; i < points; i++) {
00101 ShapeHistogram s1 = first.get(i);
00102 for (int j = 0; j < points; j++) {
00103 if (i >= size1 || j >= size2) {
00104 cost[i][j] = dummy_epsilon;
00105 continue;
00106 }
00107 ShapeHistogram s2 = second.get(j);
00108 double sum = 0;
00109 for (int k = 0; k < s1.data.length; k++) {
00110 int s1k = s1.data[k];
00111 int s2k = s2.data[k];
00112 if (s1k == s2k)
00113 continue;
00114 sum += Math.pow(s1k - s2k, 2) / (double) (s1k + s2k);
00115 }
00116 cost[i][j] = .5 * sum;
00117 }
00118 }
00119 return cost;
00120 }
00121
00122 static public void convert_path(int n, double[][] C, int[][] M, int[] R_cov, int[] C_cov,
00123 ArrayList<Pair> path) {
00124 for (int i = 0; i < path.size(); i++) {
00125 Pair point = path.get(i);
00126 if (M[point.row][point.column] == 1)
00127 M[point.row][point.column] = 0;
00128 else
00129 M[point.row][point.column] = 1;
00130 }
00131 }
00132
00133 static public double distance(double[][] costMatrix, int[] mapping, int firstSize,
00134 int secondSize) {
00135
00136 int points = mapping.length;
00137 double cost = 0;
00138 int count = Math.min(firstSize, secondSize);
00139 for (int i = 0; i < points; i++) {
00140 int first = i;
00141 int second = mapping[i];
00142 if (first >= firstSize || second >= secondSize)
00143 continue;
00144 cost += costMatrix[i][mapping[i]];
00145 }
00146 return cost / count;
00147 }
00148
00149 static public void erase_primes(int n, double[][] C, int[][] M, int[] R_cov, int[] C_cov) {
00150 for (int i = 0; i < n; i++) {
00151 for (int j = 0; j < n; j++) {
00152 if (M[i][j] == 2)
00153 M[i][j] = 0;
00154 }
00155 }
00156 }
00157
00158 static public Pair find_a_zero(int n, double[][] C, int[][] M, int[] R_cov, int[] C_cov) {
00159 int row = -1;
00160 int col = -1;
00161 boolean done = false;
00162 for (int i = 0; i < n && !done; i++)
00163 for (int j = 0; j < n; j++) {
00164 if (C[i][j] == 0 && R_cov[i] == 0 && C_cov[j] == 0) {
00165 row = i;
00166 col = j;
00167 done = true;
00168 }
00169 }
00170 return new Pair(row, col);
00171 }
00172
00173 static public Pair find_prime_in_row(int n, double[][] C, int[][] M, int[] R_cov, int[] C_cov,
00174 int row) {
00175 int column = -1;
00176 for (int j = 0; j < n; j++)
00177 if (M[row][j] == 2)
00178 column = j;
00179 return new Pair(row, column);
00180 }
00181
00182 static public double find_smallest(int n, double[][] C, int[][] M, int[] R_cov, int[] C_cov) {
00183 double minval = Double.MAX_VALUE;
00184 for (int i = 0; i < n; i++)
00185 for (int j = 0; j < n; j++)
00186 if (R_cov[i] == 0 && C_cov[j] == 0 && minval > C[i][j])
00187 minval = C[i][j];
00188 return minval;
00189 }
00190
00191 static public Pair find_star_in_col(int n, double[][] C, int[][] M, int[] R_cov, int[] C_cov,
00192 int column) {
00193 int row = -1;
00194 for (int i = 0; i < n; i++)
00195 if (M[i][column] == 1)
00196 row = i;
00197 return new Pair(row, column);
00198 }
00199
00200 static public int find_star_in_row(int n, double[][] C, int[][] M, int[] R_cov, int[] C_cov,
00201 int row) {
00202 int col = -1;
00203 for (int j = 0; j < n; j++)
00204 if (M[row][j] == 1)
00205 col = j;
00206 return col;
00207 }
00208
00209
00210 static public int[] munkres(int n, double[][] costs) {
00211
00212
00213
00214
00215 int stepnum;
00216 boolean done;
00217 double[][] C = new double[n][n];
00218 int[][] M = new int[n][n];
00219 int[] Row = new int[n];
00220 int[] Col = new int[n];
00221
00222 for (int j = 0; j < n; j++)
00223 for (int i = 0; i < n; i++)
00224 C[j][i] = costs[j][i];
00225
00226 done = false;
00227 stepnum = 1;
00228 while (!done) {
00229 switch (stepnum) {
00230 case 1:
00231 stepnum = step1(n, C, M, Row, Col);
00232 break;
00233 case 2:
00234 stepnum = step2(n, C, M, Row, Col);
00235 break;
00236 case 3:
00237 stepnum = step3(n, C, M, Row, Col);
00238 break;
00239 case 4:
00240 stepnum = step4(n, C, M, Row, Col);
00241 break;
00242 case 5:
00243 stepnum = step5(n, C, M, Row, Col);
00244 break;
00245 case 6:
00246 stepnum = step6(n, C, M, Row, Col);
00247 break;
00248 default:
00249 done = true;
00250 }
00251 }
00252 int[] matching = new int[n];
00253 for (int i = 0; i < n; i++)
00254 for (int j = 0; j < n; j++)
00255 if (M[i][j] == 1) {
00256 matching[i] = j;
00257 continue;
00258 }
00259 return matching;
00260 }
00261
00262 static public double shapeContextCost(int N, double[][] costs, boolean[] good_rows,
00263 boolean[] good_columns, int good_count) {
00264 double rowMin = 0, colMin = 0;
00265 for (int i = 0; i < N; i++) {
00266 if (!good_rows[i])
00267 continue;
00268 double m = Double.MAX_VALUE;
00269 for (int j = 0; j < N; j++)
00270 if (good_columns[j] && costs[i][j] < m)
00271 m = costs[i][j];
00272 colMin += m;
00273 }
00274 colMin /= good_count;
00275 for (int j = 0; j < N; j++) {
00276 if (!good_columns[j])
00277 continue;
00278 double m = Double.MAX_VALUE;
00279 for (int i = 0; i < N; i++)
00280 if (good_rows[i] && costs[i][j] < m)
00281 m = costs[i][j];
00282 rowMin += m;
00283 }
00284 rowMin /= good_count;
00285 return Math.max(rowMin, colMin);
00286 }
00287
00288 public static double shapeContextMetric(ShapeContext shape1, ShapeContext shape2,
00289 boolean rotationInvariant, boolean timeSensitive, boolean verbose) {
00290 int dummy_padding = 6;
00291 int N = Math.max(shape1.size(), shape2.size()) + dummy_padding;
00292 int n = N;
00293 ArrayList<ShapeHistogram> histogram1 = shape1.generateShapeHistogram(N, dummy_padding,
00294 rotationInvariant, timeSensitive);
00295 ArrayList<ShapeHistogram> histogram2 = shape2.generateShapeHistogram(N, dummy_padding,
00296 rotationInvariant, timeSensitive);
00297
00298 double[][] costs = computeCostMatrix(histogram1, histogram2, shape1.size(), shape2.size(),
00299 10);
00300 int[] matching = munkres(N, costs);
00301 double[][] X1_new = shape1.points(N - dummy_padding);
00302 double[][] X2_new = shape2.points(N - dummy_padding);
00303
00304
00305
00306 int count = 0;
00307 double[][] X1 = new double[n - dummy_padding][2];
00308 double[][] X2 = new double[n - dummy_padding][2];
00309 boolean[] good_rows = new boolean[N];
00310 boolean[] good_columns = new boolean[N];
00311 double mean_x1 = 0, mean_x2 = 0, mean_y1 = 0, mean_y2 = 0;
00312 for (int i = 0; i < X1_new.length; i++)
00313 if (matching[i] < X2_new.length) {
00314 X1[count][0] = X1_new[i][0];
00315 X1[count][1] = X1_new[i][1];
00316 X2[count][0] = X2_new[matching[i]][0];
00317 X2[count][1] = X2_new[matching[i]][1];
00318 mean_x1 += X1[count][0];
00319 mean_y1 += X1[count][1];
00320 mean_x2 += X2[count][0];
00321 mean_y2 += X2[count][1];
00322 good_rows[i] = true;
00323 good_columns[matching[i]] = true;
00324 count++;
00325 }
00326 mean_x1 /= count;
00327 mean_y1 /= count;
00328 mean_x2 /= count;
00329 mean_y2 /= count;
00330
00331
00332 for (int i = 0; i < count; i++) {
00333 X1[i][0] -= mean_x1;
00334 X1[i][1] -= mean_y1;
00335 X2[i][0] -= mean_x2;
00336 X2[i][1] -= mean_y2;
00337
00338 }
00339
00340 double[] eArray = new double[2];
00341 double dist = 0;
00342 for (int i = 0; i < count; i++)
00343 for (int j = i + 1; j < count; j++)
00344 dist += Math.sqrt(Math.pow(X1[i][0] - X1[j][0], 2)
00345 + Math.pow(X1[i][1] - X1[j][1], 2));
00346 dist /= (n * (n - 1)) / 2;
00347 double beta_k = Math.pow(dist, 2) * 1;
00348 DoubleMatrix2D c = bookstein(count, X1, X2, beta_k, eArray);
00349 double sc_cost = shapeContextCost(N, costs, good_rows, good_columns, count);
00350 double[][] Xwarped = warp(X1, X2, c, count);
00351 double mean_squared_error = 0;
00352 for (int i = 0; i < count; i++) {
00353 mean_squared_error += Math.sqrt(Math.pow(Xwarped[i][0] - X2[i][0], 2)
00354 + Math.pow(Xwarped[i][1] - X2[i][1], 2));
00355 }
00356 mean_squared_error /= count;
00357
00358
00359 double total_cost = sc_cost + costWeighting * eArray[0];
00360 if (verbose) {
00361
00362
00363
00364
00365 }
00366
00367
00368
00369
00370
00371
00372
00373
00374
00375
00376
00377 return total_cost;
00378
00379 }
00380
00381
00382
00383
00384
00385
00386
00387
00388
00389
00390 static public boolean star_in_row(int n, double[][] C, int[][] M, int[] R_cov, int[] C_cov,
00391 int row) {
00392 for (int j = 0; j < n; j++)
00393 if (M[row][j] == 1)
00394 return true;
00395 return false;
00396 }
00397
00398 static public int step1(int n, double[][] C, int[][] M, int[] Row, int[] Col) {
00399 double minval = 0;
00400 for (int i = 0; i < n; i++) {
00401 minval = C[i][0];
00402 for (int j = 1; j < n; j++) {
00403 if (minval > C[i][j])
00404 minval = C[i][j];
00405 }
00406 for (int j = 0; j < n; j++) {
00407 C[i][j] -= minval;
00408 }
00409 }
00410 return 2;
00411 }
00412
00413 static public int step2(int n, double[][] C, int[][] M, int[] R_cov, int[] C_cov) {
00414 for (int i = 0; i < n; i++) {
00415 for (int j = 0; j < n; j++) {
00416 if (C[i][j] == 0 && C_cov[j] == 0 && R_cov[i] == 0) {
00417 M[i][j] = 1;
00418 C_cov[j] = 1;
00419 R_cov[i] = 1;
00420 }
00421 }
00422 }
00423 for (int i = 0; i < n; i++) {
00424 C_cov[i] = 0;
00425 R_cov[i] = 0;
00426 }
00427 return 3;
00428 }
00429
00430 static public int step3(int n, double[][] C, int[][] M, int[] R_cov, int[] C_cov) {
00431 int count;
00432 for (int i = 0; i < n; i++) {
00433 for (int j = 0; j < n; j++) {
00434 if (M[i][j] == 1) {
00435 C_cov[j] = 1;
00436 }
00437 }
00438 }
00439 count = 0;
00440 for (int j = 0; j < n; j++)
00441 count = count + C_cov[j];
00442 int step;
00443 if (count >= n)
00444 step = 7;
00445 else
00446 step = 4;
00447 return step;
00448 }
00449
00450 static public int step4(int n, double[][] C, int[][] M, int[] R_cov, int[] C_cov) {
00451 while (true) {
00452 Pair pair = find_a_zero(n, C, M, R_cov, C_cov);
00453 if (pair.row == -1)
00454 return 6;
00455 M[pair.row][pair.column] = 2;
00456 if (star_in_row(n, C, M, R_cov, C_cov, pair.row)) {
00457 int col = find_star_in_row(n, C, M, R_cov, C_cov, pair.row);
00458 R_cov[pair.row] = 1;
00459 C_cov[col] = 0;
00460 } else {
00461 Z0 = pair;
00462 return 5;
00463 }
00464 }
00465 }
00466
00467 static public int step5(int n, double[][] C, int[][] M, int[] R_cov, int[] C_cov) {
00468 int count = 1;
00469 ArrayList<Pair> path = new ArrayList<Pair>();
00470 path.add(Z0);
00471 boolean done = false;
00472 while (!done) {
00473 Pair last = path.get(path.size() - 1);
00474 Pair star = find_star_in_col(n, C, M, R_cov, C_cov, last.column);
00475 if (star.row > -1) {
00476 path.add(new Pair(star.row, last.column));
00477 } else
00478 done = true;
00479 if (!done) {
00480 Pair prime = find_prime_in_row(n, C, M, R_cov, C_cov, star.row);
00481 path.add(new Pair(star.row, prime.column));
00482 }
00483 }
00484 convert_path(n, C, M, R_cov, C_cov, path);
00485 clear_covers(n, C, M, R_cov, C_cov);
00486 erase_primes(n, C, M, R_cov, C_cov);
00487 return 3;
00488 }
00489
00490 static public int step6(int n, double[][] C, int[][] M, int[] R_cov, int[] C_cov) {
00491 double minval = find_smallest(n, C, M, R_cov, C_cov);
00492 for (int i = 0; i < n; i++)
00493 for (int j = 0; j < n; j++) {
00494 if (R_cov[i] == 1)
00495 C[i][j] += minval;
00496 if (C_cov[j] == 0)
00497 C[i][j] -= minval;
00498 }
00499 return 4;
00500 }
00501
00502 static public double[][] warp(double[][] X, double[][] X2, DoubleMatrix2D c, int N) {
00503 Algebra algebra = new Algebra();
00504 double[][] d2 = new double[N][N];
00505 final double eps = 1e-10;
00506 for (int i = 0; i < N; i++)
00507 for (int j = i; j < N; j++) {
00508 double tmp = Math.pow(X[i][0] - X[j][0], 2) + Math.pow(X[i][1] - X[j][1], 2);
00509 d2[i][j] = d2[j][i] = tmp * Math.log(tmp + eps);
00510 }
00511 DoubleMatrix2D cCoord = new DenseDoubleMatrix2D(N, 2);
00512 for (int i = 0; i < N; i++) {
00513 cCoord.setQuick(i, 0, c.getQuick(i, 0));
00514 cCoord.setQuick(i, 1, c.getQuick(i, 1));
00515 }
00516
00517 DoubleMatrix2D warped = algebra
00518 .mult(algebra.transpose(cCoord), new DenseDoubleMatrix2D(d2));
00519
00520 DoubleMatrix2D partial = new DenseDoubleMatrix2D(2, 3);
00521 for (int i = 0; i < 3; i++) {
00522 partial.setQuick(0, i, c.getQuick(N + i, 0));
00523 partial.setQuick(1, i, c.getQuick(N + i, 1));
00524 }
00525 double[][] paddedX = new double[3][N];
00526 for (int i = 0; i < N; i++) {
00527 paddedX[0][i] = 1;
00528 paddedX[1][i] = X[i][0];
00529 paddedX[2][i] = X[i][1];
00530 }
00531 DoubleMatrix2D affine = algebra.mult(partial, new DenseDoubleMatrix2D(paddedX));
00532 double[][] result = new double[N][2];
00533 for (int i = 0; i < N; i++) {
00534 result[i][0] = affine.getQuick(0, i) + warped.getQuick(0, i);
00535 result[i][1] = affine.getQuick(1, i) + warped.getQuick(1, i);
00536 }
00537 return result;
00538 }
00539
00540 int bands;
00541
00542 int[] bin_counts = null;
00543
00544 double[][] bins = null;
00545
00546 int[] data = null;
00547
00548 boolean[] explicit_binning = null;
00549
00550 double[] maxes = null;
00551
00552 double[] mins = null;
00553
00554 public ShapeHistogram() {
00555 }
00556
00557
00558 public ShapeHistogram(int[] bin_counts, double[] mins, double[] maxes,
00559 boolean[] explicit_binning, double[][] bins, int bands) {
00560
00561 int size = 1;
00562 for (int i = 0; i < bands; i++)
00563 size *= bin_counts[i];
00564 data = new int[size];
00565 this.bands = bands;
00566 this.bins = bins;
00567 this.bin_counts = bin_counts;
00568 this.mins = mins;
00569 this.maxes = maxes;
00570 this.explicit_binning = explicit_binning;
00571 }
00572
00573 public boolean addPoint(double[] input) {
00574 assert (input.length == bands);
00575 int multiplier = 1;
00576 int index = 0;
00577 for (int i = 0; i < bands; i++) {
00578 int subindex;
00579 int bin_count = bin_counts[i];
00580 if (explicit_binning[i]) {
00581 for (subindex = 0; subindex < bin_counts[i]; subindex++)
00582 if (input[i] <= bins[i][subindex])
00583 break;
00584 if (subindex == bin_counts[i])
00585 return false;
00586 } else
00587 subindex = Math.min(
00588 (int) (((input[i] - mins[i]) / (maxes[i] - mins[i])) * bin_count),
00589 bin_count - 1);
00590 index += multiplier * subindex;
00591 multiplier *= bin_count;
00592 }
00593 data[index]++;
00594 return true;
00595 }
00596 }