pixel-drawing-pad 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,13 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .ruff_cache/
8
+ .pytest_cache/
9
+ .venv/
10
+ *.egg
11
+ .uv/
12
+ .virtual_documents/
13
+ .ipynb_checkpoints/
@@ -0,0 +1,54 @@
1
+ .DEFAULT_GOAL := help
2
+ PACKAGE_NAME := pixel_drawing_pad
3
+
4
+ .PHONY: help
5
+ help: ## Show this help
6
+ @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
7
+
8
+ .PHONY: install
9
+ install: ## Install package in editable mode with dev dependencies
10
+ uv pip install -e ".[dev]"
11
+
12
+ .PHONY: sync
13
+ sync: ## Sync environment from pyproject.toml
14
+ uv sync --extra dev
15
+
16
+ .PHONY: lint
17
+ lint: ## Run ruff linter and formatter check
18
+ uv run ruff check src/ tests/
19
+ uv run ruff format --check src/ tests/
20
+
21
+ .PHONY: format
22
+ format: ## Auto-format code with ruff
23
+ uv run ruff check --fix src/ tests/
24
+ uv run ruff format src/ tests/
25
+
26
+ .PHONY: test
27
+ test: ## Run tests with pytest
28
+ uv run pytest
29
+
30
+ .PHONY: test-cov
31
+ test-cov: ## Run tests with coverage
32
+ uv run pytest --cov=$(PACKAGE_NAME) --cov-report=term-missing
33
+
34
+ .PHONY: clean
35
+ clean: ## Remove build artifacts
36
+ rm -rf dist/ build/ *.egg-info src/*.egg-info
37
+ find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
38
+ find . -type f -name '*.pyc' -delete 2>/dev/null || true
39
+
40
+ .PHONY: dist
41
+ dist: clean ## Build source and wheel package
42
+ uv build
43
+ ls -l dist/
44
+
45
+ .PHONY: release
46
+ release: dist ## Build and upload to PyPI
47
+ uv publish --username __token__ --password "$$(python3 -c "import configparser; c=configparser.ConfigParser(); c.read('$$HOME/.pypirc'); print(c['pypi']['password'])")"
48
+
49
+ .PHONY: release-test
50
+ release-test: dist ## Build and upload to Test PyPI
51
+ uv publish --publish-url https://test.pypi.org/legacy/ --username __token__ --password "$$(python3 -c "import configparser; c=configparser.ConfigParser(); c.read('$$HOME/.pypirc'); print(c['test']['password'])")"
52
+
53
+ .PHONY: check
54
+ check: lint test ## Run lint + tests
@@ -0,0 +1,62 @@
1
+ Metadata-Version: 2.4
2
+ Name: pixel-drawing-pad
3
+ Version: 0.2.0
4
+ Summary: Interactive pixel drawing pad widget for Jupyter notebooks
5
+ Project-URL: Homepage, https://github.com/nikadon/pixel-drawig-pad
6
+ Project-URL: Repository, https://github.com/nikadon/pixel-drawig-pad
7
+ Project-URL: Issues, https://github.com/nikadon/pixel-drawig-pad/issues
8
+ Author: JN, KR
9
+ License-Expression: GPL-3.0-or-later
10
+ Keywords: drawing,jupyter,machine-learning,mnist,pixel,widget
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Framework :: Jupyter
13
+ Classifier: Framework :: Jupyter :: JupyterLab
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
+ Classifier: Topic :: Scientific/Engineering :: Visualization
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: anywidget>=0.9.0
25
+ Requires-Dist: numpy>=1.24.0
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
28
+ Requires-Dist: pytest>=8.0; extra == 'dev'
29
+ Requires-Dist: ruff>=0.4.0; extra == 'dev'
30
+ Description-Content-Type: text/plain
31
+
32
+ #+title: Pixel Drawing Pad
33
+
34
+ Interactive pixel drawing pad widget for Jupyter notebooks.
35
+
36
+ * Installation
37
+
38
+ #+begin_src sh
39
+ uv pip install pixel-drawing-pad
40
+ #+end_src
41
+
42
+ * Usage
43
+
44
+ #+begin_src python
45
+ from pixel_drawing_pad import DrawingPad
46
+
47
+ pad = DrawingPad(pix_zoom=36)
48
+ pad # display in notebook cell
49
+
50
+ # Access the drawn data as a numpy array
51
+ data = pad.get_data()
52
+
53
+ # Manipulate
54
+ pad.rot90()
55
+ pad.fliplr()
56
+ pad.checkerboard()
57
+ pad.swap()
58
+ #+end_src
59
+
60
+ * Google Colab
61
+
62
+ Works out of the box — just =pip install pixel-drawing-pad= in a Colab cell.
@@ -0,0 +1,31 @@
1
+ #+title: Pixel Drawing Pad
2
+
3
+ Interactive pixel drawing pad widget for Jupyter notebooks.
4
+
5
+ * Installation
6
+
7
+ #+begin_src sh
8
+ uv pip install pixel-drawing-pad
9
+ #+end_src
10
+
11
+ * Usage
12
+
13
+ #+begin_src python
14
+ from pixel_drawing_pad import DrawingPad
15
+
16
+ pad = DrawingPad(pix_zoom=36)
17
+ pad # display in notebook cell
18
+
19
+ # Access the drawn data as a numpy array
20
+ data = pad.get_data()
21
+
22
+ # Manipulate
23
+ pad.rot90()
24
+ pad.fliplr()
25
+ pad.checkerboard()
26
+ pad.swap()
27
+ #+end_src
28
+
29
+ * Google Colab
30
+
31
+ Works out of the box — just =pip install pixel-drawing-pad= in a Colab cell.
@@ -0,0 +1,548 @@
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "metadata": {},
6
+ "source": [
7
+ "# Pixel Drawing Pad — Examples\n",
8
+ "\n",
9
+ "Interactive pixel drawing widget for Jupyter notebooks.\n",
10
+ "\n",
11
+ "## Installation\n",
12
+ "\n",
13
+ "```sh\n",
14
+ "pip install pixel-drawing-pad\n",
15
+ "# or\n",
16
+ "uv pip install pixel-drawing-pad\n",
17
+ "```"
18
+ ]
19
+ },
20
+ {
21
+ "cell_type": "code",
22
+ "execution_count": 52,
23
+ "metadata": {},
24
+ "outputs": [],
25
+ "source": [
26
+ "# Uncomment to install in Colab:\n",
27
+ "# !pip install pixel-drawing-pad scikit-learn"
28
+ ]
29
+ },
30
+ {
31
+ "cell_type": "markdown",
32
+ "metadata": {},
33
+ "source": [
34
+ "## 1. Basic Usage"
35
+ ]
36
+ },
37
+ {
38
+ "cell_type": "code",
39
+ "execution_count": 54,
40
+ "metadata": {},
41
+ "outputs": [
42
+ {
43
+ "data": {
44
+ "application/vnd.jupyter.widget-view+json": {
45
+ "model_id": "e7808b3868e042d0a6d92f8d3b21eb91",
46
+ "version_major": 2,
47
+ "version_minor": 1
48
+ },
49
+ "text/plain": [
50
+ "<pixel_drawing_pad.widget.DrawingPad object at 0x78ad73610f50>"
51
+ ]
52
+ },
53
+ "execution_count": 54,
54
+ "metadata": {},
55
+ "output_type": "execute_result"
56
+ }
57
+ ],
58
+ "source": [
59
+ "from pixel_drawing_pad import DrawingPad\n",
60
+ "\n",
61
+ "pad = DrawingPad(pix_zoom=44)\n",
62
+ "pad"
63
+ ]
64
+ },
65
+ {
66
+ "cell_type": "code",
67
+ "execution_count": 55,
68
+ "metadata": {},
69
+ "outputs": [
70
+ {
71
+ "name": "stdout",
72
+ "output_type": "stream",
73
+ "text": [
74
+ "Shape: (7, 7)\n",
75
+ "Unique values: [0. 1.]\n",
76
+ "[[1. 1. 0. 0. 0. 0. 0.]\n",
77
+ " [1. 0. 0. 1. 0. 0. 0.]\n",
78
+ " [1. 0. 1. 1. 1. 0. 0.]\n",
79
+ " [0. 0. 0. 1. 0. 0. 0.]\n",
80
+ " [0. 0. 1. 1. 1. 0. 0.]\n",
81
+ " [0. 0. 0. 1. 0. 0. 0.]\n",
82
+ " [0. 0. 0. 0. 0. 0. 0.]]\n"
83
+ ]
84
+ }
85
+ ],
86
+ "source": [
87
+ "# Access the drawn data as a numpy array\n",
88
+ "import numpy as np\n",
89
+ "\n",
90
+ "data = pad.get_data()\n",
91
+ "print(f\"Shape: {data.shape}\")\n",
92
+ "print(f\"Unique values: {np.unique(data)}\")\n",
93
+ "print(data)"
94
+ ]
95
+ },
96
+ {
97
+ "cell_type": "markdown",
98
+ "metadata": {},
99
+ "source": [
100
+ "## 2. Manipulations"
101
+ ]
102
+ },
103
+ {
104
+ "cell_type": "code",
105
+ "execution_count": 56,
106
+ "metadata": {},
107
+ "outputs": [
108
+ {
109
+ "data": {
110
+ "application/vnd.jupyter.widget-view+json": {
111
+ "model_id": "4a3e54232921449291e984cdb3db3d6f",
112
+ "version_major": 2,
113
+ "version_minor": 1
114
+ },
115
+ "text/plain": [
116
+ "<pixel_drawing_pad.widget.DrawingPad object at 0x78ad733326d0>"
117
+ ]
118
+ },
119
+ "execution_count": 56,
120
+ "metadata": {},
121
+ "output_type": "execute_result"
122
+ }
123
+ ],
124
+ "source": [
125
+ "pad2 = DrawingPad(pix_zoom=36)\n",
126
+ "pad2"
127
+ ]
128
+ },
129
+ {
130
+ "cell_type": "code",
131
+ "execution_count": 58,
132
+ "metadata": {},
133
+ "outputs": [],
134
+ "source": [
135
+ "# Rotate 90 degrees counter-clockwise\n",
136
+ "pad2.rot90()"
137
+ ]
138
+ },
139
+ {
140
+ "cell_type": "code",
141
+ "execution_count": 59,
142
+ "metadata": {},
143
+ "outputs": [],
144
+ "source": [
145
+ "# Flip horizontally / vertically\n",
146
+ "pad2.test() # reset to default pattern\n",
147
+ "pad2.fliplr()"
148
+ ]
149
+ },
150
+ {
151
+ "cell_type": "code",
152
+ "execution_count": 60,
153
+ "metadata": {},
154
+ "outputs": [],
155
+ "source": [
156
+ "pad2.test()\n",
157
+ "pad2.flipud()"
158
+ ]
159
+ },
160
+ {
161
+ "cell_type": "code",
162
+ "execution_count": 62,
163
+ "metadata": {},
164
+ "outputs": [],
165
+ "source": [
166
+ "# Transpose\n",
167
+ "pad2.test()\n",
168
+ "pad2.transpose()"
169
+ ]
170
+ },
171
+ {
172
+ "cell_type": "code",
173
+ "execution_count": 63,
174
+ "metadata": {},
175
+ "outputs": [],
176
+ "source": [
177
+ "# Roll (shift) pixels\n",
178
+ "pad2.test()\n",
179
+ "pad2.roll(x=2, y=0) # shift right by 2"
180
+ ]
181
+ },
182
+ {
183
+ "cell_type": "code",
184
+ "execution_count": 64,
185
+ "metadata": {},
186
+ "outputs": [],
187
+ "source": [
188
+ "# Swap 0s and 1s\n",
189
+ "pad2.test()\n",
190
+ "pad2.swap()"
191
+ ]
192
+ },
193
+ {
194
+ "cell_type": "code",
195
+ "execution_count": 65,
196
+ "metadata": {},
197
+ "outputs": [],
198
+ "source": [
199
+ "# Checkerboard pattern\n",
200
+ "pad2.checkerboard(8, 8)"
201
+ ]
202
+ },
203
+ {
204
+ "cell_type": "code",
205
+ "execution_count": 66,
206
+ "metadata": {},
207
+ "outputs": [],
208
+ "source": [
209
+ "# Add noise\n",
210
+ "pad2.test()\n",
211
+ "pad2.noisify(noise_value=1, noise_threshold=0.90)"
212
+ ]
213
+ },
214
+ {
215
+ "cell_type": "code",
216
+ "execution_count": 67,
217
+ "metadata": {},
218
+ "outputs": [
219
+ {
220
+ "data": {
221
+ "application/vnd.jupyter.widget-view+json": {
222
+ "model_id": "4a3e54232921449291e984cdb3db3d6f",
223
+ "version_major": 2,
224
+ "version_minor": 1
225
+ },
226
+ "text/plain": [
227
+ "<pixel_drawing_pad.widget.DrawingPad object at 0x78ad733326d0>"
228
+ ]
229
+ },
230
+ "execution_count": 67,
231
+ "metadata": {},
232
+ "output_type": "execute_result"
233
+ }
234
+ ],
235
+ "source": [
236
+ "# Clear / clean canvas\n",
237
+ "pad2.clean(8, 9, val=0) # 10x10 grid filled with 0\n",
238
+ "pad2"
239
+ ]
240
+ },
241
+ {
242
+ "cell_type": "markdown",
243
+ "metadata": {},
244
+ "source": [
245
+ "## 3. Custom data\n",
246
+ "\n",
247
+ "You can initialize the pad with any 2D numpy array."
248
+ ]
249
+ },
250
+ {
251
+ "cell_type": "code",
252
+ "execution_count": 40,
253
+ "metadata": {},
254
+ "outputs": [
255
+ {
256
+ "data": {
257
+ "application/vnd.jupyter.widget-view+json": {
258
+ "model_id": "f7267d4590f94ef18a27e0047a708a1e",
259
+ "version_major": 2,
260
+ "version_minor": 1
261
+ },
262
+ "text/plain": [
263
+ "<pixel_drawing_pad.widget.DrawingPad object at 0x78ad737272f0>"
264
+ ]
265
+ },
266
+ "execution_count": 40,
267
+ "metadata": {},
268
+ "output_type": "execute_result"
269
+ }
270
+ ],
271
+ "source": [
272
+ "custom_data = np.array( [ \n",
273
+ " [ 0, 1, 1, 0, 0, 0, 1, 1, 0, ],\n",
274
+ " [ 1, 1, 1, 1, 0, 1, 1, 1, 1, ],\n",
275
+ " [ 1, 1, 1, 1, 1, 1, 1, 1, 1, ],\n",
276
+ " [ 1, 1, 1, 1, 1, 1, 1, 1, 1, ],\n",
277
+ " [ 0, 1, 1, 1, 1, 1, 1, 1, 0, ],\n",
278
+ " [ 0, 0, 1, 1, 1, 1, 1, 0, 0, ],\n",
279
+ " [ 0, 0, 0, 1, 1, 1, 0, 0, 0, ],\n",
280
+ " [ 0, 0, 0, 0, 1, 0, 0, 0, 0, ],\n",
281
+ "], dtype=np.float32)\n",
282
+ "\n",
283
+ "pad3 = DrawingPad(pix_zoom=48, data=custom_data)\n",
284
+ "pad3"
285
+ ]
286
+ },
287
+ {
288
+ "cell_type": "markdown",
289
+ "metadata": {},
290
+ "source": [
291
+ "## 4. Extra controls with ipywidgets\n",
292
+ "\n",
293
+ "Combine the drawing pad with ipywidgets buttons for a richer UI."
294
+ ]
295
+ },
296
+ {
297
+ "cell_type": "code",
298
+ "execution_count": 68,
299
+ "metadata": {},
300
+ "outputs": [
301
+ {
302
+ "data": {
303
+ "application/vnd.jupyter.widget-view+json": {
304
+ "model_id": "cf01988dddba4339b233af55bc6b2a61",
305
+ "version_major": 2,
306
+ "version_minor": 0
307
+ },
308
+ "text/plain": [
309
+ "HBox(children=(<pixel_drawing_pad.widget.DrawingPad object at 0x78ad737bf800>, VBox(children=(HBox(children=(B…"
310
+ ]
311
+ },
312
+ "metadata": {},
313
+ "output_type": "display_data"
314
+ }
315
+ ],
316
+ "source": [
317
+ "from IPython.display import display\n",
318
+ "from ipywidgets import Button, HBox, VBox, Layout\n",
319
+ "\n",
320
+ "pad4 = DrawingPad(pix_zoom=36)\n",
321
+ "\n",
322
+ "btn_specs = [\n",
323
+ " # (label, tooltip, callback)\n",
324
+ " (\"↺\", \"Rotate CCW\", lambda b: pad4.rot90()),\n",
325
+ " (\"▲\", \"Roll up\", lambda b: pad4.roll(0, -1)),\n",
326
+ " (\"⬕\", \"Swap 0/1\", lambda b: pad4.swap()),\n",
327
+ " (\"⬌\", \"Flip left/right\", lambda b: pad4.fliplr()),\n",
328
+ " (\"⬍\", \"Flip up/down\", lambda b: pad4.flipud()),\n",
329
+ " (\"T\", \"Transpose\", lambda b: pad4.transpose()),\n",
330
+ " (\"□\", \"Clean 0\", lambda b: pad4.clean(val=0)),\n",
331
+ " (\"◄\", \"Roll left\", lambda b: pad4.roll(-1, 0)),\n",
332
+ " (\"▼\", \"Roll down\", lambda b: pad4.roll(0, 1)),\n",
333
+ " (\"►\", \"Roll right\", lambda b: pad4.roll(1, 0)),\n",
334
+ " (\"n0\", \"Add 0 noise\", lambda b: pad4.noisify(noise_value=0)),\n",
335
+ " (\"n1\", \"Add 1 noise\", lambda b: pad4.noisify(noise_value=1)),\n",
336
+ " (\"R\", \"Reset\", lambda b: pad4.test()),\n",
337
+ " (\"■\", \"Clean 1\", lambda b: pad4.clean(val=1)),\n",
338
+ "]\n",
339
+ "\n",
340
+ "btn_layout = Layout(width=\"48px\", height=\"32px\")\n",
341
+ "buttons = [Button(description=lbl, tooltip=tip, layout=btn_layout) for lbl, tip, _ in btn_specs]\n",
342
+ "for btn, (_, _, cb) in zip(buttons, btn_specs):\n",
343
+ " btn.on_click(cb)\n",
344
+ "\n",
345
+ "controls = VBox([\n",
346
+ " HBox(buttons[:7]),\n",
347
+ " HBox(buttons[7:]),\n",
348
+ "])\n",
349
+ "\n",
350
+ "display(HBox([pad4, controls]))"
351
+ ]
352
+ },
353
+ {
354
+ "cell_type": "markdown",
355
+ "metadata": {},
356
+ "source": [
357
+ "---\n",
358
+ "\n",
359
+ "## 5. MNIST: Train a classifier and predict hand-drawn digits\n",
360
+ "\n",
361
+ "Draw a digit on the pad and classify it using a model trained on MNIST.\n",
362
+ "\n",
363
+ "Requires `scikit-learn`:\n",
364
+ "```sh\n",
365
+ "pip install scikit-learn\n",
366
+ "```"
367
+ ]
368
+ },
369
+ {
370
+ "cell_type": "code",
371
+ "execution_count": null,
372
+ "metadata": {},
373
+ "outputs": [],
374
+ "source": [
375
+ "from sklearn.datasets import load_digits\n",
376
+ "from sklearn.ensemble import RandomForestClassifier\n",
377
+ "from sklearn.model_selection import train_test_split\n",
378
+ "from sklearn.metrics import accuracy_score\n",
379
+ "\n",
380
+ "# Load the 8x8 MNIST-like digits dataset\n",
381
+ "digits = load_digits()\n",
382
+ "X, y = digits.data, digits.target\n",
383
+ "\n",
384
+ "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\n",
385
+ "\n",
386
+ "clf = RandomForestClassifier(n_estimators=100, random_state=42)\n",
387
+ "clf.fit(X_train, y_train)\n",
388
+ "\n",
389
+ "acc = accuracy_score(y_test, clf.predict(X_test))\n",
390
+ "print(f\"Test accuracy: {acc:.3f}\")"
391
+ ]
392
+ },
393
+ {
394
+ "cell_type": "markdown",
395
+ "metadata": {},
396
+ "source": [
397
+ "### 5a. Visualize some training digits"
398
+ ]
399
+ },
400
+ {
401
+ "cell_type": "code",
402
+ "execution_count": null,
403
+ "metadata": {},
404
+ "outputs": [],
405
+ "source": [
406
+ "import matplotlib.pyplot as plt\n",
407
+ "\n",
408
+ "fig, axes = plt.subplots(2, 5, figsize=(10, 4))\n",
409
+ "for ax, img, label in zip(axes.ravel(), digits.images[:10], digits.target[:10]):\n",
410
+ " ax.imshow(img, cmap=\"gray_r\", interpolation=\"nearest\")\n",
411
+ " ax.set_title(f\"Label: {label}\")\n",
412
+ " ax.axis(\"off\")\n",
413
+ "plt.suptitle(\"sklearn digits dataset (8x8)\")\n",
414
+ "plt.tight_layout()\n",
415
+ "plt.show()"
416
+ ]
417
+ },
418
+ {
419
+ "cell_type": "markdown",
420
+ "metadata": {},
421
+ "source": [
422
+ "### 5b. Draw a digit on the 8x8 pad\n",
423
+ "\n",
424
+ "The sklearn digits dataset uses 8x8 images with pixel values 0–16.\n",
425
+ "\n",
426
+ "Draw a digit below using binary (0/1) values — we will scale to 0–16 for prediction."
427
+ ]
428
+ },
429
+ {
430
+ "cell_type": "code",
431
+ "execution_count": null,
432
+ "metadata": {},
433
+ "outputs": [],
434
+ "source": [
435
+ "mnist_pad = DrawingPad(pix_zoom=48, data=np.zeros((8, 8), dtype=np.float32))\n",
436
+ "mnist_pad"
437
+ ]
438
+ },
439
+ {
440
+ "cell_type": "markdown",
441
+ "metadata": {},
442
+ "source": [
443
+ "### 5c. Predict the drawn digit\n",
444
+ "\n",
445
+ "Run this cell after drawing."
446
+ ]
447
+ },
448
+ {
449
+ "cell_type": "code",
450
+ "execution_count": null,
451
+ "metadata": {},
452
+ "outputs": [],
453
+ "source": [
454
+ "drawn = mnist_pad.get_data()\n",
455
+ "\n",
456
+ "# Scale binary (0/1) to sklearn digits range (0-16)\n",
457
+ "drawn_scaled = (drawn * 16.0).astype(np.float64)\n",
458
+ "\n",
459
+ "# Visualize what we're feeding to the classifier\n",
460
+ "fig, axes = plt.subplots(1, 2, figsize=(6, 3))\n",
461
+ "axes[0].imshow(drawn, cmap=\"gray_r\", interpolation=\"nearest\")\n",
462
+ "axes[0].set_title(\"Raw drawing (0/1)\")\n",
463
+ "axes[0].axis(\"off\")\n",
464
+ "axes[1].imshow(drawn_scaled.reshape(8, 8), cmap=\"gray_r\", interpolation=\"nearest\")\n",
465
+ "axes[1].set_title(\"Scaled (0-16)\")\n",
466
+ "axes[1].axis(\"off\")\n",
467
+ "plt.tight_layout()\n",
468
+ "plt.show()\n",
469
+ "\n",
470
+ "# Predict\n",
471
+ "prediction = clf.predict(drawn_scaled.reshape(1, -1))[0]\n",
472
+ "probas = clf.predict_proba(drawn_scaled.reshape(1, -1))[0]\n",
473
+ "\n",
474
+ "print(f\"\\nPredicted digit: {prediction}\")\n",
475
+ "print(f\"Confidence: {probas[prediction]:.1%}\")\n",
476
+ "print(f\"\\nAll probabilities:\")\n",
477
+ "for digit, prob in enumerate(probas):\n",
478
+ " bar = '█' * int(prob * 30)\n",
479
+ " print(f\" {digit}: {prob:5.1%} {bar}\")"
480
+ ]
481
+ },
482
+ {
483
+ "cell_type": "markdown",
484
+ "metadata": {},
485
+ "source": [
486
+ "### 5d. Load a real MNIST digit into the pad\n",
487
+ "\n",
488
+ "Pick a digit from the dataset and load it into the drawing pad for inspection or editing."
489
+ ]
490
+ },
491
+ {
492
+ "cell_type": "code",
493
+ "execution_count": null,
494
+ "metadata": {},
495
+ "outputs": [],
496
+ "source": [
497
+ "# Pick digit index (0-1796)\n",
498
+ "digit_index = 42\n",
499
+ "\n",
500
+ "sample = digits.images[digit_index].astype(np.float32)\n",
501
+ "label = digits.target[digit_index]\n",
502
+ "print(f\"Loading digit at index {digit_index} (label: {label})\")\n",
503
+ "\n",
504
+ "# Normalize to 0-1 range for the pad\n",
505
+ "sample_normalized = sample / 16.0\n",
506
+ "\n",
507
+ "inspect_pad = DrawingPad(pix_zoom=48, data=sample_normalized)\n",
508
+ "inspect_pad"
509
+ ]
510
+ },
511
+ {
512
+ "cell_type": "code",
513
+ "execution_count": null,
514
+ "metadata": {},
515
+ "outputs": [],
516
+ "source": [
517
+ "# Predict the (possibly edited) digit\n",
518
+ "edited = inspect_pad.get_data()\n",
519
+ "edited_scaled = (edited * 16.0).astype(np.float64)\n",
520
+ "\n",
521
+ "prediction = clf.predict(edited_scaled.reshape(1, -1))[0]\n",
522
+ "print(f\"Original label: {label}\")\n",
523
+ "print(f\"Predicted: {prediction}\")"
524
+ ]
525
+ }
526
+ ],
527
+ "metadata": {
528
+ "kernelspec": {
529
+ "display_name": "Python 3 (ipykernel)",
530
+ "language": "python",
531
+ "name": "python3"
532
+ },
533
+ "language_info": {
534
+ "codemirror_mode": {
535
+ "name": "ipython",
536
+ "version": 3
537
+ },
538
+ "file_extension": ".py",
539
+ "mimetype": "text/x-python",
540
+ "name": "python",
541
+ "nbconvert_exporter": "python",
542
+ "pygments_lexer": "ipython3",
543
+ "version": "3.13.5"
544
+ }
545
+ },
546
+ "nbformat": 4,
547
+ "nbformat_minor": 4
548
+ }