Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(649)

Side by Side Diff: webrtc/video/full_stack_tests_plot.py

Issue 2812273002: Fix lint errors to enable stricter PyLint rules (Closed)
Patch Set: Rebased Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « webrtc/tools/video_analysis_test.py ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. 2 # Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
3 # 3 #
4 # Use of this source code is governed by a BSD-style license 4 # Use of this source code is governed by a BSD-style license
5 # that can be found in the LICENSE file in the root of the source 5 # that can be found in the LICENSE file in the root of the source
6 # tree. An additional intellectual property rights grant can be found 6 # tree. An additional intellectual property rights grant can be found
7 # in the file PATENTS. All contributing project authors may 7 # in the file PATENTS. All contributing project authors may
8 # be found in the AUTHORS file in the root of the source tree. 8 # be found in the AUTHORS file in the root of the source tree.
9 9
10 """Generate graphs for data generated by loopback tests. 10 """Generate graphs for data generated by loopback tests.
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after
50 END_TO_END = TOTAL_RAW_FIELDS + 2 50 END_TO_END = TOTAL_RAW_FIELDS + 2
51 RENDERED_DELTA = TOTAL_RAW_FIELDS + 3 51 RENDERED_DELTA = TOTAL_RAW_FIELDS + 3
52 52
53 FIELD_MASK = 255 53 FIELD_MASK = 255
54 54
55 # Options 55 # Options
56 HIDE_DROPPED = 256 56 HIDE_DROPPED = 256
57 RIGHT_Y_AXIS = 512 57 RIGHT_Y_AXIS = 512
58 58
59 # internal field id, field name, title 59 # internal field id, field name, title
60 _fields = [ 60 _FIELDS = [
61 # Raw 61 # Raw
62 (DROPPED, "dropped", "dropped"), 62 (DROPPED, "dropped", "dropped"),
63 (INPUT_TIME, "input_time_ms", "input time"), 63 (INPUT_TIME, "input_time_ms", "input time"),
64 (SEND_TIME, "send_time_ms", "send time"), 64 (SEND_TIME, "send_time_ms", "send time"),
65 (RECV_TIME, "recv_time_ms", "recv time"), 65 (RECV_TIME, "recv_time_ms", "recv time"),
66 (ENCODED_FRAME_SIZE, "encoded_frame_size", "encoded frame size"), 66 (ENCODED_FRAME_SIZE, "encoded_frame_size", "encoded frame size"),
67 (PSNR, "psnr", "PSNR"), 67 (PSNR, "psnr", "PSNR"),
68 (SSIM, "ssim", "SSIM"), 68 (SSIM, "ssim", "SSIM"),
69 (RENDER_TIME, "render_time_ms", "render time"), 69 (RENDER_TIME, "render_time_ms", "render time"),
70 (ENCODE_TIME, "encode_time_ms", "encode time"), 70 (ENCODE_TIME, "encode_time_ms", "encode time"),
71 # Auto-generated 71 # Auto-generated
72 (SENDER_TIME, "sender_time", "sender time"), 72 (SENDER_TIME, "sender_time", "sender time"),
73 (RECEIVER_TIME, "receiver_time", "receiver time"), 73 (RECEIVER_TIME, "receiver_time", "receiver time"),
74 (END_TO_END, "end_to_end", "end to end"), 74 (END_TO_END, "end_to_end", "end to end"),
75 (RENDERED_DELTA, "rendered_delta", "rendered delta"), 75 (RENDERED_DELTA, "rendered_delta", "rendered delta"),
76 ] 76 ]
77 77
78 name_to_id = {field[1]: field[0] for field in _fields} 78 NAME_TO_ID = {field[1]: field[0] for field in _FIELDS}
79 id_to_title = {field[0]: field[2] for field in _fields} 79 ID_TO_TITLE = {field[0]: field[2] for field in _FIELDS}
80 80
81 def field_arg_to_id(arg): 81 def FieldArgToId(arg):
82 if arg == "none": 82 if arg == "none":
83 return None 83 return None
84 if arg in name_to_id: 84 if arg in NAME_TO_ID:
85 return name_to_id[arg] 85 return NAME_TO_ID[arg]
86 if arg + "_ms" in name_to_id: 86 if arg + "_ms" in NAME_TO_ID:
87 return name_to_id[arg + "_ms"] 87 return NAME_TO_ID[arg + "_ms"]
88 raise Exception("Unrecognized field name \"{}\"".format(arg)) 88 raise Exception("Unrecognized field name \"{}\"".format(arg))
89 89
90 90
91 class PlotLine(object): 91 class PlotLine(object):
92 """Data for a single graph line.""" 92 """Data for a single graph line."""
93 93
94 def __init__(self, label, values, flags): 94 def __init__(self, label, values, flags):
95 self.label = label 95 self.label = label
96 self.values = values 96 self.values = values
97 self.flags = flags 97 self.flags = flags
98 98
99 99
100 class Data(object): 100 class Data(object):
101 """Object representing one full stack test.""" 101 """Object representing one full stack test."""
102 102
103 def __init__(self, filename): 103 def __init__(self, filename):
104 self.title = "" 104 self.title = ""
105 self.length = 0 105 self.length = 0
106 self.samples = defaultdict(list) 106 self.samples = defaultdict(list)
107 107
108 self._read_samples(filename) 108 self._ReadSamples(filename)
109 109
110 def _read_samples(self, filename): 110 def _ReadSamples(self, filename):
111 """Reads graph data from the given file.""" 111 """Reads graph data from the given file."""
112 f = open(filename) 112 f = open(filename)
113 it = iter(f) 113 it = iter(f)
114 114
115 self.title = it.next().strip() 115 self.title = it.next().strip()
116 self.length = int(it.next()) 116 self.length = int(it.next())
117 field_names = [name.strip() for name in it.next().split()] 117 field_names = [name.strip() for name in it.next().split()]
118 field_ids = [name_to_id[name] for name in field_names] 118 field_ids = [NAME_TO_ID[name] for name in field_names]
119 119
120 for field_id in field_ids: 120 for field_id in field_ids:
121 self.samples[field_id] = [0.0] * self.length 121 self.samples[field_id] = [0.0] * self.length
122 122
123 for sample_id in xrange(self.length): 123 for sample_id in xrange(self.length):
124 for col, value in enumerate(it.next().split()): 124 for col, value in enumerate(it.next().split()):
125 self.samples[field_ids[col]][sample_id] = float(value) 125 self.samples[field_ids[col]][sample_id] = float(value)
126 126
127 self._subtract_first_input_time() 127 self._SubtractFirstInputTime()
128 self._generate_additional_data() 128 self._GenerateAdditionalData()
129 129
130 f.close() 130 f.close()
131 131
132 def _subtract_first_input_time(self): 132 def _SubtractFirstInputTime(self):
133 offset = self.samples[INPUT_TIME][0] 133 offset = self.samples[INPUT_TIME][0]
134 for field in [INPUT_TIME, SEND_TIME, RECV_TIME, RENDER_TIME]: 134 for field in [INPUT_TIME, SEND_TIME, RECV_TIME, RENDER_TIME]:
135 if field in self.samples: 135 if field in self.samples:
136 self.samples[field] = [x - offset for x in self.samples[field]] 136 self.samples[field] = [x - offset for x in self.samples[field]]
137 137
138 def _generate_additional_data(self): 138 def _GenerateAdditionalData(self):
139 """Calculates sender time, receiver time etc. from the raw data.""" 139 """Calculates sender time, receiver time etc. from the raw data."""
140 s = self.samples 140 s = self.samples
141 last_render_time = 0 141 last_render_time = 0
142 for field_id in [SENDER_TIME, RECEIVER_TIME, END_TO_END, RENDERED_DELTA]: 142 for field_id in [SENDER_TIME, RECEIVER_TIME, END_TO_END, RENDERED_DELTA]:
143 s[field_id] = [0] * self.length 143 s[field_id] = [0] * self.length
144 144
145 for k in range(self.length): 145 for k in range(self.length):
146 s[SENDER_TIME][k] = s[SEND_TIME][k] - s[INPUT_TIME][k] 146 s[SENDER_TIME][k] = s[SEND_TIME][k] - s[INPUT_TIME][k]
147 147
148 decoded_time = s[RENDER_TIME][k] 148 decoded_time = s[RENDER_TIME][k]
149 s[RECEIVER_TIME][k] = decoded_time - s[RECV_TIME][k] 149 s[RECEIVER_TIME][k] = decoded_time - s[RECV_TIME][k]
150 s[END_TO_END][k] = decoded_time - s[INPUT_TIME][k] 150 s[END_TO_END][k] = decoded_time - s[INPUT_TIME][k]
151 if not s[DROPPED][k]: 151 if not s[DROPPED][k]:
152 if k > 0: 152 if k > 0:
153 s[RENDERED_DELTA][k] = decoded_time - last_render_time 153 s[RENDERED_DELTA][k] = decoded_time - last_render_time
154 last_render_time = decoded_time 154 last_render_time = decoded_time
155 155
156 def _hide(self, values): 156 def _Hide(self, values):
157 """ 157 """
158 Replaces values for dropped frames with None. 158 Replaces values for dropped frames with None.
159 These values are then skipped by the plot() method. 159 These values are then skipped by the Plot() method.
160 """ 160 """
161 161
162 return [None if self.samples[DROPPED][k] else values[k] 162 return [None if self.samples[DROPPED][k] else values[k]
163 for k in range(len(values))] 163 for k in range(len(values))]
164 164
165 def add_samples(self, config, target_lines_list): 165 def AddSamples(self, config, target_lines_list):
166 """Creates graph lines from the current data set with given config.""" 166 """Creates graph lines from the current data set with given config."""
167 for field in config.fields: 167 for field in config.fields:
168 # field is None means the user wants just to skip the color. 168 # field is None means the user wants just to skip the color.
169 if field is None: 169 if field is None:
170 target_lines_list.append(None) 170 target_lines_list.append(None)
171 continue 171 continue
172 172
173 field_id = field & FIELD_MASK 173 field_id = field & FIELD_MASK
174 values = self.samples[field_id] 174 values = self.samples[field_id]
175 175
176 if field & HIDE_DROPPED: 176 if field & HIDE_DROPPED:
177 values = self._hide(values) 177 values = self._Hide(values)
178 178
179 target_lines_list.append(PlotLine( 179 target_lines_list.append(PlotLine(
180 self.title + " " + id_to_title[field_id], 180 self.title + " " + ID_TO_TITLE[field_id],
181 values, field & ~FIELD_MASK)) 181 values, field & ~FIELD_MASK))
182 182
183 183
184 def average_over_cycle(values, length): 184 def AverageOverCycle(values, length):
185 """ 185 """
186 Returns the list: 186 Returns the list:
187 [ 187 [
188 avg(values[0], values[length], ...), 188 avg(values[0], values[length], ...),
189 avg(values[1], values[length + 1], ...), 189 avg(values[1], values[length + 1], ...),
190 ... 190 ...
191 avg(values[length - 1], values[2 * length - 1], ...), 191 avg(values[length - 1], values[2 * length - 1], ...),
192 ] 192 ]
193 193
194 Skips None values when calculating the average value. 194 Skips None values when calculating the average value.
(...skipping 18 matching lines...) Expand all
213 def __init__(self, fields, data_list, cycle_length=None, frames=None, 213 def __init__(self, fields, data_list, cycle_length=None, frames=None,
214 offset=0, output_filename=None, title="Graph"): 214 offset=0, output_filename=None, title="Graph"):
215 self.fields = fields 215 self.fields = fields
216 self.data_list = data_list 216 self.data_list = data_list
217 self.cycle_length = cycle_length 217 self.cycle_length = cycle_length
218 self.frames = frames 218 self.frames = frames
219 self.offset = offset 219 self.offset = offset
220 self.output_filename = output_filename 220 self.output_filename = output_filename
221 self.title = title 221 self.title = title
222 222
223 def plot(self, ax1): 223 def Plot(self, ax1):
224 lines = [] 224 lines = []
225 for data in self.data_list: 225 for data in self.data_list:
226 if not data: 226 if not data:
227 # Add None lines to skip the colors. 227 # Add None lines to skip the colors.
228 lines.extend([None] * len(self.fields)) 228 lines.extend([None] * len(self.fields))
229 else: 229 else:
230 data.add_samples(self, lines) 230 data.AddSamples(self, lines)
231 231
232 def _slice_values(values): 232 def _SliceValues(values):
233 if self.offset: 233 if self.offset:
234 values = values[self.offset:] 234 values = values[self.offset:]
235 if self.frames: 235 if self.frames:
236 values = values[:self.frames] 236 values = values[:self.frames]
237 return values 237 return values
238 238
239 length = None 239 length = None
240 for line in lines: 240 for line in lines:
241 if line is None: 241 if line is None:
242 continue 242 continue
243 243
244 line.values = _slice_values(line.values) 244 line.values = _SliceValues(line.values)
245 if self.cycle_length: 245 if self.cycle_length:
246 line.values = average_over_cycle(line.values, self.cycle_length) 246 line.values = AverageOverCycle(line.values, self.cycle_length)
247 247
248 if length is None: 248 if length is None:
249 length = len(line.values) 249 length = len(line.values)
250 elif length != len(line.values): 250 elif length != len(line.values):
251 raise Exception("All arrays should have the same length!") 251 raise Exception("All arrays should have the same length!")
252 252
253 ax1.set_xlabel("Frame", fontsize="large") 253 ax1.set_xlabel("Frame", fontsize="large")
254 if any(line.flags & RIGHT_Y_AXIS for line in lines if line): 254 if any(line.flags & RIGHT_Y_AXIS for line in lines if line):
255 ax2 = ax1.twinx() 255 ax2 = ax1.twinx()
256 ax2.set_xlabel("Frame", fontsize="large") 256 ax2.set_xlabel("Frame", fontsize="large")
257 else: 257 else:
258 ax2 = None 258 ax2 = None
259 259
260 # Have to implement color_cycle manually, due to two scales in a graph. 260 # Have to implement color_cycle manually, due to two scales in a graph.
261 color_cycle = ["b", "r", "g", "c", "m", "y", "k"] 261 color_cycle = ["b", "r", "g", "c", "m", "y", "k"]
262 color_iter = itertools.cycle(color_cycle) 262 color_iter = itertools.cycle(color_cycle)
263 263
264 for line in lines: 264 for line in lines:
265 if not line: 265 if not line:
266 color_iter.next() 266 color_iter.next()
267 continue 267 continue
268 268
269 if self.cycle_length: 269 if self.cycle_length:
270 x = numpy.array(range(self.cycle_length)) 270 x = numpy.array(range(self.cycle_length))
271 else: 271 else:
272 x = numpy.array(range(self.offset, self.offset + len(line.values))) 272 x = numpy.array(range(self.offset, self.offset + len(line.values)))
273 y = numpy.array(line.values) 273 y = numpy.array(line.values)
274 ax = ax2 if line.flags & RIGHT_Y_AXIS else ax1 274 ax = ax2 if line.flags & RIGHT_Y_AXIS else ax1
275 ax.plot(x, y, "o-", label=line.label, markersize=3.0, linewidth=1.0, 275 ax.Plot(x, y, "o-", label=line.label, markersize=3.0, linewidth=1.0,
276 color=color_iter.next()) 276 color=color_iter.next())
277 277
278 ax1.grid(True) 278 ax1.grid(True)
279 if ax2: 279 if ax2:
280 ax1.legend(loc="upper left", shadow=True, fontsize="large") 280 ax1.legend(loc="upper left", shadow=True, fontsize="large")
281 ax2.legend(loc="upper right", shadow=True, fontsize="large") 281 ax2.legend(loc="upper right", shadow=True, fontsize="large")
282 else: 282 else:
283 ax1.legend(loc="best", shadow=True, fontsize="large") 283 ax1.legend(loc="best", shadow=True, fontsize="large")
284 284
285 285
286 def load_files(filenames): 286 def LoadFiles(filenames):
287 result = [] 287 result = []
288 for filename in filenames: 288 for filename in filenames:
289 if filename in load_files.cache: 289 if filename in LoadFiles.cache:
290 result.append(load_files.cache[filename]) 290 result.append(LoadFiles.cache[filename])
291 else: 291 else:
292 data = Data(filename) 292 data = Data(filename)
293 load_files.cache[filename] = data 293 LoadFiles.cache[filename] = data
294 result.append(data) 294 result.append(data)
295 return result 295 return result
296 load_files.cache = {} 296 LoadFiles.cache = {}
297 297
298 298
299 def get_parser(): 299 def GetParser():
300 class CustomAction(argparse.Action): 300 class CustomAction(argparse.Action):
301 def __call__(self, parser, namespace, values, option_string=None): 301 def __call__(self, parser, namespace, values, option_string=None):
302 if "ordered_args" not in namespace: 302 if "ordered_args" not in namespace:
303 namespace.ordered_args = [] 303 namespace.ordered_args = []
304 namespace.ordered_args.append((self.dest, values)) 304 namespace.ordered_args.append((self.dest, values))
305 305
306 parser = argparse.ArgumentParser( 306 parser = argparse.ArgumentParser(
307 description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) 307 description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
308 308
309 parser.add_argument( 309 parser.add_argument(
(...skipping 18 matching lines...) Expand all
328 parser.add_argument( 328 parser.add_argument(
329 "-O", "--output_filename", nargs=1, action=CustomAction, 329 "-O", "--output_filename", nargs=1, action=CustomAction,
330 help="Use to save the graph into a file. " 330 help="Use to save the graph into a file. "
331 "Otherwise, a window will be shown.") 331 "Otherwise, a window will be shown.")
332 parser.add_argument( 332 parser.add_argument(
333 "files", nargs="+", action=CustomAction, 333 "files", nargs="+", action=CustomAction,
334 help="List of text-based files generated by loopback tests.") 334 help="List of text-based files generated by loopback tests.")
335 return parser 335 return parser
336 336
337 337
338 def _plot_config_from_args(args, graph_num): 338 def _PlotConfigFromArgs(args, graph_num):
339 # Pylint complains about using kwargs, so have to do it this way. 339 # Pylint complains about using kwargs, so have to do it this way.
340 cycle_length = None 340 cycle_length = None
341 frames = None 341 frames = None
342 offset = 0 342 offset = 0
343 output_filename = None 343 output_filename = None
344 title = "Graph" 344 title = "Graph"
345 345
346 fields = [] 346 fields = []
347 files = [] 347 files = []
348 mask = 0 348 mask = 0
349 for key, values in args: 349 for key, values in args:
350 if key == "cycle_length": 350 if key == "cycle_length":
351 cycle_length = values[0] 351 cycle_length = values[0]
352 elif key == "frames": 352 elif key == "frames":
353 frames = values[0] 353 frames = values[0]
354 elif key == "offset": 354 elif key == "offset":
355 offset = values[0] 355 offset = values[0]
356 elif key == "output_filename": 356 elif key == "output_filename":
357 output_filename = values[0] 357 output_filename = values[0]
358 elif key == "title": 358 elif key == "title":
359 title = values[0] 359 title = values[0]
360 elif key == "drop": 360 elif key == "drop":
361 mask |= HIDE_DROPPED 361 mask |= HIDE_DROPPED
362 elif key == "right": 362 elif key == "right":
363 mask |= RIGHT_Y_AXIS 363 mask |= RIGHT_Y_AXIS
364 elif key == "field": 364 elif key == "field":
365 field_id = field_arg_to_id(values[0]) 365 field_id = FieldArgToId(values[0])
366 fields.append(field_id | mask if field_id is not None else None) 366 fields.append(field_id | mask if field_id is not None else None)
367 mask = 0 # Reset mask after the field argument. 367 mask = 0 # Reset mask after the field argument.
368 elif key == "files": 368 elif key == "files":
369 files.extend(values) 369 files.extend(values)
370 370
371 if not files: 371 if not files:
372 raise Exception("Missing file argument(s) for graph #{}".format(graph_num)) 372 raise Exception("Missing file argument(s) for graph #{}".format(graph_num))
373 if not fields: 373 if not fields:
374 raise Exception("Missing field argument(s) for graph #{}".format(graph_num)) 374 raise Exception("Missing field argument(s) for graph #{}".format(graph_num))
375 375
376 return PlotConfig(fields, load_files(files), cycle_length=cycle_length, 376 return PlotConfig(fields, LoadFiles(files), cycle_length=cycle_length,
377 frames=frames, offset=offset, output_filename=output_filename, 377 frames=frames, offset=offset, output_filename=output_filename,
378 title=title) 378 title=title)
379 379
380 380
381 def plot_configs_from_args(args): 381 def PlotConfigsFromArgs(args):
382 """Generates plot configs for given command line arguments.""" 382 """Generates plot configs for given command line arguments."""
383 # The way it works: 383 # The way it works:
384 # First we detect separators -n/--next and split arguments into groups, one 384 # First we detect separators -n/--next and split arguments into groups, one
385 # for each plot. For each group, we partially parse it with 385 # for each plot. For each group, we partially parse it with
386 # argparse.ArgumentParser, modified to remember the order of arguments. 386 # argparse.ArgumentParser, modified to remember the order of arguments.
387 # Then we traverse the argument list and fill the PlotConfig. 387 # Then we traverse the argument list and fill the PlotConfig.
388 args = itertools.groupby(args, lambda x: x in ["-n", "--next"]) 388 args = itertools.groupby(args, lambda x: x in ["-n", "--next"])
389 args = list(list(group) for match, group in args if not match) 389 args = list(list(group) for match, group in args if not match)
390 390
391 parser = get_parser() 391 parser = GetParser()
392 plot_configs = [] 392 plot_configs = []
393 for index, raw_args in enumerate(args): 393 for index, raw_args in enumerate(args):
394 graph_args = parser.parse_args(raw_args).ordered_args 394 graph_args = parser.parse_args(raw_args).ordered_args
395 plot_configs.append(_plot_config_from_args(graph_args, index)) 395 plot_configs.append(_PlotConfigFromArgs(graph_args, index))
396 return plot_configs 396 return plot_configs
397 397
398 398
399 def show_or_save_plots(plot_configs): 399 def ShowOrSavePlots(plot_configs):
400 for config in plot_configs: 400 for config in plot_configs:
401 fig = plt.figure(figsize=(14.0, 10.0)) 401 fig = plt.figure(figsize=(14.0, 10.0))
402 ax = fig.add_subplot(1, 1, 1) 402 ax = fig.add_subPlot(1, 1, 1)
403 403
404 plt.title(config.title) 404 plt.title(config.title)
405 config.plot(ax) 405 config.Plot(ax)
406 if config.output_filename: 406 if config.output_filename:
407 print "Saving to", config.output_filename 407 print "Saving to", config.output_filename
408 fig.savefig(config.output_filename) 408 fig.savefig(config.output_filename)
409 plt.close(fig) 409 plt.close(fig)
410 410
411 plt.show() 411 plt.show()
412 412
413 if __name__ == "__main__": 413 if __name__ == "__main__":
414 show_or_save_plots(plot_configs_from_args(sys.argv[1:])) 414 ShowOrSavePlots(PlotConfigsFromArgs(sys.argv[1:]))
OLDNEW
« no previous file with comments | « webrtc/tools/video_analysis_test.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698