Home | History | Annotate | Download | only in firmware_TouchMTB
      1 '''This file summarizes the results from an extended noise test.
      2 It uses the HTML report log generated at the end of the test as input.
      3 It will output a summary in the same directory as the input report log,
      4 as well as a graphic representation.
      5 
      6 Usage: python noise_summary.py report.html
      7 '''
      8 
      9 from HTMLParser import HTMLParser
     10 import matplotlib.pyplot as plt
     11 import os.path
     12 import re
     13 import sys
     14 
     15 # Constants
     16 CORRECT_NUM_FINGERS = 1
     17 CORRECT_MAX_DISTANCE = 1.0
     18 FINGERS_INDEX = 0
     19 DISTANCE_INDEX = 1
     20 
     21 
     22 # A parser to consolidate the data in the html report
     23 class 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
    100 class 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.
    123 def 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
    141 def 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.
    149 def 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
    170 def 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
    188 def 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 
    205 def 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 
    245 if __name__ == '__main__':
    246     main()
    247