diff --git a/robots/vmcd/analysis_w_plot.awk b/robots/vmcd/analysis_w_plot.awk
new file mode 100755
index 0000000000000000000000000000000000000000..4c2ae6dc9f6ca4dc50e3b7d11d8089c219f79a28
--- /dev/null
+++ b/robots/vmcd/analysis_w_plot.awk
@@ -0,0 +1,32 @@
+#! /usr/bin/awk -f
+# analysis_w_plot.awk - Make gnuplot data files from dump_analyzer output.
+# 
+# std input : an analysis_camera* file.
+# Option vars, set with -v var=arg:
+#  -v file=base. - Base name of output files, null string by default.
+#  -v mag=10 - Magnification factor on error line length, 1 by default.
+# 
+# output: file.grid_points - The section title world coordinates.
+#         file.error_lines - Lines from the grid points to mean_wx, mean_wy.
+#
+# gnuplot commands:
+#     plot "grid_points" with points
+#     plot "grid_points" with points ps 2, "error_lines" with lines lw 3
+
+BEGIN { pfile = file "grid_points"
+	efile = file "error_lines"
+	if ( !mag ) mag=1; print mag
+}
+
+/^section /{ gsub("[(,)]","")
+	     x=$2; y=$3; 
+	     print x, y > pfile
+	     next
+}
+/^  mean_wx /{ mx=x+($3-x)*mag
+	       next
+}
+/^  mean_wy /{ my=y+($3-y)*mag
+	       printf "%f %f\n%f %f\n\n", x, y, mx, my > efile
+	       next
+}
diff --git a/robots/vmcd/dump_analyzer.py b/robots/vmcd/dump_analyzer.py
index f617509b1de5652e659417b5e4605a3f5350fa6c..9f47f0698bee38497cea812a0bb27c42f1535236 100755
--- a/robots/vmcd/dump_analyzer.py
+++ b/robots/vmcd/dump_analyzer.py
@@ -33,24 +33,27 @@ def debug(msg):
 # Classes
 class ObjectData:
     
-    def __init__(self,lx,ly,lt,rx,ry,rt,gx,gy,gt):
-        self.lx = lx
-        self.ly = ly
-        self.lt = lt
-        self.rx = rx
-        self.ry = ry
-        self.rt = rt
-        self.gx = gx
-        self.gy = gy
-        self.gt = gt
-        pass
+    def __init__(self,ax,ay,bx,by,wx,wy,wa):
 
+        # Pixel coords of blob centers.
+        self.ax = ax
+        self.ay = ay
+        self.bx = bx
+        self.by = by
+
+        # Apply camera offset to world coords.
+        self.wx = wx + options['camera_x']
+        self.wy = wy + options['camera_y']
+        self.wa = wa
+
+        pass
+    
     def toString(self):
-        return 'ObjectData=(' + \
-               ' lx='+str(self.lx)+',ly='+str(self.ly)+',lt='+str(self.lt)+ \
-               ',rx='+str(self.rx)+',ry='+str(self.ry)+',rt='+str(self.rt)+ \
-               ',gx='+str(self.gx)+',gy='+str(self.gy)+',gt='+str(self.gt)+ \
-               ') '
+        return 'ObjectData=('+\
+               'ax='+str(self.ax)+',ay='+str(self.ay)+\
+               ',bx='+str(self.bx)+',by='+str(self.by)+\
+               ',wx='+str(self.wx)+',wy='+str(self.wy)+',wa='+str(self.wa)+\
+               ')'
     
     pass # end of ObjectData
 
@@ -65,8 +68,10 @@ class FrameData:
     def toString(self):
         s = 'FrameData=(n = '+str(self.id)+', t = '+str(self.timestamp)+ \
             ', objects = {'  # +str(self.objects)
-        for k,v in self.objects.iteritems():
-            s += str(k)+'=>'+str(v.toString())+','
+        l = len(self.objects)
+        for i in range(l):
+            s += str(self.objects[i].toString())
+            if i<l-1: s += ','
             pass
         s += '}'
         return s
@@ -101,7 +106,7 @@ class Section:
 
 # globals
 datafile = 0
-results = dict([])
+results = []
 framedata = []
 line = ""
 options = dict({ 'x_offset': 0.0,
@@ -109,6 +114,8 @@ options = dict({ 'x_offset': 0.0,
                  'z_offset': 0.0,
                  'number of frames': 0,
                  'frame interval': 0,
+                 'camera_x': 0.0,     # Camera offset, in meters.
+                 'camera_y': 0.0,
                  })
 
 def mean(list):
@@ -134,7 +141,11 @@ def stddev(list):
     for n in deviation_list:
         sum_of_squared_deviations += math.pow(n,2)
         pass
-    stddev = math.sqrt(sum_of_squared_deviations / (len(list) - 1))
+    if len(list) == 1:
+        stddev = -1.0
+    else:
+        stddev = math.sqrt(sum_of_squared_deviations / (len(list) - 1))
+        pass
     return stddev
 
 def analyze(section):
@@ -147,81 +158,69 @@ def analyze(section):
     sr['valid_frames'] = 0
     sr['invalid_frames'] = 0
     sr['mult_meas_frames'] = 0
-    #sr['lx_sum'] = 0.0
-
-    # these are the stats we'll be grabbing, but no point to declaring
-    # them here
-    #sr['mean_lx'] = 0.0
-    #sr['stddev_lx'] = 0.0
-    #sr['mean_lx_minus_actual_lx'] = 0.0
-    #sr['stddev_lx_minus_actual_lx'] = 0.0
-    #sr['mean_ly'] = 0.0
-    #sr['stddev_ly'] = 0.0
-    #sr['mean_ly_minus_actual_ly'] = 0.0
-    #sr['stddev_ly_minus_actual_ly'] = 0.0
-    #sr['mean_lt'] = 0.0
-    #sr['stddev_lt'] = 0.0
-    #sr['mean_lt_minus_actual_lt'] = 0.0
-    #sr['stddev_lt_minus_actual_lt'] = 0.0
-
-    #sr['mean_rx'] = 0.0
-    #sr['stddev_rx'] = 0.0
-    #sr['mean_rx_minus_actual_lx'] = 0.0
-    #sr['stddev_rx_minus_actual_lx'] = 0.0
-    #sr['mean_ry'] = 0.0
-    #sr['stddev_ry'] = 0.0
-    #sr['mean_ry_minus_actual_ly'] = 0.0
-    #sr['stddev_ry_minus_actual_ly'] = 0.0
-    #sr['mean_rt'] = 0.0
-    #sr['stddev_rt'] = 0.0
-    #sr['mean_rt_minus_actual_lt'] = 0.0
-    #sr['stddev_rt_minus_actual_lt'] = 0.0
+    sr['bad_data_frames'] = 0
 
     temp = dict([])
-    temp['lx_minus_actual_lx'] = []
-    temp['lx'] = []
-    temp['ly_minus_actual_ly'] = []
-    temp['ly'] = []
-    temp['lt_minus_actual_lt'] = []
-    temp['lt'] = []
+    temp['ax'] = []
+    temp['ay'] = []
+    temp['bx'] = []
+    temp['by'] = []
     
-    temp['rx_minus_actual_lx'] = []
-    temp['rx'] = []
-    temp['ry_minus_actual_ly'] = []
-    temp['ry'] = []
-    temp['rt_minus_actual_lt'] = []
-    temp['rt'] = []
+    temp['wx'] = []
+    temp['wx_offset'] = []
+    temp['wx_error'] = []
+
+    temp['wy'] = []
+    temp['wy_offset'] = []
+    temp['wy_error'] = []
+
+    temp['wxy_error'] = []
+    temp['wa'] = []
     
 
     for f in section.frames:
-        c = len(f.objects.keys())
+        c = len(f.objects)
         if c > 1:
             sr['mult_meas_frames'] += 1
             pass
         elif c == 1:
-            sr['valid_frames'] += 1
-            keys = f.objects.keys()
-            #sr['lx_sum'] += f.objects[keys[0]].lx
-            temp['lx_minus_actual_lx'].append(f.objects[keys[0]].lx - \
-                                              section.lx)
-            temp['lx'].append(f.objects[keys[0]].lx)
-            temp['ly_minus_actual_ly'].append(f.objects[keys[0]].ly - \
-                                              section.ly)
-            temp['ly'].append(f.objects[keys[0]].ly)
-            temp['lt_minus_actual_lt'].append(f.objects[keys[0]].lt - \
-                                              section.lt)
-            temp['lt'].append(f.objects[keys[0]].lt)
-            
-            temp['rx_minus_actual_lx'].append(f.objects[keys[0]].rx - \
-                                              section.lx)
-            temp['rx'].append(f.objects[keys[0]].rx)
-            temp['ry_minus_actual_ly'].append(f.objects[keys[0]].ry - \
-                                              section.ly)
-            temp['ry'].append(f.objects[keys[0]].ry)
-            temp['rt_minus_actual_lt'].append(f.objects[keys[0]].rt - \
-                                              section.lt)
-            temp['rt'].append(f.objects[keys[0]].rt)
-            
+
+            ax = f.objects[0].ax
+            ay = f.objects[0].ay
+            bx = f.objects[0].bx
+            by = f.objects[0].by
+
+            # Filter out bad, glitchy pixel data where the two blobs do not
+            # agree in X and Y.  They have to be within about a
+            # fiducial-diameter of each other.
+            fid = 13                    # Diameter of a fiducial at the center.
+            eps = fid*1.5
+            ##print "ax=%f, ay=%f, bx=%f, by=%f"%(ax, ay, bx, by)
+            if abs(bx-ax) > eps or abs(by-ay) > eps:
+                ##print "bad"
+                sr['bad_data_frames'] += 1
+            else:
+                ##print "good"
+                sr['valid_frames'] += 1
+                temp['ax'].append(ax)
+                temp['ay'].append(ay)
+
+                temp['bx'].append(bx)
+                temp['by'].append(by)
+
+                temp['wx'].append(f.objects[0].wx)
+                xdiff = f.objects[0].wx - section.lx
+                temp['wx_offset'].append(xdiff)
+                temp['wx_error'].append(math.fabs(xdiff))
+
+                temp['wy'].append(f.objects[0].wy)
+                ydiff = f.objects[0].wy - section.ly
+                temp['wy_offset'].append(ydiff)
+                temp['wy_error'].append(math.fabs(ydiff))
+
+                temp['wxy_error'].append(math.hypot(xdiff,ydiff))
+                temp['wa'].append(f.objects[0].wa)
+                pass
             pass
         elif c == 0:
             sr['invalid_frames'] += 1
@@ -229,39 +228,47 @@ def analyze(section):
         pass
 
     # calc the stats
-    sr['mean_lx'] = mean(temp['lx'])
-    sr['stddev_lx'] = stddev(temp['lx'])
-    sr['mean_lx_minus_actual_lx'] = mean(temp['lx_minus_actual_lx'])
-    sr['stddev_lx_minus_actual_lx'] = stddev(temp['lx_minus_actual_lx'])
-    sr['mean_ly'] = mean(temp['ly'])
-    sr['stddev_ly'] = stddev(temp['ly'])
-    sr['mean_ly_minus_actual_ly'] = mean(temp['ly_minus_actual_ly'])
-    sr['stddev_ly_minus_actual_ly'] = stddev(temp['ly_minus_actual_ly'])
-    sr['mean_lt'] = mean(temp['lt'])
-    sr['stddev_lt'] = stddev(temp['lt'])
-    sr['mean_lt_minus_actual_lt'] = mean(temp['lt_minus_actual_lt'])
-    sr['stddev_lt_minus_actual_lt'] = stddev(temp['lt_minus_actual_lt'])
+    sr['mean_ax'] = mean(temp['ax'])
+    sr['stddev_ax'] = stddev(temp['ax'])
+    sr['mean_ay'] = mean(temp['ay'])
+    sr['stddev_ay'] = stddev(temp['ay'])
     
-    sr['mean_rx'] = mean(temp['rx'])
-    sr['stddev_rx'] = stddev(temp['rx'])
-    sr['mean_rx_minus_actual_lx'] = mean(temp['rx_minus_actual_lx'])
-    sr['stddev_rx_minus_actual_lx'] = stddev(temp['rx_minus_actual_lx'])
-    sr['mean_ry'] = mean(temp['ry'])
-    sr['stddev_ry'] = stddev(temp['ry'])
-    sr['mean_ry_minus_actual_ly'] = mean(temp['ry_minus_actual_ly'])
-    sr['stddev_ry_minus_actual_ly'] = stddev(temp['ry_minus_actual_ly'])
-    sr['mean_rt'] = mean(temp['rt'])
-    sr['stddev_rt'] = stddev(temp['rt'])
-    sr['mean_rt_minus_actual_lt'] = mean(temp['rt_minus_actual_lt'])
-    sr['stddev_rt_minus_actual_lt'] = stddev(temp['rt_minus_actual_lt'])
+    sr['mean_bx'] = mean(temp['bx'])
+    sr['stddev_bx'] = stddev(temp['bx'])
+    sr['mean_by'] = mean(temp['by'])
+    sr['stddev_by'] = stddev(temp['by'])
     
+    sr['mean_wx'] = mean(temp['wx'])
+    sr['stddev_wx'] = stddev(temp['wx'])
+    sr['mean_wy'] = mean(temp['wy'])
+    sr['stddev_wy'] = stddev(temp['wy'])
+
+    sr['mean_wx_offset'] = mean(temp['wx_offset'])
+    tr['wx_offsets'].append(sr['mean_wx_offset'])
+    sr['stddev_wx_offset'] = stddev(temp['wx_offset'])
+    sr['mean_wy_offset'] = mean(temp['wy_offset'])
+    tr['wy_offsets'].append(sr['mean_wy_offset'])
+    sr['stddev_wy_offset'] = stddev(temp['wy_offset'])
+
+    sr['mean_wx_error'] = mean(temp['wx_error'])
+    tr['wx_errors'].append(sr['mean_wx_error'])
+    sr['stddev_wx_error'] = stddev(temp['wx_error'])
+    sr['mean_wy_error'] = mean(temp['wy_error'])
+    tr['wy_errors'].append(sr['mean_wy_error'])
+    sr['stddev_wy_error'] = stddev(temp['wy_error'])
+    sr['mean_wxy_error'] = mean(temp['wxy_error'])
+    tr['wxy_errors'].append(sr['mean_wxy_error'])
+    sr['stddev_wxy_error'] = stddev(temp['wxy_error'])
+
+    sr['mean_wa'] = mean(temp['wa'])
+    sr['stddev_wa'] = stddev(temp['wa'])
     
     # store results
-    results["("+str(section.lx)+", "+str(section.ly)+")"] = sr
+    results.append(["("+str(section.lx)+", "+str(section.ly)+")", sr])
     pass
 
 # start reading the file:
-if sys.argv[1] != "":
+if len(sys.argv) == 2:
     datafile = file(sys.argv[1])
     pass
 else:
@@ -275,26 +282,38 @@ in_config = True
 re_config_option_float = re.compile("  \- ([ \w]+): (\-*\d+\.\d+)")
 re_config_option_int = re.compile("  \- ([ \w]+): (\-*\d+)")
 re_config_option_string = re.compile("  \- ([ \w]+): (.*)")
-re_section = re.compile("section:\s*\((\-*\d+\.\d+)\s*,\s*(\-*\d+\.\d+)\s*\)")
+fpnum = "\s*(\-*\d+\.\d+)\s*"
+re_section = re.compile("section:\s*\("+fpnum+","+fpnum+"\)")
 re_section_sep = re.compile("\+\+\+")
-re_frame_title = re.compile("frame (\d+) \(timestamp (\d+\.\d+)\):")
-re_frame_data_line = re.compile("  \- (\w+)\[(\d+)\]:\s*x = (\-*\d+\.\d+), y = (\-*\d+\.\d+), theta = (\-*\d+\.\d+)")
+re_frame_title = re.compile("frame (\d+) \(timestamp"+fpnum+"\):")
+re_frame_data_line = re.compile("a\("+fpnum+","+fpnum+"\)\s*"
+                                "b\("+fpnum+","+fpnum+"\)\s*"
+                                "-- wc\("+fpnum+","+fpnum+","+fpnum+"\)\s*")
 
 # a Section obj
 current_section = None
 # a FrameData obj
 current_frame = None
 # ObjectData objs
-current_objs = dict([])
+current_objs = []
 
 line = datafile.readline()
 
+# Totals over sections handled in analyze().
+tr = dict([])
+tr['wx_offsets'] = []              # Bias.
+tr['wy_offsets'] = []
+tr['wx_errors'] = []               # Deviation.
+tr['wy_errors'] = []
+tr['wxy_errors'] = []
+
 while line != "":
     # chop the newline
     line = line.strip('\n')
     #print "line = "+line
 
-    if line == "":
+    # Ignore null lines and lines commented-out by pound-signs.
+    if line == "" or line[0] == '#':
         in_config = False
         pass
     elif in_config:
@@ -334,38 +353,15 @@ while line != "":
         m2 = re_frame_title.match(line)
         m3 = re_section_sep.match(line)
         if m1 != None:
-            #print "m1.group(2) = "+m1.group(2)
-            if not current_objs.has_key(int(m1.group(2))):
-                current_objs[int(m1.group(2))] = ObjectData(0,0,0,0,0,0,0,0,0)
-                pass
-            if m1.group(1) == 'local':
-                debug("found local: "+str(float(m1.group(3)))+","+ \
-                      str(float(m1.group(4)))+","+str(float(m1.group(5))))
-                current_objs[int(m1.group(2))].lx = float(m1.group(3))
-                current_objs[int(m1.group(2))].ly = float(m1.group(4))
-                current_objs[int(m1.group(2))].lt = float(m1.group(5))
-                pass
-            elif m1.group(1) == 'radial':
-                debug("found radial: "+str(float(m1.group(3)))+","+ \
-                      str(float(m1.group(4)))+","+str(float(m1.group(5))))
-                current_objs[int(m1.group(2))].rx = float(m1.group(3))
-                current_objs[int(m1.group(2))].ry = float(m1.group(4))
-                current_objs[int(m1.group(2))].rt = float(m1.group(5))
-                pass
-            elif m1.group(1) == 'global':
-                debug("found global: "+str(float(m1.group(3)))+","+ \
-                      str(float(m1.group(4)))+","+str(float(m1.group(5))))
-                current_objs[int(m1.group(2))].gx = float(m1.group(3))
-                current_objs[int(m1.group(2))].gy = float(m1.group(4))
-                current_objs[int(m1.group(2))].gt = float(m1.group(5))
-                pass
+            debug("found frame data")
+            current_objs.append(ObjectData( *[float(d) for d in m1.groups()] ))
             pass
         elif m2 != None:
             # new frame; if old frame != 0, add it
             debug("found frame title")
             if current_frame != None:
                 current_frame.objects = current_objs
-                current_objs = dict([])
+                current_objs = []
                 current_section.addFrameData(current_frame)
                 pass
             current_frame = FrameData(int(m2.group(1)),
@@ -379,7 +375,7 @@ while line != "":
             #   add current_objs to frame.objects
             if current_frame != None:
                 current_frame.objects = current_objs
-                current_objs = dict([])
+                current_objs = []
                 current_section.addFrameData(current_frame)
                 current_frame = None
                 pass
@@ -400,35 +396,72 @@ while line != "":
 
 debug(options)
 
-for k,v in results.iteritems():
+for k,v in results:
     print "section "+str(k)+" results:"
     # individual stats:
-    print "  valid_frames = "+v['valid_frames']
-    print "  invalid_frames = "+v['invalid_frames']
-    print "  mult_meas_frames = "+v['mult_meas_frames']
-    print "  mean_lx = "+v['mean_lx']
-    print "  stddev_lx = "+v['stddev_lx']
-    print "  mean_ly = "+v['mean_ly']
-    print "  stddev_ly = "+v['stddev_ly']
-    print "  mean_lt = "+v['mean_lt']
-    print "  stddev_lt = "+v['stddev_lt']
-    print "  mean_lx_minus_actual_lx = "+v['mean_lx_minus_actual_lx']
-    print "  stddev_lx_minus_actual_lx = "+v['stddev_lx_minus_actual_lx']
-    print "  mean_ly_minus_actual_ly = "+v['mean_ly_minus_actual_ly']
-    print "  stddev_ly_minus_actual_ly = "+v['stddev_ly_minus_actual_ly']
-    print "  mean_lt_minus_actual_lt = "+v['mean_lt_minus_actual_lt']
-    print "  stddev_lt_minus_actual_lt = "+v['stddev_lt_minus_actual_lt']
-    print "  mean_rx_minus_actual_lx = "+v['mean_rx_minus_actual_lx']
-    print "  stddev_rx_minus_actual_lx = "+v['stddev_rx_minus_actual_lx']
-    print "  mean_ry_minus_actual_ly = "+v['mean_ry_minus_actual_ly']
-    print "  stddev_ry_minus_actual_ly = "+v['stddev_ry_minus_actual_ly']
-    print "  mean_rt_minus_actual_lt = "+v['mean_rt_minus_actual_lt']
-    print "  stddev_rt_minus_actual_lt = "+v['stddev_rt_minus_actual_lt']
-
-
+    print "  valid_frames = "+str(v['valid_frames'])
+    print "  bad_data_frames = "+str(v['bad_data_frames'])
+    print "  invalid_frames = "+str(v['invalid_frames'])
+    print "  mult_meas_frames = "+str(v['mult_meas_frames'])
+
+    print "  mean_ax = "+str(v['mean_ax'])
+    print "  stddev_ax = "+str(v['stddev_ax'])
+    print "  mean_ay = "+str(v['mean_ay'])
+    print "  stddev_ay = "+str(v['stddev_ay'])
+
+    print "  mean_bx = "+str(v['mean_bx'])
+    print "  stddev_bx = "+str(v['stddev_bx'])
+    print "  mean_by = "+str(v['mean_by'])
+    print "  stddev_by = "+str(v['stddev_by'])
+
+    print "  mean_wx = "+str(v['mean_wx'])
+    print "  stddev_wx = "+str(v['stddev_wx'])
+    print "  mean_wy = "+str(v['mean_wy'])
+    print "  stddev_wy = "+str(v['stddev_wy'])
+    print "  mean_wa = "+str(v['mean_wa'])
+    print "  stddev_wa = "+str(v['stddev_wa'])
+
+    print "  mean_wx_offset = "+str(v['mean_wx_offset'])
+    print "  stddev_wx_offset = "+str(v['stddev_wx_offset'])
+    print "  mean_wy_offset = "+str(v['mean_wy_offset'])
+    print "  stddev_wy_offset = "+str(v['stddev_wy_offset'])
+
+    print "  mean_wx_error = "+str(v['mean_wx_error'])
+    print "  stddev_wx_error = "+str(v['stddev_wx_error'])
+    print "  mean_wy_error = "+str(v['mean_wy_error'])
+    print "  stddev_wy_error = "+str(v['stddev_wy_error'])
+    print "  mean_wxy_error = "+str(v['mean_wxy_error'])
+    print "  stddev_wxy_error = "+str(v['stddev_wxy_error'])
     
 #    for k2,v2 in v.iteritems():
 #        print "  "+str(k2)+" = "+str(v2)
 #        pass
-#    pass
 
+    pass
+
+print "\ntotal results:"
+
+print "  max_wx_offset = "+str(max(tr['wx_offsets']))
+print "  min_wx_offset = "+str(min(tr['wx_offsets']))
+print "  mean_wx_offset = "+str(mean(tr['wx_offsets']))
+print "  stddev_wx_offset = "+str(stddev(tr['wx_offsets']))
+print ""
+print "  max_wy_offset = "+str(max(tr['wy_offsets']))
+print "  min_wy_offset = "+str(min(tr['wy_offsets']))
+print "  mean_wy_offset = "+str(mean(tr['wy_offsets']))
+print "  stddev_wy_offset = "+str(stddev(tr['wy_offsets']))
+print ""
+print "  max_wx_error = "+str(max(tr['wx_errors']))
+print "  min_wx_error = "+str(min(tr['wx_errors']))
+print "  mean_wx_error = "+str(mean(tr['wx_errors']))
+print "  stddev_wx_error = "+str(stddev(tr['wx_errors']))
+print ""
+print "  max_wy_error = "+str(max(tr['wy_errors']))
+print "  min_wy_error = "+str(min(tr['wy_errors']))
+print "  mean_wy_error = "+str(mean(tr['wy_errors']))
+print "  stddev_wy_error = "+str(stddev(tr['wy_errors']))
+print ""
+print "  max_wxy_error = "+str(max(tr['wxy_errors']))
+print "  min_wxy_error = "+str(min(tr['wxy_errors']))
+print "  mean_wxy_error = "+str(mean(tr['wxy_errors']))
+print "  stddev_wxy_error = "+str(stddev(tr['wxy_errors']))