• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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