blob: 07449d4d2bc9fa0148187c49af5da647928ae627 [file] [log] [blame]
Jeff Brown4519f072011-01-23 13:16:01 -08001#!/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()