/** * The $P Point-Cloud Recognizer (Java version) * * by David White * Copyright (c) 2012, David White. All rights reserved. * * based entirely on the $P Point-Cloud Recognizer (Javascript version) * found at http://depts.washington.edu/aimgroup/proj/dollar/pdollar.html * who's original header follows: * ************************************************************************* * The $P Point-Cloud Recognizer (JavaScript version) * * Radu-Daniel Vatavu, Ph.D. * University Stefan cel Mare of Suceava * Suceava 720229, Romania * vatavu@eed.usv.ro * * Lisa Anthony, Ph.D. * UMBC * Information Systems Department * 1000 Hilltop Circle * Baltimore, MD 21250 * lanthony@umbc.edu * * Jacob O. Wobbrock, Ph.D. * The Information School * University of Washington * Seattle, WA 98195-2840 * wobbrock@uw.edu * * The academic publication for the $P recognizer, and what should be * used to cite it, is: * * Vatavu, R.-D., Anthony, L. and Wobbrock, J.O. (2012). * Gestures as point clouds: A $P recognizer for user interface * prototypes. Proceedings of the ACM Int'l Conference on * Multimodal Interfaces (ICMI '12). Santa Monica, California * (October 22-26, 2012). New York: ACM Press, pp. 273-280. * * This software is distributed under the "New BSD License" agreement: * * Copyright (c) 2012, Radu-Daniel Vatavu, Lisa Anthony, and * Jacob O. Wobbrock. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the names of the University Stefan cel Mare of Suceava, * University of Washington, nor UMBC, nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Radu-Daniel Vatavu OR Lisa Anthony * OR Jacob O. Wobbrock BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. **/ package nars.gui.input.image; import java.util.ArrayList; public class PointCloud { public static final int NUM_POINTS = 32; private String _name = null; private ArrayList<PointCloudPoint> _points = null; // the following is NOT part of the originally published javascript implementation // and has been added to support addition of directional testing for point clouds // which represent unistroke gestures private boolean _isUnistroke = true; PointCloud(String name, double[] X, double[] Y, int[]ID) { _points = new ArrayList<>(); for(int i = 0; i < X.length; i++) { _points.add(new PointCloudPoint(X[i], Y[i], ID[i])); } _name = name; int id = _points.get(0).getID(); for(int i = 1; i < _points.size(); i++) { if(_points.get(i).getID() != id) { _isUnistroke = false; break; } } } public PointCloud(String name, ArrayList<PointCloudPoint> points) { if(name == null || name == "") { throw new IllegalArgumentException("Point cloud name must be supplied"); } _name = name; if(null == points || points.size() < 2) { throw new IllegalArgumentException("Point cloud points do not define a gesture of minimum length"); } _points = points; _points = PointCloudUtils.resample(_points, NUM_POINTS); _points = PointCloudUtils.scale(_points); _points = PointCloudUtils.translateTo(_points, PointCloudUtils.ORIGIN); // the following is NOT part of the originally published javascript implementation // and has been added to support addition of directional testing for point clouds // which represent unistroke gestures int id = _points.get(0).getID(); for(int i = 1; i < _points.size(); i++) { if(_points.get(i).getID() != id) { _isUnistroke = false; break; } } } // the following is NOT part of the originally published javascript implementation // and has been added to support addition of directional testing for point clouds // which represent unistroke gestures public boolean isUnistroke() { return _isUnistroke; } public PointCloudPoint getFirstPoint() { return _points.get(0); } public PointCloudPoint getLastPoint() { return _points.get(_points.size() - 1); } public String getName() { return _name; } ArrayList<PointCloudPoint> getPoints() { return _points; } double greedyMatch(PointCloud reference) { double pointCount = (double) _points.size(); double e = 0.50; double step = Math.floor(Math.pow(pointCount, 1.0 - e)); double min = Double.POSITIVE_INFINITY; for(double i = 0.0; i < pointCount; i += step) { double d1 = this.cloudDistance(reference, i); double d2 = reference.cloudDistance(this, i); min = Math.min(min, Math.min(d1, d2)); // min3 } return min; } private double cloudDistance(PointCloud reference, double start) { ArrayList<PointCloudPoint> pts1 = _points; ArrayList<PointCloudPoint> pts2 = reference._points; if(pts1.size() != pts2.size()) { throw new IllegalArgumentException("Both point clouds must contain the same number of points"); } double pointCount = (double) pts1.size(); boolean matched[] = new boolean[(int)pointCount]; for(int k = 0; k < pointCount; k++) { matched[k] = false; } double sum = 0; double i = start; do { int index = -1; double min = Double.POSITIVE_INFINITY; for(int j = 0; j < matched.length; j++) { if (!matched[j]) { double d = PointCloudUtils.distance(pts1.get((int)i), pts2.get(j)); if (d < min) { min = d; index = j; } } } matched[index] = true; double weight = 1.0 - ((i - start + pointCount) % pointCount) / pointCount; sum += weight * min; i = (i + 1.0) % pointCount; } while (i != start); return sum; } }