00001 package edu.stanford.hci.r3.pen.gesture;
00002
00003 import java.io.IOException;
00004 import java.io.Writer;
00005 import java.text.DecimalFormat;
00006 import java.util.ArrayList;
00007
00008 import edu.stanford.hci.r3.pen.PenSample;
00009
00020 public class ShapeContext {
00021
00022 public static int bands = 3;
00023
00024 public static double[] logPolarAndTime(PenSample first, PenSample second, double distanceScaling,
00025 double baseRotation) {
00026
00027
00028 double dx = second.x - first.x;
00029 double dy = second.y - first.y;
00030 double[] results = new double[3];
00031 if (dx == 0 && dy == 0) {
00032 results[0] = distanceScaling;
00033 results[1] = 0;
00034 } else {
00035 results[0] = Math.sqrt(dx * dx + dy * dy) / distanceScaling;
00036
00037 results[1] = renormalize(Math.atan2(dy, dx) - baseRotation);
00038 }
00039 results[2] = second.timestamp - first.timestamp;
00040 return results;
00041 }
00042
00043 public static double renormalize(double theta) {
00044 if (theta < -Math.PI)
00045 return theta + 2 * Math.PI;
00046 if (theta > Math.PI)
00047 return theta - 2 * Math.PI;
00048 return theta;
00049
00050 }
00051
00052 String authorName;
00053
00054 ArrayList<PenSample> controlPoints = new ArrayList<PenSample>();
00055
00056 public ShapeContext(ArrayList<PenSample> controlPointsInput, String authorName) {
00057 this.authorName = authorName;
00058
00059 double[] times = new double[controlPointsInput.size()];
00060 for (int i = 0; i < controlPointsInput.size(); i++) {
00061 PenSample sample = controlPointsInput.get(i);
00062 boolean duplicate = false;
00063 for (PenSample old_sample : controlPoints)
00064 if (sample.x == old_sample.x && sample.y == old_sample.y) {
00065
00066
00067 duplicate = true;
00068 break;
00069 }
00070 if (duplicate)
00071 continue;
00072 times[controlPoints.size()] = sample.timestamp;
00073 controlPoints.add(sample);
00074 }
00075
00076 for (int i = 1; i < controlPoints.size() - 1; i++) {
00077 controlPoints.get(i).timestamp = (long) ((times[i - 1] + times[i] + times[i + 1]) / 3);
00078 }
00079
00080
00081 }
00082
00083 double blend(int i, double t) {
00084 switch (i) {
00085 case 0:
00086 return ((-t + 2) * t - 1) * t / 2;
00087 case 1:
00088 return (((3 * t - 5) * t) * t + 2) / 2;
00089 case 2:
00090 return ((-3 * t + 4) * t + 1) * t / 2;
00091 case 3:
00092 return ((t - 1) * t * t) / 2;
00093 }
00094 return 0;
00095 }
00096
00097
00098 PenSample blendHelper(int i, double t) {
00099 PenSample[] samples = new PenSample[4];
00100 PenSample blendedSample = new PenSample(0, 0, (int) 0, (long) 0);
00101 if (i == 0)
00102 samples[0] = controlPoints.get(0);
00103 else
00104 samples[0] = controlPoints.get(i - 1);
00105 samples[1] = controlPoints.get(i);
00106 samples[2] = controlPoints.get(i + 1);
00107 if (i + 2 >= controlPoints.size())
00108 samples[3] = controlPoints.get(controlPoints.size() - 1);
00109 else
00110 samples[3] = controlPoints.get(i + 2);
00111 for (int ii = 0; ii < 4; ii++) {
00112 double b = blend(ii, t);
00113 blendedSample.x += samples[ii].x * b;
00114 blendedSample.y += samples[ii].y * b;
00115 blendedSample.timestamp += samples[ii].timestamp * b;
00116 }
00117 return blendedSample;
00118 }
00119
00120 double dblend_du(int i, double t) {
00121 switch (i) {
00122 case 0:
00123 return (4 * t - 1 - 3 * t * t) / 2;
00124 case 1:
00125 return (9 * t * t - 10 * t) / 2;
00126 case 2:
00127 return (8 * t - 9 * t * t + 1) / 2;
00128 case 3:
00129 return (3 * t * t - 2 * t) / 2;
00130 }
00131 return 0;
00132 }
00133
00134 public ArrayList<ShapeHistogram> generateShapeHistogram(int points, int dummy_padding,
00135 boolean rotationInvariant, boolean timeSensitive) {
00136
00137 int dummy_points = points - dummy_padding;
00138 ArrayList<ShapeHistogram> histograms = new ArrayList<ShapeHistogram>();
00139 ArrayList<PenSample> samples = resample(dummy_points);
00140 ArrayList<PenSample> tangents = new ArrayList<PenSample>();
00141 for (int i = 0; i < samples.size(); i++) {
00142 PenSample last, next;
00143 if (i == 0)
00144 last = samples.get(0);
00145 else
00146 last = samples.get(i - 1);
00147 if (i == dummy_points - 1)
00148 next = samples.get(dummy_points - 1);
00149 else
00150 next = samples.get(i + 1);
00151 tangents.add(new PenSample(next.x - last.x, next.y - last.y, 0, 0));
00152 }
00153
00154 double[][] bins = new double[3][];
00155 int[] bin_counts = new int[3];
00156 boolean[] explicit_binning = new boolean[3];
00157 explicit_binning[0] = true;
00158 bin_counts[0] = 5;
00159 bin_counts[1] = 12;
00160 bin_counts[2] = 2;
00161
00162
00163 double[] mins = new double[3];
00164 mins[0] = Double.MAX_VALUE;
00165 mins[1] = -Math.PI;
00166 mins[2] = Double.MAX_VALUE;
00167 double[] maxes = new double[3];
00168 maxes[0] = -Double.MAX_VALUE;
00169 maxes[1] = Math.PI;
00170 maxes[2] = -Double.MAX_VALUE;
00171 double distance_min = Double.MAX_VALUE;
00172 double distance_max = -Double.MAX_VALUE;
00173 double timestamp_min = Double.MAX_VALUE;
00174 double timestamp_max = -Double.MAX_VALUE;
00175 double mean_distance = 0;
00176 for (PenSample sample : samples) {
00177
00178
00179 if (sample.timestamp < timestamp_min)
00180 timestamp_min = sample.timestamp;
00181 if (sample.timestamp > timestamp_max)
00182 timestamp_max = sample.timestamp;
00183 for (PenSample secondSample : samples) {
00184 if (sample.equals(secondSample))
00185 continue;
00186 double distance = Math.sqrt(Math.pow(sample.x - secondSample.x, 2)
00187 + Math.pow(sample.y - secondSample.y, 2));
00188 mean_distance += distance;
00189 if (distance > 0) {
00190 distance_min = Math.min(distance_min, distance);
00191 distance_max = Math.max(distance_max, distance);
00192 }
00193 }
00194 }
00195 mean_distance /= (dummy_points * (dummy_points - 1)) / 2;
00196 double inner_threshold = Math.log10(mean_distance / 8);
00197 double outer_threshold = Math.log10(2 * mean_distance);
00198 double logscale = (outer_threshold - inner_threshold) / (bin_counts[0] - 1);
00199
00200 bins[0] = new double[bin_counts[0]];
00201 for (int i = 0; i < bin_counts[0]; i++) {
00202 bins[0][i] = Math.pow(10, inner_threshold + i * logscale);
00203 }
00204 mins[0] = Math.log(distance_min) - .01;
00205 maxes[0] = Math.log(distance_max) + .01;
00206 mins[2] = timestamp_min - timestamp_max;
00207 maxes[2] = -mins[2];
00208
00209
00210 for (PenSample sample : samples) {
00211 ShapeHistogram histogram = new ShapeHistogram(bin_counts, mins, maxes, explicit_binning, bins,
00212 bands);
00213 PenSample tangent = tangents.get(samples.indexOf(sample));
00214 double theta = rotationInvariant ? Math.atan2(tangent.y, tangent.x) : 0;
00215 for (PenSample secondSample : samples) {
00216 if (sample.equals(secondSample))
00217 continue;
00218
00219
00220
00221
00222 histogram.addPoint(logPolarAndTime(sample, secondSample, mins[0], theta));
00223 }
00224 histograms.add(histogram);
00225 }
00226 for (int i = 0; i < points - samples.size(); i++) {
00227 histograms.add(new ShapeHistogram(bin_counts, mins, maxes, explicit_binning, bins, bands));
00228
00229 }
00230 return histograms;
00231 }
00232
00233 public double[][] points() {
00234 return points(size());
00235 }
00236
00237 public double[][] points(int N) {
00238 ArrayList<PenSample> controlPoints = resample(N);
00239 double[][] pts = new double[N][2];
00240 for (int i = 0; i < N; i++) {
00241 pts[i][0] = controlPoints.get(i).x;
00242 pts[i][1] = controlPoints.get(i).y;
00243 }
00244 return pts;
00245 }
00246
00247 public void quillWrite(Writer writer) throws IOException {
00248 final DecimalFormat df = new DecimalFormat("#");
00249 boolean normalized = false;
00250 writer.write("normalized\t" + normalized + "\n");
00251 writer.write("points\t" + controlPoints.size() + "\n");
00252 int x = Integer.MAX_VALUE;
00253 int y = Integer.MAX_VALUE;
00254 for (PenSample sample : controlPoints) {
00255 x = (int) Math.min(x, sample.x);
00256 y = (int) Math.min(y, sample.y);
00257 }
00258 for (int i = 0; i < controlPoints.size(); i++) {
00259 PenSample sample = controlPoints.get(i);
00260 writer.write("\t" + df.format(sample.x - x) + "\t" + df.format(sample.y - y) + "\t"
00261 + df.format(sample.timestamp) + "\n");
00262 }
00263 writer.write("endgesture\n");
00264 }
00265
00266
00267
00268
00269
00270
00271
00272
00273
00274
00275
00276
00277
00278
00279
00280 public ArrayList<PenSample> resample(int samples) {
00281
00282 if (samples == controlPoints.size())
00283 return (ArrayList<PenSample>) controlPoints.clone();
00284
00285 ArrayList<PenSample> sampledPoints = new ArrayList<PenSample>();
00286 assert (controlPoints.size() > 1);
00287
00288
00289
00290
00291
00292
00293
00294 int points = controlPoints.size();
00295 for (int i = 1; i < points; i++) {
00296 while (controlPoints.get(i).timestamp <= controlPoints.get(i - 1).timestamp)
00297 controlPoints.get(i).timestamp++;
00298 }
00299 double t0 = controlPoints.get(0).timestamp;
00300 double t1 = controlPoints.get(points - 1).timestamp;
00301 double fraction = (t1 - t0) / (samples - 1);
00302 for (int i = 0; i < samples; i++) {
00303 double t = t0 + fraction * i;
00304 int truncated = 0;
00305 while (truncated + 2 < points && controlPoints.get(truncated + 1).timestamp < t)
00306 truncated++;
00307 double start = controlPoints.get(truncated).timestamp;
00308 double end = controlPoints.get(truncated + 1).timestamp;
00309 double remainder = (t - start) / (end - start);
00310 sampledPoints.add(blendHelper(truncated, remainder));
00311 }
00312 return sampledPoints;
00313 }
00314
00315 public int size() {
00316 return 2 * controlPoints.size();
00317 }
00318
00319
00320 PenSample tangent(int i, double t) {
00321 PenSample[] samples = new PenSample[4];
00322 PenSample blendedSample = new PenSample(0, 0, (int) 0, (long) 0);
00323 if (i == 0)
00324 samples[0] = controlPoints.get(0);
00325 else
00326 samples[0] = controlPoints.get(i - 1);
00327 samples[1] = controlPoints.get(i);
00328 samples[2] = controlPoints.get(i + 1);
00329 if (i + 2 >= controlPoints.size())
00330 samples[3] = controlPoints.get(controlPoints.size() - 1);
00331 else
00332 samples[3] = controlPoints.get(i + 2);
00333 for (int ii = 0; ii < 4; ii++) {
00334 double b = dblend_du(ii, t);
00335 blendedSample.x += samples[ii].x * b;
00336 blendedSample.y += samples[ii].y * b;
00337 blendedSample.timestamp += samples[ii].timestamp * b;
00338 }
00339 return blendedSample;
00340 }
00341
00342 public ArrayList<PenSample> tangents(int samples) {
00343
00344 ArrayList<PenSample> sampledPoints = new ArrayList<PenSample>();
00345 assert (controlPoints.size() > 1);
00346
00347 double fraction = (controlPoints.size() - 1) / (double) (samples);
00348 for (int i = 0; i < samples; i++) {
00349 double position = fraction * i;
00350 int truncated = (int) position;
00351 double remainder = position - truncated;
00352 sampledPoints.add(tangent(truncated, remainder));
00353 }
00354 return sampledPoints;
00355 }
00356
00357 }