-Changed application to display image source at the top of the window.
-Removed the extra windows to show intermediate stages when not in debug mode. 
Interestingly, this improved performance significantly.
-Modified slider listener so that it (hopefully) doesn't cause any more segfaults.
-Hid all the calibration controls away in a separate calibration window.
They are accessed by a button on the main display. 
I also added labels to each of the sliders.
-Application now takes the IP address of the atom as a command-line argument.
-Code now actually uses result sender, which I had forgot to do last time.
-I made a small modification to Brian's code which reduced the application's 
average consumption of RAM from two gigabytes to eight hundred megabytes.


git-svn-id: https://robotics.mvla.net/svn/frc971/2013/trunk/src@4151 f308d9b7-e957-4cde-b6ac-9a88185e7312
diff --git a/971CV/src/org/frc971/AccepterThread.java b/971CV/src/org/frc971/AccepterThread.java
index e6cd5dd..1cd71e8 100644
--- a/971CV/src/org/frc971/AccepterThread.java
+++ b/971CV/src/org/frc971/AccepterThread.java
@@ -67,7 +67,8 @@
 				connected.add(clientSock);
 			}
 			catch (IOException e) {
-				LOG.warning("Socket accept failed.");
+				LOG.warning("Cannot serve image processing results to client:" + e.getMessage());
+				Messages.warning("Cannot serve image processing results to client:" + e.getMessage());
 			}
 		}
 	}
diff --git a/971CV/src/org/frc971/DebugServerRun.java b/971CV/src/org/frc971/DebugServerRun.java
index 0f60665..cccf241 100644
--- a/971CV/src/org/frc971/DebugServerRun.java
+++ b/971CV/src/org/frc971/DebugServerRun.java
@@ -12,6 +12,7 @@
 import java.nio.channels.ServerSocketChannel;
 import java.nio.channels.SocketChannel;
 
+import java.util.Arrays;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -93,6 +94,7 @@
 			}
 		}
 	}
+	
 	/** Constructor to start the server and bind it to a port. */
 	public DebugServerRun(final int port) throws IOException {
 		sock = ServerSocketChannel.open();
@@ -106,9 +108,20 @@
 		LOG.info("Writing headers...");
 		SocketCommon.sendAll(client, "donotcross\r\n");
 	}
+	
 	/** Runs the server, and concurrently starts the vision processor with -vision flag. */
 	public static void main(final String args[]) throws IOException {
 		//main function for server
+		
+		String atomIP = null;
+    	try {
+    		atomIP = args[0];
+    	}
+    	catch (ArrayIndexOutOfBoundsException e) {
+    		System.out.println("Usage: VisionTuner [atom ip]");
+    		System.exit(0);
+    	}
+		
 		//set logger to log everything
         LOG.setLevel(Level.ALL);
         try {
@@ -121,9 +134,9 @@
         	System.err.println("Warning: Logging initialization failed.");
         }
         
-		if (args[0].equals("-vision")) {
+		if (Arrays.asList(args).contains("-vision")) {
 			LOG.info("Starting vision processor.");
-			new TestClient();
+			new TestClient(atomIP);
 		}
 		
 		DebugServerRun server = new DebugServerRun(9714);
diff --git a/971CV/src/org/frc971/HTTPClient.java b/971CV/src/org/frc971/HTTPClient.java
index cc694ef..96308a1 100644
--- a/971CV/src/org/frc971/HTTPClient.java
+++ b/971CV/src/org/frc971/HTTPClient.java
@@ -24,9 +24,9 @@
 	/** whether or not to print debug messages to stdout. */
 	private final static boolean LOCAL_DEBUG = false;
 	
-	private SocketChannel sock;
+	private String atomIP;
 	
-	private final String ATOM_IP = "192.168.0.137";
+	private SocketChannel sock;
 	
 	private ChannelImageGetter cgetter;
 	
@@ -41,11 +41,12 @@
 	
 	/** the constructor, initializes connection, and sets up aos getter. 
 	 * @throws IOException */
-	public HTTPClient() throws IOException {
+	public HTTPClient(String atomIP) throws IOException {
 		//Initialize socket connection to robot
+		this.atomIP = atomIP;
 		sock = SocketChannel.open();
-		WriteDebug("Connecting to server at " + ATOM_IP);
-		sock.connect(new InetSocketAddress(ATOM_IP, 9714));
+		WriteDebug("Connecting to server at " + atomIP);
+		sock.connect(new InetSocketAddress(atomIP, 9714));
 		sock.configureBlocking(false);
 		//Write headers
 		//HTTPStreamer does not actually use the headers, so we can just write terminating chars.
@@ -63,10 +64,14 @@
 	public ImageWithTimestamp GetFrame() {
 		ImageWithTimestamp final_image = new ImageWithTimestamp();
 		//Use Brian's code to extract an image and timestamp from raw server data.
-		ByteBuffer binary_image = cgetter.getJPEG();
+		ByteBuffer binaryImage = cgetter.getJPEG();
+		if (binaryImage == null) {
+			Messages.severe("Could not parse data from robot. See the log for details.");
+			return null;
+		}
 		//Decode ByteBuffer into an IplImage
-		byte[] b = new byte[binary_image.remaining()];
-		binary_image.get(b);
+		byte[] b = new byte[binaryImage.remaining()];
+		binaryImage.get(b);
 		try {
 			InputStream iis = new ByteArrayInputStream(b);
 			BufferedImage bImageFromConvert = ImageIO.read(iis);
@@ -80,4 +85,9 @@
 			return null;
 		}
 	}	
+	
+	/** Gets the name to display at the top of the image window. */
+	public String GetName() {
+		return atomIP;
+	}
 }
diff --git a/971CV/src/org/frc971/ImageWithTimestamp.java b/971CV/src/org/frc971/ImageWithTimestamp.java
index 8b43818..51b156f 100644
--- a/971CV/src/org/frc971/ImageWithTimestamp.java
+++ b/971CV/src/org/frc971/ImageWithTimestamp.java
@@ -4,6 +4,6 @@
 
 /** Small helper class for associating images and timestamps. */
 public class ImageWithTimestamp {
-	WPIColorImage image;
+	WPIColorImage image = null;
 	double timestamp;
 }
diff --git a/971CV/src/org/frc971/Recognizer.java b/971CV/src/org/frc971/Recognizer.java
index 6ab6455..9292357 100644
--- a/971CV/src/org/frc971/Recognizer.java
+++ b/971CV/src/org/frc971/Recognizer.java
@@ -1,7 +1,6 @@
 package org.frc971;

 

 import edu.wpi.first.wpijavacv.WPIColorImage;

-import edu.wpi.first.wpijavacv.WPIImage;

 

 /**

  * Vision target recognizer.

@@ -30,5 +29,5 @@
      *<p>

      * SIDE EFFECTS: May modify cameraImage.

      */

-    WPIImage processImage(WPIColorImage cameraImage);

+    Target processImage(WPIColorImage cameraImage);

 }

diff --git a/971CV/src/org/frc971/Recognizer2013.java b/971CV/src/org/frc971/Recognizer2013.java
index 7fbf28e..812c78b 100644
--- a/971CV/src/org/frc971/Recognizer2013.java
+++ b/971CV/src/org/frc971/Recognizer2013.java
@@ -14,7 +14,6 @@
 import edu.wpi.first.wpijavacv.WPIColor;

 import edu.wpi.first.wpijavacv.WPIColorImage;

 import edu.wpi.first.wpijavacv.WPIContour;

-import edu.wpi.first.wpijavacv.WPIImage;

 import edu.wpi.first.wpijavacv.WPIPoint;

 import edu.wpi.first.wpijavacv.WPIPolygon;

 

@@ -111,7 +110,7 @@
     }

 

     @Override

-    public WPIImage processImage(WPIColorImage cameraImage) {

+    public Target processImage(WPIColorImage cameraImage) {

         // (Re)allocate the intermediate images if the input is a different

         // size than the previous image.

         if (size == null || size.width() != cameraImage.getWidth()

@@ -239,26 +238,32 @@
             }

         }

 

+        Target found = null;

         if (bestTarget != null) {

             rawImage.drawPolygon(bestTarget, targetColor, 2);

-            measureTarget(bestTarget);

+            found = measureTarget(bestTarget);

         } else {

             LOG.fine("No target found");

         }

 

         // Draw a crosshair

         rawImage.drawLine(linePt1, linePt2, targetColor, 1);

+        

+        if (found == null) {

+        	found = new Target();

+        }

+        found.editedPicture = rawImage;

 

         daisyExtensions.releaseMemory();

         //System.gc();

-

-        return rawImage;

+        

+        return found;

     }

 

     /**

      * Uses the camera, field, and robot dimensions to compute targeting info.

      */

-    private void measureTarget(WPIPolygon target) {

+    private Target measureTarget(WPIPolygon target) {

         double w = target.getWidth();

         double h = target.getHeight();

         double x = target.getX() + w / 2; // target center in view coords

@@ -274,10 +279,18 @@
         double elevationCam = Math.atan2(yc * 2 * kTanVFOV2, vh);

         double rangeIn = kTargetWidthIn * vw / (w * 2 * kTanHFOV2);

 

+        //Put results in target

+        Target data = new Target();

+        data.azimuth = (Math.toDegrees(azimuthCam) - kShooterOffsetDeg);

+        data.elevation = (Math.toDegrees(elevationCam));

+        data.range = (rangeIn / 12);

+        

         LOG.fine("Best target at (" + x + ", " + y + ") " + w +" x " + h

                 + ", shot azimuth=" + (Math.toDegrees(azimuthCam) - kShooterOffsetDeg) + 

                 " elevation=" + (Math.toDegrees(elevationCam) + kCameraPitchDeg) + 

                 " range=" + (rangeIn / 12));

+        

+        return data;

     }

 

 }

diff --git a/971CV/src/org/frc971/ResultSender.java b/971CV/src/org/frc971/ResultSender.java
index 897371d..441c105 100644
--- a/971CV/src/org/frc971/ResultSender.java
+++ b/971CV/src/org/frc971/ResultSender.java
@@ -16,7 +16,7 @@
 
 /**  Serves processing results back to the atom. */
 public class ResultSender {
-	private static final int PORT = 9715;
+	private static final int PORT = 9716;
 	
 	private ServerSocketChannel sock;
 	
@@ -39,15 +39,17 @@
 	 */
 	public void send(double azimuth, double elevation, double range) {
 		//Formulate a message as a String similar to an HTTP header.
-		StringBuilder message = new StringBuilder();
-		message.append("\r\n--boundarydonotcross\r\n");
-		message.append("Azimuth: ");
-		message.append(azimuth);
-		message.append("\r\nElevation: ");
-		message.append(elevation);
-		message.append("\r\nRange: ");
-		message.append(range);
-		
-		acceptor.sendtoAll(message.toString());
+		if (azimuth != -1 && elevation != -1 && range != -1) {
+			StringBuilder message = new StringBuilder();
+			message.append("\r\n--boundarydonotcross\r\n");
+			message.append("Azimuth: ");
+			message.append(azimuth);
+			message.append("\r\nElevation: ");
+			message.append(elevation);
+			message.append("\r\nRange: ");
+			message.append(range);
+			
+			acceptor.sendtoAll(message.toString());
+		}
 	}
 }
diff --git a/971CV/src/org/frc971/SocketCommon.java b/971CV/src/org/frc971/SocketCommon.java
index e49a82b..daf3a6c 100644
--- a/971CV/src/org/frc971/SocketCommon.java
+++ b/971CV/src/org/frc971/SocketCommon.java
@@ -53,7 +53,8 @@
 			}
 		}
 		catch (IOException e) {
-			LOG.severe("Socket read failed.");
+			LOG.severe("Socket read failed. Check your network configuration.");
+			Messages.severe("Socket read failed. Check your network configuration.");
 			return null;
 		}
 		return message;
@@ -69,7 +70,8 @@
 				sock.write(message);
 			}
 			catch (IOException e) {
-				LOG.warning("Socket write failed.");
+				LOG.warning("Socket write failed. Check your network configuration.");
+				Messages.severe("Socket write failed. Check your network configuration.");
 				return -1;
 			}
 		}
diff --git a/971CV/src/org/frc971/TestClient.java b/971CV/src/org/frc971/TestClient.java
index 926a817..227c929 100644
--- a/971CV/src/org/frc971/TestClient.java
+++ b/971CV/src/org/frc971/TestClient.java
@@ -11,15 +11,18 @@
 /** Small thread for running vision code concurrently with debug server. */
 public class TestClient extends Thread {
 	
+	private String atomIP;
+	
 	/** Constructor to set up new thread. */
-	public TestClient() {
+	public TestClient(String atomIP) {
 		super("Test Client");
+		this.atomIP = atomIP;
 		start();
 	}
 	
 	/** Simple thread, runs the vision code. */
 	public void run() {
-		String[] args = {};
+		String[] args = {atomIP};
 		VisionTuner.main(args);
 	}
 }
diff --git a/971CV/src/org/frc971/TestImageGetter.java b/971CV/src/org/frc971/TestImageGetter.java
index a61ed60..2577fa2 100644
--- a/971CV/src/org/frc971/TestImageGetter.java
+++ b/971CV/src/org/frc971/TestImageGetter.java
@@ -59,6 +59,11 @@
 			return path1 + "/" + path2;
 	}
 	
+	/** Gets the name to display at the top of the image window. */
+	public String GetName() {
+		return images[image_index];
+	}
+	
 	/** Constructor
 	 * 
 	 * @param path_to_images is the path to the directory where our images are.
diff --git a/971CV/src/org/frc971/VisionTuner.java b/971CV/src/org/frc971/VisionTuner.java
index 4aba589..357b58f 100644
--- a/971CV/src/org/frc971/VisionTuner.java
+++ b/971CV/src/org/frc971/VisionTuner.java
@@ -2,6 +2,8 @@
 

 import java.awt.BorderLayout;

 import java.awt.GridLayout;

+import java.awt.event.ActionEvent;

+import java.awt.event.ActionListener;

 import java.awt.event.KeyEvent;

 

 import java.util.Arrays;

@@ -11,6 +13,8 @@
 import java.io.FileNotFoundException;

 import java.io.IOException;

 

+import javax.swing.JButton;

+import javax.swing.JLabel;

 import javax.swing.JPanel;

 import javax.swing.JSlider;

 import javax.swing.WindowConstants;

@@ -55,10 +59,11 @@
     private final JSlider hueMaxSlider = new JSlider();

     private final JSlider satMinSlider = new JSlider();

     private final JSlider valMinSlider = new JSlider();

+    private final JButton showCalibration = new JButton("Calibrate");

     

-    private ResultSender sender;

+    private ResultSender sender = null;

 

-    private int totalFrames = -1; // don't count the first (warmup) frame

+    private int totalFrames = -1; // don't count the first (warm-up) frame

     private double totalMsec;

     private double minMsec = Double.MAX_VALUE;

     private double maxMsec;

@@ -66,18 +71,22 @@
     private TestImageGetter getter;

     

     private WPIColorImage current;

+    

+    private String currentWindowTitle;

+    

+    private boolean debug = false;

 

     public VisionTuner() {

     	//set logger to log everything

         LOG.setLevel(Level.ALL);

         try {

-        	LogHandler handler = new LogHandler("../src/org/frc971/ds_vision.log");

+        	LogHandler handler = new LogHandler("ds_vision.log");

         	TimeFormatter formatter = new TimeFormatter();

             handler.setFormatter(formatter);

             LOG.addHandler(handler);

         }

         catch (FileNotFoundException e) {

-        	System.err.println("Warning: Logging initialization failed.");

+        	Messages.warning("Logging initialization failed.");

         }

         

         //initialize result sender

@@ -85,15 +94,73 @@
         	sender = new ResultSender();

         }

         catch (IOException e) {

-        	LOG.severe("Server initialization failed: " + e.getMessage() + " Result reporting disabled.");

+        	LOG.severe("Server initialization failed: " + e.getMessage() + ". Result reporting disabled.");

         }

         cameraFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

 

-        recognizer.showIntermediateStages(true);

-

         cameraFrame.getContentPane().add(panel, BorderLayout.SOUTH);

-        panel.setLayout(new GridLayout(0, 2, 0, 0));

+        panel.setLayout(new GridLayout(0, 1, 0, 0));

+        

+        showCalibration.setToolTipText("Click here if the system is not finding targets well enough.");

+        panel.add(showCalibration);

+        showCalibration.addActionListener(new ActionListener() {

+        	public void actionPerformed(ActionEvent e) {

+        		showCalibrationWindow();

+        	}

+        });

 

+        LOG.fine("Initial HSV range ["

+                + hueMinSlider.getValue() + " .. "

+                + hueMaxSlider.getValue() + "] "

+                + satMinSlider.getValue() + "+ "

+                + valMinSlider.getValue() + "+");

+    }

+    

+    /** Shows a calibration window when the user clicks the Calibrate button. */

+    private void showCalibrationWindow() {

+    	final CanvasFrame calibrationWindow = new CanvasFrame("Calibration");

+    	calibrationWindow.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);

+    	

+    	final JPanel panel = new JPanel();

+    	calibrationWindow.getContentPane().add(panel, BorderLayout.SOUTH);

+        panel.setLayout(new GridLayout(3, 3, 0, 0));

+        

+        hueMinSlider.setToolTipText("minimum HSV hue");

+        hueMinSlider.setMaximum(255);

+        hueMinSlider.setValue(recognizer.getHueMin());

+        panel.add(hueMinSlider);

+        

+        panel.add(new JLabel("min hue                          max hue")); 

+

+        hueMaxSlider.setToolTipText("maximum HSV hue");

+        hueMaxSlider.setMaximum(255);

+        hueMaxSlider.setValue(recognizer.getHueMax());

+        panel.add(hueMaxSlider);

+

+        satMinSlider.setToolTipText("minimum HSV color saturation");

+        satMinSlider.setMaximum(255);

+        satMinSlider.setValue(recognizer.getSatMin());

+        panel.add(satMinSlider);

+        

+        panel.add(new JLabel("min saturation  max saturation")); 

+

+        valMinSlider.setToolTipText("minimum HSV brightness value");

+        valMinSlider.setMaximum(255);

+        valMinSlider.setValue(recognizer.getValMin());

+        panel.add(valMinSlider);

+        

+        panel.add(new JLabel("")); //empty cells can cause problems

+        

+        final JButton done = new JButton("Done");

+        panel.add(done);

+        done.addActionListener(new ActionListener() {

+        	public void actionPerformed(ActionEvent e) {

+        		calibrationWindow.dispose();

+        	}

+        });

+        

+        panel.add(new JLabel("")); //empty cells can cause problems

+        

         ChangeListener sliderListener = new ChangeListener() {

             @Override

             public void stateChanged(ChangeEvent e) {

@@ -106,53 +173,37 @@
                         hueMinSlider.getValue(), hueMaxSlider.getValue(),

                         satMinSlider.getValue(),

                         valMinSlider.getValue());

-                processImage(current);

+                if (debug) {

+                	processImage(current, null);

+                }

             }

         };

-

-        hueMinSlider.setToolTipText("minimum HSV hue");

-        hueMinSlider.setMaximum(255);

-        hueMinSlider.setValue(recognizer.getHueMin());

-        panel.add(hueMinSlider);

-

-        hueMaxSlider.setToolTipText("maximum HSV hue");

-        hueMaxSlider.setMaximum(255);

-        hueMaxSlider.setValue(recognizer.getHueMax());

-        panel.add(hueMaxSlider);

-

-        satMinSlider.setToolTipText("minimum HSV color saturation");

-        satMinSlider.setMaximum(255);

-        satMinSlider.setValue(recognizer.getSatMin());

-        panel.add(satMinSlider);

-

-        valMinSlider.setToolTipText("minimum HSV brightness value");

-        valMinSlider.setMaximum(255);

-        valMinSlider.setValue(recognizer.getValMin());

-        panel.add(valMinSlider);

-

-        LOG.fine("Initial HSV range ["

-                + hueMinSlider.getValue() + " .. "

-                + hueMaxSlider.getValue() + "] "

-                + satMinSlider.getValue() + "+ "

-                + valMinSlider.getValue() + "+");

-

+        

         hueMinSlider.addChangeListener(sliderListener);

         hueMaxSlider.addChangeListener(sliderListener);

         satMinSlider.addChangeListener(sliderListener);

         valMinSlider.addChangeListener(sliderListener);

+        

+        calibrationWindow.pack();

+  

     }

 

     /**

      * Loads the named test image files.

      * Sets testImageFilenames and testImages.

      */

-

-    private void processImage(WPIColorImage cameraImage) {

+    private void processImage(WPIColorImage cameraImage, String title) {

     	current = cameraImage;

-        cameraFrame.setTitle("Input:");

+    	

+    	//set window title if it needs to be changed

+    	if (title != null && !title.equals(currentWindowTitle)) {

+    		cameraFrame.setTitle(title);

+    		currentWindowTitle = title;

+    	}

 

         long startTime = System.nanoTime();

-        WPIImage processedImage = recognizer.processImage(cameraImage);

+        Target target = recognizer.processImage(cameraImage);

+        WPIImage processedImage = target.editedPicture;

         long endTime = System.nanoTime();

 

         cameraFrame.showImage(processedImage.getBufferedImage());

@@ -167,19 +218,22 @@
         }

         

         //send results to atom. (and any connected clients)

+        if (sender != null) {

+        	sender.send(target.azimuth, target.elevation, target.range);

+        }

         

     }

 

     private void previousImage() {

     	WPIColorImage to_process = getter.GetPrev();

     	if (to_process != null)

-    		processImage(to_process);

+    		processImage(to_process, getter.GetName());

     }

 

     private void nextImage() {

     	WPIColorImage to_process = getter.GetNext();

     	if (to_process != null)

-    		processImage(to_process);

+    		processImage(to_process, getter.GetName());

     }

 

     private void processEvents() {

@@ -201,32 +255,51 @@
 

     public static void main(final String[] args) {

     	VisionTuner tuner = new VisionTuner();

+    	Messages.SetWindow(tuner.cameraFrame);

+    	

+    	String atomIP = null;

+    	try {

+    		atomIP = args[0];

+    	}

+    	catch (ArrayIndexOutOfBoundsException e) {

+    		System.out.println("Usage: VisionTuner [atom ip]");

+    		System.exit(0);

+    	}

+    	

         if (Arrays.asList(args).contains("-debug")) {

         	//debug mode has been requested

+        	tuner.debug = true;

+        	

+        	//show debugging windows

+        	tuner.recognizer.showIntermediateStages(true);

+        	

         	tuner.getter = new TestImageGetter(".");

         	WPIColorImage to_process = tuner.getter.GetNext();

         	if (to_process != null) {

-        		tuner.processImage(to_process);

+        		tuner.processImage(to_process, tuner.getter.GetName());

         		for (;;) {

         			tuner.processEvents();

         		}

         	}

-        	else

-        		LOG.severe("Cannot find test images.");

+        	else {

+        		LOG.severe("Could not load test images.");

+        		Messages.severe("Could not load test images.");

+        	}	

         }

         else {

         	try {

-        		HTTPClient client = new HTTPClient();

+        		HTTPClient client = new HTTPClient(atomIP);

         		for (;;) {

             		ImageWithTimestamp to_process = client.GetFrame();

             		if (to_process.image != null) {

-            			tuner.processImage(to_process.image);

+            			tuner.processImage(to_process.image, client.GetName());

             			LOG.fine("Captured time: " + Double.toString(to_process.timestamp));

             		}

             	}

         	}

         	catch (IOException e) {

-        		LOG.severe("Client initialization failed.");

+        		LOG.severe("Client initialization failed: " + e.getMessage() + ".");

+        		Messages.severe("Client initialization failed: " + e.getMessage() + ".");

         	}

         }

     }

diff --git a/971CV/src/org/frc971/private_aos_camera_jar.jar b/971CV/src/org/frc971/private_aos_camera_jar.jar
index c202fe8..3836b5b 100644
--- a/971CV/src/org/frc971/private_aos_camera_jar.jar
+++ b/971CV/src/org/frc971/private_aos_camera_jar.jar
Binary files differ