]> git.donarmstrong.com Git - qmk_firmware.git/blob - keyboards/ergodox/keymaps/algernon/tools/log-to-heatmap.py
Merge pull request #715 from profet23/master
[qmk_firmware.git] / keyboards / ergodox / keymaps / algernon / tools / log-to-heatmap.py
1 #! /usr/bin/env python
2 import json
3 import os
4 import sys
5 import re
6 import argparse
7
8 from math import floor
9 from os.path import dirname
10
11 class Heatmap(object):
12     coords = [
13         [
14             # Row 0
15             [ 4,  0], [ 4,  2], [ 2,  0], [ 1,  0], [ 2,  2], [ 3,  0], [ 3,  2],
16             [ 3,  4], [ 3,  6], [ 2,  4], [ 1,  2], [ 2,  6], [ 4,  4], [ 4,  6],
17         ],
18         [
19             # Row 1
20             [ 8,  0], [ 8,  2], [ 6,  0], [ 5,  0], [ 6,  2], [ 7,  0], [ 7,  2],
21             [ 7,  4], [ 7,  6], [ 6,  4], [ 5,  2], [ 6,  6], [ 8,  4], [ 8,  6],
22         ],
23         [
24             # Row 2
25             [12,  0], [12,  2], [10,  0], [ 9,  0], [10,  2], [11, 0], [     ],
26             [      ], [11,  2], [10,  4], [ 9,  2], [10,  6], [12, 4], [12, 6],
27         ],
28         [
29             # Row 3
30             [17,  0], [17,  2], [15,  0], [14,  0], [15,  2], [16,  0], [13,  0],
31             [13,  2], [16,  2], [15,  4], [14,  2], [15,  6], [17,  4], [17,  6],
32         ],
33         [
34             # Row 4
35             [20,  0], [20,  2], [19,  0], [18,  0], [19,  2], [], [], [], [],
36             [19,  4], [18,  2], [19,  6], [20,  4], [20,  6],
37         ],
38         [
39             # Row 5
40             [     ], [23,  0], [22,  2], [22,  0], [22,  4], [21,  0], [21,  2],
41             [24, 0], [24,  2], [25,  0], [25,  4], [25,  2], [26,  0], [      ],
42         ],
43     ]
44
45     def set_attr_at(self, block, n, attr, fn, val):
46         blk = self.heatmap[block][n]
47         if attr in blk:
48             blk[attr] = fn(blk[attr], val)
49         else:
50             blk[attr] = fn(None, val)
51
52     def coord(self, col, row):
53         return self.coords[row][col]
54
55     @staticmethod
56     def set_attr(orig, new):
57         return new
58
59     def set_bg(self, (block, n), color):
60         self.set_attr_at(block, n, "c", self.set_attr, color)
61         #self.set_attr_at(block, n, "g", self.set_attr, False)
62
63     def set_tap_info(self, (block, n), count, cap):
64         def _set_tap_info(o, _count, _cap):
65             ns = 4 - o.count ("\n")
66             return o + "\n" * ns + "%.02f%%" % (float(_count) / float(_cap) * 100)
67
68         if not cap:
69             cap = 1
70         self.heatmap[block][n + 1] = _set_tap_info (self.heatmap[block][n + 1], count, cap)
71
72     @staticmethod
73     def heatmap_color (v):
74         colors = [ [0.3, 0.3, 1], [0.3, 1, 0.3], [1, 1, 0.3], [1, 0.3, 0.3]]
75         fb = 0
76         if v <= 0:
77             idx1, idx2 = 0, 0
78         elif v >= 1:
79             idx1, idx2 = len(colors) - 1, len(colors) - 1
80         else:
81             val = v * (len(colors) - 1)
82             idx1 = int(floor(val))
83             idx2 = idx1 + 1
84             fb = val - float(idx1)
85
86         r = (colors[idx2][0] - colors[idx1][0]) * fb + colors[idx1][0]
87         g = (colors[idx2][1] - colors[idx1][1]) * fb + colors[idx1][1]
88         b = (colors[idx2][2] - colors[idx1][2]) * fb + colors[idx1][2]
89
90         r, g, b = [x * 255 for x in r, g, b]
91         return "#%02x%02x%02x" % (r, g, b)
92
93     def __init__(self, layout):
94         self.log = {}
95         self.total = 0
96         self.max_cnt = 0
97         self.layout = layout
98
99     def update_log(self, (c, r)):
100         if not (c, r) in self.log:
101             self.log[(c, r)] = 0
102         self.log[(c, r)] = self.log[(c, r)] + 1
103         self.total = self.total + 1
104         if self.max_cnt < self.log[(c, r)]:
105             self.max_cnt = self.log[(c, r)]
106
107     def get_heatmap(self):
108         with open("%s/heatmap-layout.%s.json" % (dirname(sys.argv[0]), self.layout), "r") as f:
109             self.heatmap = json.load (f)
110
111         ## Reset colors
112         for row in self.coords:
113             for coord in row:
114                 if coord != []:
115                     self.set_bg (coord, "#d9dae0")
116
117         for (c, r) in self.log:
118             coords = self.coord(c, r)
119             b, n = coords
120             cap = self.max_cnt
121             if cap == 0:
122                 cap = 1
123             v = float(self.log[(c, r)]) / cap
124             self.set_bg (coords, self.heatmap_color (v))
125             self.set_tap_info (coords, self.log[(c, r)], self.total)
126         return self.heatmap
127
128     def get_stats(self):
129         usage = [
130             # left hand
131             [0, 0, 0, 0, 0],
132             # right hand
133             [0, 0, 0, 0, 0]
134         ]
135         finger_map = [0, 0, 1, 2, 3, 4, 4]
136         for (c, r) in self.log:
137             if r == 5: # thumb cluster
138                 if c <= 6: # left side
139                     usage[0][4] = usage[0][4] + self.log[(c, r)]
140                 else:
141                     usage[1][4] = usage[1][4] + self.log[(c, r)]
142             else:
143                 fc = c
144                 hand = 0
145                 if fc >= 7:
146                     fc = fc - 7
147                     hand = 1
148                 fm = finger_map[fc]
149                 usage[hand][fm] = usage[hand][fm] + self.log[(c, r)]
150         hand_usage = [0, 0]
151         for f in usage[0]:
152             hand_usage[0] = hand_usage[0] + f
153         for f in usage[1]:
154             hand_usage[1] = hand_usage[1] + f
155
156         total = self.total
157         if total == 0:
158             total = 1
159         stats = {
160             "hands": {
161                 "left": {
162                     "usage": float(hand_usage[0]) / total * 100,
163                     "fingers": {
164                         "0 - pinky": 0,
165                         "1 - ring": 0,
166                         "2 - middle": 0,
167                         "3 - index": 0,
168                         "4 - thumb": 0,
169                     }
170                 },
171                 "right": {
172                     "usage": float(hand_usage[1]) / total * 100,
173                     "fingers": {
174                         "0 - thumb": 0,
175                         "1 - index": 0,
176                         "2 - middle": 0,
177                         "3 - ring": 0,
178                         "4 - pinky": 0,
179                     }
180                 },
181             }
182         }
183
184         hmap = ['left', 'right']
185         fmap = ['0 - pinky', '1 - ring', '2 - middle', '3 - index', '4 - thumb',
186                 '0 - thumb', '1 - index', '2 - middle', '3 - ring', '4 - pinky']
187         for hand_idx in range(len(usage)):
188             hand = usage[hand_idx]
189             for finger_idx in range(len(hand)):
190                 stats['hands'][hmap[hand_idx]]['fingers'][fmap[finger_idx + hand_idx * 5]] = float(hand[finger_idx]) / total * 100
191         return stats
192
193 def dump_all(out_dir, heatmaps):
194     for layer in heatmaps.keys():
195         if len(heatmaps[layer].log) == 0:
196             continue
197
198         with open ("%s/%s.json" % (out_dir, layer), "w") as f:
199             json.dump(heatmaps[layer].get_heatmap(), f)
200         print >>sys.stderr, "%s stats:" % (layer)
201         json.dump (heatmaps[layer].get_stats(), sys.stderr,
202                    indent = 4, sort_keys = True)
203         print >>sys.stderr, ""
204         print >>sys.stderr, ""
205
206 def main(opts):
207
208     heatmaps = {"Dvorak": Heatmap("Dvorak"),
209                 "ADORE": Heatmap("ADORE")
210     }
211     cnt = 0
212     restrict_row = opts.restrict_row
213     out_dir = opts.outdir
214
215     while True:
216         line = sys.stdin.readline()
217         if not line:
218             break
219         m = re.search ('KL: col=(\d+), row=(\d+), pressed=(\d+), layer=(.*)', line)
220         if not m:
221             continue
222
223         cnt = cnt + 1
224         (c, r, l) = (int(m.group (2)), int(m.group (1)), m.group (4))
225         if restrict_row != -1 and r != restrict_row:
226             continue
227         if c in opts.ignore_columns:
228             continue
229
230         heatmaps[l].update_log ((c, r))
231
232         if opts.dump_interval != -1 and cnt >= opts.dump_interval:
233             cnt = 0
234             dump_all(out_dir, heatmaps)
235
236     dump_all (out_dir, heatmaps)
237
238 if __name__ == "__main__":
239     parser = argparse.ArgumentParser (description = "keylog to heatmap processor")
240     parser.add_argument ('outdir', action = 'store',
241                          help = 'Output directory')
242     parser.add_argument ('--row', dest = 'restrict_row', action = 'store', type = int,
243                          default = -1, help = 'Restrict processing to this row only')
244     parser.add_argument ('--dump-interval', dest = 'dump_interval', action = 'store', type = int,
245                          default = 100, help = 'Dump stats and heatmap at every Nth event, -1 for dumping at EOF only')
246     parser.add_argument ('--ignore-column', dest = 'ignore_columns', action = 'append', type = int,
247                          default = [], help = 'Ignore the specified columns')
248     args = parser.parse_args()
249     main(args)