-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.py
325 lines (268 loc) · 11.1 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
"""
main.py
~~~~~~~~~~~~~~~
Touch Calculator
for Mac OS X 10.6+
Richard Zhao 2015
15-112 Spring 2015 Term Project
~~~~~~~~~~~~~~~
A calculator with the ability to touch type as well as draw out special
characters. Drawings are recognized using a custom feature detection algorithm
and standard machine learning techniques (namely knn).
Installation:
PyObjC is needed for touchpad and mouse controlling functionality. Touchpad
frameworks are meant for Mac OS X versions 10.6 and higher.
Getting Started:
The window opens to touch calculator input at first. The bottom portion
of the screen is essentially a one-to-one mapping of the physical
touchpad. Press where the keys are to type. Input is displayed in the top
input box. Pressing the '=' key evaluates the input.
Toggle between touch calculator and drawing calculator by pressing either
'123' or 'draw', the third button down from the left. In drawing mode,
recognition suggestions appear on the right hand side. Under each match,
there is a number between 0.0 and 1.0 that indicates the recognition
confidence.
Training the Classifier:
The default character set is described in the readme.txt. To train any of
these characters, uncomment the desired sets in the dataCollection's
MainWindow class init() function.
Then, in model.py, load the most recent model, extend it using the new data,
and save it.
Adding new characters is similar. First, train them in dataCollection.py.
Then, add them to the model. Finally, the user must define how to both
display the character and evaluate them. This is done in evaluate.py.
In each of the dictionaries, define the desired displayChar mapping and
evalChar mapping.
Troubleshooting:
The classifier is only lightly trained at first. Users have a wide variance
of handwriting styles. For best results, users should personalize the
classifier by performing a few training rounds.
"""
# Standard libraries
import math
# 15-112 libraries
import eventBasedAnimation
# Packaged libraries
import classifier
import calculator
import evaluate
import mouse
class MainWindow(eventBasedAnimation.Animation):
"""The main program window.
Input and output displays use images that are loaded using Tkinter's
PhotoImage class. Tkinter import occurs in eventBasedAnimation.
By default, the program initializes with the button based calculator.
This class handles mouse anchoring. Evaluation of input is passed to
the evaluate module.
Attributes:
input (TextDisplay): Displays what the user enters.
ouput (TextDisplay): Displays the program's evaluation.
clsf (classifier.Classifier): Touch drawing classifier.
calc (calculator.Calculator): Touch calculator.
isPaused (bool): Value controls mouse anchoring.
"""
def onInit(self):
self.windowTitle = "Touch Calculator"
self.aboutText = """\
Touch Calculator
for Mac OS X 10.6+
Richard Zhao 2015
~~~~~~~~~~~~~~~~~~~
Press space to pause and see instructions.
"""
self.margin = self.width / 23
# self.fg = "#666666" # medium grey
self.fg = "#ffffff"
self.bg = "#e5e6e6" # background color: light grey
self.input = TextDisplay(0, 0, self.width, 100, margin=15,
font=("Helvetica Neue UltraLight", "48"),
bgImage=eventBasedAnimation.PhotoImage(
file="graphics/top_690.gif"))
self.output = TextDisplay(
0, 100, self.width, 150, margin=15,
font=("Helvetica Neue UltraLight", "80"),
bgImage=eventBasedAnimation.PhotoImage(
file="graphics/bottom_690.gif"))
self.clsf = classifier.Classifier(0, 250, self.width, 300,
"model3",
state="inactive")
self.calc = calculator.Calculator(0, 250, self.width, 300,
state="active")
self.isPaused = False
self.pauseScreenImage = eventBasedAnimation.PhotoImage(
file="graphics/pauseScreen.gif")
mouse.lockCursor(10, 50)
def onStep(self):
if (self.clsf.state == "active"):
self.clsf.step()
else:
self.calc.step()
self.getInput()
def getInput(self):
"""Checks active input source for a result."""
src = self.clsf if self.clsf.state == "active" else self.calc
if (src.result != None):
res = src.result
if (res == "clear"):
self.clear()
elif (res == "allClear"):
self.allClear()
elif (res == "equals"):
self.evaluate()
elif (res == "switch"):
print "switching..."
self.switch()
else:
self.input.addInput(res)
self.clsf.reset() # clear drawing board after input
src.result = None # reset result
def clear(self):
"""Standard functioning clear function. If there is drawing happening,
the trackpad is cleared. Else, the most recent input is removed."""
if (self.clsf.trackpad.touchData != []): # clear drawing
self.clsf.trackpad.reset()
else: # del input
self.input.delete()
def allClear(self):
"""Complete reset of input and output."""
self.clsf.trackpad.reset()
self.input.reset()
self.output.reset()
def evaluate(self):
"""Pass input to be evaluated."""
print self.input.evalString
result = evaluate.evaluate(self.input.evalString)
self.output.displayString = [result]
def switch(self):
"""Switch between two input sources."""
self.clsf.trackpad.reset()
self.calc.trackpad.reset()
if (self.clsf.state == "active"):
self.clsf.state = "inactive"
self.calc.state = "active"
else:
self.clsf.state = "active"
self.calc.state = "inactive"
def onDraw(self, canvas):
if (self.isPaused == True):
self.drawPauseScreen(canvas)
else:
self.input.draw(canvas)
self.output.draw(canvas)
if (self.clsf.state == "active"):
self.clsf.draw(canvas)
else:
self.calc.draw(canvas)
def drawPauseScreen(self, canvas):
canvas.create_image(0, 0, anchor="nw", image=self.pauseScreenImage)
cx = self.width / 2
titleFont = ("Helvetica Neue UltraLight", "48")
title = "Touch Calculator"
# canvas.create_rectangle(0, 0, self.width, self.height, fill=self.bg)
canvas.create_text(cx, self.margin, anchor="n", text=title,
font=titleFont, fill=self.fg)
about = """\
Use the trackpad for input. 123 mode has numerical buttons and basic \
operators. In draw mode, use your finger to trace out numbers, constants, \
and a variety of functions."""
aboutFont = ("Helvetica Neue UltraLight", "24")
canvas.create_text(self.margin, 4 * self.margin, anchor="nw",
text=about, font=aboutFont, fill=self.fg,
width=self.width - 2 * self.margin)
subTitle = "Character Set:"
subTitleFont = ("Helvetica Neue UltraLight", "32")
canvas.create_text(cx, 8 * self.margin, anchor="n", text=subTitle,
font=subTitleFont, fill=self.fg)
leftCharacterSet = u"""\
0 1 2 3 4
5 6 7 8 9
+ - * / ( ) ^ .
\u03c0 e \u00f7 \u00d7 \u221a %"""
rightCharacterSet = """\
sin\tasin
cos\tacos
tan\tatan
ln\tlog"""
leftcx = self.width / 4
rightcx = self.width * 3 / 4
y = 10 * self.margin
characterSetFont = ("Helvetica Neue UltraLight", "24")
canvas.create_text(leftcx, y, anchor="n", text=leftCharacterSet,
font=characterSetFont, fill=self.fg,
justify="center")
canvas.create_text(rightcx, y, anchor="n", text=rightCharacterSet,
font=characterSetFont, fill=self.fg,
justify="center")
def onKey(self, event):
if (event.keysym == "space"):
if (self.isPaused == True):
self.unpause()
else:
self.pause()
def pause(self):
self.isPaused = True
self.clsf.trackpad.stop()
self.calc.trackpad.stop()
# mouse.freeCursor()
def unpause(self):
self.isPaused = False
self.clsf.trackpad.start()
self.calc.trackpad.start()
# mouse.lockCursor(10, 50)
def onQuit(self):
mouse.freeCursor()
class TextDisplay(object):
"""Handles visual display of mathematical strings.
Each instance keeps two lists of chars--one for display and one for
evaluation. In most cases, the display string differs in that mathematical
characters (such as sqrt) are displayed with glyphs. The evaluation string
contains Python types or functions that evaluate as desired.
Attributes:
displayString (list): The visual characters. Some are unicode.
evalString (list): The evaluation characters.
"""
def __init__(self, x, y, width, height, **kwargs):
self.x, self.y, self.width, self.height = x, y , width, height
self.margin = self.width / 10
self.displayString = []
self.evalString = []
self.font = ("Helvetica Neue UltraLight", str(self.height / 3))
self.bg = "#212834"
self.fg = "#ffffff"
self.bgImage = None
self.__dict__.update(kwargs)
def addInput(self, char):
"""Add a character to the display and eval strings."""
if (type(char) != str and type(char) != unicode):
return -1
self.displayString.append(evaluate.displayChar(char))
self.evalString.append(evaluate.evalChar(char))
print "display:", repr("".join(self.displayString))
print "eval:", repr("".join(self.evalString))
def delete(self):
if (self.displayString != []):
self.displayString.pop()
if (self.evalString != []):
self.evalString.pop()
def reset(self):
del self.displayString[:]
del self.evalString[:]
def draw(self, canvas):
if (self.bgImage != None):
canvas.create_image(self.x, self.y, image=self.bgImage,
anchor="nw")
else:
x0 = self.x
x1 = self.x + self.width
y0 = self.y
y1 = self.y + self.height
canvas.create_rectangle(x0, y0, x1, y1, fill=self.bg, width=0)
cx = self.x + self.width - self.margin
cy = self.y + self.height - self.margin
msg = "".join(self.displayString)
canvas.create_text(cx, cy, anchor="se", text=msg,
fill=self.fg, font=self.font)
width = 690
height = 550
timerDelay = 8
MainWindow(width=width, height=height, timerDelay=timerDelay).run()