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.
- pixel_drawing_pad-0.2.0/.gitignore +13 -0
- pixel_drawing_pad-0.2.0/Makefile +54 -0
- pixel_drawing_pad-0.2.0/PKG-INFO +62 -0
- pixel_drawing_pad-0.2.0/README.org +31 -0
- pixel_drawing_pad-0.2.0/example.ipynb +548 -0
- pixel_drawing_pad-0.2.0/pyproject.toml +59 -0
- pixel_drawing_pad-0.2.0/src/pixel_drawing_pad/__init__.py +6 -0
- pixel_drawing_pad-0.2.0/src/pixel_drawing_pad/_version.py +1 -0
- pixel_drawing_pad-0.2.0/src/pixel_drawing_pad/widget.js +279 -0
- pixel_drawing_pad-0.2.0/src/pixel_drawing_pad/widget.py +126 -0
- pixel_drawing_pad-0.2.0/tests/__init__.py +0 -0
- pixel_drawing_pad-0.2.0/tests/test_widget.py +98 -0
- pixel_drawing_pad-0.2.0/uv.lock +791 -0
|
@@ -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
|
+
}
|