sclass OCRCompass implements Renderable { // compass can look for dark or bright lines, set this flag accordingly: bool brightForeground; IBWIntegralImage image; Pt center; // point we're looking at double radius = 16; // radius of circle int sensorFootprint = 0; // size of each "pixel" (rectangle) to grab - defaults to radius int steps = 12; // how many segments of circle (=steps/2 line orientations). should be even int blur; // blur width double[] values; // brightnesses at each point (low=foreground) L sensors() { ret squaresAroundCircle(center, steps, radius, sensorFootprint()); } int angles() { ret steps/2; } int sensorFootprint() { ret sensorFootprint == 0 ? iceil(radius*12/max(steps, 12)) : sensorFootprint; } void compute { L sensors = sensors(); int angles = angles(); if (l(values) != angles) values = new double[angles]; for i over values: values[i] = avg( image.getPixelAverage(sensors.get(i)), image.getPixelAverage(sensors.get(i+angles))); } void ensureComputed { if (values == null) compute(); } double mostLikelyAngle() { ensureComputed(); ret indexOfMinOrMaxEntryInDoubleArray(brightForeground, values)*twoPi()/steps; } public void renderOn(Graphics2D g, int w, int h) { ensureComputed(); double angle = mostLikelyAngle(); renderRenderables(g, w, h, zipAndMap(sensors(), repeatList(2, asList(values)), (r, value) -> BoxWithColor(r, 0, 0, iround(value)))); double radius2 = radius*0.75; drawRoundEdgeLine(g, pointOnCircle(center, radius2, angle), pointOnCircle(center, radius2, angle+pi()), Color.green, radius/3f); } }