Skip to content

Commit 47e07f0

Browse files
committed
MnistApp
1 parent 66321eb commit 47e07f0

2 files changed

Lines changed: 241 additions & 1 deletion

File tree

_test.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,13 @@ def run_sliding_puzzle_app():
8080
app = SlidingPuzzleApp(create_example_wifi_dd(), suggest_move_from_dir_func=suggest_move_from_dir_func)
8181
app.run()
8282

83+
def run_mnist_app():
84+
from dumbdisplay_examples.mnist.mnist_app import MnistApp
85+
print(f"*** MnistApp ***")
86+
inference_func = lambda board_manager: random.randint(0, 10)
87+
app = MnistApp(create_example_wifi_dd(), inference_func=inference_func)
88+
app.run()
89+
8390

8491

8592
def test_read_readme():
@@ -97,7 +104,8 @@ def test_find_packages():
97104

98105
if __name__ == "__main__":
99106
#run_passive_blink_app()
100-
run_sliding_puzzle_app()
107+
#run_sliding_puzzle_app()
108+
run_mnist_app()
101109

102110
#run_debug()
103111
#run_doodle()
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
import random
2+
import time
3+
4+
from dumbdisplay.core import *
5+
from dumbdisplay.ddlayer import DDLayer, DD_RGB_COLOR
6+
from dumbdisplay.ddlayer_graphical import DDLayerGraphical
7+
from dumbdisplay.ddlayer_lcd import DDLayerLcd
8+
from dumbdisplay.ddlayer_7segrow import DDLayer7SegmentRow
9+
10+
11+
THICKER_LINE_SHADE = 223 # 0 to disable; other values can be 191 / 255
12+
13+
14+
class MnistApp():
15+
def __init__(self, dd: DumbDisplay, inference_func = None):
16+
self.dd = dd
17+
self.inference_func = inference_func
18+
self.draw_layer: DDLayerGraphical = None
19+
self.copy_layer: DDLayerGraphical = None
20+
self.clear_btn: DDLayerLcd = None
21+
self.center_btn: DDLayerLcd = None
22+
self.inference_btn: DDLayerLcd = None
23+
self.result_layer: DDLayer7SegmentRow = None
24+
self.auto_center: bool = False
25+
self.last_x: int = -1
26+
self.last_y: int = -1
27+
self.pixels = None # pixels data (28x28)
28+
29+
30+
def run(self):
31+
while True:
32+
(connected, reconnecting) = self.dd.connectPassive()
33+
if connected:
34+
if self.draw_layer is None:
35+
self.initializeDD()
36+
elif reconnecting:
37+
self.dd.masterReset()
38+
self.draw_layer = None
39+
else:
40+
self.updateDD()
41+
elif reconnecting:
42+
self.dd.masterReset()
43+
self.draw_layer = None
44+
45+
def initializeDD(self):
46+
self.draw_layer = DDLayerGraphical(self.dd, 28, 28)
47+
self.draw_layer.border(1, "lightgray", "round", 0.5)
48+
self.draw_layer.enableFeedback("fs:drag", self._handleDrawLayerFeedback)
49+
50+
self.copy_layer = DDLayerGraphical(self.dd, 28, 28)
51+
self.copy_layer.border(2, "blue", "round", 1)
52+
53+
self.clear_btn = DDLayerLcd(self.dd, 7, 1)
54+
self.clear_btn.backgroundColor("lightgreen")
55+
self.clear_btn.pixelColor("darkblue")
56+
self.clear_btn.writeCenteredLine("clear")
57+
self.clear_btn.border(2, "darkgreen", "raised")
58+
self.clear_btn.enableFeedback("f", lambda *args: self._resetPixels())
59+
60+
self.center_btn = DDLayerLcd(self.dd, 8, 1)
61+
self.center_btn.writeCenteredLine("center")
62+
self.center_btn.enableFeedback("fl", lambda *args: self._toggleAutoCenter())
63+
64+
self.inference_btn = DDLayerLcd(self.dd, 3, 3)
65+
self.inference_btn.pixelColor("darkblue")
66+
self.inference_btn.writeCenteredLine(">>>", 1)
67+
self.inference_btn.border(2, "gray", "raised")
68+
self.inference_btn.enableFeedback("f", self._handleInferenceBtnFeedback)
69+
70+
self.result_layer = DDLayer7SegmentRow(self.dd)
71+
self.result_layer.border(10, "blue", "round", 5)
72+
self.result_layer.segmentColor("darkblue")
73+
74+
AutoPin('V',
75+
AutoPin('H', self.clear_btn, self.center_btn),
76+
self.draw_layer,
77+
AutoPin('H', self.copy_layer, self.inference_btn, self.result_layer),
78+
).pin(self.dd)
79+
80+
self.auto_center = False
81+
self._toggleAutoCenter()
82+
self._resetPixels()
83+
84+
def updateDD(self):
85+
self.dd.timeslice()
86+
87+
def _handleDrawLayerFeedback(self, layer: DDLayer, type: str, x: int, y: int):
88+
if x == -1:
89+
self.last_x = -1
90+
self.last_y = -1
91+
else:
92+
update = True
93+
if self.last_x == -1:
94+
self._drawPixel(x, y)
95+
else:
96+
if self.last_x != x or self.last_y != y:
97+
update = self._drawLine(self.last_x, self.last_y, x, y)
98+
if update:
99+
self.last_x = x
100+
self.last_y = y
101+
102+
def _handleInferenceBtnFeedback(self, *args):
103+
self.draw_layer.disabled(True)
104+
if self.auto_center:
105+
self._autoCenterPixels(update_draw_layer = False)
106+
try:
107+
self.dd.log("<<< ...")
108+
start_time = time.time()
109+
if self.inference_func is not None:
110+
inference_data = self._pixelsToInferenceData()
111+
best = self.inference_func(inference_data)
112+
else:
113+
best = random.randint(0, 10)
114+
taken_time = time.time() - start_time
115+
self.dd.log(f"... >>> in {taken_time:0.2f}s ==> [{best}]")
116+
self.result_layer.showDigit(best)
117+
self._drawPixelsTo(self.copy_layer)
118+
self._resetPixels()
119+
except Exception as e:
120+
self.dd.log(f"Error during inference: {e}", is_error=True)
121+
finally:
122+
self.draw_layer.disabled(False)
123+
124+
def _toggleAutoCenter(self):
125+
self.auto_center = not self.auto_center
126+
if self.auto_center:
127+
self.center_btn.pixelColor("darkblue")
128+
self.center_btn.border(2, "gray", "flat")
129+
else:
130+
self.center_btn.pixelColor("gray")
131+
self.center_btn.border(2, "gray", "hair")
132+
133+
134+
def _renderPixel(self, x: int, y: int, shade: int):
135+
if self.pixels[x][y] < shade:
136+
color = DD_RGB_COLOR(shade, shade, shade)
137+
self.draw_layer.drawPixel(x, y, color)
138+
self.pixels[x][y] = shade
139+
140+
def _drawPixel(self, x: int, y: int):
141+
if THICKER_LINE_SHADE > 0:
142+
if x > 0:
143+
self._renderPixel(x - 1, y, THICKER_LINE_SHADE)
144+
if x < 27:
145+
self._renderPixel(x + 1, y, THICKER_LINE_SHADE)
146+
if y > 0:
147+
self._renderPixel(x, y - 1, THICKER_LINE_SHADE)
148+
if y < 27:
149+
self._renderPixel(x, y + 1, THICKER_LINE_SHADE)
150+
self._renderPixel(x, y, 255)
151+
152+
def _drawLine(self, x1: int, y1: int, x2: int, y2: int) -> bool:
153+
delt_x = x2 - x1
154+
delt_y = y2 - y1
155+
if abs(delt_x) > abs(delt_y):
156+
steps = abs(delt_x)
157+
if steps == 0:
158+
return False # nothing to draw
159+
inc_x = -1 if delt_x < 0 else 1
160+
inc_y = int(float(delt_y) / float(steps))
161+
else:
162+
steps = abs(delt_y)
163+
if steps == 0:
164+
return False # nothing to draw
165+
inc_y = -1 if delt_y < 0 else 1
166+
inc_x = int(float(delt_x) / float(steps))
167+
x = float(x1)
168+
y = float(y1)
169+
self.dd.recordLayerCommands()
170+
for i in range(0, steps):
171+
self._drawPixel(round(x), round(y))
172+
x += inc_x
173+
y += inc_y
174+
self.dd.playbackLayerCommands()
175+
return True
176+
177+
def _resetPixels(self):
178+
self.last_x = -1
179+
self.last_y = -1
180+
self.draw_layer.clear()
181+
self.pixels = [[0 for _ in range(28)] for _ in range(28)]
182+
183+
def _drawPixelsTo(self, target_layer: DDLayerGraphical):
184+
self.dd.recordLayerCommands()
185+
target_layer.clear()
186+
for x in range(0, 28):
187+
for y in range(0, 28):
188+
shade = self.pixels[x][y]
189+
if shade != 0:
190+
target_layer.drawPixel(x, y, DD_RGB_COLOR(shade, shade, shade))
191+
self.dd.playbackLayerCommands()
192+
193+
def _autoCenterPixels(self, update_draw_layer: bool = True):
194+
min_x = 27
195+
max_x = 0
196+
min_y = 27
197+
max_y = 0
198+
for x in range(0, 28):
199+
for y in range(0, 28):
200+
if self.pixels[x][y] != 0:
201+
if x < min_x:
202+
min_x = x
203+
if x > max_x:
204+
max_x = x
205+
if y < min_y:
206+
min_y = y
207+
if y > max_y:
208+
max_y = y
209+
x_delta = int((((27 - max_x) + min_x) / 2) - min_x)
210+
y_delta = int((((27 - max_y) + min_y) / 2) - min_y)
211+
if x_delta != 0 or y_delta != 0:
212+
new_pixels = [[0 for _ in range(28)] for _ in range(28)]
213+
for x in range(0, 28):
214+
for y in range(0, 28):
215+
new_x = x - x_delta
216+
new_y = y - y_delta
217+
pixel = 0
218+
if new_x >= 0 and new_x <= 27 and new_y >= 0 and new_y <= 27:
219+
pixel = self.pixels[new_x][new_y]
220+
new_pixels[x][y] = pixel
221+
self.pixels = new_pixels
222+
if update_draw_layer:
223+
self._drawPixelsTo(self.draw_layer)
224+
225+
def _pixelsToInferenceData(self) -> list[float]:
226+
data = []
227+
for y in range(0, 28):
228+
for x in range(0, 28):
229+
shade = self.pixels[x][y]
230+
data.append(shade / 255.0)
231+
return data
232+

0 commit comments

Comments
 (0)