/**
* 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 PointCloudUtils
{
public static final PointCloudPoint ORIGIN = new PointCloudPoint(0.0,0.0,0);
private PointCloudUtils()
{
// prevent instantiation which is un-needed to use the static methods in this class
}
public static ArrayList<PointCloudPoint> translateTo(ArrayList<PointCloudPoint> points, PointCloudPoint pt) // translates points' centroid
{
PointCloudPoint c = centroid(points);
ArrayList<PointCloudPoint> newpoints = new ArrayList<>();
for(int i = 0; i < points.size(); i++)
{
PointCloudPoint p = points.get(i);
double qx = p.getX() + pt.getX() - c.getX();
double qy = p.getY() + pt.getY() - c.getY();
newpoints.add(new PointCloudPoint(qx, qy, p.getID()));
}
return newpoints;
}
public static PointCloudPoint centroid(ArrayList<PointCloudPoint> points)
{
double x = 0.0;
double y = 0.0;
for(int i = 0; i < points.size(); i++)
{
PointCloudPoint p = points.get(i);
x += p.getX();
y += p.getY();
}
x /= (double) points.size();
y /= (double) points.size();
return new PointCloudPoint(x, y, 0);
}
// the following appears to scale all x,y values to between 0 and 1
public static ArrayList<PointCloudPoint> scale(ArrayList<PointCloudPoint> points)
{
double minX = Double.POSITIVE_INFINITY;
double maxX = Double.NEGATIVE_INFINITY;
double minY = Double.POSITIVE_INFINITY;
double maxY = Double.NEGATIVE_INFINITY;
for(int i = 0; i < points.size(); i++)
{
PointCloudPoint p = points.get(i);
minX = Math.min(minX, p.getX());
minY = Math.min(minY, p.getY());
maxX = Math.max(maxX, p.getX());
maxY = Math.max(maxY, p.getY());
}
double size = Math.max(maxX - minX, maxY - minY);
ArrayList<PointCloudPoint> newpoints = new ArrayList<>();
for(int i = 0; i < points.size(); i++)
{
PointCloudPoint p = points.get(i);
double qx = (p.getX() - minX) / (double) size;
double qy = (p.getY() - minY) / (double) size;
newpoints.add(new PointCloudPoint(qx, qy, p.getID()));
}
return newpoints;
}
public static double pathLength(ArrayList<PointCloudPoint> points) // length traversed by a point path
{
double d = 0.0;
for(int i = 1; i < points.size(); i++)
{
PointCloudPoint p1 = points.get(i);
PointCloudPoint p2 = points.get(i - 1);
if(p1.getID() == p2.getID())
{
d += distance(p2, p1);
}
}
return d;
}
public static ArrayList<PointCloudPoint> resample(ArrayList<PointCloudPoint> points, int numPoints)
{
double I = pathLength(points) / (numPoints - 1); // interval length
double D = 0.0;
ArrayList<PointCloudPoint> newpoints = new ArrayList<>();
PointCloudPoint p = points.get(0);
newpoints.add(new PointCloudPoint(p.getX(), p.getY(), p.getID()));
for(int i = 1; i < points.size(); i++)
{
PointCloudPoint p1 = points.get(i);
PointCloudPoint p2 = points.get(i - 1);
if(p1.getID() == p2.getID())
{
double d = distance(p2, p1);
if((D + d) >= I)
{
double qx = p2.getX() + ((I - D) / d) * (p1.getX() - p2.getX());
double qy = p2.getY() + ((I - D) / d) * (p1.getY() - p2.getY());
PointCloudPoint q = new PointCloudPoint(qx, qy, p1.getID());
newpoints.add(q); // append new point 'q'
points.add(i, q); // insert 'q' at position i in points s.t. 'q' will be the next i
D = 0.0;
}
else
{
D += d;
}
}
}
if (newpoints.size() == numPoints - 1) // sometimes we fall a rounding-error short of adding the last point, so add it if so
{
p = points.get(points.size() - 1);
newpoints.add(new PointCloudPoint(p.getX(), p.getY(), p.getID()));
}
return newpoints;
}
public static double pathDistance(ArrayList<PointCloudPoint> pts1, ArrayList<PointCloudPoint> pts2) // average distance between corresponding points in two paths
{
if(pts1.size() != pts2.size())
{
throw new IllegalArgumentException("Both arrays must be of same length");
}
double d = 0.0;
for(int i = 0; i < pts1.size(); i++) // assumes pts1.length == pts2.length
{
d += distance(pts1.get(i), pts2.get(i));
}
return d / (double) pts1.size();
}
public static double distance(PointCloudPoint p1, PointCloudPoint p2) // Euclidean distance between two points
{
double dx = p2.getX() - p1.getX();
double dy = p2.getY() - p1.getY();
return Math.sqrt(dx * dx + dy * dy);
}
}