Home | History | Annotate | Download | only in velocityplot
      1 #!/usr/bin/env python2.6
      2 #
      3 # Copyright (C) 2011 The Android Open Source Project
      4 #
      5 # Licensed under the Apache License, Version 2.0 (the "License");
      6 # you may not use this file except in compliance with the License.
      7 # You may obtain a copy of the License at
      8 #
      9 #      http://www.apache.org/licenses/LICENSE-2.0
     10 #
     11 # Unless required by applicable law or agreed to in writing, software
     12 # distributed under the License is distributed on an "AS IS" BASIS,
     13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14 # See the License for the specific language governing permissions and
     15 # limitations under the License.
     16 #
     17 
     18 #
     19 # Plots debug log output from VelocityTracker.
     20 # Enable DEBUG_VELOCITY to print the output.
     21 #
     22 # This code supports side-by-side comparison of two algorithms.
     23 # The old algorithm should be modified to emit debug log messages containing
     24 # the word "OLD".
     25 #
     26 
     27 import numpy as np
     28 import matplotlib.pyplot as plot
     29 import subprocess
     30 import re
     31 import fcntl
     32 import os
     33 import errno
     34 import bisect
     35 from datetime import datetime, timedelta
     36 
     37 # Parameters.
     38 timespan = 15 # seconds total span shown
     39 scrolljump = 5 # seconds jump when scrolling
     40 timeticks = 1 # seconds between each time tick
     41 
     42 # Non-blocking stream wrapper.
     43 class NonBlockingStream:
     44   def __init__(self, stream):
     45     fcntl.fcntl(stream, fcntl.F_SETFL, os.O_NONBLOCK)
     46     self.stream = stream
     47     self.buffer = ''
     48     self.pos = 0
     49 
     50   def readline(self):
     51     while True:
     52       index = self.buffer.find('\n', self.pos)
     53       if index != -1:
     54         result = self.buffer[self.pos:index]
     55         self.pos = index + 1
     56         return result
     57 
     58       self.buffer = self.buffer[self.pos:]
     59       self.pos = 0
     60       try:
     61         chunk = os.read(self.stream.fileno(), 4096)
     62       except OSError, e:
     63         if e.errno == errno.EAGAIN:
     64           return None
     65         raise e
     66       if len(chunk) == 0:
     67         if len(self.buffer) == 0:
     68           raise(EOFError)
     69         else:
     70           result = self.buffer
     71           self.buffer = ''
     72           self.pos = 0
     73           return result
     74       self.buffer += chunk
     75 
     76 # Plotter
     77 class Plotter:
     78   def __init__(self, adbout):
     79     self.adbout = adbout
     80 
     81     self.fig = plot.figure(1)
     82     self.fig.suptitle('Velocity Tracker', fontsize=12)
     83     self.fig.set_dpi(96)
     84     self.fig.set_size_inches(16, 12, forward=True)
     85 
     86     self.velocity_x = self._make_timeseries()
     87     self.velocity_y = self._make_timeseries()
     88     self.velocity_magnitude = self._make_timeseries()
     89     self.velocity_axes = self._add_timeseries_axes(
     90         1, 'Velocity', 'px/s', [-5000, 5000],
     91         yticks=range(-5000, 5000, 1000))
     92     self.velocity_line_x = self._add_timeseries_line(
     93         self.velocity_axes, 'vx', 'red')
     94     self.velocity_line_y = self._add_timeseries_line(
     95         self.velocity_axes, 'vy', 'green')
     96     self.velocity_line_magnitude = self._add_timeseries_line(
     97         self.velocity_axes, 'magnitude', 'blue')
     98     self._add_timeseries_legend(self.velocity_axes)
     99 
    100     shared_axis = self.velocity_axes
    101 
    102     self.old_velocity_x = self._make_timeseries()
    103     self.old_velocity_y = self._make_timeseries()
    104     self.old_velocity_magnitude = self._make_timeseries()
    105     self.old_velocity_axes = self._add_timeseries_axes(
    106         2, 'Old Algorithm Velocity', 'px/s', [-5000, 5000],
    107         sharex=shared_axis,
    108         yticks=range(-5000, 5000, 1000))
    109     self.old_velocity_line_x = self._add_timeseries_line(
    110         self.old_velocity_axes, 'vx', 'red')
    111     self.old_velocity_line_y = self._add_timeseries_line(
    112         self.old_velocity_axes, 'vy', 'green')
    113     self.old_velocity_line_magnitude = self._add_timeseries_line(
    114         self.old_velocity_axes, 'magnitude', 'blue')
    115     self._add_timeseries_legend(self.old_velocity_axes)
    116 
    117     self.timer = self.fig.canvas.new_timer(interval=100)
    118     self.timer.add_callback(lambda: self.update())
    119     self.timer.start()
    120 
    121     self.timebase = None
    122     self._reset_parse_state()
    123 
    124   # Initialize a time series.
    125   def _make_timeseries(self):
    126     return [[], []]
    127 
    128   # Add a subplot to the figure for a time series.
    129   def _add_timeseries_axes(self, index, title, ylabel, ylim, yticks, sharex=None):
    130     num_graphs = 2
    131     height = 0.9 / num_graphs
    132     top = 0.95 - height * index
    133     axes = self.fig.add_axes([0.1, top, 0.8, height],
    134         xscale='linear',
    135         xlim=[0, timespan],
    136         ylabel=ylabel,
    137         yscale='linear',
    138         ylim=ylim,
    139         sharex=sharex)
    140     axes.text(0.02, 0.02, title, transform=axes.transAxes, fontsize=10, fontweight='bold')
    141     axes.set_xlabel('time (s)', fontsize=10, fontweight='bold')
    142     axes.set_ylabel(ylabel, fontsize=10, fontweight='bold')
    143     axes.set_xticks(range(0, timespan + 1, timeticks))
    144     axes.set_yticks(yticks)
    145     axes.grid(True)
    146 
    147     for label in axes.get_xticklabels():
    148       label.set_fontsize(9)
    149     for label in axes.get_yticklabels():
    150       label.set_fontsize(9)
    151 
    152     return axes
    153 
    154   # Add a line to the axes for a time series.
    155   def _add_timeseries_line(self, axes, label, color, linewidth=1):
    156     return axes.plot([], label=label, color=color, linewidth=linewidth)[0]
    157 
    158   # Add a legend to a time series.
    159   def _add_timeseries_legend(self, axes):
    160     axes.legend(
    161         loc='upper left',
    162         bbox_to_anchor=(1.01, 1),
    163         borderpad=0.1,
    164         borderaxespad=0.1,
    165         prop={'size': 10})
    166 
    167   # Resets the parse state.
    168   def _reset_parse_state(self):
    169     self.parse_velocity_x = None
    170     self.parse_velocity_y = None
    171     self.parse_velocity_magnitude = None
    172     self.parse_old_velocity_x = None
    173     self.parse_old_velocity_y = None
    174     self.parse_old_velocity_magnitude = None
    175 
    176   # Update samples.
    177   def update(self):
    178     timeindex = 0
    179     while True:
    180       try:
    181         line = self.adbout.readline()
    182       except EOFError:
    183         plot.close()
    184         return
    185       if line is None:
    186         break
    187       print line
    188 
    189       try:
    190         timestamp = self._parse_timestamp(line)
    191       except ValueError, e:
    192         continue
    193       if self.timebase is None:
    194         self.timebase = timestamp
    195       delta = timestamp - self.timebase
    196       timeindex = delta.seconds + delta.microseconds * 0.000001
    197 
    198       if line.find(': position') != -1:
    199         self.parse_velocity_x = self._get_following_number(line, 'vx=')
    200         self.parse_velocity_y = self._get_following_number(line, 'vy=')
    201         self.parse_velocity_magnitude = self._get_following_number(line, 'speed=')
    202         self._append(self.velocity_x, timeindex, self.parse_velocity_x)
    203         self._append(self.velocity_y, timeindex, self.parse_velocity_y)
    204         self._append(self.velocity_magnitude, timeindex, self.parse_velocity_magnitude)
    205 
    206       if line.find(': OLD') != -1:
    207         self.parse_old_velocity_x = self._get_following_number(line, 'vx=')
    208         self.parse_old_velocity_y = self._get_following_number(line, 'vy=')
    209         self.parse_old_velocity_magnitude = self._get_following_number(line, 'speed=')
    210         self._append(self.old_velocity_x, timeindex, self.parse_old_velocity_x)
    211         self._append(self.old_velocity_y, timeindex, self.parse_old_velocity_y)
    212         self._append(self.old_velocity_magnitude, timeindex, self.parse_old_velocity_magnitude)
    213 
    214     # Scroll the plots.
    215     if timeindex > timespan:
    216       bottom = int(timeindex) - timespan + scrolljump
    217       self.timebase += timedelta(seconds=bottom)
    218       self._scroll(self.velocity_x, bottom)
    219       self._scroll(self.velocity_y, bottom)
    220       self._scroll(self.velocity_magnitude, bottom)
    221       self._scroll(self.old_velocity_x, bottom)
    222       self._scroll(self.old_velocity_y, bottom)
    223       self._scroll(self.old_velocity_magnitude, bottom)
    224 
    225     # Redraw the plots.
    226     self.velocity_line_x.set_data(self.velocity_x)
    227     self.velocity_line_y.set_data(self.velocity_y)
    228     self.velocity_line_magnitude.set_data(self.velocity_magnitude)
    229     self.old_velocity_line_x.set_data(self.old_velocity_x)
    230     self.old_velocity_line_y.set_data(self.old_velocity_y)
    231     self.old_velocity_line_magnitude.set_data(self.old_velocity_magnitude)
    232 
    233     self.fig.canvas.draw_idle()
    234 
    235   # Scroll a time series.
    236   def _scroll(self, timeseries, bottom):
    237     bottom_index = bisect.bisect_left(timeseries[0], bottom)
    238     del timeseries[0][:bottom_index]
    239     del timeseries[1][:bottom_index]
    240     for i, timeindex in enumerate(timeseries[0]):
    241       timeseries[0][i] = timeindex - bottom
    242 
    243   # Extract a word following the specified prefix.
    244   def _get_following_word(self, line, prefix):
    245     prefix_index = line.find(prefix)
    246     if prefix_index == -1:
    247       return None
    248     start_index = prefix_index + len(prefix)
    249     delim_index = line.find(',', start_index)
    250     if delim_index == -1:
    251       return line[start_index:]
    252     else:
    253       return line[start_index:delim_index]
    254 
    255   # Extract a number following the specified prefix.
    256   def _get_following_number(self, line, prefix):
    257     word = self._get_following_word(line, prefix)
    258     if word is None:
    259       return None
    260     return float(word)
    261 
    262   # Add a value to a time series.
    263   def _append(self, timeseries, timeindex, number):
    264     timeseries[0].append(timeindex)
    265     timeseries[1].append(number)
    266 
    267   # Parse the logcat timestamp.
    268   # Timestamp has the form '01-21 20:42:42.930'
    269   def _parse_timestamp(self, line):
    270     return datetime.strptime(line[0:18], '%m-%d %H:%M:%S.%f')
    271 
    272 # Notice
    273 print "Velocity Tracker plotting tool"
    274 print "-----------------------------------------\n"
    275 print "Please enable debug logging and recompile the code."
    276 
    277 # Start adb.
    278 print "Starting adb logcat.\n"
    279 
    280 adb = subprocess.Popen(['adb', 'logcat', '-s', '-v', 'time', 'Input:*', 'VelocityTracker:*'],
    281     stdout=subprocess.PIPE)
    282 adbout = NonBlockingStream(adb.stdout)
    283 
    284 # Prepare plotter.
    285 plotter = Plotter(adbout)
    286 plotter.update()
    287 
    288 # Main loop.
    289 plot.show()
    290