Hi. My name is Mike Brady, and welcome to my website. I've been fortunate to work on some great projects and have
collected some of that work here. Enjoy.
Here are a few examples of my code. I've included examples in Java, C++, assembler, and bagel to give a feel
for my style across multiple languages.
Select Example:
package com.rst.vision;
import com.rst.tools.awt.*;
import com.rst.tools.lang.*;
import com.rst.tools.math.*;
import com.rst.tools.util.*;
import com.rst.tools.util.List;
import com.rst.vision.gui.*;
import java.awt.*;
import java.util.*;
/**
* A representation of a contiguous collection of pixels in an image. This class takes the blobs
* output by the <code>BlobLabeller</code> class and characterizes them in various ways. The goal
* is to be able to determine the locatation, orientation, and identification of the blob. The
* starting point for all calculations is the centroid. This is the center of mass of the blob if
* we take each "on" pixel as a unit point mass. From there we calculate the line of maximal
* symmetry as described below.
*
* <pre>
* ,-----------------------,~~~~~.-, ,-----------------------,~~~~~.-,
* | ,-~~~-., ___-~~' )| | ,-~~~-.,| ___-~~' )|
* | / `--~~' __-~' | | / |--~~' __-~' |
* |( __,~' | |( | __,~' |
* | `~~, c ,--~~' | |-`--,---- c --,----'-----------|
* | / / | | / | / |
* | ( ,' | | ( |,' |
* l___l,_____/____________________| l___l,_____|____________________|
*
* Blob with rectalinear bounding box. The centroid The bounding box is divided into 4
* of the blob is labelled c. quadrants centered on that centroid.
*
*
*
* ,----------- ------------,~~~~~.-,
* | ,-~~~-.,| | ___-~~' )|
* | / q | |--~~' q __-~' |
* |( | | __,~' | ,-----------------------,~~~~~.-,
* `-`------- c c --,----'-----------' | ,-~~~-.,| ___-~~' )|
* | / q |--~~' q __-~' |
* 1 2 |( | __,~' |
* |-`--,---- c --,----'-----------|
* .----,---- c c --,----------------, | / |q / |
* | / | |q / | | ( q |,' |
* | ( q | |,' | l___l,_____|____________________|
* l___l,_____| |____________________|
* We use the quadrant centroid q that's
* 4 3 most distant from c as the secondary
* centroid. In this example that is
* We consider each quadrant in isolation. We compute the centroid of quadrant 2.
* the centroid of only those pixels in that quadrant.
* These centroids are labelled q. We also calculate
* the distance between q and c for each quadrant.
*
* . '
* . '
* ,~~~.~'
* ,-~~~-., ___-~~'. ' )
* / `--~~' q ' __-~'
* ( . ' __,~'
* `~~, c ' ,--~~'
* / . ' /
* . ' ,'
* . ' l,_____/
* . '
*
* We use these 2 points to define the line of
* maximal symmetry.
* </pre>
*
* <p>This results in a reliable location (the full centroid) and orientation (the line of maximal
* symmetry) for most blobs we see in our domain. In particular, closed scissor-shaped blobs can be
* located, oriented, and identified with remarkable accuracy this manner. One exception is discussed
* below.
*
* <pre>
* ,-------------, ,-------------,
* | h | | | |
* |--- c -------| | c h |
* | | | | |
* ,---_____,-------------------, l_____________| l____l________|
* | / q `-|-------------, | 1 2
* | / | q \ | ,-------------, ,-------------,
* |(`-------- c --------------)| | | | | |
* | \ | q / | |--- c -------| | h c |
* | \ q ,-|-------------' | | h | | | |
* l___`____/__|________________| l_____________| l____l________|
* 3 4
*
* There's a special case when the blob is When we've detected this special case, we use
* oriented horizontally or vertically. In one of the 4 half centroids, labelled h above.
* this case the centroid for quadrants 2 We take the half centroid most distant from c
* and 3 are nearly equidistant from c. as the secondary centroid. In this example
* Choosing either one would result in an that is the centroid of half 2, giving us a
* errant line of maximal symmetry. nearly horizontal line of maximal symmetry.
*
*
*
* _____,
* / `---------------,
* / \
* , . , . ( . , . , .c, . , .h, . , .), . , .
* \ /
* \ ,---------------'
* `____/
*
* We use these 2 points, c and second half
* centroid h, to define the line of maximal
* symmetry in this special case.
* </pre>
**/
public class Blob extends ROI {
protected VisionSource visionSource;
protected byte[] blobLabelsData;
protected int label;
protected int index;
protected int mass;
protected Color color;
protected Object identification = "???";
// Computed values
protected AreaCentroid centroid = new AreaCentroid();
protected AreaCentroid secondaryCentroid = new AreaCentroid();
protected AreaCentroid[] quarterCentroids = new AreaCentroid[] { new AreaCentroid(), new AreaCentroid(), new AreaCentroid(), new AreaCentroid() };
protected AreaCentroid[] halfCentroids = new AreaCentroid[] { new AreaCentroid(), new AreaCentroid(), new AreaCentroid(), new AreaCentroid() };
protected double angle;
protected double normal;
protected double slope;
protected LinearMeasurement.Length length;
protected LinearMeasurement.Breadth breadth;
protected static final int horizontalAngle = 1;
protected static final int verticalAngle = 2;
public Blob() {
}
public void init(VisionSource visionSource, int index, int containerWidth) {
this.visionSource = visionSource;
this.index = index;
this.containerWidth = containerWidth;
length = new LinearMeasurement.Length(this);
breadth = new LinearMeasurement.Breadth(this);
color = GraphicsTools.getCommonColor(index);
}
public void setBlobLabelsArray(ImageArray blobLabelsArray) {
blobLabelsData = blobLabelsArray.getGrayArray();
}
public void setLabel(int label, int mass, int xTotal, int yTotal, LTRB ltrb) {
this.label = label;
this.mass = mass;
centroid.modSet((double)xTotal / mass, (double)yTotal / mass);
set(ltrb);
}
public void computeSize() {
length.measure((byte)label, true);
breadth.measure((byte)label, true);
}
public int getMass() {
return mass;
}
// Angles
protected void computeLateralAxis() {
double dx = secondaryCentroid.x - centroid.x;
double dy = secondaryCentroid.y - centroid.y;
angle = NumberTools.zeroTo2PiAtan(dx, -dy);
normal = angle + NumberTools.piOver2;
slope = (dx == 0) ? Double.POSITIVE_INFINITY : dy / dx;
}
public String describeAngles() {
return "a: " + (int)Math.toDegrees(angle) + " n: " + (int)Math.toDegrees(normal) + " m: " + NumberTools.roundTo2(slope);
}
// Centroids
public void computeCentroids() {
computeQuarterCentroids();
computeHalfCentroids();
secondaryCentroid = findSecondaryCentroid();
computeLateralAxis();
}
/**
* Finds the centroid within the given bounding box, puts the centroid point
* into the <code>result</code> argument, and returns the number of
* non-screened pixels that were counted within that area.
*/
protected void computeCentroidInternal(int left, int top, int right, int bottom, AreaCentroid result) {
result.xTotal = 0;
result.yTotal = 0;
result.nPixels = 0;
for (int x = left; x <= right; x++) {
for (int y = top; y <= bottom; y++) {
if (0 != blobLabelsData[y * containerWidth + x]) {
result.xTotal += x;
result.yTotal += y;
result.nPixels++;
}
}
}
result.compute();
}
/**
* <pre>
* _______________ _______________ _______________
* | | | | | | | |
* | 0 | 1 | | 0 | | | |
* |--------c------| |--------c------| | c |
* | | | | | | 3 | 1 |
* | | | | | | | |
* | 3 | 2 | | 2 | | | |
* | | | | | | | |
* l________!______| l_______________| l________!______|
*
* Quarter Centroids Half Centroids
* </pre>
*
**/
public void computeQuarterCentroids() {
int centroidXInt = centroid.getXInt();
int centroidYInt = centroid.getYInt();
computeCentroidInternal(left, top, centroidXInt, centroidYInt, quarterCentroids[0]);
computeCentroidInternal(centroidXInt + 1, top, right, centroidYInt, quarterCentroids[1]);
computeCentroidInternal(centroidXInt + 1, centroidYInt + 1, right, bottom, quarterCentroids[2]);
computeCentroidInternal(left, centroidYInt + 1, centroidXInt, bottom, quarterCentroids[3]);
}
public void computeHalfCentroids() {
computeHalfCentroid(halfCentroids[0], quarterCentroids[0], quarterCentroids[1]);
computeHalfCentroid(halfCentroids[1], quarterCentroids[1], quarterCentroids[2]);
computeHalfCentroid(halfCentroids[2], quarterCentroids[2], quarterCentroids[3]);
computeHalfCentroid(halfCentroids[3], quarterCentroids[3], quarterCentroids[0]);
}
protected void computeHalfCentroid(AreaCentroid halfCentroid, AreaCentroid quarterCentroidA, AreaCentroid quarterCentroidB) {
halfCentroid.xTotal = quarterCentroidA.xTotal + quarterCentroidB.xTotal;
halfCentroid.yTotal = quarterCentroidA.yTotal + quarterCentroidB.yTotal;
halfCentroid.nPixels = quarterCentroidA.nPixels + quarterCentroidB.nPixels;
halfCentroid.compute();
}
public AreaCentroid findSecondaryCentroid() {
int angleResult = detectPerfectAngle();
if (angleResult == verticalAngle)
return (halfCentroids[0].distanceToCenter > halfCentroids[2].distanceToCenter) ? halfCentroids[0] : halfCentroids[2];
if (angleResult == horizontalAngle)
return (halfCentroids[1].distanceToCenter > halfCentroids[3].distanceToCenter) ? halfCentroids[1] : halfCentroids[3];
sortQuarterCentroids();
for (int i = 0; i < quarterCentroids.length; i++)
if (quarterCentroids[i].nPixels >= centroid.nPixels / 15)
return quarterCentroids[i];
return quarterCentroids[0];
}
protected UtilTools.BinaryPredicate quarterCentroidSorter = new UtilTools.BinaryPredicate() {
public boolean predicate(Object obj1, Object obj2) {
return (((AreaCentroid)obj1).distanceToCenter >= ((AreaCentroid)obj2).distanceToCenter);
}};
protected void sortQuarterCentroids() {
QuickSort.sort(quarterCentroids, quarterCentroidSorter);
}
// Perfect angle detection
protected int detectPerfectAngle() {
if (halfCentroids[0].dx + halfCentroids[1].dx + halfCentroids[2].dx + halfCentroids[3].dx <
halfCentroids[0].dy + halfCentroids[1].dy + halfCentroids[2].dy + halfCentroids[3].dy) {
// more vertical
if ((rectilinearDeltaIsSmall(quarterCentroids[0], quarterCentroids[1], true) ||
rectilinearDeltaIsSmall(quarterCentroids[2], quarterCentroids[3], true))
&&
(NumberTools.isWithinUnordered(angleOfLine(centroid, halfCentroids[0]), 80, 100) ||
NumberTools.isWithinUnordered(angleOfLine(centroid, halfCentroids[2]), 260, 280)))
return verticalAngle;
}
else {
// more horizontal
if ((rectilinearDeltaIsSmall(quarterCentroids[0], quarterCentroids[3], false) ||
rectilinearDeltaIsSmall(quarterCentroids[1], quarterCentroids[2], false))
&&
(NumberTools.isWithinUnordered(angleOfLine(centroid, halfCentroids[1]), 350, 10) ||
NumberTools.isWithinUnordered(angleOfLine(centroid, halfCentroids[3]), 170, 190)))
return horizontalAngle;
}
return 0;
}
protected boolean rectilinearDeltaIsSmall(AreaCentroid centroid1, AreaCentroid centroid2, boolean useDx) {
double d1 = centroid1.getRectilinearDistance(useDx);
double d2 = centroid2.getRectilinearDistance(useDx);
return Math.abs(d1 - d2) <= (d1 + d2) / 4.0;
}
public boolean isPixelOn(int x, int y) {
return (label == blobLabelsData[y * containerWidth + x]);
}
public boolean isPixelOn(int x, int y, byte label, boolean labelIsOn) {
return
labelIsOn
? (blobLabelsData[y * containerWidth + x] == label)
: (blobLabelsData[y * containerWidth + x] != label);
}
public int getPixel(int x, int y) {
return blobLabelsData[y * containerWidth + x];
}
public static double angleOfLine(AreaCentroid p1, AreaCentroid p2) {
return Math.toDegrees(NumberTools.zeroTo2PiAtan(p2.x - p1.x, p1.y - p2.y));
}
public class AreaCentroid extends Vector2D {
public int nPixels;
public int xTotal;
public int yTotal;
public double distanceToCenter = 0;
public double dx = 0;
public double dy = 0;
public void compute() {
if (nPixels == 0)
modSet(centroid);
else
modSet((double)xTotal / nPixels, (double)yTotal / nPixels);
computeDistanceToCenter(centroid);
}
public void computeDistanceToCenter(AreaCentroid centroid) {
distanceToCenter = computeDistanceBetween(centroid, false);
dx = Math.abs(this.x - centroid.x);
dy = Math.abs(this.y - centroid.y);
}
public int getXInt() {
return (int)Math.round(x);
}
public int getYInt() {
return (int)Math.round(y);
}
public double getRectilinearDistance(boolean useDx) {
return useDx ? dx : dy;
}
public String toString() {
return "(" + NumberTools.roundTo1(x) + " " + NumberTools.roundTo1(y) + ")";
}
}
public String toString() {
return "#<Blob " + identification + "@" + centroid + ">";
}
// Introspection
public LTRB getBoundingBox() {
return this;
}
public AreaCentroid getCentroid() {
return centroid;
}
public AreaCentroid getSecondaryCentroid() {
return secondaryCentroid;
}
public void setSecondaryCentroid(AreaCentroid secondaryCentroid) {
this.secondaryCentroid = secondaryCentroid;
}
public AreaCentroid[] getQuarterCentroids() {
return quarterCentroids;
}
public AreaCentroid[] getHalfCentroids() {
return halfCentroids;
}
public double getAngle() {
return angle;
}
public double getNormal() {
return normal;
}
public double getSlope() {
return slope;
}
public int getLabel() {
return label;
}
public int getIndex() {
return index;
}
public LinearMeasurement.Length getLength() {
return length;
}
public LinearMeasurement.Breadth getBreadth() {
return breadth;
}
public VisionSource getVisionSource() {
return visionSource;
}
public byte[] getBlobLabelsData() {
return blobLabelsData;
}
public void setIdentification(Object identification) {
this.identification = identification;
}
public Object getIdentification() {
return identification;
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
}
package com.rst.tools.lang;
import com.rst.tools.io.*;
import com.rst.tools.swing.*;
import com.rst.tools.util.*;
import com.rst.tools.util.List;
import java.io.*;
import java.lang.reflect.*;
import java.util.*;
/**
* An <code>EventHub</code> is hub on which listeners can register event handlers and
* through which event producers can fire events. The idea is to have all interesting
* events passing through one of these hubs. This is good because the hub can implement
* some generally useful features, <i>and</i> this class can use <code>static</code>
* variables and methods to monitor all event traffic in the application. Hopefully
* this facility can replace the usual epidemic of home-grown listener maintainence
* capabilties that random classes implement over and over.
*
* <p>This facility lets you specify an ordinary method as an event handler, as opposed
* to the usual way of implementing some listener interface. Some people may dislike
* this approach, but I like it because it results in far simpler code.
*
* <p>We want to make sure the event handler methods have access to all the info/objects
* they'll need. One useful thing to know is the <code>Actor</code> who initiated the
* event. Aside from that we also allow arbitrary objects to be tied to the event both
* when the handler is registered (by calling <code>addHandler</code>) and when the
* handler is invoked (by calling <code>invokeHandlers</code>). The first object is called
* the <code>handlerObject</code> because it's associated with the handler itself, not
* with any particular event on which the handler may be invoked. The second object is
* called the <code>invocationArg</code> because it's specified when the event is fired
* and the handlers are invoked.
*
* <p>By default handers are invoked in the order they were added. You can call
* <code>addHandler</code> with the <code>addToFront</code> arg set to true to have your
* handler inserted at the front of the list of handlers. This will ensure your handler
* is invoked first (unless a later call to <code>addHandler</code> also uses the
* <code>addToFront</code> feature).
*
* <p>Another useful feature is the use of wildcards when adding event handlers. Handlers
* are added with an <code>eventFilter</code>, a string used to match against the event
* name to determine if the handler is invoked. These <code>eventFilters</code> can name
* specific events or they can include *'s to match a variety of events. This same
* capability is also available for actor string matching via the <code>actorFilter</code>
* arg to <code>addHandler</code>. These wildcard facilities have been carefully optimized
* for speed. Furthermore, if an <code>EventHub</code> doesn't use wildcards, there is
* essentially <i>no</i> time overhead.
*
* <p>Handlers can also be added with a specified <code>sampleRate</code>. In this case
* the handler will be invoked once every <code>sampleRate</code> times the event is fired.
* So if you add a handler for "video.frameCaptured" events with a <code>sampleRate</code>
* of 5, your code will be called once every 5 frames.
* <p>A final feature is the ability to specify that your handler should be run in it's
* own thread. If the <code>runInThread</code> arg to <code>addHandler</code> is true,
* a new thread will be forked for the invocation of the handler code.
*
* <p>There's a trade-off to consider between single and multiple hub approachs. As a rule
* I'd say that you should create a new hub when efficiency is extremely important or when
* the event domain is very well contained. A single hub can be slightly more efficient
* just because the list of handlers is presumably smaller. So when every millisecond
* counts you're a little better off creating a dedicated hub than sharing part of a
* more general one. In the other case a hub is created to map to a well defined domain,
* like a partucular GUI panel. Things like naming conflicts can make it more practical
* in these cases to create individual hubs. Aside from these situations, a single hub
* can handle a wide variety of events and a large number of handlers with no worries.
*
* @see Actor
**/
public class EventHub {
protected Vector handlers = new Vector();
protected EventMonitor eventMonitor = null;
protected static Vector hubs = new Vector();
public EventHub() {
hubs.add(this);
}
// Handlers
public void addHandler(String actorFilter, String eventFilter,
Object definer, String handlerMethod,
Object handlerObject, boolean runInThread) {
addHandler(actorFilter, eventFilter, -1, definer, handlerMethod, handlerObject, runInThread);
}
public void addHandler(String actorFilter, String eventFilter, int sampleRate,
Object definer, String handlerMethod,
Object handlerObject, boolean runInThread) {
addHandler(actorFilter, eventFilter, sampleRate, definer, handlerMethod, handlerObject, runInThread, false);
}
protected void addHandler(String actorFilter, String eventFilter, int sampleRate,
Object definer, String handlerMethod,
Object handlerObject, boolean runInThread,
boolean addToFront) {
try {
HandlerWrapper handlerWrapper = new HandlerWrapper(("*".equals(actorFilter)) ? null : actorFilter,
("*".equals(eventFilter)) ? null : eventFilter,
sampleRate,
definer,
confirmDefiningClass(definer).getMethod(handlerMethod, getHandlerSignature()),
handlerObject,
runInThread);
if (addToFront)
handlers.add(0, handlerWrapper);
else
handlers.add(handlerWrapper);
}
catch (Exception exception) {
Application.logger.write(exception, "Error adding handler " + handlerMethod + " for event " + eventFilter +
".\nDefining class: " + definer.getClass());
}
}
public void removeHandler(final String eventFilter, final Object definer, final String handlerMethod) {
UtilTools.removeIf(handlers, new UtilTools.Predicate() {
public boolean predicate(Object arg) {
HandlerWrapper wrapper = (HandlerWrapper)arg;
return (((wrapper.eventFilter == null && (eventFilter == null || eventFilter.equals("*"))) ||
wrapper.eventFilter.equals(eventFilter)) &&
wrapper.definer.equals(definer) &&
wrapper.handler.getName().equals(handlerMethod));
}});
}
public void removeAllHandlers() {
handlers.clear();
}
public HandlerWrapper getHandlerWrapper(final String eventFilter, final Object definer, final String handlerMethod) {
return (HandlerWrapper)UtilTools.findIf(handlers, new UtilTools.Predicate() {
public boolean predicate(Object arg) {
HandlerWrapper wrapper = (HandlerWrapper)arg;
return (((wrapper.eventFilter == null && (eventFilter == null || eventFilter.equals("*"))) ||
wrapper.eventFilter.equals(eventFilter)) &&
wrapper.definer.equals(definer) &&
wrapper.handler.getName().equals(handlerMethod));
}});
}
public void setSampleRate(String eventFilter, Object definer, String handlerMethod, int sampleRate) {
HandlerWrapper wrapper = getHandlerWrapper(eventFilter, definer, handlerMethod);
if (wrapper != null)
wrapper.setSampleRate(sampleRate);
}
public void setHandlerObject(String eventFilter, Object definer, String handlerMethod, Object handlerObject) {
HandlerWrapper wrapper = getHandlerWrapper(eventFilter, definer, handlerMethod);
if (wrapper != null)
wrapper.setHandlerObject(handlerObject);
}
// Handler invocation
public void invokeHandlers(Actor actor, String event, Object invocationArg) {
invokeHandlers(actor, event, -1, invocationArg);
}
public void invokeHandlers(Actor actor, String event, int sampleNumber, Object invocationArg) {
if (eventMonitor != null)
eventMonitor.noteFired(Thread.currentThread(), this, actor, event, invocationArg);
for (int i = 0; i < handlers.size(); i++) {
HandlerWrapper wrapper = (HandlerWrapper)handlers.elementAt(i);
if (isMatch(wrapper.actorFilter, wrapper.actorFilterParts, wrapper.actorFilterIsWildcard, actor.getName()) &&
isMatch(wrapper.eventFilter, wrapper.eventFilterParts, wrapper.eventFilterIsWildcard, event) &&
(sampleNumber == -1 || wrapper.sampleRate == -1 || sampleNumber % wrapper.sampleRate == 0)) {
invokeHandler(wrapper, actor, event, invocationArg);
}
}
}
protected void invokeHandler(final HandlerWrapper wrapper, final Actor actor, final String event, final Object invocationArg) {
if (wrapper.runInThread)
ThreadTools.runInThread(Application.threadGroup, "Event Handler: " + event, new ThreadTools.Job() {
public void runJob(Thread jobThread, int jobCallIndex) {
invokeHandlerInternal(wrapper, actor, event, invocationArg);
}});
else
invokeHandlerInternal(wrapper, actor, event, invocationArg);
}
protected void invokeHandlerInternal(HandlerWrapper wrapper, Actor actor, String event, Object invocationArg) {
if (eventMonitor != null)
eventMonitor.noteHandlerStart(Thread.currentThread(), this, wrapper.definer, wrapper.handler, actor, event, invocationArg, wrapper.handlerObject);
invoke(wrapper.definer, wrapper.handler, actor, event, wrapper.handlerObject, invocationArg);
if (eventMonitor != null)
eventMonitor.noteHandlerStop(Thread.currentThread(), this, wrapper.definer, wrapper.handler, actor, event, invocationArg, wrapper.handlerObject);
}
// Customizable handler signatures
protected Class[] getHandlerSignature() {
// actor event handlerObject invocationArg
return new Class[] { Actor.class, String.class, Object.class, Object.class };
}
protected void invoke(Object definer, Method handler, Actor actor, String event, Object handlerObject, Object invocationArg) {
try {
handler.invoke(definer, new Object[] { actor, event, handlerObject, invocationArg });
}
catch (Exception exception) {
Application.logger.write(exception, "Error invoking handler for event: " + event);
}
}
// Monitoring events
public static interface EventMonitor {
public void noteFired(Thread onThread, EventHub hub,
Actor actor, String event, Object invocationArg);
public void noteHandlerStart(Thread onThread, EventHub hub,
Object handlerDefiner, Method handler,
Actor actor, String event, Object invocationArg, Object handlerObject);
public void noteHandlerStop(Thread onThread, EventHub hub,
Object handlerDefiner, Method handler,
Actor actor, String event, Object invocationArg, Object handlerObject);
}
public void setEventMonitor(EventMonitor eventMonitor) {
this.eventMonitor = eventMonitor;
}
public EventMonitor getEventMonitor() {
return eventMonitor;
}
protected Class confirmDefiningClass(Object definer) {
return (definer instanceof Class) ? (Class)definer : definer.getClass();
}
// Utils
/**
* We're trying to balance flexibility and efficiency here, allowing wildcard string
* matching for actors and events while still identifying simple matches very quickly.
* First we check for a "*" filter, implemented as a <code>null</code>. Then if there
* were no *'s in the filter we can do a simple string comparison. This checks for
* reference equality first, so in most cases this won't require a character by
* character analysis of the strings. Finally we resort to a <code>wildcardMatch</code>
* which is efficient, but does require some extra work. To maximize the efficiency
* even in this case we preparse the wildcard string into an array of parts ahead of
* time. So the <code>filterParts</code> array for the wildcard string "airplane.*.setting"
* contains the strings "airplane." and ".settings". This preparsing greatly improves
* the invocation efficiency.
**/
protected boolean isMatch(String filter, String[] filterParts, boolean filterIsWildcard, String stringToMatch) {
if (filter == null)
return true;
if (! filterIsWildcard)
return filter.equals(stringToMatch);
return StringTools.wildcardMatch(filterParts, filter, stringToMatch, true);
}
// Wrappers
public static class HandlerWrapper {
protected String actorFilter;
protected boolean actorFilterIsWildcard;
protected String[] actorFilterParts;
protected String eventFilter;
protected boolean eventFilterIsWildcard;
protected String[] eventFilterParts;
protected int sampleRate;
protected Object definer;
protected Method handler;
protected Object handlerObject;
protected boolean runInThread;
protected HandlerWrapper(String actorFilter, String eventFilter, int sampleRate,
Object definer, Method handler,
Object handlerObject, boolean runInThread) {
this.actorFilter = actorFilter;
if (actorFilter != null) {
actorFilterIsWildcard = StringTools.contains(actorFilter, '*', 0, false);
actorFilterParts = StringTools.parseTokens(actorFilter, "*");
}
this.eventFilter = eventFilter;
if (eventFilter != null) {
eventFilterIsWildcard = StringTools.contains(eventFilter, '*', 0, false);
eventFilterParts = StringTools.parseTokens(eventFilter, "*");
}
this.sampleRate = sampleRate;
this.definer = definer;
this.handler = handler;
this.handlerObject = handlerObject;
this.runInThread = runInThread;
}
public void setSampleRate(int sampleRate) {
this.sampleRate = sampleRate;
}
public void setHandlerObject(Object handlerObject) {
this.handlerObject = handlerObject;
}
public String toString() {
return "#<HandlerWrapper actorFilter='" + actorFilter + "' eventFilter = '" + eventFilter + "'>";
}
}
}
package com.rst.tools.net;
import com.rst.tools.io.*;
import com.rst.tools.lang.*;
import com.rst.tools.util.*;
import java.net.*;
import java.io.*;
import java.util.*;
import java.util.zip.*;
import java.util.jar.*;
/**
* A simple http server supporting standard http traffic (like html files, images, and such) and dynamic content through plugins.
* Standard http traffic is served as files under the <code>wwwRoot</code>, much like any web server.
*
* <p>Plugins are Java classes extending the abstract class <code>HttpServer.Plugin</code>. The following example shows how to
* write a plugin ...
*
* <ul>
* <li><code>http://MyHost/timeteller</code> generates ...
* <br><br>The current time is Fri Feb 28 20:13:56 EST 2003.
* <br><br>
* <li><code>http://MyHost/timeteller?myName=Bob</code> generates ...
* <br><br>Hello Bob.
* <br><br>The current time is Fri Feb 28 20:16:11 EST 2003.
* </ul>
*
* <p>Here is the source code.
*
* <pre>
* public class TimeTellerPlugin extends HttpServer.Plugin {
*
* public boolean canHandle(HttpServer.Request request) {
* return StringTools.endsWith(request.getResource(), "timeteller", true);
* }
*
* public void writeResponse(BufferedOutputStream out, HttpServer.Request request) throws IOException {
* httpServer.writeStatusLine(out, request, HttpServer.statusCodeOK);
* httpServer.writeStandardResponseHeaders(out, request);
* httpServer.writeHeader(out, "Content-Type", "text/html");
* httpServer.endHeaders(out);
*
* if (request.parameters != null && request.parameters.get("myName") != null)
* out.write(("Hello " + request.parameters.get("myName") + "!").getBytes());
* out.write(("<p>The current time is " + new java.util.Date() + ".").getBytes());
* }
* }
* </pre>
**/
public class HttpServer implements ThreadPool.Job {
protected int port = 80;
protected File wwwRoot = new File(System.getProperty("user.dir"));
protected String serverName;
protected Plugin[] plugins = new Plugin[0];
protected int logLevel = 1;
protected ThreadPool threadPool;
protected String urlBase;
protected boolean reportFilePermissions = false;
public static String statusCodeOK = HttpURLConnection.HTTP_OK + " OK";
public static String statusCodeNotFound = HttpURLConnection.HTTP_NOT_FOUND + " Not found";
public static String statusClientTimeout = HttpURLConnection.HTTP_CLIENT_TIMEOUT + " Client Timeout";
public static String statusCodeCreated = HttpURLConnection.HTTP_CREATED + " Created";
public HttpServer(String serverName, int nThreadsInPool) {
this.serverName = serverName;
threadPool = new ThreadPool(serverName + " Thread Pool", nThreadsInPool, this, HttpPoolMember.class);
setURLBase();
}
public void setPort(int port) {
this.port = port;
setURLBase();
}
public int getPort() {
return port;
}
public void setWWWRoot(File wwwRoot) {
this.wwwRoot = FileTools.makeCanonical(wwwRoot);
}
public void setWWWRoot(String wwwRootString) {
setWWWRoot(new File((".".equals(wwwRootString)) ? System.getProperty("user.dir") : wwwRootString));
}
public File getWWWRoot() {
return wwwRoot;
}
protected void setURLBase() {
try {
urlBase = "http://" + InetAddress.getLocalHost().getHostAddress() + ((port == 80) ? "" : ":" + port);
}
catch (Exception exception) {
Application.logger.write(exception, "Unable to set base URL.");
}
}
public String getURLBase() {
return urlBase;
}
public static void main(String[] args) {
Application.init("HttpServer", args, "&key (:wwwRoot \".\") (:port 80) (:plugins \"\") (:help false)");
HttpServer httpd = new HttpServer("Mike's Simple HTTP Server", 8);
try {
httpd.setWWWRoot(Application.getCommandArg(":wwwRoot").toString());
httpd.setPort((int)Application.getNumericCommandArg(":port", 80));
httpd.printBanner();
httpd.addPlugins(StringTools.parseTokens(Application.getCommandArg(":plugins").toString(), ":"));
httpd.runServer();
}
catch (Exception exception) {
Application.exitWithUsageMessage(exception);
}
}
public void runServer() throws IOException {
addPlugin(standardCorePlugin);
runServerInternal();
}
protected void runServerInternal() throws IOException {
ServerSocket serverSocket = new ServerSocket(port, 50, null);
while (true) {
ThreadTools.noteThreadWaitBegin(Thread.currentThread(), serverSocket);
Socket socket = serverSocket.accept();
ThreadTools.noteThreadWaitEnd(Thread.currentThread(), serverSocket);
threadPool.runInFreeThread(new Object[] { socket });
}
}
// Thread pool members
public static class HttpPoolMember extends ThreadPool.Member {
protected Request reusableRequest = new Request();
}
// ThreadPool.Job interface
public void runJob(ThreadPool.Member poolMember, Thread jobThread, Object[] jobArgs, int jobCallIndex) {
Socket socket = (Socket)jobArgs[0];
try {
BufferedInputStream in = new BufferedInputStream(socket.getInputStream(), 512);
BufferedOutputStream out = new BufferedOutputStream(socket.getOutputStream(), 2048);
handleRequest(((HttpPoolMember)poolMember).reusableRequest, in, out);
out.flush();
}
catch (IOException exception) {
Application.logger.write(0, "Error processing HTTP request: " + exception);
}
finally {
if (! StreamTools.tryToClose(socket))
Application.logger.write(0, "Unable to close socket.");
}
}
/**
* Handles one HTTP transaction. Parses the HTTP request from the input stream,
* finds the appropriate plugin to handle the request, and invokes the plugin.
**/
protected boolean handleRequest(Request request, BufferedInputStream in, BufferedOutputStream out) throws IOException {
request.read(in);
if (request.getResource().length() == 0)
request.setResource("index.html");
if (logLevel == 2)
request.log();
for (int i = 0; i < plugins.length; i++)
if (plugins[i].canHandle(request)) {
plugins[i].writeResponse(out, request);
return true;
}
return false;
}
// Plugins
public static abstract class Plugin {
protected String name;
protected HttpServer httpServer;
public abstract void writeResponse(BufferedOutputStream out, Request request) throws IOException;
public boolean canHandle(Request request) {
return false;
}
public void init() {
}
/**
* Returns the root URL for the plugin, for programmatically constructing
* URLs served by the plugin.
**/
// TODO: this is probably no longer correct -JK 6/13/07
public String getURLBase() {
try {
//if (this == httpServer.standardCorePlugin)
return "http://" + InetAddress.getLocalHost().getHostAddress() +
((httpServer.port == 80) ? "" : ":" + httpServer.port) + "/";
//else
// return "http://" + InetAddress.getLocalHost().getHostAddress() +
// ((httpServer.port == 80) ? "" : ":" + httpServer.port) + "/plugin/" + this.toString() + "/";
}
catch (UnknownHostException exception) {
exception.printStackTrace();
}
return null;
}
public HttpServer getHttpServer() {
return httpServer;
}
public String toString() {
return name;
}
}
public void addPlugin(Plugin plugin) {
plugin.httpServer = this;
plugin.init();
plugins = (Plugin[])ArrayTools.pushEnd(plugin, plugins);
}
public void addPlugins(String[] pluginClasses) {
for (int i = 0; i < pluginClasses.length; i++) {
try {
addPlugin((Plugin)Class.forName(pluginClasses[i]).newInstance());
}
catch (Exception exception) {
Application.logger.write(exception, "Error adding plugin class: " + pluginClasses[i] + "\n " + exception);
}
}
}
// Requests
public static class Request {
protected String method;
protected String uri;
protected String resource;
protected String httpVersion;
protected Hashtable headers = new Hashtable(37);
protected Hashtable parameters = new Hashtable(37);
protected Hashtable cookies = new Hashtable(37);
protected byte[] content;
protected Calendar calendar = Calendar.getInstance();
public void reset() {
cookies.clear();
headers.clear();
parameters.clear();
}
public void read(BufferedInputStream in) throws IOException {
reset();
method = StreamTools.readThrough(in, " ", false, false);
uri = StreamTools.readThrough(in, " HTTP", false, false);
httpVersion = StreamTools.readLine(in);
parseHeaders(in);
int contentLength = NumberTools.getInt(headers.get("CONTENT-LENGTH"), 0);
content = new byte[contentLength];
StreamTools.copy(in, content, 0, contentLength);
parseParameters(this);
resource = trimQueryString(uri);
}
public String getResource() {
return resource;
}
public void setResource(String resource) {
this.resource = resource;
}
public String getURI() {
return uri;
}
public String getMethod() {
return method;
}
public Hashtable getParameters() {
return parameters;
}
public byte[] getContent() {
return content;
}
public void parseHeaders(BufferedInputStream in) throws IOException {
while (true) {
if (StreamTools.peek(in, "\r\n", false))
return;
String header = StreamTools.readThrough(in, ":", false, false);
if (header == null)
return;
String value = StreamTools.readLine(in);
if (value != null)
headers.put(header.trim().toUpperCase(), value.trim());
}
}
/**
* Parses the query string parameters from the <code>resource</code> string.
* Returns <code>null</code> if there are no parameters so we don't cons up
* empty <code>Hashtables</code> all over the place.
**/
public void parseParameters(Request request) {
if ("GET".equalsIgnoreCase(request.method))
NetTools.urlDecodeParameters(request.uri, parameters);
else
if (StringTools.wildcardMatch("*/x-www-form-urlencoded", (String)request.headers.get("CONTENT-TYPE"), true))
NetTools.urlDecodeParameters(NetTools.queryStringBeginChar + new String(request.content), parameters);
}
public String getHeader(String name) {
if (headers == null)
return null;
return (String)headers.get(name);
}
public String getCookie(String name) {
String cookieHeader;
if (cookies.isEmpty() && (cookieHeader = getHeader("COOKIE")) != null) {
String[] parts = StringTools.parseTokens(cookieHeader, ";=");
for (int i = 0; i < parts.length; i = i + 2)
cookies.put(parts[i].trim(), parts[i + 1].trim());
}
return (String)cookies.get(name.trim());
}
public String trimQueryString(String uri) {
int eoFile = uri.indexOf(NetTools.queryStringBeginChar);
if (eoFile == -1)
return uri;
return uri.substring(0, eoFile);
}
public void log() {
System.out.print("REQ> ");
System.out.print(method);
System.out.print(' ');
System.out.print(uri);
System.out.print(' ');
System.out.print("HTTP");
System.out.println(httpVersion);
Enumeration headersEnum = headers.keys();
while (headersEnum.hasMoreElements()) {
Object key = headersEnum.nextElement();
System.out.print("REQ> ");
System.out.print(key);
System.out.print(':');
System.out.print(' ');
System.out.println(headers.get(key));
}
System.out.println("REQ> CONTENT");
if (content.length > 40) {
for (int i = 0; i < 10; i++)
System.out.print(content[i] + " ");
System.out.print(" ...");
}
else
for (int i = 0; i < content.length; i++)
System.out.print(content[i] + " ");
System.out.println();
System.out.println("REQ> END OF CONTENT");
System.out.println();
if (parameters != null) {
System.out.println("REQ> PARAMETERS");
Enumeration parametersEnum = parameters.keys();
while (parametersEnum.hasMoreElements()) {
Object key = parametersEnum.nextElement();
System.out.print(key);
System.out.print(' ');
System.out.print('=');
System.out.print(' ');
System.out.print('"');
System.out.print(parameters.get(key));
System.out.println('"');
}
System.out.println("REQ> END OF PARAMETERS");
}
System.out.println();
}
}
/**
* This <code>Plugin</code> object handles all standard static web server resources, which essentially means files like html, images, css, js, and
* so on. This plugin is in contrast to the others which generally handle some sort of dynamic resource like cgi, servlets, and so on. This plugin
* is loaded automatically but can be removed if you want a web server that will <i>not</i> handle normal static resources.
**/
protected Plugin standardCorePlugin = new StandardCorePlugin();
public Plugin getStandardCorePlugin() {
return standardCorePlugin;
}
protected static class StandardCorePlugin extends Plugin {
public StandardCorePlugin() {
name = "Standard Core Plugin";
}
public boolean canHandle(Request request) {
return true;
}
public void writeResponse(BufferedOutputStream out, Request request) throws IOException {
if ("PUT".equals(request.method)) {
httpServer.handlePUTRequest(out, request);
return;
}
// check if file exists
File file = new File(httpServer.wwwRoot, request.resource);
if (! file.exists()) {
httpServer.writeCodeNotFoundReponse(out, request);
return;
}
// write nominal headers
httpServer.writeStatusLine(out, request, HttpServer.statusCodeOK);
httpServer.writeStandardResponseHeaders(out, request, file);
httpServer.writeHeader(out, "Content-Type", HttpServer.getContentType(request.resource));
httpServer.writeHeader(out, "Content-Length", "" + file.length());
if (httpServer.reportFilePermissions && FileTools.canManagePermissions())
httpServer.writeHeader(out, "Permissions", FileTools.getFilePermissions(file.toString()));
httpServer.endHeaders(out);
if ("HEAD".equals(request.method))
return;
// write response body -- the file contents
InputStream fileIn = new FileInputStream(file);
StreamTools.copy(fileIn, out);
fileIn.close();
}
}
protected void handlePUTRequest(BufferedOutputStream out, Request request) throws IOException {
File file = new File(wwwRoot, request.resource);
FileTools.makeDirectory(file.toString());
BufferedOutputStream fileOut = new BufferedOutputStream(new FileOutputStream(file), 1024);
fileOut.write(request.content);
fileOut.close();
writeStatusLine(out, request, HttpServer.statusCodeCreated);
writeStandardResponseHeaders(out, request, null);
writeHeader(out, "Content-Length", "0");
writeHeader(out, "Location", urlBase + request.resource);
endHeaders(out);
}
protected void writeCodeNotFoundReponse(BufferedOutputStream out, Request request) throws IOException {
writeStatusLine(out, request, HttpServer.statusCodeNotFound);
writeStandardResponseHeaders(out, request, null);
endHeaders(out);
}
public static String getContentType(String uri) {
String contentType = URLConnection.getFileNameMap().getContentTypeFor(uri);
if (contentType != null)
return contentType;
if (StringTools.endsWith(uri, ".css", true))
return "text/css";
if (StringTools.endsWith(uri, ".js", true))
return "text/javascript";
if (StringTools.endsWith(uri, ".ogv", true))
return "video/ogg";
if (StringTools.endsWith(uri, ".mp4", true))
return "video/mp4";
return "text/plain";
}
// Reporting file permissions
/**
* If so configured, add a header called "Permissions" to pass file resources permissions
* on to the client.
*
* @see FileTools#setFilePermissions
**/
public void setReportFilePermissions(boolean reportFilePermissions) {
this.reportFilePermissions = reportFilePermissions;
}
public boolean getReportFilePermissions() {
return reportFilePermissions;
}
// Traffic logging
public void setLogLevel(int logLevel) {
this.logLevel = logLevel;
}
public int getLogLevel() {
return logLevel;
}
public boolean shouldLog() {
return (logLevel > 0);
}
// Utils
public void writeStandardResponseHeaders(BufferedOutputStream out, Request request, File file) throws IOException {
writeHeader(out, "Server", serverName);
writeDateHeader(out, "Date", request.calendar, System.currentTimeMillis());
if (file != null) {
writeDateHeader(out, "Last-Modified", request.calendar, file.lastModified());
request.calendar.add(Calendar.YEAR, 1);
writeDateHeader(out, "Expires", request.calendar, request.calendar.getTimeInMillis());
}
}
public void writeCookie(BufferedOutputStream out, String variable, Object value, int expirationDelta) throws IOException {
String expirationPart = (expirationDelta < 0) ? "" : ("; expires=" + NetTools.toGMTString(new Date(new Date().getTime() + expirationDelta)));
writeHeader(out, "Set-Cookie", NetTools.urlEncode(variable) + "=" + NetTools.urlEncode(value.toString()) + expirationPart);
}
public void writeStatusLine(BufferedOutputStream out, Request request, String statusCode) throws IOException {
write(out, "HTTP/1.1 ");
if (logLevel == 1)
System.out.println(request.getMethod() + " " + request.resource + " => " + statusCode);
write(out, statusCode);
write(out, '\r');
write(out, '\n');
}
public void writeHeader(BufferedOutputStream out, String header, String value) throws IOException {
write(out, header);
write(out, ':');
write(out, ' ');
write(out, value);
write(out, '\r');
write(out, '\n');
}
public void endHeaders(BufferedOutputStream out) throws IOException {
write(out, '\r');
write(out, '\n');
}
protected void write(BufferedOutputStream out, String string) throws IOException {
if (logLevel == 3)
System.out.print(string);
out.write(string.getBytes());
}
protected void write(BufferedOutputStream out, char character) throws IOException {
if (logLevel == 3)
System.out.print(character);
out.write(character);
}
// Writing HTTP dates
protected int gmtTimezoneDelta = -(Calendar.getInstance().get(Calendar.ZONE_OFFSET) + Calendar.getInstance().get(Calendar.DST_OFFSET)) / (60 * 1000);
protected static String days[] = { "Sat, ", "Sun, ", "Mon, ", "Tue, ", "Wed, ", "Thu, " , "Fri, " };
protected static String months[] = { " Jan ", " Feb ", " Mar ", " Apr ", " May ", " Jun ", " Jul ", " Aug ", " Sep ", " Oct ", " Nov ", " Dec " };
public void writeDateHeader(BufferedOutputStream out, String header, Calendar calendar, long timestamp) throws IOException {
write(out, header);
write(out, ':');
write(out, ' ');
calendar.setTimeInMillis(timestamp + gmtTimezoneDelta * 60 * 1000);
// Wed, 07 Jun 2006 19:38:13 GMT
write(out, days[calendar.get(Calendar.DAY_OF_WEEK) - 1]);
int date = calendar.get(Calendar.DATE);
if (date < 10)
write(out, '0');
write(out, "" + date);
write(out, months[calendar.get(Calendar.MONTH)]);
write(out, "" + calendar.get(Calendar.YEAR));
write(out, ' ');
int hour = calendar.get(Calendar.HOUR_OF_DAY);
if (hour < 10)
write(out, '0');
write(out, "" + hour);
write(out, ':');
int minute = calendar.get(Calendar.MINUTE);
if (minute < 10)
write(out, '0');
write(out, "" + minute);
write(out, ':');
int second = calendar.get(Calendar.SECOND);
if (second < 10)
write(out, '0');
write(out, "" + second);
write(out, " GMT\r\n");
}
public void printBanner() {
System.out.println("Serving HTTP requests\n" +
"Server: " + serverName + "\n" +
"Port: " + port + "\n" +
"Document root: " + wwwRoot.getAbsolutePath() + "\n" +
"Serving URLs of type: " + urlBase + "/index.html" + "\n\n");
}
public void setServerName(String serverName) {
this.serverName = serverName;
}
public String getServerName() {
return serverName;
}
}
package com.rst.vision;
import com.rst.tools.lang.*;
import com.rst.tools.math.*;
import com.rst.tools.util.*;
import com.rst.vision.*;
/**
* This class calculates the minimun blob bounding box about an arbitrary axis defined by a point, the centroid,
* and a slope, m. This bounding box is aligned parallel to the axis, as opposed to the rectilinear bounding
* box which is always parallel to the x and y axes. As shown below, we find the pixel with the greatest
* perpendicular distance on each side of the line. The sum of these 2 maximum perpendicular distances is the
* breadth of the bounding box. We then repeat the process with an axis perpendicular to the first axis. This
* line's slope is -1/m. The maximum dimension about this axis is the length of the bounding box.
*
* <pre>
* .'
* .'
* .' .'
* .' .' __
* .' atan(m) .' l`.
* p .'-----------> x p .' .' `.
* p .' p* `. .' p* `. dimension
* p .' `. p `. .' .' `.
* .' `. `. .' .' `.
* .' `. .'`. .' `.
* p .' p `.' `. `.| .
* c' `. c' `. c --` .'
* .' `. .' `. `. .'
* p .' p p `.'`. `. `p* p*
* .' `. .' `. `. .'
* .' p p .' `p `p .'
* .' .' .'
*
*
* (a) A blob of pixels p centered around (b) We calculate the perpendicular (c) The dimension of the blob about this
* the centroid c. An axis is defined by distance of each pixel to the axis. axis is the sum of the perpendicular
* the point c and a slope m. The angle We find the pixel, p*, with the distances for the 2 p* pixels.
* between the x axis and this line is greatest perpendicular distance on
* the inverse tangent of m. each side of the axis.
*
* </pre>
*
* <p>For efficiency we maximize the rectilinear distance, shown below, rather than the perpendicular distance.
* Since all triangles are congruent, the pixel with the greatest rectilinear distance will also have the
* greatest perpendicular distance. The advantage is that the rectilinear distance can be computed with a
* simple subtraction. Our algorithm scans either horizontally or vertically depending on the slope of the
* axis. We scan from the edge of the rectilinear bounding box, backwards long either a horizontal line
* (vertical == false) or a vertical line (vertical == true). The first on pixel we encounter has the biggest
* rectilinear distance for that scan line and we can continue on to the next scan line.
*
* <pre>
* .'
* .' | right side of
* .' | rectilinear
* .' `. perpendicular | bounding box
* .' 90 `. distance |
* .' `. |
* .' `. |
* .'- - - - - - - - -`p <------|
* .' rectilinear scan |
* .' distance dir |
* axis .' |
* '
*
* (a) The perpendicular distance is meausured at a right angle from the axis
* along a vector we call the axis normal. In this example we're scanning
* horizontally so we connect a horizontal line from pixel p to the axis to
* form a right triangle. We call this the rectilinear distance. Note that
* a congruent triangle can be constructed for any pixel p. Note also that
* we scan along this horizontal line from right to left. The first on pixel
* we encounter has the biggest rectilinear distance. Analogous calculations
* are made for vertical scanning.
* </pre>
*
* <p>The axis divides the space into 2 halves. When we're scanning horizontally there's a left half and a
* right half. When we're scanning vertically there's a top half and a bottom half. We generically call
* these the forward (right or bottom) and the backward (left or top) halves. In figure (a) above the
* example pixel p is on the forward (right in this case) side of the axis.
*
* <p>We must calculate one of the angles of our congruent triangles so we can convert the final max rectilinear
* distance to a perpendicular distance. We use the angle between the hypotenuse (either a horizontal or
* vertical scan line) and the axis normal. We call this angle a. This calculation is shown below.
*
* <pre>
*
* vertical scanning
* | | | | | | | | | |
* v v v v v v v v v v
*
* horizontal p
* scanning .'!
* --> .' !
* .' `. .' a !
* --> .' `. .' !
* .' `.' 90 !
* --> .' `. `. !
* .' 90 `. `. !
* --> .' `. `. !
* .' a `. `. !
* --> .'- - - - - - - - -`p `!
* .' `.
* --> .' |atan(m)| ______`._______________ x
* .'______________ x `. |atan(m)|
* --> .' `.
* `.
*
* a = pi/2 - |atan(m)| a = |atan(m)|
*
*
* (a) In both the horizontal and vertical scan cases the angle between the x axis
* and the symmetry axis is the absolute value of the inverse tangent of the slope.
* For horizontal scanning the angle, a, between the horizontal hypotenuse and the
* axis normal is pi/2 minus the slope angle, |atan(m)|. For vertical scanning the
* angle, a, between the vertical hypotenuse and the axis normal is just the slope
* angle, again |atan(m)|.
* </pre>
*
* <p>To further optimize the performance of this algorithm we limit our scanning according to our current max
* rectilinear distance as we loop through the blob. If our current max rectilinear distance is at pixel P, we
* needn't search through the area of the blob proximal to P as we know the final answer will be P or some
* pixel distal to P.
*
* <pre>
*
* .'
* .' axis
* .'
* .'
* .'
* .'
* .' p1
* .'
* .' p2
* .' p3
* .'
* .' p4
* .'
* .'
*
* (a) 4 blob points on one side of the axis. We're searching for the point most distant
* from the axis which will turn out to be p4. Note that this example shows the horizontal
* case, but the algorithm is the same for the vertical case.
*
*
* scan
* 00 .'<------------
* 01 .'<--------------|
* 02 .'<----------------|
* 03 .'<------------------|
* 04 .'<--------------------|
* 05 .'<----------------------|
* 06 .' p1<------|
* 07 .' .<--------|
* 08 .' p2 .<----------|
* 09 .' p3 .<------------|
* 10 .' .<--------------|
* 11 .' pruned p4<----------|
* 12 .' area .<------------|
* 13 .' .<--------------|
*
* (b) Scanning starts at the top and proceeds from right to left on each line. We start
* at the right end of each scan line and search backward until we find an on pixel or we
* reach the intersection of the scan line and the axis. In the example, scans 00 through
* 05 reach the axis without finding an on pixel. On scan line 06 we find p1. The
* rectilinear distance from p1 to the axis is now our current max. From this scan line
* on we don't search all the way back to the axis. If we don't find an on pixel we can
* stop when we're within the current max distance from the axis, thus pruning the area
* proximal to p1 from our search space. p2 and p3 are never encountered since we know p1
* is further out than they are. On scan line 11 we find p4 and make that our new max.
* This further prunes our search space, eliminating an even wider proximal area.
* </pre>
*
* <p>One further, and quite beneficial, efficiency enhancement comes from our choice for the initial value for
* the max rectilinear distance. Typically when you want to maximize a value you initialize your max with
* negative infinity so that the first value you encounter will be greater. We can choose a much better initial
* value however. We know that each edge of the rectilinear bounding box (left, right, top, and bottom) contains
* at least one on pixel. So we first search for one of those pixels and use its rectilinear distance as an
* initial max. We know the blob extends at least this far from the axis. This defines a reasonable and often
* large pruned area (described above) before we even begin scanning.
*
*
* <p>We have to avoid the case where the horizontal (or vertical) scan lines are parallel to the axis. To avoid
* this we decide whether to use vertical or horizontal scanning based on the slope as shown below.
*
* <pre>
* ------------------>
* | | | | | | | '
* __!__!__!__!__!__!__!__ ___________'___________
* | | | ' |
* | __ | |---| __ ' |--->
* | / \__________ | | | / \__'_______ |
* | / . .|. .|' ' ' ' |---| / ' | |--->
* |. .|. . ' ' ' ' __/ | | | | ' __/ |
* ' ' ' '| \_____ _/ | |---| \_____ ' _/ |--->
* | \__/ | | | \__' |
* | | |---| ' |--->
* l_______________________| v l_____________'_________|
* | | | | | | | '
* v v v v v v v '
* '
* (a) The vertical case when slope is between (b) The horizontal case when slope is below
* -1 and 1. The outer scanning loop goes -1 or above 1. The outer scanning loop
* from left to right. The inner loop goes goes from top to bottom. The inner loop
* from top to bottom. goes from left to right.
*
* </pre>
**/
public class LinearMeasurement {
protected Blob blob;
protected byte label;
protected boolean labelIsOn;
protected Vector2D forwardEndpoint = new Vector2D();
protected Vector2D backwardEndpoint = new Vector2D();
protected double dForward;
protected double dBackward;
protected double measurement;
protected DistalPoint forwardDistalPoint = new DistalPoint();
protected DistalPoint backwardDistalPoint = new DistalPoint();
protected Line line = new Line();
protected IterationBounds bounds = new IterationBounds();
public LinearMeasurement(Blob blob) {
this.blob = blob;
}
public static class Length extends LinearMeasurement {
public Length(Blob blob) {
super(blob);
}
public void measureInternal() {
findEndpoints(-1 / blob.getSlope());
}
}
public static class Breadth extends LinearMeasurement {
public Breadth(Blob blob) {
super(blob);
}
public void measureInternal() {
findEndpoints(blob.getSlope());
}
}
public void measure(byte label, boolean labelIsOn) {
this.label = label;
this.labelIsOn = labelIsOn;
measureInternal();
}
public void measureInternal() {
}
protected void findEndpoints(double m) {
boolean vertical = -1 < m && m < 1;
Blob.AreaCentroid centroid = blob.getCentroid();
LTRB ltrb = blob.getBoundingBox();
line.init(m, centroid.x, centroid.y);
if (vertical)
bounds.modSet(ltrb.left, ltrb.right, ltrb.top, ltrb.bottom);
else
bounds.modSet(ltrb.top, ltrb.bottom, ltrb.left, ltrb.right);
findSeedD(forwardDistalPoint, vertical, bounds.max, bounds.from, bounds.to);
findSeedD(backwardDistalPoint, vertical, bounds.min, bounds.from, bounds.to);
for (int i = bounds.from; i <= bounds.to; i++) {
double lineCoordinate = line.solve(vertical, i);
// Forward
for (int j = bounds.max; j >= Math.max(lineCoordinate + forwardDistalPoint.rectilinearD, bounds.min); j--) {
if (isPixelOn(vertical, i, j)) {
double rectilinearD = j - lineCoordinate;
if (rectilinearD > forwardDistalPoint.rectilinearD)
forwardDistalPoint.modSet(vertical, i, j, rectilinearD);
break;
}
}
// Backward
for (int j = bounds.min; j <= Math.min(lineCoordinate - backwardDistalPoint.rectilinearD, bounds.max); j++) {
if (isPixelOn(vertical, i, j)) {
double rectilinearD = lineCoordinate - j;
if (rectilinearD > backwardDistalPoint.rectilinearD)
backwardDistalPoint.modSet(vertical, i, j, rectilinearD);
break;
}
}
}
double angle = Math.abs(Math.atan(m));
if (! vertical)
angle = NumberTools.piOver2 - angle;
int mSign = NumberTools.sign(m);
double sinA = Math.sin(angle);
double cosA = Math.cos(angle);
dForward = cosA * forwardDistalPoint.rectilinearD;
dBackward = cosA * backwardDistalPoint.rectilinearD;
measurement = dForward + dBackward;
double xFactor = vertical ? -mSign * sinA : cosA;
double yFactor = vertical ? cosA : -mSign * sinA;
forwardEndpoint.modSet( centroid.x + xFactor * dForward, centroid.y + yFactor * dForward);
backwardEndpoint.modSet(centroid.x - xFactor * dBackward, centroid.y - yFactor * dBackward);
}
protected void findSeedD(DistalPoint point, boolean vertical, int reference, int from, int to) {
for (int i = from; i <= to; i++)
if (isPixelOn(vertical, i, reference)) {
point.modSet(vertical, i, reference, Math.abs(reference - line.solve(vertical, i)));
return;
}
}
protected boolean isPixelOn(boolean vertical, int i, int j) {
return blob.isPixelOn(vertical ? i : j, vertical ? j : i, label, labelIsOn);
}
public Vector2D getForwardEndpoint() {
return forwardEndpoint;
}
public Vector2D getBackwardEndpoint() {
return backwardEndpoint;
}
public double getMeasurement() {
return measurement;
}
/**
* A utility class used to represent the equation of the line about which we are measuring.
* The <code>init</code> method initializes the line. It's takes the slope and a point on
* the line. It calculates the y-intercept, <code>b</code>. It also holds onto the x and
* y point used to define the line. When the line is vertical (slope is infinite), there
* is no y-intercept. We need the defining x value in that case to solve for x later.
**/
protected class Line {
protected double m;
protected double b;
protected boolean isVertical;
protected double definingX;
protected double definingY;
protected void init(double m, double x, double y) {
this.m = m;
definingX = x;
definingY = y;
b = y - m * x;
isVertical = (m == Double.POSITIVE_INFINITY);
}
protected double solve(boolean vertical, double otherCoordinate) {
return vertical ? (m * otherCoordinate + b) : (isVertical ? definingX : ((otherCoordinate - b) / m));
}
}
protected class DistalPoint extends Point {
protected double rectilinearD;
public DistalPoint modSet(boolean vertical, int i, int j, double rectilinearD) {
this.x = vertical ? i : j;
this.y = vertical ? j : i;
this.rectilinearD = rectilinearD;
return this;
}
public void setRectilinearD(double rectilinearD) {
this.rectilinearD = rectilinearD;
}
public double getRectilinearD() {
return rectilinearD;
}
}
protected class IterationBounds {
protected int from;
protected int to;
protected int min;
protected int max;
protected void modSet(int from, int to, int min, int max) {
this.from = from;
this.to = to;
this.min = min;
this.max = max;
}
}
}
package com.rst.tools.math;
import com.rst.tools.lang.*;
/**
* A basic implementation of a 3D vector.
*
* <p>For efficiency this class promotes the reuse of <code>Vector3D</code>
* objects wherever possible. This limits the need for new memory allocation,
* but it places extra responsibility on the caller. Namely the caller must
* know which operations modify the <code>Vector3D</code> instance. These
* rules govern this behavior.
*
* <ul>
* <li> Only the constructors and the <code>copy</code> method create new
* <code>Vector3D</code> instances. No other methods allocate any
* new memory for objects.
* <li> No <code>Vector3D</code> object passed in as an argument to any
* method is modified, unless it is named <code>result</code>.
* <li> Methods beginning with the prefix "get" retrieve components of the
* <code>Vector3D</code> instance. No instance is modified.
* <li> Methods beginning with the prefix "compute" perform some calulation
* on the <code>Vector3D</code> instance. No instance is modified.
* <li> Only methods beginning with the prefix "mod" modify the <code>Vector3D</code>
* instance.
* <li> Methods ending with the postfix "Into" do not modify the <code>Vector3D</code>
* instance, but they do modify the <code>Vector3D</code> argument
* named <code>result</code>.
* </ul>
*
* <p>This last category of methods, those ending in "Into", are particularly
* useful for object reuse, though they're a little more difficult to use. These
* methods all take a <code>Vector3D</code> argument named <code>result</code>
* into which the result of the operation is loaded. An efficiency minded caller
* can keep a temporary <code>Vector3D</code> object and reuse it over and
* over to hold the results of various operations. This works as long as each result is
* consumed (used for whatever purpose it was computed) before the temporary
* <code>Quaternion</code> is reused again.
**/
public class Vector3D implements VectorAccessor, LangTools.StateSettable, LangTools.StateGettable {
public double x, y, z;
public static final Vector3D origin = new Vector3D(0, 0, 0);
public static final Vector3D xAxis = new Vector3D(1, 0, 0);
public static final Vector3D yAxis = new Vector3D(0, 1, 0);
public static final Vector3D zAxis = new Vector3D(0, 0, 1);
public static final Vector3D minusXAxis = new Vector3D(-1, 0, 0);
public static final Vector3D minusYAxis = new Vector3D(0, -1, 0);
public static final Vector3D minusZAxis = new Vector3D(0, 0, -1);
public Vector3D() {
this(0.0, 0.0, 0.0);
}
public Vector3D(double x, double y, double z) {
modSet(x, y, z);
}
public Object copy() {
return new Vector3D(x, y, z);
}
public Vector3D copyInto(Vector3D result) {
result.x = x;
result.y = y;
result.z = z;
return result;
}
public double getComponent(int index) {
return (index == 0) ? x : ((index == 1) ? y : z);
}
public double modSetComponent(int index, double newValue) {
switch (index) {
case 0: return (x = newValue);
case 1: return (y = newValue);
case 2: return (z = newValue);
}
return newValue;
}
public double getX() {
return x;
}
public double getY() {
return y;
}
public double getZ() {
return z;
}
// Vector comparison
public boolean isCoincident(Object withVector) {
if (this == withVector)
return true;
if (withVector instanceof Vector3D) {
Vector3D otherVector = (Vector3D)withVector;
return (x == otherVector.x && y == otherVector.y && z == otherVector.z);
}
return false;
}
public boolean isCoincident(double x, double y, double z) {
return (x == this.x && y == this.y && z == this.z);
}
// Basic artihmetic
public Vector3D modSet(double x, double y, double z) {
this.x = x;
this.y = y;
this.z = z;
return this;
}
public Vector3D modSet(Vector3D v) {
return modSet(v.x, v.y, v.z);
}
/**
* Computes v = v + w, where v is <code>this</code> vector and
* w is the given vector. w is not modified, but v is.
**/
public Vector3D modAdd(Vector3D w) {
return modSet(x + w.x, y + w.y, z + w.z);
}
/**
* Computes v = v + [x, y, z], where v is <code>this</code> vector and
* [x, y, z] is the given vector. v is modified.
**/
public Vector3D modAdd(double x, double y, double z) {
return modSet(this.x + x, this.y + y, this.z + z);
}
/**
* Computes r = v + w, where r is the result vector, v is <code>this</code> vector, and
* w is the given vector. v and w are not modified, but r is.
**/
public Vector3D addInto(Vector3D w, Vector3D result) {
return result.modSet(x + w.x, y + w.y, z + w.z);
}
/**
* Computes v = v - w, where v is <code>this</code> vector and
* w is the given vector. w is not modified, but v is. If v and
* w are thought of as position vectors from the origin, v - w
* is a vector pointing from w to v.
**/
public Vector3D modSubtract(Vector3D w) {
return modSet(x - w.x, y - w.y, z - w.z);
}
/**
* Computes v = v - [x, y, z], where v is <code>this</code> vector and
* [x, y, z] is the given vector. v is modified.
**/
public Vector3D modSubtract(double x, double y, double z) {
return modSet(this.x - x, this.y - y, this.z - z);
}
/**
* Computes r = v - w, where r is the result vector, v is <code>this</code> vector, and
* w is the given vector. v and w are not modified, but r is.
**/
public Vector3D subtractInto(Vector3D w, Vector3D result) {
return result.modSet(x - w.x, y - w.y, z - w.z);
}
/**
* Computes v = -v, where v is <code>this</code> vector, which
* is modified.
**/
public Vector3D modNegate() {
return modSet(-x, -y, -z);
}
/**
* Computes r = -v, where r is the result vector and v
* is <code>this</code> vector. v is not modified, but r is.
**/
public Vector3D negateInto(Vector3D result) {
return result.modSet(-x, -y, -z);
}
/**
* Computes v = s * v, where v is <code>this</code> vector and
* s is the given scalar. v is modified.
**/
public Vector3D modScalarMultiply(double s) {
return modSet(s * x, s * y, s * z);
}
/**
* Computes r = s * v, where r is the result vector, s is the
* given scalar, and v is <code>this</code> vector. v is not
* modified, but r is.
**/
public Vector3D scalarMultiplyInto(double s, Vector3D result) {
return result.modSet(s * x, s * y, s * z);
}
/**
* Computes |v|, where v is <code>this</code> vector. No vector
* is modified.
**/
public double computeLength() {
return Math.sqrt(x * x + y * y + z * z);
}
/**
* Computes v = v / |v|, where v is <code>this</code> vector. v is
* modified.
**/
public Vector3D modNormalize() {
double length = computeLength();
if (length == 0)
return modSet(0.0, 0.0, 0.0);
else
return modScalarMultiply(1.0 / length);
}
/**
* Computes r = v / |v|, where r is the result vector and v is
* <code>this</code> vector. v is not modified, but r is.
**/
public Vector3D normalizeInto(Vector3D result) {
return result.modSet(this).modNormalize();
}
/**
* Computes v = (l / |v|) * v, where v is <code>this</code> vector and l
* is the new desired length. v is modified.
**/
public Vector3D modSetLength(double newLength) {
double length = computeLength();
if (length == 0)
return modSet(0.0, 0.0, 0.0);
else
return modScalarMultiply(newLength / length);
}
/**
* Computes r = (l / |v|) * v, where r is the result vector and v is
* <code>this</code> vector. v is not modified, but r is.
**/
public Vector3D setLengthInto(double newLength, Vector3D result) {
return result.modSet(this).modSetLength(newLength);
}
/**
* Computes v · w, where v is <code>this</code> vector. No vector
* is modified.
**/
public double computeDot(Vector3D w) {
return x * w.x + y * w.y + z * w.z;
}
/**
* Computes r = v X w, where r is the result vector, v is <code>this</code> vector, and
* w is the given vector. v and w are not modified, but r is.
**/
public Vector3D crossInto(Vector3D w, Vector3D result) {
Vector3D v = this;
return result.modSet(v.y * w.z - v.z * w.y,
v.z * w.x - v.x * w.z,
v.x * w.y - v.y * w.x);
}
/**
* Computes the euclidean distance between v and w, where v is <code>this</code>
* vector and w is the given vector. No vector is modified. If the <code>sqrt</code>
* arg is false the square root of the sum of the squares will be omitted. This
* is useful sometimes for efficiency. For example, to sort vectors by distance
* one needn't incur the expensive <code>Math.sqrt</code> call.
**/
public double computeDistanceBetween(Vector3D w, boolean sqrt) {
double term = (x - w.x) * (x - w.x) +
(y - w.y) * (y - w.y) +
(z - w.z) * (z - w.z);
return sqrt ? Math.sqrt(term) : term;
}
/**
* Computes the angle in radians between v and w, where v is <code>this</code>
* vector and w is the given vector. No vector is modified. The angle is in
* the range 0 to pi and is the smaller, or inner, angle between the vectors.
**/
public double computeAngle(Vector3D w) {
Vector3D v = this;
double vLength = v.computeLength();
double wLength = w.computeLength();
if (vLength == 0 || wLength == 0)
return 0;
else
return NumberTools.zeroToPIAcos(v.computeDot(w) / (vLength * wLength));
}
/**
* Computes the signed angle in radians from v to w, where v is <code>this</code>
* vector and w is the given vector. The sign of the angle is determined using
* the righthand rule relative to the given axis of rotation vector as shown below.
* No vector is modified. The angle is in the range 0 to pi and is the smaller,
* or inner, angle between the vectors.
*
* <p>The <code>computeAngle</code> method above takes 2 vectors and returns the
* positive anlge between them. This operation is commutative.
* So v.computeAngle(w) == w.computeAngle(v). In <code>computeSignedAngle</code>
* we want to put a sign on the angle to indicate the direction from v to w. So
* we want v.computeSignedAngle(w) == - w.computeSignedAngle(v). But this direction
* depends on the way you view v and w. v to w from above may look like w to v from
* below. To consistently report a directed (signed) angle we need a third vector,
* the <code>axisOfRotation</code>. This vector must be perpendicular to both v and w.
* This is used to define a right-handed coordinate system. The direction of the
* <code>axisOfRotation</code> determines the sign of the resulting angle. Scalar
* multiplying the <code>axisOfRotation</code> by -1 will negate the angle.
*
* @see #computeAngle
*
* <pre>
* y
* ^
* | 90
* | |
* |<-. positive angle !
* | `. . ' | ' .
* | , . | .
* !------.---> x -180 ---.------!------.--- 0
* z . | .
* (out) . | .
* ' ! '
* |
* -90
*
* (a) In a right-handed coordinate (b) The resultant angles (shown in degrees) for 4
* system, positive rotation about a rectilinear w vectors if v is along the x axis and
* given axis turns counter-clockwise the axis of rotation is the z axis. If w is also
* when viewed straight down that along the x axis, the angle is 0. If it is along
* axis. So here, a positive rotation the y, the angle is 90. When w is along the negative
* about the z axis turns counter- x axis the angle is -180 and when along the negative
* clockwise. y axis the angle is -90. Thus the angle is positive
* in the range of [0-180) and negative in [180-0)
* </pre>
**/
public double computeSignedAngle(Vector3D w, Vector3D axisOfRotation) {
Vector3D v = this;
double unsignedAngle = v.computeAngle(w);
double axisOfRotationDotCross =
axisOfRotation.x * (v.y * w.z - v.z * w.y) +
axisOfRotation.y * (v.z * w.x - v.x * w.z) +
axisOfRotation.z * (v.x * w.y - v.y * w.x);
return (unsignedAngle == Math.PI || axisOfRotationDotCross < 0) ? -unsignedAngle : unsignedAngle;
}
/**
* Computes the unsigned angle in radians from v to w, where v is <code>this</code>
* vector and w is the given vector. The angle is in the range [0-2pi) as shown
* below. No vector is modified.
*
* @see #computeSignedAngle
*
* <pre>
* 90
* |
* !
* . ' | ' .
* . | .
* 180 ---.------!------.--- 0
* . | .
* . | .
* ' ! '
* |
* 270
*
* The resultant angles for the same examples shown in figures (a) and
* (b) above. Angles for 4 rectilinear w vectors are shown. v is along
* the x axis and the axis of rotation is the z axis. Angles are always
* positive and are in the range [0-360).
* </pre>
**/
public double computeUnsignedAngle(Vector3D w, Vector3D axisOfRotation) {
double signedInnerAngle = computeSignedAngle(w, axisOfRotation);
return (signedInnerAngle < 0) ? (2 * Math.PI + signedInnerAngle) : signedInnerAngle;
}
/**
* Returns 0 if the angle from v to w is less than pi and 1 otherwise, where the
* meanings of these vectors are as described in the documentation for
* <code>computeSignedAngle</code> above. This tests whether w is above or below
* the x axis as shown in figures (a) and (b) above.
*
* @see #computeSignedAngle
* </pre>
**/
public int computeArcHalf(Vector3D w, Vector3D axisOfRotation) {
double signedInnerAngle = computeSignedAngle(w, axisOfRotation);
return (signedInnerAngle >= 0) ? 0 : 1;
}
/**
* Computes r = angle(v, w) * [(v X w) / |v X w|], where r is the result vector,
* v is <code>this</code> vector, w is the given vector, and angle(v, w) is the
* angle between v and w. v and w are not modified, but r is.
*
* <p>Note that for efficiency v and w are assumed to be be unit vectors.
**/
public Vector3D angularDisplacementVectorInto(Vector3D w, Vector3D result) {
Vector3D v = this;
double angle = Math.acos(v.computeDot(w));
if (angle == 0 || Double.isNaN(angle))
return result.modSet(0, 0, 0);
return v.crossInto(w, result).modNormalize().modScalarMultiply(angle);
}
/**
* Computes r = (x . u)u + (x . v)v, where r is the result vector,
* x is <code>this</code> vector, and u and v are unit vectors defining
* a plane. This is the projection of x onto the plane. x, u, and v are
* not modified, but r is.
*
* <p>Note that for efficiency u and v are assumed to be be unit vectors.
**/
public Vector3D planarProjectionInto(Vector3D u, Vector3D v, Vector3D result) {
result.modSet(u).modScalarMultiply(computeDot(u));
double xDotV = computeDot(v);
return result.modAdd(xDotV * v.x, xDotV * v.y, xDotV * v.z);
}
/**
* Computes |l X (o - v)| / |l|, where v is <code>this</code> vector, o is
* the origin of the line, and l is the direction vector for the line. This
* is the minimal distance between the point defined by <code>this</code>
* vector and the line from <code>lineOrigin</code> in the direction of
* <code>lineVector</code>. v, o, and l are not modified. Note that we
* perform the intermediate subtraction and cross product operations by
* hand, rather than calling <code>subtractInto</code> and <code>crossInto</code>.
* This saves us the need for extra vectors (and memory) for these intermediate
* results.
**/
public double pointLineDistance(Vector3D lineOrigin, Vector3D lineVector) {
double lineOriginToThisX = lineOrigin.x - x;
double lineOriginToThisY = lineOrigin.y - y;
double lineOriginToThisZ = lineOrigin.z - z;
double crossX = lineVector.y * lineOriginToThisZ - lineVector.z * lineOriginToThisY;
double crossY = lineVector.z * lineOriginToThisX - lineVector.x * lineOriginToThisZ;
double crossZ = lineVector.x * lineOriginToThisY - lineVector.y * lineOriginToThisX;
return Math.sqrt(crossX * crossX + crossY * crossY + crossZ * crossZ) / lineVector.computeLength();
}
/**
* Computes |v| == 0, where v is <code>this</code> vector. Calling
* <code>computeIsZero</code> is more efficient than computing the
* length and comparing that to zero. No vector is modified.
**/
public boolean computeIsZero() {
return x == 0 && y == 0 && z == 0;
}
public boolean computeIsNaN() {
return Double.isNaN(x) || Double.isNaN(y) || Double.isNaN(z);
}
public boolean computeIsInfinite() {
return Double.isInfinite(x) || Double.isInfinite(y) || Double.isInfinite(z);
}
public static Vector3D averageInto(Vector3D[] vectors, Vector3D result) {
result.modSet(0, 0, 0);
for (int i = 0; i < vectors.length; i++)
result.modAdd(vectors[i]);
return result.modScalarMultiply(1.0 / vectors.length);
}
/**
* Computes r = [(a . v) * a] + [cos(theta) * (v - (a . v) * a)] - [sin(theta) * (v X a)],
* where r is the result vector, v is <code>this</code> vector, and a is this given
* <code>axis</code> vector. This is a rotation of v by <code>angle</code> radians about
* the given arbitrary axis. r is modified, but v and a are not. v and r may be the
* same vector.
**/
public Vector3D rotateInto(Vector3D axis, double angle, Vector3D result) {
double dot = computeDot(axis);
double term1X = dot * axis.x;
double term1Y = dot * axis.y;
double term1Z = dot * axis.z;
double cosAngle = Math.cos(angle);
double term2X = cosAngle * (x - term1X);
double term2Y = cosAngle * (y - term1Y);
double term2Z = cosAngle * (z - term1Z);
crossInto(axis, result).modScalarMultiply(Math.sin(angle));
return result.modSet(term1X + term2X - result.x, term1Y + term2Y - result.y, term1Z + term2Z - result.z);
}
public Vector3D modRotate(Vector3D axis, double angle) {
return rotateInto(axis, angle, this);
}
public Vector3D rotateInto(double elevation, double azimuth, Vector3D result) {
rotateInto(Vector3D.yAxis, elevation, result);
return result.rotateInto(Vector3D.zAxis, azimuth, result);
}
public String toString() {
return "#<Vector3D " + NumberTools.roundToN(x, 4) + " " + NumberTools.roundToN(y, 4) + " " + NumberTools.roundToN(z, 4) + ">";
}
public String toStringNoRound() {
return "#<Vector3D " + x + " " + y + " " + z + ">";
}
public String toRoundedString(int nDecimals) {
return "#<Vector3D " + NumberTools.roundToN(x, nDecimals) + " " + NumberTools.roundToN(y, nDecimals) + " " + NumberTools.roundToN(z, nDecimals) + ">";
}
// Rounding
public Vector3D roundTo1() {
return modSet(NumberTools.roundTo1(x), NumberTools.roundTo1(y), NumberTools.roundTo1(z));
}
public Vector3D roundTo2() {
return modSet(NumberTools.roundTo2(x), NumberTools.roundTo2(y), NumberTools.roundTo2(z));
}
public Vector3D roundTo3() {
return modSet(NumberTools.roundTo3(x), NumberTools.roundTo3(y), NumberTools.roundTo3(z));
}
public Vector3D roundToN(int n) {
return modSet(NumberTools.roundToN(x, n), NumberTools.roundToN(y, n), NumberTools.roundToN(z, n));
}
// The VectorAccessor interface
public int nIndices() {
return 3;
}
public boolean isIntegral() {
return false;
}
public int getIntIndex(int index) {
return NumberTools.round(index == 0 ? x : (index == 1 ? y : z));
}
public double getDoubleIndex(int index) {
return index == 0 ? x : (index == 1 ? y : z);
}
public void setIntIndex(int index, int value) {
setDoubleIndex(index, value);
}
public void setDoubleIndex(int index, double value) {
if (index == 0)
x = value;
else
if (index == 1)
y = value;
else
z = value;
}
public String getIndexName(int index) {
return index == 0 ? "x" : (index == 1 ? "y" : "z");
}
// StateSettable and StateGettable interfaces
/**
* Sets the object's state from a formatted array of values.
* <p>The format for this object's state is
* <pre>
* (x y z)
* </pre>
* <p>See documentation for {@link LangTools.StateSettable} for more info
* on the use of <code>setState</code> methods.
**/
public void setState(Object[] state) {
modSet(((Number)state[0]).doubleValue(),
((Number)state[1]).doubleValue(),
((Number)state[2]).doubleValue());
}
/**
* Loads the object's state into a formatted array of values.
* <p>The format for this object's state is
* <p>See documentation for {@link LangTools.StateGettable} for more info
* on the use of <code>getState</code> methods.
*
* <br><br>
* @see #setState
**/
public Object[] getState() {
return new Object[] { new Double(x), new Double(y), new Double(z) };
}
}
;;; ____________________________
;;; Firmware for EZ-USB FX1 CY7C64713
; The 48MHz clock rate means each instructionCycle takes 83.3 nanoseconds or 0.0000833 milleseconds. So the
; formula for the amount of time (in ms) it takes to run nInstructionCycles worth of code is ...
;
; timeMs = 0.0000833 ms/instructionCycle * nInstructionCycles
; IO port variables
; -----------------
; EZ-USB parlance:
.equ portAEnable, 0x00b2h ; OEA
.equ portA, 0x0080h ; IOA
.equ portBEnable, 0x00B3h ; OEB
.equ portB, 0x0090h ; IOB
.equ portCEnable, 0x00B4h ; OEC
.equ portC, 0x00A0h ; IOC
.equ portDEnable, 0x00B5h ; OED
.equ portD, 0x00B0h ; IOD
; I^2C-compatible bus registers
.equ i2cStatus, 0xe678h ; Status
.equ i2cData, 0xe679h ; Data
.equ cpuCS, 0xe600h ; CPU control register
.equ dpl0, 0x0082h ; DPTR low
.equ dph0, 0x0083h ; DPTR high
.flag yellowLed, portB.0 ; Front panel LEDs
.flag redLed, portB.1
.flag greenLed, portB.2
; USB variables
; -------------
.equ EP1OutConfig, 0xE610h ; Endpoint 1 out configuration (valid and type) (p 8-3)
.equ EP1InConfig, 0xE611h ; Endpoint 1 in configuration (valid and type) (p 8-3)
.equ EP1Status, 0x00bah ; EP01STAT: Endpoint 0 and 1 busy status
.equ EP1OutByteCount, 0xe68dh ; EP1OUTBC (p 8-6)
.equ EP1InByteCount, 0xe68fh ; EP1INBC (p 8-6)
.equ IN1BUF, 0xe7c0h ; EP1INBUF (p 8-4)
.equ OUT1BUF, 0xe780h ; EP1OUTBUF (p 8-4)
.equ USBFrameHigh, 0xE684h ; (p 15-76)
.equ USBFrameLow, 0xE685h ; (p 15-76)
; Memory Allocation
; ; Upper 128 (indirect addressing)
; 128 ; Stack
; -----------------
; 127 ; usbOutBuffer is 24 bytes from 104 to 127
.equ usbOutBufferArgs, 106 ; :
.equ commandIndex, 105 ; :
.equ usbOutBuffer, 104 ; _____:_____
; 103 ; usbInBuffer is 24 bytes from 80 to 103
.equ usbInBufferData, 81 ; :
.equ usbInBuffer, 80 ; _____:_____
; 79 ; Max of 15 i2cRetry frames from 65 to 79
.equ i2cRetryBuffer, 65
.equ i2cRetryCount, 64
.equ i2cRetryType, 63
.equ commandNResults, 58 ; Number of bytes in a commands's results.
.equ i2cReadDataBERRRetries, 56 ;
.equ i2cWriteDataNoAckRetries, 55 ;
.equ i2cWriteDataBERRRetries, 54 ;
.equ i2cAddressNoAckRetries, 53 ;
.equ i2cAddressBERRRetries, 52 ;
.equ i2cStartBERRRetries, 51 ; The number of times a bus error on start caused a retry.
.equ i2cNBytesTransfered, 50 ; A count of the number of data bytes transfered.
.equ i2cStopCount, 49 ; The number of times we looped before STOP was 0.
.equ i2cDoneCount, 48 ; The number of times we looped before DONE was set.
; ________________________________________________________________
; bit addressable [32-47]
.equ i2cStatusSave, 47 ; A copy of the i2cStatus register for reporting back to bagel.
.equ i2cNBytes, 46 ; A local used in the i2c utils to indicate the number of bytes to read/write.
.equ i2cAddress, 45 ; Settable address for i2c communications.
.equ i2cBufferPtr, 44 ; A pointer to the location for data read/written from/to the i2c bus.
.equ delayOuterCounter, 43 ; A counter used for delays.
.equ delayInnerCounter, 42 ; A counter used for delays.
.equ byteToolsPtr, 41 ; A pointer to some data to be manipulated by the nBytes utils.
.equ byteToolsN, 40 ; The number of bytes of data to be manipulated by the nBytes utils.
.equ i2cProgress, 39 ; A progress byte for I2C debugging
.equ i2cErrorFlags, 38 ; A byte for flags used to indicate various specific i2c errors.
.equ bigTimerHigh, 37 ; The high byte for our 3 byte timer (implemented using timer 0).
.equ flagsByte2, 36 ; Another byte for various flags.
.equ flagsByte, 35 ; A byte for various flags to be defined later.
.equ currentPortValue, 34 ; An arg to the getModifiedPortValue subroutine
.equ portMask, 33 ; A mask identifying the pin in getModifiedPortValue
; ...
; 7 r7. Not used.
; 6 r6. Not used.
; 5 r5. Not used.
; 4 r4. Not used.
; 3 r3. For i2c transfers.
; 2 r2. For nBytes utils.
; 1 r1. (Indirectly addressable) Used for i2c transfers as a global variable.
; 0 r0. (Indirectly addressable) Used by nBytes utils as local variable. Also by i2cNoteRetry as local.
; Misc Flags
; ----------
.flag byteToolsIsZeroFlag, flagsByte.0 ; The result of a byteToolsIsZero test.
.flag newPortBitValue, flagsByte.1 ; An arg to the getModifiedPortValue subroutine.
.flag timerFlag, flagsByte.2 ; Used to indicate that the main timer has fired
.flag tempFlag, flagsByte.3 ; A generic, reusable local boolean.
.flag i2cErrorCondition, i2cErrorFlags.0 ; Set by the i2c routines to indicate any of the following IO errors,
.flag i2cNoAckFlag, i2cErrorFlags.1 ; - A no acknowledgment error
.flag i2cBERRFlag, i2cErrorFlags.2 ; - A bus error
.flag i2cDoneTimeoutFlag, i2cErrorFlags.3 ; - A timeout waiting for done
.flag i2cStopTimeoutFlag, i2cErrorFlags.4 ; - A timeout waiting for STOP to drop
.flag i2cProgressStartSet, i2cProgress.0
.flag i2cProgressAddressSent, i2cProgress.1
.flag i2cProgressAddressAcked, i2cProgress.2
.flag i2cProgressLastRDSet, i2cProgress.3
.flag i2cProgressStopSet, i2cProgress.4
.flag i2cProgressAbortStopSet, i2cProgress.5
.flag i2cProgressStopDropped, i2cProgress.6
; Timer variables
; ---------------
; Timers count up so we need to subtract the desired time from ffff.
; The 4 is the processor speed of 4 timerTicks/microsec which is calculated as follows
; cpuSpeed = 48Mhz = 48 clockCycles/microsec
; timersSpeed = 12 clockCycles/timerTicks
; 4 timerTicks/microsec = cpuSpeed / timersSpeed
; (Note also that there are 4 clockCycles/instructionCycle)
; So this gives us a delay of 40 millisec between timer firings.
.equ timerDuration, (4 * 40000)
.flag timer0Run, TCON.4
.org 0
ljmp start
.org 0bh
ljmp timerInterruptServiceRoutine
.org 030h
start:
; CPU Setup
; ---------
mov DPTR, #cpuCS ; Set clockspeed to 48MHz
mov a, #00010010b ; xx 0 10 0 1 x
movx @DPTR, a ; | | | l___ drive clock out pin (CLKOUT pin driven) (default)
; | | l_____ CLKOUT not inverted (default)
; | l________ Clockspeed 48MHz
; l__________ Port C access does not generate strobe (default)
mov SP, #128 ; Move the system stack to the "upper 128" so it's out of our way.
; IO port initialization
; ----------------------
; Configure IO pins (1 = output, 0 = input)
mov portAEnable, #00111111b
mov portBEnable, #11111111b
mov portCEnable, #11110000b
mov portDEnable, #11111111b
mov portC, #00000000b
; Timer0 initialization
; ---------------------
; Timer mode register (TMOD):
; 0 0 00 0 0 01
; | | | | | l_ timer0 mode = 1 (16 bit counter)
; | | | | l___ timer0 clocked by clkout (normal timer)
; | | | l_____ timer0 gate controlled by timer0Run
; | | l________ timer1 unused
; | l__________ timer1 unused
; l____________ timer1 unused
mov TMOD, #00000001b
clr TCON.5 ; Clear overflow flag for timer0, just to be sure it doesn't fire immediately.
lcall reloadTimers ; Load the big (3 byte) timer with timerDuration
; Interrupt Enables
; -----------------
; Interrupt Enable register (IE):
; 1 00000 1 0
; | | | l_ external interrupt 0 on pin portA.0 disabled
; | | l___ timer0 interrupt enabled
; | l_________ unused interrupts disabled
; l___________ global interrupt enable
mov IE, #10000010b
clr yellowLed
clr redLed
clr greenLed
setb portC.5 ; Set Adept Controller to Auto control (versus Manual control).
;; Endpoint 1
mov DPTR, #EP1OutConfig ; ---------
mov a, #10100000b ; 1 x 10 xxxx
movx @DPTR, a ; | l_______ Type 10: bulk endpoint
; l___________ Valid = 1: enable this endpoint
mov DPTR, #EP1InConfig
mov a, #10100000b
movx @DPTR, a
; It's very tricky getting the read/write protocol in sync between Java and the firmware. The problem has to do with the
; state of the EZ-USB board when it's first powered on, versus its state on subsequent runs. The following statement is
; a bogus initial write corresponding to the initial read in MicroControllerBridge.java. The processor seems to power up
; ready to do a USB IN transfer (firmware to Java), but may or may not be in that state on subsequent runs without an
; intervening power down (or reset). This call to enableUSBIn ensures the firmware starts the same in all cases. If
; Java didn't call read, the first sendMessage call will get bogus results after an EZ_USB power down. The read in that
; sendMessage call will return immediately (before the command has been executed in the firmware). So we issue a bogus
; read in Java and disregard the results. Btw, this comment is duplicated in MicroControllerBridge.java.
lcall enableUSBIn
;;; ____________________________
;;; Main loop
topOfMainLoop:
lcall readMessage
lcall commandDispatch
lcall writeMessage
ljmp topOfMainLoop
commandDispatch:
mov a, commandIndex
mov b, #4 ; Each case below takes 4 bytes in memory (one lcall(addr16) + one ret()).
mul ab ; We multiply this by commandIndex to get our jump offset from the commandDispatch_cases label.
mov dptr, #commandDispatch_cases
jmp @a+dptr
commandDispatch_cases:
lcall i2cWriteCommand ; Command 0
ret
lcall i2cReadCommand ; Command 1
ret
lcall i2cGetRetries ; Command 2
ret
lcall writePort ; Command 3
ret
lcall readPort ; Command 4
ret
lcall writePin ; Command 5
ret
lcall writeRegister ; Command 6
ret
lcall readRegister ; Command 7
ret
;;; Commands
;;;
;;; See src/bagel/penelopeCS/robot/hw.bgl for the other side of these commands
i2cWriteCommand: ; (i2cWriteCommand i2cAddress nBytes &rest bytes)
mov i2cAddress, (usbOutBufferArgs + 0)
mov i2cNBytes, (usbOutBufferArgs + 1)
mov i2cBufferPtr, #(usbOutBufferArgs + 2)
lcall i2cResetDataRetries
lcall i2cWrite
mov commandNResults, #0
lcall i2cPrepareReturn
ret
i2cReadCommand: ; (i2cReadCommand i2cAddress nBytes)
mov i2cAddress, (usbOutBufferArgs + 0)
mov i2cNBytes, (usbOutBufferArgs + 1)
mov i2cBufferPtr, #(usbInBufferData + 5)
lcall i2cResetDataRetries
lcall i2cRead
mov commandNResults, i2cNBytes
lcall i2cPrepareReturn
ret
i2cPrepareReturn:
mov (usbInBufferData + 0), i2cErrorFlags
mov (usbInBufferData + 1), i2cStatusSave
mov (usbInBufferData + 2), i2cProgress
mov (usbInBufferData + 3), i2cNBytesTransfered
mov (usbInBufferData + 4), i2cRetryCount
mov a, commandNResults
add a, #5
mov commandNResults, a
ret
i2cGetRetries: ; (i2cGetRetries)
mov commandNResults, i2cRetryCount
mov (usbInBufferData + 0), (i2cRetryBuffer + 0)
mov (usbInBufferData + 1), (i2cRetryBuffer + 1)
mov (usbInBufferData + 2), (i2cRetryBuffer + 2)
mov (usbInBufferData + 3), (i2cRetryBuffer + 3)
mov (usbInBufferData + 4), (i2cRetryBuffer + 4)
mov (usbInBufferData + 5), (i2cRetryBuffer + 5)
mov (usbInBufferData + 6), (i2cRetryBuffer + 6)
mov (usbInBufferData + 7), (i2cRetryBuffer + 7)
mov (usbInBufferData + 8), (i2cRetryBuffer + 8)
mov (usbInBufferData + 9), (i2cRetryBuffer + 9)
mov (usbInBufferData + 10), (i2cRetryBuffer + 10)
mov (usbInBufferData + 11), (i2cRetryBuffer + 11)
mov (usbInBufferData + 12), (i2cRetryBuffer + 12)
mov (usbInBufferData + 13), (i2cRetryBuffer + 13)
mov (usbInBufferData + 14), (i2cRetryBuffer + 14)
ret
writePort: ; (writePort portIndex value)
mov commandNResults, #0
mov a, (usbOutBufferArgs + 0)
mov b, #4 ; Each case below takes 4 bytes in memory (one mov(direct, direct) + one ret()).
mul ab ; We multiply this by portIndex to get our jump offset from the writePort_cases label.
mov dptr, #writePort_cases
jmp @a+dptr
writePort_cases:
mov portA, (usbOutBufferArgs + 1)
ret
mov portB, (usbOutBufferArgs + 1)
ret
mov portC, (usbOutBufferArgs + 1)
ret
mov portD, (usbOutBufferArgs + 1)
ret
readPort: ; (readPort portIndex)
mov commandNResults, #1
mov a, (usbOutBufferArgs + 0)
mov b, #4 ; Each case below takes 4 bytes in memory (one mov(direct, direct) + one ret()).
mul ab ; We multiply this by portIndex to get our jump offset from the readPort_cases label.
mov dptr, #readPort_cases
jmp @a+dptr
readPort_cases:
mov usbInBufferData, portA
ret
mov usbInBufferData, portB
ret
mov usbInBufferData, portC
ret
mov usbInBufferData, portD
ret
writePin: ; (writePin portIndex portMask onOrOff)
mov commandNResults, #0
mov portMask, (usbOutBufferArgs + 1)
mov acc, (usbOutBufferArgs + 2)
mov c, acc.0
mov newPortBitValue, c
mov a, (usbOutBufferArgs + 0)
mov b, #9 ; Each case below takes 9 bytes in memory (mov(direct, direct) + lcall() + mov(direct, a) + ret() => 3 + 3 + 2 + 1).
mul ab ; We multiply this by portIndex to get our jump offset from the writePin_cases label.
mov dptr, #writePin_cases
jmp @a+dptr
writePin_cases:
mov currentPortValue, portA
lcall getModifiedPortValue
mov portA, a
ret
mov currentPortValue, portB
lcall getModifiedPortValue
mov portB, a
ret
mov currentPortValue, portC
lcall getModifiedPortValue
mov portC, a
ret
mov currentPortValue, portD
lcall getModifiedPortValue
mov portD, a
ret
writeRegister: ; (writeRegister registerHigh registerLow value)
mov commandNResults, #0
setb yellowLed
mov dph0, (usbOutBufferArgs + 0)
mov dpl0, (usbOutBufferArgs + 1)
mov a, (usbOutBufferArgs + 2)
movx @dptr, a
ret
readRegister: ; (readRegister registerHigh registerLow)
mov commandNResults, #1
mov dph0, (usbOutBufferArgs + 0)
mov dpl0, (usbOutBufferArgs + 1)
movx a, @dptr
mov (usbInBufferData + 0), a
ret
;;; Messages
readMessage:
lcall waitForUSBOutReady
mov dptr, #OUT1BUF
mov byteToolsPtr, #usbOutBuffer
mov byteToolsN, #24
lcall byteToolsMoveFromDptrN
lcall enableUSBOut
ret
writeMessage:
mov dptr, #IN1BUF
mov usbInBuffer, commandNResults
mov byteToolsPtr, #usbInBuffer
inc commandNResults
mov byteToolsN, commandNResults
lcall byteToolsMoveToDptrN
lcall enableUSBIn
lcall waitForUSBInReady
ret
; The idea here is that we want to change one pin on a port, but not the others. This util tells us what
; the resulting port value would be, so we can write that value to the port.
;
; API: getModifiedPortValue(byte portMask, flag newPortBitValue, byte currentPortValue)
;
; The portMask identifies the pin in question. For example, #00001000 is pin 3. The newPortBitValue
; bit is the new value for the pin. The currentPortValue byte should be set to the current value of the
; port in question. The resulting new port value is stored in the accumulator.
getModifiedPortValue:
mov a, portMask ; a = portMask;
jb newPortBitValue, getModifiedPortValue_BitIs1 ; if (newPortBitValue == 0)
cpl a
anl a, currentPortValue ; a = ~a & currentPortValue;
ret
getModifiedPortValue_BitIs1: ; else
orl a, currentPortValue ; a = a | currentPortValue;
ret
;;; ____________________________
;;; Main Timer
; This timer interrupt service routine implements an effective 3-byte, or big, timer.
; The duration of the timer should be set in timerDuration, which can be
; any number from 0 to 16,777,215 (though very small timer durations should
; be avoided). The timer expiration is signalled by setting the timerFlag.
; Any code waiting for the timerFlag is responsible for clearing it once the
; firing has been noted.
waitForTimer:
waitForTimerLoop:
jnb timerFlag, waitForTimerLoop
clr timerFlag
ret
timerInterruptServiceRoutine:
clr timer0Run
push acc
mov a, bigTimerHigh
jnz decByte2 ; if (bigTimerHigh == 0) {
;
setb timerFlag ; timerFlag = 1;
lcall reloadTimers ; reinitialize timer;
ljmp timerISRReturn ; return;
; }
decByte2: ; else {
dec a ; bigTimerHigh--;
mov bigTimerHigh, a ;
mov TL0, #0 ; load timer with full amount;
mov TH0, #0 ; return;
ljmp timerISRReturn ; }
timerISRReturn:
pop acc
setb timer0Run
reti
reloadTimers:
; The lower 2 bytes of timerDuration are values we'll put directly in the timers (which
; count _up_ to 0ffffh) so we subtract the values we want from 0ffh.
; bigTimerHigh however, is a counter used in the ISR, so it doesn't
; need to be flipped this way.
mov bigTimerHigh, #((timerDuration >> 16) & 0ffh)
mov TL0, #(0ffh - (timerDuration & 0ffh))
mov TH0, #(0ffh - ((timerDuration >> 8) & 0ffh))
ret
;;; ____________________________
;;; USB utils
waitForUSBInReady:
waitForUSBInReadyLoop:
mov a, EP1Status ; Hang here until the busy bit drops indicating that C++ has read the last packet.
jb acc.2, waitForUSBInReadyLoop
ret
enableUSBIn:
mov dptr, #EP1InByteCount ; This sets the busy bit indicating that C++ may read. Will remain set until C++ reads the data.
mov a, #24
movx @dptr, a
ret
waitForUSBOutReady:
waitForUSBOutReadyLoop:
mov a, EP1Status ; Hang here until the busy bit drops indicating that C++ has written a new packet.
jb acc.1, waitForUSBOutReadyLoop
ret
enableUSBOut: ; each call:
mov dptr, #EP1OutByteCount ; Rearm the OUT transfer by writing any value to the byte count.
mov a, #24 ; This sets the busy bit which will remain set until C++ sends
movx @dptr, a ; another out packet.
ret
resetUSBIn:
mov dptr, #EP1InByteCount
mov a, #0
movx @dptr, a
ret
;;; ____________________________
;;; i2c
i2cInitTransfer:
mov i2cErrorFlags, #0
mov i2cProgress, #0
mov i2cNBytesTransfered, #0
mov i2cStatusSave, #255
mov i2cStartBERRRetries, #7
mov i2cAddressBERRRetries, #7
mov i2cAddressNoAckRetries, #7
ret
i2cResetDataRetries:
mov i2cRetryCount, #0
mov (i2cRetryBuffer + 0), #0
mov (i2cRetryBuffer + 1), #0
mov (i2cRetryBuffer + 2), #0
mov (i2cRetryBuffer + 3), #0
mov (i2cRetryBuffer + 4), #0
mov (i2cRetryBuffer + 5), #0
mov (i2cRetryBuffer + 6), #0
mov (i2cRetryBuffer + 7), #0
mov (i2cRetryBuffer + 8), #0
mov (i2cRetryBuffer + 9), #0
mov (i2cRetryBuffer + 10), #0
mov (i2cRetryBuffer + 11), #0
mov (i2cRetryBuffer + 12), #0
mov (i2cRetryBuffer + 13), #0
mov (i2cRetryBuffer + 14), #0
mov i2cWriteDataBERRRetries, #7
mov i2cWriteDataNoAckRetries, #7
mov i2cReadDataBERRRetries, #7
ret
i2cNoteRetry: ; i2cNoteRetry(i2cRetryType)
mov a, i2cRetryCount
cjne a, #15, i2cNoteRetry_SpaceAvailable
ret
i2cNoteRetry_SpaceAvailable:
add a, #i2cRetryBuffer
mov r0, a
mov @r0, i2cRetryType
inc i2cRetryCount
ret
i2cSendAddress:
;;; Start = 1
mov dptr, #i2cStatus
movx a, @dptr
setb acc.7
movx @dptr, a
;;; Check bus error
lcall i2cCheckBusError
jnb i2cBERRFlag, i2c_StartComplete
;lcall delay10ms
mov i2cRetryType, #1
lcall i2cNoteRetry
;djnz i2cStartBERRRetries, i2cSendAddress
;lcall i2cFlagError
;ret
i2c_StartComplete:
setb i2cProgressStartSet
;;; Send address
mov dptr, #i2cData
mov a, i2cAddress
movx @dptr, a
;;; Wait for DONE
lcall i2cWaitForDone
jnb i2cDoneTimeoutFlag, i2cAddressDone
mov i2cRetryType, #7
lcall i2cNoteRetry
;lcall i2cFlagError
;ret
i2cAddressDone:
;;; Check bus error
lcall i2cCheckBusError
jnb i2cBERRFlag, i2cAddressNoBERR
lcall delay10ms
mov i2cRetryType, #2
lcall i2cNoteRetry
djnz i2cAddressBERRRetries, i2cSendAddress
lcall i2cFlagError
ret
i2cAddressNoBERR:
setb i2cProgressAddressSent
;;; Check ACK
lcall i2cCheckAck
jnb i2cNoAckFlag, i2cInitiated
jnb i2cStopTimeoutFlag, i2cAddressNoAckRetry
lcall i2cFlagError
ret
i2cAddressNoAckRetry:
lcall delay10ms
mov i2cRetryType, #3
lcall i2cNoteRetry
djnz i2cAddressNoAckRetries, i2cSendAddress
lcall i2cFlagError
ret
i2cInitiated:
setb i2cProgressAddressAcked
ret
;;; ____________________________
;;; i2c Writing
; Writes i2cNBytes bytes to the i2c device whose address is in i2cAddress. The data to write is
; taken from the memory location pointed to by i2cBufferPtr. Performs error checking throughout the
; process. If an error is encountered, the i2cErrorCondition flag is set and i2cWrite returns.
i2cWrite:
lcall i2cInitTransfer
lcall i2cSendAddress
jb i2cErrorCondition, i2cWrite_Return
mov r3, i2cNBytes
mov r1, i2cBufferPtr
i2cWrite_Loop:
mov dptr, #i2cData
mov a, @r1
movx @dptr, a
lcall i2cWaitForDone
jnb i2cDoneTimeoutFlag, i2cWriteDataDone
mov i2cRetryType, #8
lcall i2cNoteRetry
i2cWriteDataDone:
lcall i2cCheckBusError
jnb i2cBERRFlag, i2c_WriteDataNoBERR
mov i2cRetryType, #4
lcall i2cNoteRetry
djnz i2cWriteDataBERRRetries, i2cWrite
lcall i2cFlagError
ret
i2c_WriteDataNoBERR:
lcall i2cCheckAck
jnb i2cNoAckFlag, i2cWriteDataAcked
jnb i2cStopTimeoutFlag, i2cWriteDataNoAckRetry
lcall i2cFlagError
ret
i2cWriteDataNoAckRetry:
lcall delay100ms
mov i2cRetryType, #5
lcall i2cNoteRetry
djnz i2cWriteDataNoAckRetries, i2cWrite
lcall i2cFlagError
ret
i2cWriteDataAcked:
inc i2cNBytesTransfered
inc r1
djnz r3, i2cWrite_Loop
lcall i2cSetStopBit
setb i2cProgressStopSet
lcall i2cWaitForStopToDrop
jnb i2cStopTimeoutFlag, i2cWrite_Return
mov i2cRetryType, #10
lcall i2cNoteRetry
i2cWrite_Return:
ret
;;; ____________________________
;;; i2c Reading
; Reads i2cNBytes bytes from the i2c device whose address is in i2cAddress. The results are stored
; starting at the memory location pointed to by i2cBufferPtr. Performs error checking throughout the
; process. If an error is encountered, the i2cErrorCondition flag is set and i2cRead returns.
;
; API: public void i2cRead(address i2cAddress, int i2cBufferPtr, int i2cNBytes)
i2cRead:
lcall i2cInitTransfer
lcall i2cSendAddress
jb i2cErrorCondition, i2cRead_Return
mov r3, i2cNBytes
inc r3
mov r1, i2cBufferPtr
setb tempFlag
i2cRead_Loop:
cjne r3, #2, i2cRead_afterIEquals2Check
mov dptr, #i2cStatus
movx a, @dptr
setb acc.5
movx @dptr, a
setb i2cProgressLastRDSet
i2cRead_afterIEquals2Check:
mov dptr, #i2cData
movx a, @dptr
mov @r1, a
lcall i2cWaitForDone
jnb i2cDoneTimeoutFlag, i2cReadDataDone
mov i2cRetryType, #9
lcall i2cNoteRetry
i2cReadDataDone:
lcall i2cCheckBusError
jnb i2cBERRFlag, i2c_ReadDataNoBERR
mov i2cRetryType, #6
lcall i2cNoteRetry
djnz i2cReadDataBERRRetries, i2cRead
lcall i2cFlagError
ret
i2c_ReadDataNoBERR:
inc i2cNBytesTransfered
jb tempFlag, i2cRead_afterFirstTime
inc r1
i2cRead_afterFirstTime:
clr tempFlag
dec r3
cjne r3, #1, i2cRead_Loop
mov dptr, #i2cStatus
movx a, @dptr
setb acc.6
movx @dptr, a
setb i2cProgressStopSet
mov dptr, #i2cData
movx a, @dptr
mov @r1, a
lcall i2cWaitForStopToDrop
jnb i2cStopTimeoutFlag, i2cRead_Return
mov i2cRetryType, #11
lcall i2cNoteRetry
i2cRead_Return:
ret
;;; ____________________________
;;; i2c Utils
; Timing note
; i2c bus 100 kHz = 100 cyc/ms
; So one byte should take (/ 9 100.0) or 0.09 ms, since there are 9 cycles per byte.
; Loops until the DONE bit (bit 0 of the i2cStatus register) is 1. There is also a timeout period for the loop
; waiting for the DONE bit to be set to avoid the possibility of an infinite loop. The i2cDoneTimeoutFlag will
; be set iff this occurs. This loop will run for a max of 30 iterations, with a 100us delay each time. So we'll
; wait up to 3 ms.
i2cWaitForDone:
clr i2cDoneTimeoutFlag
mov i2cDoneCount, #30
mov dptr, #i2cStatus
i2cWaitForDone_Loop:
movx a, @dptr
jb acc.0, i2cWaitForDone_Return
lcall delay100us
djnz i2cDoneCount, i2cWaitForDone_Loop
setb i2cDoneTimeoutFlag
i2cWaitForDone_Return:
ret
i2cSetStopBit:
mov dptr, #i2cStatus
movx a, @dptr
setb acc.6
movx @dptr, a
ret
i2cWaitForStopToDrop:
clr i2cStopTimeoutFlag
mov dptr, #i2cStatus
mov i2cStopCount, #255
i2cWaitForStopToDrop_Loop:
movx a, @dptr
jnb acc.6, i2cWaitForStopToDrop_Return
djnz i2cStopCount, i2cWaitForStopToDrop_Loop
setb i2cStopTimeoutFlag
ret
i2cWaitForStopToDrop_Return:
setb i2cProgressStopDropped
ret
i2cCheckAck:
clr i2cNoAckFlag
mov dptr, #i2cStatus
movx a, @dptr
jb acc.1, i2cCheckAck_Return
setb i2cNoAckFlag
lcall i2cSetStopBit
setb i2cProgressAbortStopSet
lcall i2cWaitForStopToDrop
ret
i2cCheckAck_Return:
ret
i2cCheckBusError:
clr i2cBERRFlag
mov dptr, #i2cStatus
movx a, @dptr
jnb acc.2, i2cCheckBusError_Return
setb i2cBERRFlag
i2cCheckBusError_Return:
ret
i2cFlagError:
setb i2cErrorCondition
mov dptr, #i2cStatus
movx a, @dptr
mov i2cStatusSave, a
ret
; Delay for 1 millesecond. 48MHz means each instructionCycle takes 83.3 nanoseconds or 0.0000833 milleseconds. So we need to
; eat up one over that, or 12000 instructionCycles. The outer loop runs 100 times. The body of the outer loop is 1 mov call
; (3 instructionCycles), 1 nop (1 instructionCycle), 28 inner loop djnz calls (4 instructionCycles), and 1 outer loop djnz call.
; So that's (+ 3 1 (* 28 4) 4) => 120 instructionCycles. Run 100 times, that's 12000 instructionCycles.
delay1ms:
mov delayOuterCounter, #100
delay1ms_OuterLoop:
mov delayInnerCounter, #28
nop
delay1ms_InnerLoop:
djnz delayInnerCounter, delay1ms_InnerLoop
djnz delayOuterCounter, delay1ms_OuterLoop
ret
delay10ms:
lcall delay1ms
lcall delay1ms
lcall delay1ms
lcall delay1ms
lcall delay1ms
lcall delay1ms
lcall delay1ms
lcall delay1ms
lcall delay1ms
lcall delay1ms
ret
delay100ms:
lcall delay10ms
lcall delay10ms
lcall delay10ms
lcall delay10ms
lcall delay10ms
lcall delay10ms
lcall delay10ms
lcall delay10ms
lcall delay10ms
lcall delay10ms
ret
; Delay for 100 microseconds. 48MHz means each instructionCycle takes 83.3 nanoseconds or 0.0833 microseconds. So we need to (/ 1 0.0833)
; eat up one over that, or 12 instructionCycles for 1 us or 1200 for 100 us. So we'll do 200 loops that take 6 instructionCycles each.
delay100us:
mov delayOuterCounter, #200
delay100us_Loop:
nop
nop
djnz delayOuterCounter, delay100us_Loop
ret
; _____________________________________
; ByteTools Utils
; This is general purpose set of utils for clearing, incing, decing, moving, and isZero testing an
; arbitrary set of bytes in memory. There are 2 main inputs to these utils: the number of bytes
; to manipulate and the location of the bytes. These are byteToolsN and byteToolsPtr, respectively.
; There are arbitrary versions of these tools (like byteToolsClrN) for any N, and convenience
; versions for N=1 (like byteToolsClr1) and N=2 (like byteToolsClr2).
;
; byteToolsClrN sets all bytes to 0. byteToolsIncN increments the bytes as if they were an
; byteToolsN-long integer. So if byteToolsN=4, this inc is the same as the Java int ++
; operator. byteToolsDecN decrements in the same fashion. byteToolsMoveToDptrN copies byteToolsN
; bytes starting from byteToolsPtr to dptr. byteToolsIsZeroN sets byteToolsIsZeroFlag iff
; all the bytes are 0.
;
; The following summarizes the ByteTools API
;
; public void byteToolsClrN(int byteToolsN, address byteToolsPtr);
; public void byteToolsIncN(int byteToolsN, address byteToolsPtr);
; public void byteToolsDecN(int byteToolsN, address byteToolsPtr);
; public void byteToolsMoveToDptrN(int byteToolsN, address byteToolsPtr);
; public boolean byteToolsIsZeroN(int byteToolsN, address byteToolsPtr);
;
; public void byteToolsClr1(address byteToolsPtr);
; public void byteToolsInc1(address byteToolsPtr);
; public void byteToolsDec1(address byteToolsPtr);
; public void byteToolsMoveToDptr1(address byteToolsPtr);
; public boolean byteToolsIsZero1(address byteToolsPtr);
;
; public void byteToolsClr2(address byteToolsPtr);
; public void byteToolsInc2(address byteToolsPtr);
; public void byteToolsDec2(address byteToolsPtr);
; public void byteToolsMoveToDptr2(address byteToolsPtr);
; public boolean byteToolsIsZero2(address byteToolsPtr);
byteToolsClrN:
mov r2, byteToolsN
mov r0, byteToolsPtr
byteToolsClrN_Loop:
mov @r0, #0
inc r0
djnz r2, byteToolsClrN_Loop
ret
byteToolsIncN:
mov r2, byteToolsN
mov r0, byteToolsPtr
byteToolsIncN_Loop:
inc @r0
mov a, @r0
jnz byteToolsIncN_Return
inc r0
djnz r2, byteToolsIncN_Loop
byteToolsIncN_Return:
ret
byteToolsDecN:
mov r2, byteToolsN
mov r0, byteToolsPtr
byteToolsDecN_Loop: ; for each byte
mov a, @r0
jnz byteToolsDecN_NotZero ; if byte is 0 {
dec @r0 ; dec byte
inc r0 ; inc byte ptr
djnz r2, byteToolsDecN_Loop ; if byte ptr is not 0, next byte
ljmp byteToolsDecN_Return ; return
; }
byteToolsDecN_NotZero: ; else {
dec @r0 ; dec byte
byteToolsDecN_Return: ; return
ret ; }
byteToolsMoveToDptrN:
mov r2, byteToolsN
mov r0, byteToolsPtr
byteToolsMoveToDptrN_Loop:
mov a, @r0
movx @dptr, a ; memory[dptr++] = memory[byteToolsPtr++]
inc dptr
inc r0
djnz r2, byteToolsMoveToDptrN_Loop
ret
byteToolsMoveFromDptrN:
mov r2, byteToolsN
mov r0, byteToolsPtr
byteToolsMoveFromDptrN_Loop:
movx a, @dptr
mov @r0, a ; memory[byteToolsPtr++] = memory[dptr++]
inc r0
inc dptr
djnz r2, byteToolsMoveFromDptrN_Loop
ret
byteToolsIsZeroN:
mov r2, byteToolsN
mov r0, byteToolsPtr
setb byteToolsIsZeroFlag
byteToolsIsZeroN_Loop:
mov a, @r0
jnz byteToolsIsZeroN_NotZero
inc r0
djnz r2, byteToolsIsZeroN_Loop
ret
byteToolsIsZeroN_NotZero:
clr byteToolsIsZeroFlag
ret
; Byte utils
byteToolsClr1:
mov byteToolsN, #1
lcall byteToolsClrN
ret
byteToolsInc1:
mov byteToolsN, #1
lcall byteToolsIncN
ret
byteToolsDec1:
mov byteToolsN, #1
lcall byteToolsDecN
ret
byteToolsMoveToDptr1:
mov byteToolsN, #1
lcall byteToolsMoveToDptrN
ret
byteToolsIsZero1:
mov byteToolsN, #1
lcall byteToolsIsZeroN
ret
; Word utils
byteToolsClr2:
mov byteToolsN, #2
lcall byteToolsClrN
ret
byteToolsInc2:
mov byteToolsN, #2
lcall byteToolsIncN
ret
byteToolsDec2:
mov byteToolsN, #2
lcall byteToolsDecN
ret
byteToolsMoveToDptr2:
mov byteToolsN, #2
lcall byteToolsMoveToDptrN
ret
byteToolsIsZero2:
mov byteToolsN, #2
lcall byteToolsIsZeroN
ret
#include <CPPUtils.h>
#include <PixelScoreScreenerPeer.h>
#include <UVScattergramPeer.h>
#include <IOKit/firewire/IOFireWireLibIsoch.h>
#include <IOKit/firewire/IOFireWireFamilyCommon.h>
#include <mach/mach.h>
#include <CoreFoundation/CoreFoundation.h>
#include <CoreServices/CoreServices.h>
#include <unistd.h>
#include <math.h>
#include <mach/ppc/kern_return.h>
#include <mach/mach_error.h>
#include "com_rst_vision_NativeCamera.h"
#import <sys/mman.h>
#include <pthread.h>
/**
* See documentation for NativeCamera.java. Note that one can change the number of frames, though the trivial case of nFrames = 1 seems to
* work just fine.
**/
const int nFrames = 1;
class ROI {
public:
int left;
int top;
int right;
int bottom;
int pixelCount;
void set(int left, int top, int right, int bottom);
};
void ROI::set(int left, int top, int right, int bottom) {
(*this).left = left;
(*this).top = top;
(*this).right = right;
(*this).bottom = bottom;
(*this).pixelCount = 0;
}
class CameraPeer {
public:
int cameraIndex;
int cameraID;
jobject javaNativeCamera;
JNIEnv* cameraThreadJavaEnv;
IOFireWireLibDeviceRef deviceInterface;
bool isInitializing;
long long frameTimestamp;
// Frame memory
UInt8* dclFrameBuffers[nFrames];
UInt8* javaFrameBuffer;
UInt8* screeningData;
// Input parameters from the operating mode
int width;
int height;
int colorModel;
int bytesPerPixel;
int packetsPerLine;
int pixelsPerSample;
int speedCode;
int frameRequests;
// Computed sizes
int nPixels;
int pixelsPerPacket;
int bytesPerPacket;
int packetsPerPage;
int packetsPerFrame;
int pageRemainder;
// ROI
ROI cameraROI;
// Screening
int screenerType;
UInt8 screenerOnValue;
PixelScoreScreenerPeer pixelScoreScreener;
UVScattergramPeer uvScattergram;
jint* uvScattergramData;
int unscreenedPixels;
bool clearScreening;
// DCL variables
DCLCommandStruct* dclProgramStart;
// Ports
IOFireWireLibIsochChannelRef isochChannelRef;
// Methods
CameraPeer(int cameraIndex, int cameraID, int width, int height,
int roiLeft, int roiTop, int roiRight, int roiBottom,
int colorModel, int bytesPerPixel, int packetsPerLine, int pixelsPerSample, int speedCode);
void cameraThreadJobInternal();
void allocateFrameMemory();
bool findInterface();
// DCL
void createDCLProgram();
void noteFrameReceived(int);
void printDCLProgram();
// Ports
void createPorts();
void restartCamera();
void startCamera(JNIEnv* javaEnvironment);
void stopCamera();
void noteStopHandlerInvoked();
// Accessing frame data
void setDirectByteBuffers(JNIEnv* javaEnvironment);
void updateJavaFrameBuffer(int forFrame);
// Camera registers
UInt32 readRegister(int baseAddress, int address);
int writeRegister(int baseAddress, int address, UInt32 value);
// Utils
inline void CameraPeer::setJavaPixel(int* javaFrameBufferPtr, int pixelCount, int r, int g, int b, int y, int u, int v);
void CameraPeer::computeRGBFromYUV(int y, int u, int v, int* rOut, int* gOut, int* bOut);
jvalue callJavaNativeCameraMethod(JNIEnv* javaEnvironment, char* name, char returnType, char* signature, ...);
};
static CameraPeer* cameras[10];
CFDictionaryRef createMatchingDictionary(int unitSpecID, int unitSWVersion);
IOReturn allocatePortHandler(IOFireWireLibIsochPortRef interface, IOFWSpeed inSpeed, UInt32 inChannelNum);
IOReturn getSupportedHandler(IOFireWireLibIsochPortRef interface, IOFWSpeed* outMaxSpeed, UInt64* outChanSupported);
IOReturn releasePortHandler(IOFireWireLibIsochPortRef interface);
IOReturn startHandler(IOFireWireLibIsochPortRef interface);
IOReturn stopHandler(IOFireWireLibIsochPortRef interface);
static void frameReceivedCallProc(DCLCommandPtr pDCLCommand);
static void* cameraThreadJob(CameraPeer* camera);
JNIEXPORT void JNICALL Java_com_rst_vision_NativeCamera_initJavaBridge(JNIEnv* env, jclass jclassObject) {
cacheJavaVMInfo(env, (jclass)env->NewGlobalRef(jclassObject));
}
JNIEXPORT void JNICALL Java_com_rst_vision_NativeCamera_createCameraPeer(JNIEnv* env, jclass jclassObject,
jint i, jint cameraID, jint width, jint height,
jint roiLeft, jint roiTop, jint roiRight, jint roiBottom,
jint colorModel, jint bytesPerPixel, jint packetsPerLine, jint pixelsPerSample, jint speedCode) {
cameras[i] = new CameraPeer(i, cameraID, width, height, roiLeft, roiTop, roiRight, roiBottom, colorModel, bytesPerPixel, packetsPerLine, pixelsPerSample, speedCode);
}
CameraPeer::CameraPeer(int cameraIndex, int cameraID, int width, int height,
int roiLeft, int roiTop, int roiRight, int roiBottom,
int colorModel, int bytesPerPixel, int packetsPerLine, int pixelsPerSample, int speedCode) {
(*this).isInitializing = true;
(*this).cameraIndex = cameraIndex;
(*this).cameraID = cameraID;
(*this).width = width;
(*this).height = height;
(*this).cameraROI.set(roiLeft, roiTop, roiRight, roiBottom);
(*this).colorModel = colorModel;
(*this).bytesPerPixel = bytesPerPixel;
(*this).packetsPerLine = packetsPerLine;
(*this).pixelsPerSample = pixelsPerSample;
(*this).speedCode = speedCode;
(*this).frameRequests = 0;
(*this).clearScreening = true;
deviceInterface = NULL;
cameraThreadJavaEnv = NULL;
uvScattergramData = NULL;
}
JNIEXPORT jboolean JNICALL Java_com_rst_vision_NativeCamera_initCamera(JNIEnv *env, jclass jclassObject, jint cameraIndex) {
// Create the real-time thread which will instantiate, setup, and run the camera
pthread_t rtThread;
pthread_attr_t threadAttr;
pthread_attr_init(&threadAttr);
pthread_attr_setdetachstate(&threadAttr, PTHREAD_CREATE_DETACHED);
pthread_create(&rtThread, &threadAttr, (void *(*)(void *))cameraThreadJob, cameras[cameraIndex]);
pthread_attr_destroy(&threadAttr);
while ((*cameras[cameraIndex]).isInitializing)
usleep(1000);
if ((*cameras[cameraIndex]).deviceInterface == NULL)
return false;
(*cameras[cameraIndex]).startCamera(env);
return true;
}
static void* cameraThreadJob(CameraPeer* camera) {
(*camera).cameraThreadJobInternal();
return nil;
}
void CameraPeer::cameraThreadJobInternal() {
cameraThreadJavaEnv = getJavaEnvironment();
allocateFrameMemory();
if (! findInterface()) {
isInitializing = false;
return;
}
(*deviceInterface)->AddIsochCallbackDispatcherToRunLoop(deviceInterface, CFRunLoopGetCurrent());
jobject nativeCamera = (jobject)callStaticJavaMethod(cameraThreadJavaEnv, "cCallbackGetNativeCamera", 'O', "(II)Lcom/rst/vision/NativeCamera;", cameraIndex, cameraID).l;
javaNativeCamera = (jobject)cameraThreadJavaEnv->NewGlobalRef(nativeCamera);
callJavaNativeCameraMethod(cameraThreadJavaEnv, "cCallbackInitCameraRegisters", 'V', "()V");
setDirectByteBuffers(cameraThreadJavaEnv);
createDCLProgram();
createPorts();
makeCurrentThreadTimeContraintThread();
isInitializing = false;
CFRunLoopRun();
}
bool CameraPeer::findInterface() {
mach_port_t masterPort;
IOMasterPort(MACH_PORT_NULL, &masterPort);
io_iterator_t iterator;
// As per the DCAM spec Unit_Spec_ID is 0x00a02d and Unit_SW_Vers is 0x000100
IOServiceGetMatchingServices(masterPort, createMatchingDictionary(0x00a02d, 0x000100), &iterator);
int interfaceIndex = 0;
io_service_t service;
while ((service = IOIteratorNext(iterator))) {
SInt32 unusedScore;
IOCFPlugInInterface** ioCFPlugInInterface;
IOCreatePlugInInterfaceForService(service, kIOFireWireLibTypeID, kIOCFPlugInInterfaceID, &ioCFPlugInInterface, &unusedScore);
(*ioCFPlugInInterface)->QueryInterface(ioCFPlugInInterface, CFUUIDGetUUIDBytes(kIOFireWireNubInterfaceID), (void**)&deviceInterface);
IODestroyPlugInInterface(ioCFPlugInInterface);
(*deviceInterface)->Open(deviceInterface);
int thisCameraID = readRegister(0x000000, 0x0410);
if (thisCameraID == cameraID || (cameraID == -1 && interfaceIndex == cameraIndex)) {
cameraID = thisCameraID;
return true;
}
(*deviceInterface)->Close(deviceInterface);
interfaceIndex++;
}
return false;
}
CFDictionaryRef createMatchingDictionary(int unitSpecID, int unitSWVersion) {
CFMutableDictionaryRef matchingDictionary = IOServiceMatching("IOFireWireUnit");
CFDictionarySetValue(matchingDictionary, CFSTR("Unit_Spec_ID"), CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &unitSpecID));
CFDictionarySetValue(matchingDictionary, CFSTR("Unit_SW_Vers"), CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &unitSWVersion));
return matchingDictionary;
}
JNIEXPORT void JNICALL Java_com_rst_vision_NativeCamera_setFrameRequests(JNIEnv *env, jclass jclassObject, jint cameraIndex, jint frameRequests) {
(*cameras[cameraIndex]).frameRequests = frameRequests;
}
JNIEXPORT void JNICALL Java_com_rst_vision_NativeCamera_setROI(JNIEnv *env, jclass jclassObject, jint cameraIndex, jint roiLeft, jint roiTop, jint roiRight, jint roiBottom) {
(*cameras[cameraIndex]).cameraROI.set(roiLeft, roiTop, roiRight, roiBottom);
}
JNIEXPORT jint JNICALL Java_com_rst_vision_NativeCamera_unscreenedPixelCount(JNIEnv *env, jclass jclassObject, jint cameraIndex) {
return (*cameras[cameraIndex]).unscreenedPixels;
}
JNIEXPORT jint JNICALL Java_com_rst_vision_NativeCamera_readRegister(JNIEnv *env, jclass jclassObject, jint cameraIndex, jint baseAddress, jint address) {
return (*cameras[cameraIndex]).readRegister(baseAddress, address);
}
JNIEXPORT void JNICALL Java_com_rst_vision_NativeCamera_writeRegister(JNIEnv *env, jclass jclassObject, jint cameraIndex, jint baseAddress, jint address, jint value) {
(*cameras[cameraIndex]).writeRegister(baseAddress, address, (UInt32)value);
}
JNIEXPORT void JNICALL Java_com_rst_vision_NativeCamera_printDCLProgram(JNIEnv *env, jclass jclassObject, jint cameraIndex) {
(*cameras[cameraIndex]).printDCLProgram();
}
JNIEXPORT void JNICALL Java_com_rst_vision_NativeCamera_restartCamera(JNIEnv *env, jclass jclassObject, jint cameraIndex) {
(*cameras[cameraIndex]).restartCamera();
}
JNIEXPORT void JNICALL Java_com_rst_vision_NativeCamera_stopCamera(JNIEnv *env, jclass jclassObject, jint cameraIndex) {
(*cameras[cameraIndex]).stopCamera();
}
JNIEXPORT void JNICALL Java_com_rst_vision_NativeCamera_setClearScreening(JNIEnv *env, jclass jclassObject, jint cameraIndex, jboolean clearScreening) {
(*cameras[cameraIndex]).clearScreening = clearScreening;
}
// Screening
JNIEXPORT void JNICALL Java_com_rst_vision_NativeCamera_setScreenerType(JNIEnv *env, jclass jclassObject, jint cameraIndex, jint screenerType, jint screenerOnValue) {
(*cameras[cameraIndex]).screenerType = screenerType;
(*cameras[cameraIndex]).screenerOnValue = (UInt8)screenerOnValue;
}
JNIEXPORT void JNICALL Java_com_rst_vision_NativeCamera_setPixelScoreScreenerParams(JNIEnv *env, jclass jclassObject, jint cameraIndex,
jint screeningMethod, jint rangeMin, jint rangeMax, jboolean useScreeningAverage) {
(*cameras[cameraIndex]).pixelScoreScreener.setParams(screeningMethod, rangeMin, rangeMax, useScreeningAverage);
}
JNIEXPORT void JNICALL Java_com_rst_vision_NativeCamera_setUVScattergram(JNIEnv *env, jclass jclassObject, jint cameraIndex, jint size, jintArray javaArray) {
if ((*cameras[cameraIndex]).uvScattergramData == NULL) {
vm_address_t vmAddress;
vm_allocate(mach_task_self(), &vmAddress, size * 4, VM_FLAGS_ANYWHERE);
(*cameras[cameraIndex]).uvScattergramData = (jint*)vmAddress;
}
env->GetIntArrayRegion(javaArray, 0, size, (*cameras[cameraIndex]).uvScattergramData);
}
JNIEXPORT void JNICALL Java_com_rst_vision_NativeCamera_setUVScattergramParams(JNIEnv *env, jclass jclassObject, jint cameraIndex,
jint gridWidth, jint gridOriginU, jint gridOriginV, jdouble uvToGridScale,
jint trainingCountThreshold) {
(*cameras[cameraIndex]).uvScattergram.setParams(gridWidth, gridOriginU, gridOriginV, uvToGridScale, trainingCountThreshold);
}
// Allocating memory
void CameraPeer::allocateFrameMemory() {
pixelsPerPacket = width / packetsPerLine;
// Each recieve packet will consist of the packet header (4 bytes) followed by 2 bytes of YUV data or 3 bytes of RGB data per pixel in the packet.
bytesPerPacket = 4 + bytesPerPixel * pixelsPerPacket;
nPixels = width * height;
int bytesPerPage = getpagesize();
packetsPerPage = bytesPerPage / bytesPerPacket;
packetsPerFrame = height * packetsPerLine;
pageRemainder = bytesPerPage - packetsPerPage * bytesPerPacket;
int bytesPerFrame = (packetsPerFrame / packetsPerPage) * bytesPerPage;
vm_address_t vmAddress;
vm_allocate(mach_task_self(), &vmAddress, bytesPerFrame * nFrames, VM_FLAGS_ANYWHERE);
for (int frame = 0; frame < nFrames; frame++)
dclFrameBuffers[frame] = (UInt8*)(vmAddress + (frame * bytesPerFrame));
vm_allocate(mach_task_self(), &vmAddress, nPixels * 4, VM_FLAGS_ANYWHERE);
javaFrameBuffer = (UInt8*)vmAddress;
vm_allocate(mach_task_self(), &vmAddress, nPixels, VM_FLAGS_ANYWHERE);
screeningData = (UInt8*)vmAddress;
}
/**
* Here we create a java.nio.ByteBuffer object that will share memory with the javaFrameBuffer
* array we've created here on the C side. The CameraPeer::updateJavaFrameBuffer method writes
* argb image data into this memory which is then read by Java.
**/
void CameraPeer::setDirectByteBuffers(JNIEnv* javaEnvironment) {
jobject frameByteBuffer = javaEnvironment->NewDirectByteBuffer(javaFrameBuffer, width * height * 4);
jobject screeningByteBuffer = javaEnvironment->NewDirectByteBuffer(screeningData, width * height);
callJavaNativeCameraMethod(javaEnvironment, "cCallbackSetDirectByteBuffers", 'V', "(Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;)V", frameByteBuffer, screeningByteBuffer);
}
/**
* This method is called at the frame rate of the camera. It extracts the image data from the receive
* packets written by the DCL. It puts that image data, one int per pixel, in the javaFrameBuffer array.
**/
void CameraPeer::updateJavaFrameBuffer(int forFrame) {
if (frameRequests == 0)
return;
if (frameRequests > 0)
frameRequests--;
int u, v, y0, y1;
int r, g, b;
int dclFrameBufferPtr = 0;
int javaFrameBufferPtr = 0;
int pixelCount = 0;
cameraROI.pixelCount = 0;
unscreenedPixels = 0;
int samplesPerPacket = pixelsPerPacket / pixelsPerSample;
UInt8* dclFrameBuffer = dclFrameBuffers[forFrame];
for (int packet = 0; packet < packetsPerFrame; packet++) {
dclFrameBufferPtr += 4;
for (int i = 0; i < samplesPerPacket; i++) {
if (colorModel == 0) {
u = dclFrameBuffer[dclFrameBufferPtr++] - 128;
y0 = dclFrameBuffer[dclFrameBufferPtr++];
v = dclFrameBuffer[dclFrameBufferPtr++] - 128;
y1 = dclFrameBuffer[dclFrameBufferPtr++];
setJavaPixel(&javaFrameBufferPtr, pixelCount++, 0, 0, 0, y0, u, v);
setJavaPixel(&javaFrameBufferPtr, pixelCount++, 0, 0, 0, y1, u, v);
}
else {
r = dclFrameBuffer[dclFrameBufferPtr++];
g = dclFrameBuffer[dclFrameBufferPtr++];
b = dclFrameBuffer[dclFrameBufferPtr++];
setJavaPixel(&javaFrameBufferPtr, pixelCount++, r, g, b, 0, 0, 0);
}
}
if ((packet + 1) % packetsPerPage == 0)
dclFrameBufferPtr += pageRemainder;
}
switch (screenerType) {
case 0:
pixelScoreScreener.noteFrameProcessed(cameraROI.pixelCount);
break;
case 1:
uvScattergram.noteFrameProcessed(cameraROI.pixelCount);
break;
}
callJavaNativeCameraMethod(cameraThreadJavaEnv, "cCallbackNoteFrameProcessed", 'V', "(J)V", frameTimestamp);
}
inline void CameraPeer::setJavaPixel(int* javaFrameBufferPtr, int pixelCount, int r, int g, int b, int y, int u, int v) {
int pixelX = pixelCount % width;
int pixelY = pixelCount / width;
bool isInROI = true;
if (cameraROI.left != -1)
isInROI = (cameraROI.left <= pixelX && pixelX <= cameraROI.right && cameraROI.top <= pixelY && pixelY <= cameraROI.bottom);
if (cameraROI.left <= pixelX && pixelX <= cameraROI.right && cameraROI.top <= pixelY && pixelY <= cameraROI.bottom) {
cameraROI.pixelCount++;
if (colorModel == 0)
computeRGBFromYUV(y, u, v, &r, &g, &b);
switch (screenerType) {
case 0:
if (pixelScoreScreener.setScreeningData(screeningData, pixelCount, screenerOnValue, r, g, b, y, u, v))
unscreenedPixels++;
break;
case 1:
if (uvScattergram.setScreeningData(uvScattergramData, screeningData, pixelCount, clearScreening, screenerOnValue, r, g, b))
unscreenedPixels++;
break;
}
}
javaFrameBuffer[(*javaFrameBufferPtr)++] = 0xff;
javaFrameBuffer[(*javaFrameBufferPtr)++] = (UInt8)r;
javaFrameBuffer[(*javaFrameBufferPtr)++] = (UInt8)g;
javaFrameBuffer[(*javaFrameBufferPtr)++] = (UInt8)b;
}
void CameraPeer::computeRGBFromYUV(int y, int u, int v, int* rOut, int* gOut, int* bOut) {
int intensity = 1.164383;
// From http://www.fourcc.org/fccyvrgb.php
int r = (int)(intensity * y + 1.596027 * v);
int g = (int)(intensity * y - 0.391762 * u - 0.812968 * v);
int b = (int)(intensity * y + 2.017232 * u);
if (r > 255)
r = 255;
if (g > 255)
g = 255;
if (b > 255)
b = 255;
if (r < 0)
r = 0;
if (g < 0)
g = 0;
if (b < 0)
b = 0;
(*rOut) = r;
(*bOut) = b;
(*gOut) = g;
}
void CameraPeer::noteStopHandlerInvoked() {
JNIEnv* env = getJavaEnvironment();
printf("in CameraPeer::noteStopHandlerInvoked env = %p\n", env);
callJavaNativeCameraMethod(env, "cCallbackNoteStopHandlerInvoked", 'V', "()V");
}
// DCL
/**
* The general structure for our DCL program is as follows.
*
* start:
* for each frame {
* LabelDCL
* for each packet
* ReceivePacketStartDCL
* UpdateDCLListDCL
* CallProcDCL frameReceivedCallProc(cameraIndex, frame);
* }
* JumpDCL start
*
*
* The second arg to the CreateDCLCommandPool method is called size. It's not documented, but
* apparently you can either put in a ginormous number here or overestimate the byte size of
* your DCL program. I'm overestimating by a factor of 1.25.
**/
void CameraPeer::createDCLProgram() {
IOFireWireLibDCLCommandPoolRef pool = (*deviceInterface)->CreateDCLCommandPool(deviceInterface,
(int)(1.25 * nFrames * (height * packetsPerLine * sizeof(DCLTransferBufferStruct)
+ sizeof(DCLLabelStruct)
+ sizeof(DCLUpdateDCLListStruct)
+ sizeof(DCLCallProcStruct))),
CFUUIDGetUUIDBytes(kIOFireWireDCLCommandPoolInterfaceID));
DCLCommandStruct* lastDCL = nil;
for (int frame = 0; frame < nFrames; frame++) {
lastDCL = (*pool)->AllocateLabelDCL(pool, lastDCL);
if (frame == 0)
dclProgramStart = lastDCL;
int receiveBufferPtr = 0;
DCLCommandPtr* updateDCLList = (DCLCommandPtr*)malloc(packetsPerFrame * sizeof(DCLCommandPtr));
for (int packet = 0; packet < packetsPerFrame; packet++) {
lastDCL = (*pool)->AllocateReceivePacketStartDCL(pool, lastDCL, dclFrameBuffers[frame] + receiveBufferPtr, bytesPerPacket);
receiveBufferPtr += bytesPerPacket;
updateDCLList[packet] = lastDCL;
if ((packet + 1) % packetsPerPage == 0)
receiveBufferPtr += pageRemainder;
}
lastDCL = (*pool)->AllocateUpdateDCLListDCL(pool, lastDCL, &updateDCLList[0], packetsPerFrame);
// DMB: Note that newer Mac HW has a 64bit architecture which causes this code to break.
// It seems the compiler changes from g++-4.0 to g++-4.2 under that architecture too.
// Unlcear if the AllocateCallProcDCL API has changed or if the relationship between ints and pointers has changed.
lastDCL = (*pool)->AllocateCallProcDCL(pool, lastDCL, &frameReceivedCallProc, (cameraIndex << 8) | (frame & 0x0ff));
}
lastDCL = (*pool)->AllocateJumpDCL(pool, lastDCL, (DCLLabel*)dclProgramStart);
lastDCL->pNextDCLCommand = nil;
}
static void frameReceivedCallProc(DCLCommandPtr pDCLCommand) {
int procData = ((DCLCallProcStruct*)pDCLCommand)->procData;
(*cameras[procData >> 8]).frameTimestamp = utc();
(*cameras[procData >> 8]).updateJavaFrameBuffer(procData & 0x0ff);
}
void CameraPeer::printDCLProgram() {
(**deviceInterface).PrintDCLProgram(deviceInterface, dclProgramStart, 2000);
}
// Talker and listener ports
void CameraPeer::createPorts() {
IOFireWireLibRemoteIsochPortRef talkerPortRef = (*deviceInterface)->CreateRemoteIsochPort(deviceInterface, true,
CFUUIDGetUUIDBytes(kIOFireWireRemoteIsochPortInterfaceID));
(*talkerPortRef)->SetRefCon((IOFireWireLibIsochPortRef)talkerPortRef, this);
(*talkerPortRef)->SetGetSupportedHandler(talkerPortRef, &getSupportedHandler);
(*talkerPortRef)->SetAllocatePortHandler(talkerPortRef, &allocatePortHandler);
(*talkerPortRef)->SetStartHandler(talkerPortRef, &startHandler);
(*talkerPortRef)->SetStopHandler(talkerPortRef, &stopHandler);
(*talkerPortRef)->SetReleasePortHandler(talkerPortRef, &releasePortHandler);
IOFireWireLibLocalIsochPortRef
listenerPortRef = (*deviceInterface)->CreateLocalIsochPort(deviceInterface, false,
dclProgramStart, //0, 0, 0,
kFWDCLSyBitsEvent,
1,
//mask for the sync field: the DMA will start when packet sync == inStartEvent & inStartMask
0x000F,
nil, 0, nil, 0,
CFUUIDGetUUIDBytes(kIOFireWireLocalIsochPortInterfaceID));
isochChannelRef = (*deviceInterface)->CreateIsochChannel(deviceInterface, true, bytesPerPacket, (IOFWSpeed)speedCode,
CFUUIDGetUUIDBytes(kIOFireWireIsochChannelInterfaceID));
(*isochChannelRef)->AddListener(isochChannelRef, (IOFireWireLibIsochPortRef)listenerPortRef);
(*isochChannelRef)->SetTalker(isochChannelRef, (IOFireWireLibIsochPortRef)talkerPortRef);
(*isochChannelRef)->TurnOnNotification(isochChannelRef);
}
IOReturn getSupportedHandler(IOFireWireLibIsochPortRef portRef, IOFWSpeed* outMaxSpeed, UInt64* outChanSupported) {
// we support all channels
*outChanSupported = (UInt64)0xFFFFFFFF << 32 | (UInt64)0xFFFFFFFF;
*outMaxSpeed = kFWSpeed400MBit;
return kIOReturnSuccess;
}
IOReturn allocatePortHandler(IOFireWireLibIsochPortRef portRef, IOFWSpeed inSpeed, UInt32 inChannelNum) {
// inChannelNum is 0 then 1 with 2 cameras
return kIOReturnSuccess;
}
IOReturn startHandler(IOFireWireLibIsochPortRef portRef) {
return kIOReturnSuccess;
}
IOReturn stopHandler(IOFireWireLibIsochPortRef portRef) {
CameraPeer* cameraPeer = (CameraPeer*)(*portRef)->GetRefCon(portRef);
cameraPeer->noteStopHandlerInvoked();
return kIOReturnSuccess;
}
IOReturn releasePortHandler(IOFireWireLibIsochPortRef portRef) {
return kIOReturnSuccess;
}
void CameraPeer::startCamera(JNIEnv* javaEnvironment) {
(*isochChannelRef)->AllocateChannel(isochChannelRef);
(*isochChannelRef)->Start(isochChannelRef);
callJavaNativeCameraMethod(javaEnvironment, "cCallbackNoteCameraReady", 'V', "()V");
}
void CameraPeer::restartCamera() {
(*isochChannelRef)->Start(isochChannelRef);
}
void CameraPeer::stopCamera() {
printf("in CameraPeer::stopCamera\n");
(*isochChannelRef)->ReleaseChannel(isochChannelRef);
(*isochChannelRef)->Stop(isochChannelRef);
}
// Camera registers
UInt32 CameraPeer::readRegister(int baseAddress, int address) {
FWAddress fwAddress = FWAddress(0xFFFF, 0xF0000000 + baseAddress + address);
UInt32 value;
(*deviceInterface)->ReadQuadlet(deviceInterface, (*deviceInterface)->GetDevice(deviceInterface), &fwAddress, &value, kFWDontFailOnReset, 0);
// The call to CFSwapInt32BigToHost here is crucial. The Intel processor is little-endian while the PowerPC was big. CFSwapInt32BigToHost
// take care of swapping the 4 bytes if we're running on Intel.
return CFSwapInt32BigToHost(value);
}
int CameraPeer::writeRegister(int baseAddress, int address, UInt32 value) {
FWAddress fwAddress = FWAddress(0xFFFF, 0xF0000000 + baseAddress + address);
// The call to CFSwapInt32BigToHost here is crucial. The Intel processor is little-endian while the PowerPC was big. CFSwapInt32BigToHost
// take care of swapping the 4 bytes if we're running on Intel.
return (*deviceInterface)->WriteQuadlet(deviceInterface, (*deviceInterface)->GetDevice(deviceInterface), &fwAddress, CFSwapInt32BigToHost(value), kFWDontFailOnReset, 0);
}
// Utils
// callJavaNativeCameraMethod(env, "cCallbackGetImageDataBuffer", "()O").l
jvalue CameraPeer::callJavaNativeCameraMethod(JNIEnv* javaEnvironment, char* name, char returnType, char* signature, ...) {
va_list args;
va_start(args, signature);
return callJavaMethod(javaEnvironment, javaNativeCamera, name, returnType, signature, args);
}
(defun main (&rest args)
(println t "Hello world!"))
;;; fileOrder: 2;
import com.rst.tools.io.*;
import com.rst.tools.lang.*;
import com.rst.tools.net.*;
;;; Page wrapper
(defineContent :pageHeader (&key page)
(head :contents (block
(title :string (makeString "Mike Brady • " (get:title page)))
(includeViaPageMethod includeMetaData page nil)
(script :src "scripts.js")
(headerLink :href "basic.css" :rel "stylesheet" :type "text/css")
(headerLink :href "images/icon4.png" :rel "icon"))))
(defineContent :pageWrapper (&rest keys &key page)
(body :contents
(block
(div :position 'absolute :width "1028px" :margin-left "-514px" :left "50%" :top "10px"
:contents (block
(div :background-image "url('images/HeaderBG.png')" :background-repeat 'no-repeat
:height "160px"
:contents (block
(div :position 'absolute :left "900px" :top "25px"
:contents (includeViaPageMethod includeTopRightHeader page keys))
(div :position 'absolute :left "256px" :top "123px"
:contents (apply 'include true :mainMenu keys))))
(div :background-image "url('images/BodyBG.png')" :background-repeat 'repeat-y
:width "1028px"
:contents (block
(div :float 'left :position 'relative :left "27px" :top "-30px" :width "197px" :height "500px"
:contents (includeViaPageMethod includeSidebar page keys))
(div :float 'left :position 'relative :left "75px" :width "700px"
:contents (includeViaPageMethod includeMainContent page keys))
; Very important to do this or the wrapping div (with the BodyBG.png) won't include the previous 2 divs.
(div :clear 'both)))
(div :background-image "url('images/FooterBG.png')" :background-repeat 'no-repeat
:width "1028px" :height "50px"
:contents (space))))
(include t :statCounter))))
(defun includeViaPageMethod (_method page keys)
(apply method page (get:id page) (getProp keys :topic) keys))
;;; Main menu
(defineContent :mainMenu (&rest keys &key page)
(link :class "noDecoration" :href (makePageLink :index) :contents (span :class "darkGray size11 expanded" :value "home"))
(horizontalSpacer :width 15)
(link :class "noDecoration" :href (makePageLink :resume) :contents (span :class "darkGray size11 expanded" :value "resume"))
(horizontalSpacer :width 15)
(link :class "noDecoration" :href (makePageLink :projects :topic :penelopeCS) :contents (span :class "darkGray size11 expanded" :value "projects"))
(horizontalSpacer :width 15)
(link :class "noDecoration" :href (makePageLink :movies) :contents (span :class "darkGray size11 expanded" :value "movies"))
(horizontalSpacer :width 15)
(link :class "noDecoration" :href (makePageLink :projects :topic :bagel) :contents (span :class "darkGray size11 expanded" :value "bagel"))
(horizontalSpacer :width 15)
(link :class "noDecoration" :href (makePageLink :code) :contents (span :class "darkGray size11 expanded" :value "code"))
(horizontalSpacer :width 15)
(link :class "noDecoration" :href (makePageLink :writing) :contents (span :class "darkGray size11 expanded" :value "writing"))
(horizontalSpacer :width 15)
(link :class "noDecoration" :href (makePageLink :hobbies) :contents (span :class "darkGray size11 expanded" :value "hobbies")))
(defineContent :mainContentHeader (&key header)
(lineSpacer :height 8)
(span :class "size12 expanded " :value header)
(horizontalRule :class "darkGray")
(lineSpacer :height 20))
(defineMethod includeTopRightHeader ((MBPage this) id topic &key)
(span :class "mediumGray size10" :value "560 W 218th St")
(lineSpacer :height 2)
(span :class "mediumGray size10" :value "Apt 3C")
(lineSpacer :height 2)
(span :class "mediumGray size10" :value "New York, NY 10034"))
(defineContent :sidebarMenuItem (&key link topic name selectedTopic)
(div :float 'right
:contents (link :href (makePageLink link :topic topic)
:contents (block
(span :class (if (wildcardMatch (makeString topic "*") selectedTopic) "size11 expanded black" "size11 expanded2px darkGray") :value name)
(image :src "images/leftArrow.png" :vertical-align 'middle))))
(lineSpacer :height 5 :clear 'both))
(defineContent :mainContentHeader (&key label _contents)
(lineSpacer :height 6)
(span :class "size12 expanded" :value label)
(lineSpacer :height 12)
(div :class "size12" :width "609px" :contents (insert contents))
(lineSpacer :height 15)
(div :position 'relative :left "-27px" :contents (image :src "images/horizontalBar.jpg"))
(lineSpacer :height 10))
(defineContent :rstWebsiteContent (&key content)
(div :clear 'both)
(div :float 'left :width "520px" :contents (include t content)))
;;; Index
;;; _____________________________________________________________________________________________________________________________
(definePage :index IndexPage.class)
(defineMethod includeSidebar ((IndexPage this) id topic &key)
(span :class "size11 expanded2px darkGray"
:value "Hi. My name is Mike Brady, and welcome to my website.")
(lineSpacer :height 20))
(defineMethod includeMainContent ((IndexPage this) id topic &rest keys)
(image :src "images/headShot.jpg" :float 'right)
(div :width "500px"
:contents (flow :contents ((lineSpacer :height 20)
(span :value "Featured Projects" :class "expanded")
(lineSpacer :height 30)
(link :href (makePageLink :projects :topic :bagel) :contents (image :src "images/bagelLogo.png"))
(lineSpacer :height 10)
"I've developed a full-featured object-oriented scripting language called bagel. bagel combines the elegant and
expressive modelling capabilities of Lisp with the modern, lightweight, web-enabled sensibility of Java to create a
wholly new language. Click "
(link :href (makePageLink :projects :topic :bagel.features) :string "here")
" to learn more."
(lineSpacer :height 20)
(link :href (makePageLink :projects :topic :penelopeCS) :contents (image :src "images/PenelopeCSLogo.jpg"))
(lineSpacer :height 10)
"I led the development team creating PenelopeCS, the first robot designed to work in the sterile supply department of
the hospital. Penelope is pure innovation — applying tested robotics technology from the manufacturing world to
to do a real job in the hospital."
(lineSpacer :height 30)
(div :width "395px" :position 'relative
:contents (block
(link :href (makeMovieLink :penelopePress)
:contents (image :src "images/movieStills/PenelopePress.jpg"))
(div :width "180px" :position 'absolute :right "0px" :bottom "0px"
:contents (flow :class "size11"
:contents ("This is me playing the part of a surgeon on TV. Click "
(link :href (makePageLink :movies) :string "here")
" to see this and other of movies."))))))))
(lineSpacer :height 15))
;;; Projects
;;; _____________________________________________________________________________________________________________________________
(definePage :projects ProjectsPage.class)
(defineMethod includeSidebar ((ProjectsPage this) id topic &key)
(span :class "size12 expanded darkGray" :value "projects")
(lineSpacer :height 45)
(include t :sidebarMenuItem :name "penelopeCS" :link :projects :topic :penelopeCS :selectedTopic topic)
(include t :sidebarMenuItem :name "bagel" :link :projects :topic :bagel :selectedTopic topic)
(include t :sidebarMenuItem :name "cpacs" :link :projects :topic :cpacs :selectedTopic topic)
(include t :sidebarMenuItem :name "bsp" :link :projects :topic :bsp :selectedTopic topic)
(include t :sidebarMenuItem :name "websites" :link :projects :topic :websites :selectedTopic topic))
(defineMethod includeMainContent ((ProjectsPage this) id ((equals :penelopeCS) topic) &rest keys)
(include t :mainContentHeader :label "projects > penelopeCS >"
:contents (flow :contents ("I led the development team creating PenelopeCS, a first-in-class robot designed to work in the sterile supply
department of hospitals. Following the Agile development methodology, my team took this robot from
concept to a saleable product. Go to the "
(link :href (makePageLink :index) :string "movies")
" page to watch PenelopeCS in action. You can also click "
(link :href "http://www.roboticsystech.com" :string "here")
" to learn more.")))
(include t :rstWebsiteContent :content :rstWebsitePenelopeCS))
(defineContent :rstWebsitePenelopeCS (&key)
(flowAroundImage :src "images/PenelopeCSIsolatedCloseup.png" :side 'left :left "-27px"
:contents (block
(image :src "images/PenelopeCSLogo.jpg")
(lineSpacer :height 5)
(flow :contents ("The PenelopeCS family of products focuses on automation in the hospital's sterile supply department. There
are many manual, repetitive tasks in this process well suited for automation. RST's core technology focuses
on autonomous robotic workcells that can manipulate, track, sort, and process surgical instruments and supplies.
With the PenelopeCS product line, RST will apply this technology to various sterile supply tasks to increase efficiency,
reduce errors, enhance inventory control, and reduce costs."))
(lineSpacer :height 20)
(horizontalSpacer :width 30)
(image :src "images/PenelopeCSSystem.jpg")))
(span :class "gray expanded" :value "Background")
(lineSpacer :height 10)
(flow :contents ("The sterile supply department is the heart of the hospital's surgical instrument supply chain. Every day, thousands of used instruments
pass through sterile supply to be cleaned, counted, inspected, repacked, and sterilized. A delay or an error in sterile supply can impact
scheduling, cost, and even patient safety in the OR. RST feels the time is right for automation in the sterile supply department.
<p>Robots have been a mainstay in the manufacturing world for decades, helping make everything from cars to cookies. PenelopeCS will bring
some of these tried-and-true automation techniques to one of the most labor-intensive and error-prone departments in the hospital — sterile
supply. "
(lineSpacer :height 15)))
(image :float 'left :floatHorizontalMargin 15 :src "images/PenelopeCSFunctions.jpg")
(span :class "gray expanded" :value "Functions")
(lineSpacer :height 10 :clear nil)
(flow :contents ("The first product in RST's PenelopeCS line will focus on the <i>clean</i> side of sterile supply.
The advance will be dramatic. The robot will ensure that each tray sent to the OR will have exactly
those instruments specified on the count sheet and that they are all in good working order. And like
a good robot, it will do this automatically, precisely, and reliably — 24 hours a day, 7 days a week."
(lineSpacer :height 10 :clear nil)
"Our "
(link :href "http://www.roboticsystech.com/infosterileSupply.html" :string "Sterile Supply 101")
" page describes how sterile supply is divided into a <i>dirty</i> and a <i>clean</i> side. Our flagship PenelopeCS will work on the clean side;
counting, sorting, and packing instruments. The goal is to ensure that the contents of each container have been verified and neatly
packed before it is closed and placed in the autoclave. Furthermore, as these instruments are loaded, PenelopeCS will seamlessly update the
hospital's inventory control system — tracking each container and the instruments within it, while reducing the workload on sterile supply staff.
<p>PenelopeCS will work in conjunction with a variety of third-party instrument identification systems, using either RFIDs or barcodes to \"mark\"
individual instruments with a unique code. The robot is identification system agnostic in this regard. PenelopeCS is based on RST's
extensible hardware/software framework comprised of a <i>Core Robotic Workcell</i> and a diverse set of <i>Plugin Components</i> — task-specific
hardware assemblies installed within the robot's workcell. A variety of RFID and/or barcode scanners will be packaged as <i>Plugins</i> and
integrated within PenelopeCS, providing great flexibility for our customers.")))
;;; bagel
(defineContent :bagelMenuItem (&key topic name selectedTopic)
(span :class "courier size22 greenGray" :value "(" :position 'relative :top "-10px")
(link :href (makePageLink :projects :topic topic)
:class "hoverBold hoverNoDecoration"
:contents (span :class (if (equals topic selectedTopic) "courier size16 greenGray underlined" "courier size16 greenGray")
:position 'relative :top "-10px" :left "-2px"
:value name))
(span :class "courier size22 greenGray" :value ")" :position 'relative :top "-10px")
(horizontalSpacer :width 10))
(defineMethod includeMainContent ((ProjectsPage this) id ((matches ":bagel*") topic) &rest keys)
(include t :mainContentHeader :label "projects > bagel >"
:contents (flow :contents ("I created a general-purpose, high-level programming language called bagel. bagel is a lightweight yet extensible Lisp-like
scripting language written in and easily integrated with Java. It's called bagel because it goes so well with Java.")))
(include t :bagelMenuItem :topic :bagel :name "home" :selectedTopic topic)
(include t :bagelMenuItem :topic :bagel.background :name "background" :selectedTopic topic)
(include t :bagelMenuItem :topic :bagel.features :name "features" :selectedTopic topic)
(include t :bagelMenuItem :topic :bagel.examples :name "examples" :selectedTopic topic)
(lineSpacer :height 20)
(div :min-height "400px"
:contents (block
(image :src "images/bagelLogo.png")
(apply 'includeBagelMainContent this id topic keys))))
;;; :bagel home
(defineMethod includeBagelMainContent ((ProjectsPage this) id ((equals :bagel) topic) &rest keys)
(image :src "images/bagelIs.png")
(lineSpacer :height 20)
(div :position 'absolute
:contents (block
(include t :bagelIsElement :left "30px" :top "140px" :size "size36" :name "object-oriented")
(include t :bagelIsElement :left "90px" :top "50px" :size "size28" :name "flexible")
(include t :bagelIsElement :left "420px" :top "200px" :size "size36" :name "powerful")
(include t :bagelIsElement :left "110px" :top "4210px" :size "size32" :name "fun")
(include t :bagelIsElement :left "190px" :top "250px" :size "size24" :name "persistent")
(include t :bagelIsElement :left "340px" :top "70px" :size "size24" :name "self-aware")
(include t :bagelIsElement :left "440px" :top "130px" :size "size30" :name "concise")
(include t :bagelIsElement :left "300px" :top "0px" :size "size34" :name "elegant")
(include t :bagelIsElement :left "490px" :top "30px" :size "size22" :name "embedded"))))
(defineContent :bagelIsElement (&key left top size name)
(let ((id (java:generateRandomString StringTools.class 10 #\A #\Z)))
(div :position 'absolute :left left :top top
:contents (link :contents (span :id id :class (makeString size " greenGray expanded") :value name :white-space 'nowrap)))
(attachShaker id :dt 70 :nFrames 15)
; (attachFontSizeShifter id :onMouseOver false :nFrames -1 :dt 200 :sizeDelta 0.05)
(attachColorShifter id :onMouseOver false :dt 200 :nFrames -1)))
(defun attachShaker (id &key (shakeDelta 1) (nFrames 20) (dt 50) (onMouseOver true))
(createInlineJSFuncall "new Shaker" id dt nFrames onMouseOver shakeDelta))
(defun attachFontSizeShifter (id &key (sizeDelta 0.05) (shiftOdds 0.1) (minSize 6) (maxSize 42) (nFrames 20) (dt 50) (onMouseOver true))
(createInlineJSFuncall "new FontSizeShifter" id dt nFrames onMouseOver sizeDelta shiftOdds minSize maxSize))
(defun attachColorShifter (id &key (shiftFactor 0.95) (shiftOdds 0.1) (nFrames 20) (dt 50) (onMouseOver true) (minValue 100) (maxValue 200))
(createInlineJSFuncall "new ColorShifter" id dt nFrames onMouseOver shiftFactor shiftOdds minValue maxValue))
;;; :bagel.background
(defineMethod includeBagelMainContent ((ProjectsPage this) id ((equals :bagel.background) topic) &rest keys)
(lineSpacer :height 20)
(image :src "images/bagelSnippet1.jpg" :float 'right :position 'relative :margin-bottom "12px")
(span :value "I created bagel because I was working in Java, but missed the expressiveness and elegance of Lisp. I missed the freedom, the speed, and
the sheer joy of programming in Lisp. Since then, less restrictive scripting languages like Python have become more popular than ever —
perhaps an indication that I was not alone.")
(lineSpacer :height 15)
(span :value "bagel started out as a faithful Common Lisp implementation. I decided at some point though that this was actaully a great opportunity to
incorporate some of the great features of Lisp into a more modern language. So while bagel owes everything to Lisp, it barrows a bit from
Java and the internet era as well.")
(lineSpacer :height 15)
(span :value "The symbiotic relationship between bagel and Java has evolved as well. bagel is written in Java. You create an instance of the class
<code>Interpreter</code> in your Java application and load and run bagel code within that interpreter. You can easily and seamlessly call
Java from bagel and bagel from Java. Generally speaking I find it more natural to express the high-level business logic for my application
in bagel and use Java for the low-level general-purpose functions."))
;;; :bagel.features
(defineMethod includeBagelMainContent ((ProjectsPage this) id ((equals :bagel.features) topic) &rest keys)
(lineSpacer :height 20)
(include t :bagelFeature :title "Syntactic Simplicity"
:contents (flow :contents ((image :src "images/bagelSyntax.jpg" :float 'right :position 'relative :top "-90px")
"bagel's syntax is straight from Lisp. While other languages use a bewhildering array of characters to define syntax
bagel uses just parens and white space.")))
(include t :bagelFeature :title "Mixins"
:contents (flow :contents ("bagel classes can extend any number of superclasses. When you define a class you can <i>mix in</i> whichever classes apply.")))
(include t :bagelFeature :title "Self Awareness"
:contents (flow :contents ("Like Lisp, bagel makes little distinction between data and code. bagel programs can write other programs, and you can easily
extend the language, with your additions being indistinguishable from core bagel.")))
(include t :bagelFeature :title "OODB"
:contents (flow :contents ("bagel classes have an integrated object-oriented database. Instances of any class can be persisted by extending the
<code>PersistedObject</code> class. You can also <i>query</i> the universe of runtime objects.")))
(include t :bagelFeature :title "Values have types, not variables"
:contents (flow :contents ("Like most modern scripting languages, bagel variables aren't typed. Any <i>value</i> can be tested for it's type though.")))
(include t :bagelFeature :title "Powerful Arguments"
:contents (flow :contents ("bagel functions and methods support flexible and expressive argument lists. You can specify that your function takes any
number of arguments, that some are optional with default values, and that some should not be evaluated when the funciton is called.
Most powerfully, you can specify keyword arguments that can be supplied in function calls as keyword-value pairs in any order.")))
(include t :bagelFeature :title "Method Specializers"
:contents (flow :contents ("One of the most unique features of bagel is that it's methods can be specialized on more than just the class of it's arguments.
Methods can even specialize on a string match in bagel.")))
(include t :bagelFeature :title "BSP"
:contents (flow :contents ("bagel is web-enabled. With bagel server pages you can create dynamic websites in bagel. This site is one!")))
(include t :bagelFeature :title "Built-ins"
:contents (flow :contents ("bagel has over 300 built-in primitives. To name a few: <code>defun</code>, <code>let</code>, <code>set</code>, <code>loop</code>,
<code>if</code>, <code>cond</code>, <code>find</code>, <code>defineClass</code>, <code>defineMethod</code>, <code>not</code>,
<code>or</code>, and so on!")))
(include t :bagelFeature :title "Destructuring"
:contents (flow :contents ("You can create a variable structure that matches your data to assign many values in one statement. ")))
(include t :bagelFeature :title "Read"
:contents (flow :contents ("bagel implements Lisp's <code>read</code> function and includes a rich and powerful input stream parser.")))
(include t :bagelFeature :title "Prefix Notation"
:contents (flow :contents ("bagel uses prefix notation from mathematics. <code>f(x, y)</code> becomes <code>f(x y)</code> because the comma serves
no purpose, then suck the function inside the parens to get <code>(f x y)</code>. That's bagel's syntax — simple and clean.")))
(include t :bagelFeature :title "Loop!"
:contents (flow :contents ("bagel fully implements the amazing <code>loop</code> marco from Common Lisp.")))
(include t :bagelFeature :title "Java Integration"
:contents (flow :contents ("Java methods, objects, and instance variables can be accessed from bagel. So <code>myString.toLowerCase()</code> in Java
becomes <code>(java:toLowerCase myString)</code> in bagel.")))
(include t :bagelFeature :title "Multiple Values"
:contents (flow :contents ("Your bagel functions can return more than one value if you'd like."))))
(defineContent :bagelFeature (&key title _contents)
(span :value title :class "expanded")
(lineSpacer :height 10)
(insert contents)
(lineSpacer :height 20))
;;; :bagel.examples
(defineMethod includeBagelMainContent ((ProjectsPage this) id ((equals :bagel.examples) topic)
&key (examples '("HelloWorld.bgl" "Goals.bgl" "ThisPage.bgl" "oodb.bgl" "threads.bgl" "TransformedImages.bgl"))
(selected (first examples)))
(lineSpacer :height 20)
(span :value "Select Example:")
(horizontalSpacer :width 8)
(select :id "exampleSelector" :options (loop for example in examples
for selected = true then nil
do (option :string example :selected selected)))
(lineSpacer :height 10)
(div :width "700px" :height "420px" :overflow 'scroll :border "1px solid #999999" :padding "5px"
:contents (pre :id "exampleContainer" :contents nil))
(loop for example in examples
do (div :display 'none :id example :contents (insertFile (makeString "examples/" example) :escapeForHTML true)))
(apply 'createInlineJSFuncall "new SelectableDIV" "exampleSelector" "exampleContainer" examples)
(lineSpacer :height 20))
(defun readExample (file)
(java:escape HTMLEscaper.class (java:readFile FileTools.class (makeString wwwRoot "/examples/" file))))
;;; CPACS
(defineMethod includeMainContent ((ProjectsPage this) id ((equals :cpacs) topic) &rest keys)
(include t :mainContentHeader :label "projects > cpacs >"
:contents (flow :contents ("While at Boeing I led the development team creating a software tool called CPACS that manifested and laid out all the
cargo going into the Space Shuttle for resupply flights to the International Space Station. This was very advanced
software for the time, tackling the NP-hard 3D bin packing problem. We delivered CPACS to NASA under budget and
ahead of schedule.")))
(image :src "images/cpacs/cpacsLogo.gif")
(lineSpacer :height 10)
(flow :contents ("CPACS is a software application written to support cargo planning and analysis for the "
(link :href "http://www.nasa.gov/topics/shuttle_station/index.html" :string "International Space Station")
". The Space Station will be periodically resupplied using "
(link :href "http://www.nasa.gov/" :string "NASA")
"'s fleet of space shuttles. Each shuttle will transport cargo carriers to and from the station, packed with experiment payloads, crew supplies, spare parts, and other items essential to maintaining station operations. CPACS will help produce near-optimal configurations for these carriers, maximizing the efficiency of this complex logistics system."
(lineSpacer :height 10)
"CPACS is a comprehensive, multi-user planning and automation tool designed to ease this complex load planning task. CPACS provides its users with two major features: an integrated set of graphical user interfaces designed to support the development, analysis, and integration of load plans, and a set of fully automated optimization algorithms which \"pack\" cargo carriers and perform other load planning tasks."
(lineSpacer :height 10)
"The Configuration Editor supports the main functionality of CPACS -- building load plans.
A load plan specifies a position for each item on each packing level.
The screen below shows the stowage tray packing level. On the right we see the trays we are
packing and on the left are some as yet unpositioned cargo items."
(lineSpacer :height 20)
(image :src "images/cpacs/configuration.gif")
(lineSpacer :height 20)
"CPACS draws a cargo item as a black box while the user positions it. He/she can
move the item around within the tray using the mouse. Note how CPACS redraws the
tray views and the weight scale when the new cargo item is added."
(lineSpacer :height 20)
(image :src "images/cpacs/ci-pos.gif")
(lineSpacer :height 20)
"Here's a detailed look at a stowage tray. The cargo items shown in the top-down and
perspective views are listed to the right by part ID. This is the tray's stowage list.
The color of a cargo item indicates its customer. Item number 1, CPACS-PRESS-CI-1462,
is yellow -- so it belongs to the same customer we saw in the Zone Editor example."
(lineSpacer :height 10)
"We can see by the weight scale in the lower left-hand corner, that we are very near
the mass limit for this tray. The scale shows 56.31 lbs as the sum of the weight of the
tray itself, the cargo items, and the weight of the foam that will surround the cargo."
(lineSpacer :height 20)
(image :src "images/cpacs/tray.gif")
(lineSpacer :height 20)
"These are examples of trays that CPACS has packed automatically. For small
problems like these, CPACS can produce a pack in just a few seconds. The
automated algorithms try the cargo items in various orders and orientations
much as a person would. The user can adjust the algorithms so that they
will try more combinations. This makes them run longer, but they may come
up with slightly better results."
(lineSpacer :height 10)
"Whether you pack automatically or manually, CPACS is always keeping track
of several packing constraints for you."
"<table cellpadding=0 cellspacing=20>
<tr valign=center><td align=center><img src='images/cpacs/auto-tray1.gif'>
<td align=center><img src='images/cpacs/auto-tray2.gif'>
<tr valign=center><td align=center><img src='images/cpacs/auto-tray3.gif'>
<td align=center><img src='images/cpacs/auto-tray4.gif'>
</table>"
(lineSpacer :height 20)
"CPACS tracks a wide variety of packing constraints as the configuration is
being developed. Here are some examples ..."
(lineSpacer :height 20)
"<table cellpadding=0>
<tr><td><img alt='o' align=center src='images/cpacs/logo-bullet.gif' width=35 height=32>
<td><i>Weight limits</i>
</tr>
<tr><td> </td>
<td>
No container can be packed beyond its loading limit.
</td>
</tr>
<tr><td><img alt='o' align=center src='images/cpacs/logo-bullet.gif' width=35 height=32>
<td><i>Center of gravity</i>
</tr>
<tr><td> </td>
<td>
Containers also have center of gravity requirements. CPACS will insure that the
CG stays within a given range.
</td>
</tr>
<tr><td><img alt='o' align=center src='images/cpacs/logo-bullet.gif' width=35 height=32>
<td><i>Mass allocations</i>
</tr>
<tr><td> </td>
<td>
A customer's packed cargo cannot weigh more than their mass allocation.
</td>
</tr>
<tr><td><img alt='o' align=center src='images/cpacs/logo-bullet.gif' width=35 height=32>
<td><i>Occlusion</i>
</tr>
<tr><td> </td>
<td>
Occlusion means that two items are occupying the same physical space at the same
time! This is not allowed.
</td>
</tr>
<tr><td><img alt='o' align=center src='images/cpacs/logo-bullet.gif' width=35 height=32>
<td><i>Orientation</i>
</tr>
<tr><td> </td>
<td>
Items can have required orientations. CPACS will inforce this.
</td>
</tr>
<tr><td><img alt='o' align=center src='images/cpacs/logo-bullet.gif' width=35 height=32>
<td><i>Zones</i>
</tr>
<tr><td> </td>
<td>
Customers must keep their cargo within the zones defined in the Zone Editor.
</td>
</tr>
</table>"
(lineSpacer :height 20)
"Once the trays are packed to our liking, we can move on to the next level -- the
stowage rack. This interface is analogous to the tray level. The containers, racks
this time, are on the right and the unpositioned containees, trays, are on the left."
(lineSpacer :height 20)
(image :src "images/cpacs/rsr.gif")
(lineSpacer :height 20)
"This window offers more mass properties detail for the stowage rack. CPACS always keeps
track of weights, centers of gravity, and moments and products of inertia. It's important
to remember that these values represent the <i>integrated</i> stowage rack, including the
trays and cargo items within them."
(lineSpacer :height 20)
(image :src "images/cpacs/rsr-describe.gif")
(lineSpacer :height 20)
"The Mini-Pressurized Logistics Element, or MPLM, is a large cylindrical module that
sits in the orbiter's payload bay. It accommodates various kinds of racks including
the stowage rack we've been working on. Again, the process is similar -- just on a larger
scale."
(lineSpacer :height 20)
(image :src "images/cpacs/mplm.gif")
(lineSpacer :height 20)
"And that pretty much wraps it up! This is the payload bay of the Space
Shuttle orbiter with the MPLM, racks, trays, and cargo items we've been
packing."
(lineSpacer :height 10)
"The final product of this process is a complete configuration for an entire
flight -- including pictures of each container and its cargo."
(lineSpacer :height 20)
(image :src "images/cpacs/orbiter.gif")
(lineSpacer :height 100)
)))
;;; Movies
(definePage :movies MoviesPage.class)
(set movies '(:penelopeCSTour (:title "PenelopeCS Guided Tour" :file "movies/PenelopeCSTour" :blurbImage "images/movieStills/PenelopeCSTour.jpg"
:description "<i>Hello. My name is Penelope. Let me show you around.</i>
<p>In this video Penelope gives a guided tour of the system. Who better than Penelope herself to explain the
features of RST's exciting new product for the hospital sterile supply department")
:penelopeAndM7 (:title "Penelope and the M-7" :file "movies/ATA06" :blurbImage "images/movieStills/PenelopeAndM7.jpg"
:description "RST and SRI International joined forces on May 7th 2006 for a first-ever demonstration of unmanned
telesurgery involving a robotic surgeon and a robotic scrub technician. Penelope 3.0 delivered
supplies to SRI’s M-7 robot which used them to carry out sponging and suturing of a simulated surgical
wound. This demonstration was sponsored by the US Army’s Telemedicine & Advanced Technology
Research Center (<a href='http://www.tatrc.org/' target='_blank'>TATRC</a>).")
:penelopePress (:title "Penelope Press" :file "movies/PenelopePress" :blurbImage "images/movieStills/PenelopePress.jpg"
:description "Some highlights from the press coverage of the original Penelope robot.")
:penelopeCSIntro (:title "PenelopeCS Intro" :file "movies/PenelopeCSIntro" :blurbImage "images/movieStills/PenelopeCSIntro.jpg"
:description "Introducing PenelopeCS, RST's exciting new product for the hospital sterile supply department. This short video
starts with an introduction to the hospital's sterile supply department, including some of the challenges there. It
then goes on to our solution: PenelopeCS.")))
(defun getMovieProp (movieID prop &optional default)
(getProp (getProp movies movieID) prop default))
(defun makeMovieLink (movieID)
(makePageLink :movies :topic :movie :movieID movieID))
(defineMethod includeMainContent ((MoviesPage this) id topic &key movieID)
(include t :mainContentHeader :label "movies >"
:contents (flow :contents ("I've created several promotional movies for my work. It's a quick and fun way to get an overview of some of
the projects I've worked on. I wrote the copy, shot the video, and edited these movies.")))
(lineSpacer :height 20)
(cond (movieID (includeMoviePage this movieID))
(true (includeMoviesList this))))
(defineMethod includeMoviesList ((MoviesPage this))
(loop for (movieID nil) on movies by 'rrest
as title = (getMovieProp movieID :title)
as description = (getMovieProp movieID :description)
as blurbImage = (getMovieProp movieID :blurbImage)
do
(horizontalRule :color "#cfdbec" :height "1px" :class "lineHeight130Percent" :size "1px")
(div :float 'left :padding-right "15px" :width "207px"
:contents (block
(link :href (makeMovieLink movieID) :contents (image :src blurbImage))
(link :href (makeMovieLink movieID) :string "Play movie »")))
(link :href (makeMovieLink movieID) :contents (span :class "size12 expanded" :value title))
(lineSpacer :height 8)
(span :value description)
(lineSpacer :height 20 :clear 'left)))
(defineMethod includeMoviePage ((MoviesPage this) movieID)
(div :background-image "url('/images/MovieBG.jpg')" :background-repeat 'no-repeat
:position 'relative :left "50px" :width "377px" :height "286px"
:contents (div :position 'relative :left "28px" :top "18px" :width "320px" :height "240px" :border "1px solid black"
:contents (webMovie :mp4File (makeString (getMovieProp movieID :file) ".mp4")
:ogvFile (makeString (getMovieProp movieID :file) ".ogv")
:width "320px" :height "240px")))
(lineSpacer :height 20)
(span :class "size12 expanded" :value (getMovieProp movieID :title))
(lineSpacer :height 8)
(span :value (getMovieProp movieID :description))
(lineSpacer :height 15)
(link :href (makePageLink :movies :topic :movies) :string "« More Movies")
(lineSpacer :height 30))
;;; Code
(definePage :code CodePage.class)
(defineMethod includeMainContent ((CodePage this) id topic &key (examples '("Blob.java" "EventHub.java" "HttpServer.java" "LinearMeasurement.java" "Vector3D.java"
"Firmware.asm" "CameraPeer.cpp" "Goals.bgl" "HelloWorld.bgl" "ThisPage.bgl"
"TransformedImages.bgl" "oodb.bgl" "threads.bgl"))
(selected (first examples)))
(include t :mainContentHeader :label "code >"
:contents (flow :contents ("Here are a few examples of my code. I've included examples in Java, C++, assembler, and bagel to give a feel
for my style across multiple languages.")))
(lineSpacer :height 20)
(span :value "Select Example:")
(horizontalSpacer :width 8)
(select :id "exampleSelector" :options (loop for example in examples
for selected = true then nil
do (option :string example :selected selected)))
(lineSpacer :height 10)
(div :width "700px" :height "420px" :overflow 'scroll :border "1px solid #999999" :padding "5px"
:contents (pre :id "exampleContainer" :contents nil))
(loop for example in examples
do (div :display 'none :id example :contents (insertFile (makeString "examples/" example) :escapeForHTML true)))
(apply 'createInlineJSFuncall "new SelectableDIV" "exampleSelector" "exampleContainer" examples)
(lineSpacer :height 20))
;;; Writing
(definePage :writing WritingPage.class)
(defineMethod includeMainContent ((WritingPage this) id topic &key)
(include t :mainContentHeader :label "writing >"
:contents (flow :contents ("Writing is an underapprectiated skill for technical people. Through presentations, demos, or the written word, I've
always enjoyed process of communicating my work to others. Here are a few examples.")))
(lineSpacer :height 20)
(includeWritingMainContent this topic))
(defineMethod includeWritingMainContent ((WritingPage this) ((equals nil) topic) &key (examples '("PhaseIITechReport.pdf" "PenelopeCSAllenPilot.pdf" "PenelopePatent.pdf"
"SBIRTechReport.pdf")))
(select :id "writingExampleSelector" :options (loop for example in examples
for selected = true then nil
do (option :string example :selected selected)))
(lineSpacer :height 10)
(div :width "700px" :id "writingExampleContainer"
:contents (include t :embedContentFile :file (makeString "examples/" (first examples))))
(loop for example in examples
do (div :display 'none :id example :contents (include t :embedContentFile :file (makeString "examples/" example))))
(apply 'createInlineJSFuncall "new SelectableDIV" "writingExampleSelector" "writingExampleContainer" examples)
(lineSpacer :height 20))
(defineContent :embedContentFile (&key file)
(cond ((isPage file)
(include t file))
((wildcardMatch "*.jpg" file true)
(image :src file))
((wildcardMatch "*.pdf" file true)
(embed :src file :width 600 :height 500))
((wildcardMatch "*.mp3" file true)
(embed :src file :width 465 :height 100))
(true (includeFile t (makeString wwwRoot "/" file)))))
;;; Resume
(definePage :resume ResumePage.class)
(defineMethod includeMainContent ((ResumePage this) id topic &key)
(include t :mainContentHeader :label "resume >"
:contents (space))
(lineSpacer :height 20)
(include t :embedContentFile :file "examples/MikeBrady.pdf"))
;;; Hobbies
(definePage :hobbies HobbiesPage.class)
(defineMethod includeMainContent ((HobbiesPage this) id topic &key)
(include t :mainContentHeader :label "hobbies >"
:contents (flow :contents ("Here are a few of my interests outside of work.")))
(lineSpacer :height 20)
(div :min-height "420px"
:contents (span :value "Coming soon.")))