1'''This file summarizes the results from an extended noise test. 2It uses the HTML report log generated at the end of the test as input. 3It will output a summary in the same directory as the input report log, 4as well as a graphic representation. 5 6Usage: python noise_summary.py report.html 7''' 8 9from HTMLParser import HTMLParser 10import matplotlib.pyplot as plt 11import os.path 12import re 13import sys 14 15# Constants 16CORRECT_NUM_FINGERS = 1 17CORRECT_MAX_DISTANCE = 1.0 18FINGERS_INDEX = 0 19DISTANCE_INDEX = 1 20 21 22# A parser to consolidate the data in the html report 23class ParseReport(HTMLParser): 24 def __init__(self, num_iterations): 25 HTMLParser.__init__(self) 26 self.curr_freq = 0 27 self.last_freq = self.curr_freq 28 self.curr_dict_index = 0 29 self.miscounted_fingers = 0 30 self.over_distance = 0 31 self.num_iterations = num_iterations 32 self.data_dict_list = [] 33 34 for x in range(0, self.num_iterations): 35 # Each dictionary in the list represents 36 # one iteration of data 37 self.data_dict_list.append({}) 38 39 # extracts the frequency from a line in the html report like this: 40 # noise_stationary_extended. 41 # ('0Hz', 'max_amplitude', 'square_wave', 'center') 42 def _extract_frequency(self, data): 43 return int(re.findall(r'\d+', data)[0]) 44 45 # extracts the tids from a line in the html report like this: 46 # count of trackid IDs: 1 47 # criteria: == 1 48 def _extract_num_ids(self, data): 49 return float(re.findall(r'\d+', data)[0]) 50 51 # extracts the distance from a line in the html report like this: 52 # Max distance slot0: 0.00 mm 53 # criteria: <= 1.0 54 def _extract_distance(self, data): 55 return float(re.findall(r'[-+]?\d*\.\d+|\d+', data)[0]) 56 57 # Add the value read to the dictionary. 58 def _update_data_dict(self, value, val_index): 59 curr_freq = self.curr_freq 60 if curr_freq not in self.data_dict_list[self.curr_dict_index]: 61 self.data_dict_list[self.curr_dict_index][curr_freq] = [None, None] 62 63 self.data_dict_list[self.curr_dict_index][curr_freq][val_index] = value 64 65 # Handler for HTMLParser for whenever it encounters text between tags 66 def handle_data(self, data): 67 # Get the current frequency 68 if 'noise_stationary_extended' in data: 69 self.curr_freq = self._extract_frequency(data) 70 71 # Update the current iteration we're on. 72 if self.curr_freq == self.last_freq: 73 self.curr_dict_index = self.curr_dict_index + 1 74 else: 75 self.last_freq = self.curr_freq 76 self.curr_dict_index = 0 77 78 # Update number of fingers data 79 if 'count of trackid IDs:' in data: 80 num_ids = self._extract_num_ids(data) 81 82 if num_ids != CORRECT_NUM_FINGERS: 83 self.miscounted_fingers = self.miscounted_fingers + 1 84 self._update_data_dict(num_ids, FINGERS_INDEX) 85 else: 86 self._update_data_dict(None, FINGERS_INDEX) 87 88 # Update maximum distance data 89 if 'Max distance' in data: 90 distance = self._extract_distance(data) 91 92 if distance > CORRECT_MAX_DISTANCE: 93 self.over_distance = self.over_distance + 1 94 self._update_data_dict(distance, DISTANCE_INDEX) 95 else: 96 self._update_data_dict(None, DISTANCE_INDEX) 97 98 99# A parser to count the number of iterations 100class CountIterations(ParseReport): 101 def __init__(self): 102 ParseReport.__init__(self, num_iterations=0) 103 self.counting_iterations = True 104 105 # Handler for HTMLParser for whenever it encounters text between tags 106 def handle_data(self, data): 107 # Get the current frequency 108 if 'noise_stationary_extended' in data: 109 self.curr_freq = self._extract_frequency(data) 110 111 if self.counting_iterations: 112 if self.curr_freq == self.last_freq: 113 self.num_iterations = self.num_iterations + 1 114 else: 115 self.counting_iterations = False 116 117 118# A weighting function to determine how badly 119# a frequency failed. It outputs the total number 120# of errors, where each misread or additionally read 121# finger counts as one error, and each 0.2mm over the 122# maximum distance counts as one error. 123def weighting_function(data): 124 num_fingers = data[FINGERS_INDEX] 125 max_dist = data[DISTANCE_INDEX] 126 127 if num_fingers is None: 128 num_fingers = CORRECT_NUM_FINGERS 129 if max_dist is None: 130 max_dist = 0 131 132 finger_val = abs(num_fingers - CORRECT_NUM_FINGERS) 133 dist_val = 5 * (max_dist - CORRECT_MAX_DISTANCE) 134 dist_val = 0 if dist_val < 0 else dist_val 135 136 return finger_val + dist_val 137 138 139# Returns a list of frequencies in order of how 140# 'badly' they failed 141def value_sorted_freq(data_dict): 142 list_of_tuples = sorted(data_dict.iteritems(), reverse=True, 143 key=lambda (k, v): weighting_function(v)) 144 return [i[0] for i in list_of_tuples] 145 146 147# Print out the summary of results for a single iteration, 148# ordered by how badly each frequency failed. 149def print_iteration_summary(data_dict, iteration, outfile): 150 outfile.write('\n') 151 outfile.write("Iteration %d\n" % iteration) 152 outfile.write('-------------\n') 153 154 for freq in value_sorted_freq(data_dict): 155 num_fingers = data_dict[freq][FINGERS_INDEX] 156 max_dist = data_dict[freq][DISTANCE_INDEX] 157 158 # Don't output anything if there was no error 159 if num_fingers is None and max_dist is None: 160 continue 161 else: 162 num_fingers = '' if num_fingers is None else '%s tids' % num_fingers 163 max_dist = '' if max_dist is None else '%s mm' % max_dist 164 165 outfile.write('{:,}Hz \t %s \t %s \n'.format(freq) % 166 (num_fingers, max_dist)) 167 168 169# Print out a summary of errors for each iteration 170def print_summary(parse_report, output_file): 171 outfile = open(output_file, 'w') 172 outfile.write('Summary: \n') 173 outfile.write(' %d issues with finger tracking over all iterations. \n' % 174 parse_report.miscounted_fingers) 175 outfile.write(' %d issues with distance over all iterations. \n' % 176 parse_report.over_distance) 177 outfile.write('\n\n') 178 179 outfile.write('Worst frequencies:\n') 180 181 for iteration, data_dict in enumerate(parse_report.data_dict_list): 182 print_iteration_summary(data_dict, iteration, outfile) 183 184 outfile.close() 185 186 187# For each iteration, generate a subplot 188def show_graph(parse_report): 189 for iteration, data_dict in enumerate(parse_report.data_dict_list): 190 sorted_by_freq = sorted(parse_report.data_dict_list[iteration].items()) 191 frequencies = [i[0] for i in sorted_by_freq] 192 values = [weighting_function(i[1]) for i in sorted_by_freq] 193 194 plt.subplot(parse_report.num_iterations, 1, iteration) 195 plt.plot(frequencies, values) 196 197 plt.xlabel('Frequency (Hz)') 198 plt.ylabel('Number of problems') 199 plt.legend(("Iteration %d" % iteration,)) 200 201 plt.title('Graphic Summary of Extended Noise Test') 202 plt.show() 203 204 205def main(): 206 # Error checking 207 if len(sys.argv) != 2: 208 print 'Usage: python noise_summary.py report.html' 209 return 210 211 input_file = sys.argv[1] 212 if '.html' not in input_file: 213 print 'File must be an html firmware report.' 214 print 'An example report name is:' 215 print 'touch_firmware_report-swanky-fw_2.0-noise-20140826_173022.html' 216 return 217 218 # Create filepaths 219 directory = os.path.dirname(input_file) 220 output_file = '%s_summary.txt' % \ 221 os.path.splitext(os.path.basename(input_file))[0] 222 output_path = os.path.join(directory, output_file) 223 224 try: 225 html_file = open(input_file) 226 except: 227 print '%s could not be found.' % input_file 228 return 229 230 # Parse the report 231 html = html_file.read() 232 c = CountIterations() 233 c.feed(html) 234 p = ParseReport(c.num_iterations) 235 p.feed(html) 236 html_file.close() 237 p.close() 238 239 # Display the result 240 print_summary(p, output_path) 241 print 'The summary has been saved to %s' % output_path 242 show_graph(p) 243 244 245if __name__ == '__main__': 246 main() 247