orientationplot.py revision 4519f07e9c6b993fbe7a3d3df24d71d9450a54f1
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 WindowOrientationListener. 20# See README.txt for details. 21# 22 23import numpy as np 24import matplotlib.pyplot as plot 25import subprocess 26import re 27import fcntl 28import os 29import errno 30import bisect 31from datetime import datetime, timedelta 32 33# Parameters. 34timespan = 15 # seconds total span shown 35scrolljump = 5 # seconds jump when scrolling 36timeticks = 1 # seconds between each time tick 37 38# Non-blocking stream wrapper. 39class NonBlockingStream: 40 def __init__(self, stream): 41 fcntl.fcntl(stream, fcntl.F_SETFL, os.O_NONBLOCK) 42 self.stream = stream 43 self.buffer = '' 44 self.pos = 0 45 46 def readline(self): 47 while True: 48 index = self.buffer.find('\n', self.pos) 49 if index != -1: 50 result = self.buffer[self.pos:index] 51 self.pos = index + 1 52 return result 53 54 self.buffer = self.buffer[self.pos:] 55 self.pos = 0 56 try: 57 chunk = os.read(self.stream.fileno(), 4096) 58 except OSError, e: 59 if e.errno == errno.EAGAIN: 60 return None 61 raise e 62 if len(chunk) == 0: 63 if len(self.buffer) == 0: 64 raise(EOFError) 65 else: 66 result = self.buffer 67 self.buffer = '' 68 self.pos = 0 69 return result 70 self.buffer += chunk 71 72# Plotter 73class Plotter: 74 def __init__(self, adbout): 75 self.adbout = adbout 76 77 self.fig = plot.figure(1) 78 self.fig.suptitle('Window Orientation Listener', fontsize=12) 79 self.fig.set_dpi(96) 80 self.fig.set_size_inches(16, 12, forward=True) 81 82 self.raw_acceleration_x = self._make_timeseries() 83 self.raw_acceleration_y = self._make_timeseries() 84 self.raw_acceleration_z = self._make_timeseries() 85 self.raw_acceleration_axes = self._add_timeseries_axes( 86 1, 'Raw Acceleration', 'm/s^2', [-20, 20], 87 yticks=range(-15, 16, 5)) 88 self.raw_acceleration_line_x = self._add_timeseries_line( 89 self.raw_acceleration_axes, 'x', 'red') 90 self.raw_acceleration_line_y = self._add_timeseries_line( 91 self.raw_acceleration_axes, 'y', 'green') 92 self.raw_acceleration_line_z = self._add_timeseries_line( 93 self.raw_acceleration_axes, 'z', 'blue') 94 self._add_timeseries_legend(self.raw_acceleration_axes) 95 96 shared_axis = self.raw_acceleration_axes 97 98 self.filtered_acceleration_x = self._make_timeseries() 99 self.filtered_acceleration_y = self._make_timeseries() 100 self.filtered_acceleration_z = self._make_timeseries() 101 self.magnitude = self._make_timeseries() 102 self.filtered_acceleration_axes = self._add_timeseries_axes( 103 2, 'Filtered Acceleration', 'm/s^2', [-20, 20], 104 sharex=shared_axis, 105 yticks=range(-15, 16, 5)) 106 self.filtered_acceleration_line_x = self._add_timeseries_line( 107 self.filtered_acceleration_axes, 'x', 'red') 108 self.filtered_acceleration_line_y = self._add_timeseries_line( 109 self.filtered_acceleration_axes, 'y', 'green') 110 self.filtered_acceleration_line_z = self._add_timeseries_line( 111 self.filtered_acceleration_axes, 'z', 'blue') 112 self.magnitude_line = self._add_timeseries_line( 113 self.filtered_acceleration_axes, 'magnitude', 'orange', linewidth=2) 114 self._add_timeseries_legend(self.filtered_acceleration_axes) 115 116 self.tilt_angle = self._make_timeseries() 117 self.tilt_angle_axes = self._add_timeseries_axes( 118 3, 'Tilt Angle', 'degrees', [-105, 105], 119 sharex=shared_axis, 120 yticks=range(-90, 91, 30)) 121 self.tilt_angle_line = self._add_timeseries_line( 122 self.tilt_angle_axes, 'tilt', 'black') 123 self._add_timeseries_legend(self.tilt_angle_axes) 124 125 self.orientation_angle = self._make_timeseries() 126 self.orientation_angle_axes = self._add_timeseries_axes( 127 4, 'Orientation Angle', 'degrees', [-25, 375], 128 sharex=shared_axis, 129 yticks=range(0, 361, 45)) 130 self.orientation_angle_line = self._add_timeseries_line( 131 self.orientation_angle_axes, 'orientation', 'black') 132 self._add_timeseries_legend(self.orientation_angle_axes) 133 134 self.actual_orientation = self._make_timeseries() 135 self.proposed_orientation = self._make_timeseries() 136 self.orientation_axes = self._add_timeseries_axes( 137 5, 'Actual / Proposed Orientation and Confidence', 'rotation', [-1, 4], 138 sharex=shared_axis, 139 yticks=range(0, 4)) 140 self.actual_orientation_line = self._add_timeseries_line( 141 self.orientation_axes, 'actual', 'black', linewidth=2) 142 self.proposed_orientation_line = self._add_timeseries_line( 143 self.orientation_axes, 'proposed', 'purple', linewidth=3) 144 self._add_timeseries_legend(self.orientation_axes) 145 146 self.confidence = [[self._make_timeseries(), self._make_timeseries()] for i in range(0, 4)] 147 self.confidence_polys = [] 148 149 self.combined_confidence = self._make_timeseries() 150 self.orientation_confidence = self._make_timeseries() 151 self.tilt_confidence = self._make_timeseries() 152 self.magnitude_confidence = self._make_timeseries() 153 self.confidence_axes = self._add_timeseries_axes( 154 6, 'Proposed Orientation Confidence Factors', 'confidence', [-0.1, 1.1], 155 sharex=shared_axis, 156 yticks=[0.0, 0.2, 0.4, 0.6, 0.8, 1.0]) 157 self.combined_confidence_line = self._add_timeseries_line( 158 self.confidence_axes, 'combined', 'purple', linewidth=2) 159 self.orientation_confidence_line = self._add_timeseries_line( 160 self.confidence_axes, 'orientation', 'black') 161 self.tilt_confidence_line = self._add_timeseries_line( 162 self.confidence_axes, 'tilt', 'brown') 163 self.magnitude_confidence_line = self._add_timeseries_line( 164 self.confidence_axes, 'magnitude', 'orange') 165 self._add_timeseries_legend(self.confidence_axes) 166 167 self.sample_latency = self._make_timeseries() 168 self.sample_latency_axes = self._add_timeseries_axes( 169 7, 'Accelerometer Sampling Latency', 'ms', [-10, 500], 170 sharex=shared_axis, 171 yticks=range(0, 500, 100)) 172 self.sample_latency_line = self._add_timeseries_line( 173 self.sample_latency_axes, 'latency', 'black') 174 self._add_timeseries_legend(self.sample_latency_axes) 175 176 self.timer = self.fig.canvas.new_timer(interval=100) 177 self.timer.add_callback(lambda: self.update()) 178 self.timer.start() 179 180 self.timebase = None 181 self._reset_parse_state() 182 183 # Initialize a time series. 184 def _make_timeseries(self): 185 return [[], []] 186 187 # Add a subplot to the figure for a time series. 188 def _add_timeseries_axes(self, index, title, ylabel, ylim, yticks, sharex=None): 189 num_graphs = 7 190 height = 0.9 / num_graphs 191 top = 0.95 - height * index 192 axes = self.fig.add_axes([0.1, top, 0.8, height], 193 xscale='linear', 194 xlim=[0, timespan], 195 ylabel=ylabel, 196 yscale='linear', 197 ylim=ylim, 198 sharex=sharex) 199 axes.text(0.02, 0.02, title, transform=axes.transAxes, fontsize=10, fontweight='bold') 200 axes.set_xlabel('time (s)', fontsize=10, fontweight='bold') 201 axes.set_ylabel(ylabel, fontsize=10, fontweight='bold') 202 axes.set_xticks(range(0, timespan + 1, timeticks)) 203 axes.set_yticks(yticks) 204 axes.grid(True) 205 206 for label in axes.get_xticklabels(): 207 label.set_fontsize(9) 208 for label in axes.get_yticklabels(): 209 label.set_fontsize(9) 210 211 return axes 212 213 # Add a line to the axes for a time series. 214 def _add_timeseries_line(self, axes, label, color, linewidth=1): 215 return axes.plot([], label=label, color=color, linewidth=linewidth)[0] 216 217 # Add a legend to a time series. 218 def _add_timeseries_legend(self, axes): 219 axes.legend( 220 loc='upper left', 221 bbox_to_anchor=(1.01, 1), 222 borderpad=0.1, 223 borderaxespad=0.1, 224 prop={'size': 10}) 225 226 # Resets the parse state. 227 def _reset_parse_state(self): 228 self.parse_raw_acceleration_x = None 229 self.parse_raw_acceleration_y = None 230 self.parse_raw_acceleration_z = None 231 self.parse_filtered_acceleration_x = None 232 self.parse_filtered_acceleration_y = None 233 self.parse_filtered_acceleration_z = None 234 self.parse_magnitude = None 235 self.parse_tilt_angle = None 236 self.parse_orientation_angle = None 237 self.parse_proposed_orientation = None 238 self.parse_combined_confidence = None 239 self.parse_orientation_confidence = None 240 self.parse_tilt_confidence = None 241 self.parse_magnitude_confidence = None 242 self.parse_actual_orientation = None 243 self.parse_confidence = None 244 self.parse_sample_latency = None 245 246 # Update samples. 247 def update(self): 248 timeindex = 0 249 while True: 250 try: 251 line = self.adbout.readline() 252 except EOFError: 253 plot.close() 254 return 255 if line is None: 256 break 257 print line 258 259 try: 260 timestamp = self._parse_timestamp(line) 261 except ValueError, e: 262 continue 263 if self.timebase is None: 264 self.timebase = timestamp 265 delta = timestamp - self.timebase 266 timeindex = delta.seconds + delta.microseconds * 0.000001 267 268 if line.find('Raw acceleration vector:') != -1: 269 self.parse_raw_acceleration_x = self._get_following_number(line, 'x=') 270 self.parse_raw_acceleration_y = self._get_following_number(line, 'y=') 271 self.parse_raw_acceleration_z = self._get_following_number(line, 'z=') 272 273 if line.find('Filtered acceleration vector:') != -1: 274 self.parse_filtered_acceleration_x = self._get_following_number(line, 'x=') 275 self.parse_filtered_acceleration_y = self._get_following_number(line, 'y=') 276 self.parse_filtered_acceleration_z = self._get_following_number(line, 'z=') 277 278 if line.find('magnitude=') != -1: 279 self.parse_magnitude = self._get_following_number(line, 'magnitude=') 280 281 if line.find('tiltAngle=') != -1: 282 self.parse_tilt_angle = self._get_following_number(line, 'tiltAngle=') 283 284 if line.find('orientationAngle=') != -1: 285 self.parse_orientation_angle = self._get_following_number(line, 'orientationAngle=') 286 287 if line.find('Proposal:') != -1: 288 self.parse_proposed_orientation = self._get_following_number(line, 'proposedOrientation=') 289 self.parse_combined_confidence = self._get_following_number(line, 'combinedConfidence=') 290 self.parse_orientation_confidence = self._get_following_number(line, 'orientationConfidence=') 291 self.parse_tilt_confidence = self._get_following_number(line, 'tiltConfidence=') 292 self.parse_magnitude_confidence = self._get_following_number(line, 'magnitudeConfidence=') 293 294 if line.find('Result:') != -1: 295 self.parse_actual_orientation = self._get_following_number(line, 'rotation=') 296 self.parse_confidence = self._get_following_array_of_numbers(line, 'confidence=') 297 self.parse_sample_latency = self._get_following_number(line, 'timeDeltaMS=') 298 299 for i in range(0, 4): 300 if self.parse_confidence is not None: 301 self._append(self.confidence[i][0], timeindex, i) 302 self._append(self.confidence[i][1], timeindex, i + self.parse_confidence[i]) 303 else: 304 self._append(self.confidence[i][0], timeindex, None) 305 self._append(self.confidence[i][1], timeindex, None) 306 307 self._append(self.raw_acceleration_x, timeindex, self.parse_raw_acceleration_x) 308 self._append(self.raw_acceleration_y, timeindex, self.parse_raw_acceleration_y) 309 self._append(self.raw_acceleration_z, timeindex, self.parse_raw_acceleration_z) 310 self._append(self.filtered_acceleration_x, timeindex, self.parse_filtered_acceleration_x) 311 self._append(self.filtered_acceleration_y, timeindex, self.parse_filtered_acceleration_y) 312 self._append(self.filtered_acceleration_z, timeindex, self.parse_filtered_acceleration_z) 313 self._append(self.magnitude, timeindex, self.parse_magnitude) 314 self._append(self.tilt_angle, timeindex, self.parse_tilt_angle) 315 self._append(self.orientation_angle, timeindex, self.parse_orientation_angle) 316 self._append(self.actual_orientation, timeindex, self.parse_actual_orientation) 317 self._append(self.proposed_orientation, timeindex, self.parse_proposed_orientation) 318 self._append(self.combined_confidence, timeindex, self.parse_combined_confidence) 319 self._append(self.orientation_confidence, timeindex, self.parse_orientation_confidence) 320 self._append(self.tilt_confidence, timeindex, self.parse_tilt_confidence) 321 self._append(self.magnitude_confidence, timeindex, self.parse_magnitude_confidence) 322 self._append(self.sample_latency, timeindex, self.parse_sample_latency) 323 self._reset_parse_state() 324 325 # Scroll the plots. 326 if timeindex > timespan: 327 bottom = int(timeindex) - timespan + scrolljump 328 self.timebase += timedelta(seconds=bottom) 329 self._scroll(self.raw_acceleration_x, bottom) 330 self._scroll(self.raw_acceleration_y, bottom) 331 self._scroll(self.raw_acceleration_z, bottom) 332 self._scroll(self.filtered_acceleration_x, bottom) 333 self._scroll(self.filtered_acceleration_y, bottom) 334 self._scroll(self.filtered_acceleration_z, bottom) 335 self._scroll(self.magnitude, bottom) 336 self._scroll(self.tilt_angle, bottom) 337 self._scroll(self.orientation_angle, bottom) 338 self._scroll(self.actual_orientation, bottom) 339 self._scroll(self.proposed_orientation, bottom) 340 self._scroll(self.combined_confidence, bottom) 341 self._scroll(self.orientation_confidence, bottom) 342 self._scroll(self.tilt_confidence, bottom) 343 self._scroll(self.magnitude_confidence, bottom) 344 self._scroll(self.sample_latency, bottom) 345 for i in range(0, 4): 346 self._scroll(self.confidence[i][0], bottom) 347 self._scroll(self.confidence[i][1], bottom) 348 349 # Redraw the plots. 350 self.raw_acceleration_line_x.set_data(self.raw_acceleration_x) 351 self.raw_acceleration_line_y.set_data(self.raw_acceleration_y) 352 self.raw_acceleration_line_z.set_data(self.raw_acceleration_z) 353 self.filtered_acceleration_line_x.set_data(self.filtered_acceleration_x) 354 self.filtered_acceleration_line_y.set_data(self.filtered_acceleration_y) 355 self.filtered_acceleration_line_z.set_data(self.filtered_acceleration_z) 356 self.magnitude_line.set_data(self.magnitude) 357 self.tilt_angle_line.set_data(self.tilt_angle) 358 self.orientation_angle_line.set_data(self.orientation_angle) 359 self.actual_orientation_line.set_data(self.actual_orientation) 360 self.proposed_orientation_line.set_data(self.proposed_orientation) 361 self.combined_confidence_line.set_data(self.combined_confidence) 362 self.orientation_confidence_line.set_data(self.orientation_confidence) 363 self.tilt_confidence_line.set_data(self.tilt_confidence) 364 self.magnitude_confidence_line.set_data(self.magnitude_confidence) 365 self.sample_latency_line.set_data(self.sample_latency) 366 367 for poly in self.confidence_polys: 368 poly.remove() 369 self.confidence_polys = [] 370 for i in range(0, 4): 371 self.confidence_polys.append(self.orientation_axes.fill_between(self.confidence[i][0][0], 372 self.confidence[i][0][1], self.confidence[i][1][1], 373 facecolor='goldenrod', edgecolor='goldenrod')) 374 375 self.fig.canvas.draw_idle() 376 377 # Scroll a time series. 378 def _scroll(self, timeseries, bottom): 379 bottom_index = bisect.bisect_left(timeseries[0], bottom) 380 del timeseries[0][:bottom_index] 381 del timeseries[1][:bottom_index] 382 for i, timeindex in enumerate(timeseries[0]): 383 timeseries[0][i] = timeindex - bottom 384 385 # Extract a word following the specified prefix. 386 def _get_following_word(self, line, prefix): 387 prefix_index = line.find(prefix) 388 if prefix_index == -1: 389 return None 390 start_index = prefix_index + len(prefix) 391 delim_index = line.find(',', start_index) 392 if delim_index == -1: 393 return line[start_index:] 394 else: 395 return line[start_index:delim_index] 396 397 # Extract a number following the specified prefix. 398 def _get_following_number(self, line, prefix): 399 word = self._get_following_word(line, prefix) 400 if word is None: 401 return None 402 return float(word) 403 404 # Extract an array of numbers following the specified prefix. 405 def _get_following_array_of_numbers(self, line, prefix): 406 prefix_index = line.find(prefix + '[') 407 if prefix_index == -1: 408 return None 409 start_index = prefix_index + len(prefix) + 1 410 delim_index = line.find(']', start_index) 411 if delim_index == -1: 412 return None 413 414 result = [] 415 while start_index < delim_index: 416 comma_index = line.find(', ', start_index, delim_index) 417 if comma_index == -1: 418 result.append(float(line[start_index:delim_index])) 419 break; 420 result.append(float(line[start_index:comma_index])) 421 start_index = comma_index + 2 422 return result 423 424 # Add a value to a time series. 425 def _append(self, timeseries, timeindex, number): 426 timeseries[0].append(timeindex) 427 timeseries[1].append(number) 428 429 # Parse the logcat timestamp. 430 # Timestamp has the form '01-21 20:42:42.930' 431 def _parse_timestamp(self, line): 432 return datetime.strptime(line[0:18], '%m-%d %H:%M:%S.%f') 433 434# Notice 435print "Window Orientation Listener plotting tool" 436print "-----------------------------------------\n" 437print "Please turn on the Window Orientation Listener logging in Development Settings." 438 439# Start adb. 440print "Starting adb logcat.\n" 441 442adb = subprocess.Popen(['adb', 'logcat', '-s', '-v', 'time', 'WindowOrientationListener:V'], 443 stdout=subprocess.PIPE) 444adbout = NonBlockingStream(adb.stdout) 445 446# Prepare plotter. 447plotter = Plotter(adbout) 448plotter.update() 449 450# Main loop. 451plot.show() 452