package org.jactr.modules.pm.visual.memory.impl.filter; /* * default logging */ import java.util.Comparator; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jactr.core.chunk.IChunk; import org.jactr.core.logging.Logger; import org.jactr.core.model.IModel; import org.jactr.core.production.request.ChunkTypeRequest; import org.jactr.core.slot.IConditionalSlot; import org.jactr.modules.pm.common.memory.filter.IIndexFilter; /** * enables search along a path defined by one visual-location, an angle (from * vertical, -PI - PI), and a threshold. The filter will prioritize * visual-location candidates based on their distance from the line defined by * visual-location and angle, culling any of those beyond the threshold. * * @author harrison */ public class VectorVisualLocationFilter extends AbstractVisualLocationIndexFilter<Double> { /** * Logger definition */ static private final transient Log LOGGER = LogFactory .getLog(VectorVisualLocationFilter.class); static public final String ANGLE_SLOT = ":vector-angle"; static public final String ORIGIN_SLOT = ":vector-origin"; static public final String DESTINATION_SLOT = ":vector-destination"; static public final String THRESHOLD_SLOT = ":vector-threshold"; private double[] _v0; private double[] _v1; private double _threshold = Double.POSITIVE_INFINITY; private double[] _minValue; private double[] _maxValue; public VectorVisualLocationFilter() { } protected VectorVisualLocationFilter(double[] origin, double[] destination, double threshold, double angle) { _threshold = threshold; _v0 = origin; _v1 = destination; if (_threshold == Double.POSITIVE_INFINITY) _threshold = 180; /* * compute bounds... */ computeBounds(_v0, _v1, _threshold, angle, 0.001); } @Override protected Double compute(ChunkTypeRequest request) { IChunk visualLocation = getVisualLocation(request); if (visualLocation != null) { double[] ref = getCoordinates(visualLocation); double a = ref[0] - _v0[0]; double b = ref[1] - _v0[1]; double c = _v1[0] - _v0[0]; double d = _v1[1] - _v0[1]; /* * distance = |AD - CB| / sqrt(d^2 + c^2) divisor is the length of the * base vector, which is 1. */ double distance = Math.abs(a * d - c * b) / Math.sqrt(d * d + c * c); if (LOGGER.isDebugEnabled()) LOGGER.debug("(" + ref[0] + "," + ref[1] + ") is " + distance + " from unit line segment defined by (" + _v0[0] + "," + _v0[1] + ")-(" + _v1[0] + "," + _v1[1] + ")"); return distance; } return null; } public boolean accept(ChunkTypeRequest visualLocationTemplate) { Double distance = get(visualLocationTemplate); if (distance == null || distance > _threshold) return false; IChunk visualLocation = getVisualLocation(visualLocationTemplate); if (visualLocation == null) return false; double[] ref = getCoordinates(visualLocation); /* * is it _v0 or _v1? We want to exclude the boundaries.. */ if (ref[0] == _v0[0] && ref[1] == _v0[1] || ref[0] == _v1[0] && ref[1] == _v1[1]) return false; return _minValue[0] <= ref[0] && ref[0] <= _maxValue[0] && _minValue[1] <= ref[1] && ref[1] <= _maxValue[1]; } public Comparator<ChunkTypeRequest> getComparator() { return new Comparator<ChunkTypeRequest>() { public int compare(ChunkTypeRequest o1, ChunkTypeRequest o2) { if (o1 == o2) return 0; Double d1 = get(o1); Double d2 = get(o2); if (d1 < d2) return -1; if (d1 > d2) return 1; return 0; } }; } public IIndexFilter instantiate(ChunkTypeRequest request) { double angle = Double.NaN; double threshold = Double.POSITIVE_INFINITY; double[] origin = null; double[] destination = null; int index = 0; int weight = 0; VectorVisualLocationFilter filter = null; for (IConditionalSlot cSlot : request.getConditionalSlots()) { index++; if (cSlot.getCondition() == IConditionalSlot.EQUALS) if (cSlot.getName().equals(ORIGIN_SLOT)) { origin = getCoordinates((IChunk) cSlot.getValue()); weight = index; } else if (cSlot.getName().equals(DESTINATION_SLOT)) destination = getCoordinates((IChunk) cSlot.getValue()); else if (cSlot.getName().equals(ANGLE_SLOT)) angle = ((Number) cSlot.getValue()).doubleValue(); else if (cSlot.getName().equals(THRESHOLD_SLOT)) threshold = ((Number) cSlot.getValue()).doubleValue(); } /* * nothing was defined.. */ if (origin == null && destination == null && Double.isNaN(angle)) return null; if (origin == null || destination == null && Double.isNaN(angle)) { String msg = "Cannot search along vector without both " + ORIGIN_SLOT + " and either " + ANGLE_SLOT + " or " + DESTINATION_SLOT + " defined. Ignoring."; if (LOGGER.isWarnEnabled()) LOGGER.warn(msg); IModel model = getVisualMemory().getModule().getModel(); if (Logger.hasLoggers(model)) Logger.log(model, Logger.Stream.VISUAL, msg); } else { if (destination == null) destination = computeDestination(origin, angle); else { double dx = destination[0] - origin[0]; double dy = destination[1] - origin[1]; // intentionally swapped since we are measuring from the vertical angle = Math.toDegrees(Math.atan2(dx, dy)); } filter = new VectorVisualLocationFilter(origin, destination, threshold, angle); filter.setWeight(weight); filter.setPerceptualMemory(getVisualMemory()); if (LOGGER.isDebugEnabled()) LOGGER.debug("Will search from (" + origin[0] + "," + origin[1] + ") along " + angle + " (" + destination[0] + "," + destination[1] + ") +/- " + threshold); } return filter; } /** * computes the destination point that is the intersection of the line * (origin-angle) with the bounds of the visual field * * @param origin * @param angle * @return */ private double[] computeDestination(double[] origin, double angle) { /* * and compute a new point from origin along angle */ double[] tmp = new double[2]; tmp[0] = origin[0] + Math.sin(Math.toRadians(angle)); tmp[1] = origin[1] + Math.cos(Math.toRadians(angle)); /* * compute the intersection of v0,tmp and the bounds of the visual field and * use that as the destination point */ double w2 = getVisualMemory().getHorizontalSpan() / 2.0; double h2 = getVisualMemory().getVerticalSpan() / 2.0; double[][] otherLine = null; // top if (Math.abs(angle) <= 45) otherLine = new double[][] { { -w2, h2 }, { w2, h2 } }; else // bottom if (Math.abs(angle) >= 135) otherLine = new double[][] { { -w2, -h2 }, { w2, -h2 } }; else // right if (angle > 0) otherLine = new double[][] { { w2, -h2 }, { w2, h2 } }; else // left otherLine = new double[][] { { -w2, -h2 }, { -w2, h2 } }; return intersection(new double[][] { origin, tmp }, otherLine, 0.01); } /** * computes the bounds of the visual search area * * @param v0 * @param v1 * @param threshold * @param angle * @param epsilon */ private void computeBounds(double[] v0, double[] v1, double threshold, double angle, double epsilon) { double v0X = v0[0]; double v0Y = v0[1]; double v1X = v1[0]; double v1Y = v1[1]; double xSign = 1; double ySign = 1; double absAngle = Math.abs(angle); if (Math.abs(absAngle - 0) <= epsilon || Math.abs(absAngle - 180) <= epsilon) { /* * vertical */ xSign = -1; ySign = 0; } else if (Math.abs(absAngle - 90) <= epsilon) { /* * horizontal */ xSign = 0; ySign = -1; } else { // angle > 0, subtract xSign = -Math.signum(angle); if (absAngle > 90) ySign = -1; else ySign = 1; } double rotatedAngle = 90 - angle; double rad = Math.toRadians(rotatedAngle); v1X = v0X = Math.sin(rad) * threshold; v1Y = v0Y = Math.cos(rad) * threshold; v0X = xSign * v0X + v0[0]; v0Y = ySign * v0Y + v0[1]; v1X = -xSign * v1X + v1[0]; v1Y = -ySign * v1Y + v1[1]; _minValue = new double[] { Math.min(v0X, v1X), Math.min(v0Y, v1Y) }; _maxValue = new double[] { Math.max(v0X, v1X), Math.max(v0Y, v1Y) }; } /** * compute the intersection of two lines. adapted from * http://www.pdas.com/lineint.htm * * @param l1 * @param l2 * @return intersecting point or null if parallel */ private double[] intersection(double[][] l1, double[][] l2, double resolution) { double a1 = l1[1][1] - l1[0][1]; double b1 = l1[0][0] - l1[1][0]; double c1 = l1[1][0] * l1[0][1] - l1[0][0] * l1[1][1]; // l1 : a1*x + b1*y + c1 =0; double a2 = l2[1][1] - l2[0][1]; double b2 = l2[0][0] - l2[1][0]; double c2 = l2[1][0] * l2[0][1] - l2[0][0] * l2[1][1]; double denom = a1 * b2 - a2 * b1; // parallel if (Math.abs(denom) <= resolution) return null; double x = (b1 * c2 - b2 * c1) / denom; double y = (a2 * c1 - a1 * c2) / denom; return new double[] { x, y }; } public void normalizeRequest(ChunkTypeRequest request) { // TODO Auto-generated method stub } }