ai2pixelart 0.1.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.
- ai2pixelart-0.1.0/LICENSE +112 -0
- ai2pixelart-0.1.0/PKG-INFO +140 -0
- ai2pixelart-0.1.0/README.md +97 -0
- ai2pixelart-0.1.0/pyproject.toml +56 -0
- ai2pixelart-0.1.0/setup.cfg +4 -0
- ai2pixelart-0.1.0/src/ai2pixelart/__init__.py +5 -0
- ai2pixelart-0.1.0/src/ai2pixelart/autoqa.py +147 -0
- ai2pixelart-0.1.0/src/ai2pixelart/cli.py +516 -0
- ai2pixelart-0.1.0/src/ai2pixelart/corrupt.py +64 -0
- ai2pixelart-0.1.0/src/ai2pixelart/corruptor.py +190 -0
- ai2pixelart-0.1.0/src/ai2pixelart/demo.py +83 -0
- ai2pixelart-0.1.0/src/ai2pixelart/grid.py +415 -0
- ai2pixelart-0.1.0/src/ai2pixelart/imgedit.py +72 -0
- ai2pixelart-0.1.0/src/ai2pixelart/metrics.py +95 -0
- ai2pixelart-0.1.0/src/ai2pixelart/models.py +247 -0
- ai2pixelart-0.1.0/src/ai2pixelart/nndata.py +227 -0
- ai2pixelart-0.1.0/src/ai2pixelart/nninfer.py +152 -0
- ai2pixelart-0.1.0/src/ai2pixelart/nnmodel.py +76 -0
- ai2pixelart-0.1.0/src/ai2pixelart/pairgen.py +253 -0
- ai2pixelart-0.1.0/src/ai2pixelart/palette.py +285 -0
- ai2pixelart-0.1.0/src/ai2pixelart/pipeline.py +287 -0
- ai2pixelart-0.1.0/src/ai2pixelart/rails.py +43 -0
- ai2pixelart-0.1.0/src/ai2pixelart/spritegen.py +326 -0
- ai2pixelart-0.1.0/src/ai2pixelart/train.py +141 -0
- ai2pixelart-0.1.0/src/ai2pixelart/vae.py +62 -0
- ai2pixelart-0.1.0/src/ai2pixelart/viewer.html +849 -0
- ai2pixelart-0.1.0/src/ai2pixelart/webapp.py +471 -0
- ai2pixelart-0.1.0/src/ai2pixelart.egg-info/PKG-INFO +140 -0
- ai2pixelart-0.1.0/src/ai2pixelart.egg-info/SOURCES.txt +42 -0
- ai2pixelart-0.1.0/src/ai2pixelart.egg-info/dependency_links.txt +1 -0
- ai2pixelart-0.1.0/src/ai2pixelart.egg-info/entry_points.txt +2 -0
- ai2pixelart-0.1.0/src/ai2pixelart.egg-info/requires.txt +21 -0
- ai2pixelart-0.1.0/src/ai2pixelart.egg-info/top_level.txt +1 -0
- ai2pixelart-0.1.0/tests/test_autoqa.py +32 -0
- ai2pixelart-0.1.0/tests/test_grid.py +84 -0
- ai2pixelart-0.1.0/tests/test_metrics.py +54 -0
- ai2pixelart-0.1.0/tests/test_models.py +135 -0
- ai2pixelart-0.1.0/tests/test_nn.py +372 -0
- ai2pixelart-0.1.0/tests/test_palette.py +112 -0
- ai2pixelart-0.1.0/tests/test_pipeline.py +179 -0
- ai2pixelart-0.1.0/tests/test_rails.py +30 -0
- ai2pixelart-0.1.0/tests/test_spritegen.py +47 -0
- ai2pixelart-0.1.0/tests/test_vae.py +90 -0
- ai2pixelart-0.1.0/tests/test_webapp.py +263 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
ai2pixelart — source code license
|
|
2
|
+
|
|
3
|
+
The ai2pixelart source code is licensed under the PolyForm Noncommercial
|
|
4
|
+
License 1.0.0 (full text below), together with the additional permission
|
|
5
|
+
stated in "Output and production use" immediately below. In short: you may
|
|
6
|
+
use, modify, and share the software for any noncommercial purpose, provided
|
|
7
|
+
you keep the required notice; you may also run it to produce pixel art you
|
|
8
|
+
sell; but you may not commercialize the software itself.
|
|
9
|
+
|
|
10
|
+
The trained model weights are NOT covered by this license — they are
|
|
11
|
+
distributed separately under Creative Commons Attribution-NonCommercial
|
|
12
|
+
4.0 International (CC BY-NC 4.0), with the same output carve-out. See
|
|
13
|
+
LICENSE-WEIGHTS.
|
|
14
|
+
|
|
15
|
+
Required Notice: Copyright © 2026 Arne Kummerow — ai2pixelart
|
|
16
|
+
|
|
17
|
+
----------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
## Additional permission: Output and production use
|
|
20
|
+
|
|
21
|
+
As an exception to the noncommercial restriction in the PolyForm
|
|
22
|
+
Noncommercial License 1.0.0 below, the licensor additionally grants you the
|
|
23
|
+
right to run the ai2pixelart software (and the trained model weights) to
|
|
24
|
+
produce pixel art and other images ("Output"), including in a commercial
|
|
25
|
+
setting and including to produce Output that you sell or license. You own
|
|
26
|
+
your Output; the licensor claims no rights in it, and it carries no
|
|
27
|
+
noncommercial restriction or attribution requirement under these terms.
|
|
28
|
+
|
|
29
|
+
What remains prohibited without a separate commercial license is
|
|
30
|
+
commercializing the software or the model weights *themselves* — for
|
|
31
|
+
example: selling or sublicensing them; offering their functionality to
|
|
32
|
+
others as a paid, subscription, or advertising-supported product, service,
|
|
33
|
+
platform, or API; or shipping them as a feature of a commercial product.
|
|
34
|
+
|
|
35
|
+
In other words: making pixel art with the tool and using that art
|
|
36
|
+
commercially is fine; turning the tool itself into a business is not.
|
|
37
|
+
|
|
38
|
+
----------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
# PolyForm Noncommercial License 1.0.0
|
|
41
|
+
|
|
42
|
+
<https://polyformproject.org/licenses/noncommercial/1.0.0>
|
|
43
|
+
|
|
44
|
+
## Acceptance
|
|
45
|
+
|
|
46
|
+
In order to get any license under these terms, you must agree to them as both strict obligations and conditions to all your licenses.
|
|
47
|
+
|
|
48
|
+
## Copyright License
|
|
49
|
+
|
|
50
|
+
The licensor grants you a copyright license for the software to do everything you might do with the software that would otherwise infringe the licensor's copyright in it for any permitted purpose. However, you may only distribute the software according to [Distribution License](#distribution-license) and make changes or new works based on the software according to [Changes and New Works License](#changes-and-new-works-license).
|
|
51
|
+
|
|
52
|
+
## Distribution License
|
|
53
|
+
|
|
54
|
+
The licensor grants you an additional copyright license to distribute copies of the software. Your license to distribute covers distributing the software with changes and new works permitted by [Changes and New Works License](#changes-and-new-works-license).
|
|
55
|
+
|
|
56
|
+
## Notices
|
|
57
|
+
|
|
58
|
+
You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms or the URL for them above, as well as copies of any plain-text lines beginning with `Required Notice:` that the licensor provided with the software. For example:
|
|
59
|
+
|
|
60
|
+
> Required Notice: Copyright Yoyodyne, Inc. (http://example.com)
|
|
61
|
+
|
|
62
|
+
## Changes and New Works License
|
|
63
|
+
|
|
64
|
+
The licensor grants you an additional copyright license to make changes and new works based on the software for any permitted purpose.
|
|
65
|
+
|
|
66
|
+
## Patent License
|
|
67
|
+
|
|
68
|
+
The licensor grants you a patent license for the software that covers patent claims the licensor can license, or becomes able to license, that you would infringe by using the software.
|
|
69
|
+
|
|
70
|
+
## Noncommercial Purposes
|
|
71
|
+
|
|
72
|
+
Any noncommercial purpose is a permitted purpose.
|
|
73
|
+
|
|
74
|
+
## Personal Uses
|
|
75
|
+
|
|
76
|
+
Personal use for research, experiment, and testing for the benefit of public knowledge, personal study, private entertainment, hobby projects, amateur pursuits, or religious observance, without any anticipated commercial application, is use for a permitted purpose.
|
|
77
|
+
|
|
78
|
+
## Noncommercial Organizations
|
|
79
|
+
|
|
80
|
+
Use by any charitable organization, educational institution, public research organization, public safety or health organization, environmental protection organization, or government institution is use for a permitted purpose regardless of the source of funding or obligations resulting from the funding.
|
|
81
|
+
|
|
82
|
+
## Fair Use
|
|
83
|
+
|
|
84
|
+
You may have "fair use" rights for the software under the law. These terms do not limit them.
|
|
85
|
+
|
|
86
|
+
## No Other Rights
|
|
87
|
+
|
|
88
|
+
These terms do not allow you to sublicense or transfer any of your licenses to anyone else, or prevent the licensor from granting licenses to anyone else. These terms do not imply any other licenses.
|
|
89
|
+
|
|
90
|
+
## Patent Defense
|
|
91
|
+
|
|
92
|
+
If you make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.
|
|
93
|
+
|
|
94
|
+
## Violations
|
|
95
|
+
|
|
96
|
+
The first time you are notified in writing that you have violated any of these terms, or done anything with the software not covered by your licenses, your licenses can nonetheless continue if you come into full compliance with these terms, and take practical steps to correct past violations, within 32 days of receiving notice. Otherwise, all your licenses end immediately.
|
|
97
|
+
|
|
98
|
+
## No Liability
|
|
99
|
+
|
|
100
|
+
***As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim.***
|
|
101
|
+
|
|
102
|
+
## Definitions
|
|
103
|
+
|
|
104
|
+
The **licensor** is the individual or entity offering these terms, and the **software** is the software the licensor makes available under these terms.
|
|
105
|
+
|
|
106
|
+
**You** refers to the individual or entity agreeing to these terms.
|
|
107
|
+
|
|
108
|
+
**Your company** is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. **Control** means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.
|
|
109
|
+
|
|
110
|
+
**Your licenses** are all the licenses granted to you for the software under these terms.
|
|
111
|
+
|
|
112
|
+
**Use** means anything you do with the software requiring one of your licenses.
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ai2pixelart
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Turn AI-generated pseudo pixel art into pixel-perfect, palette-clean pixel art
|
|
5
|
+
Author-email: Arne Kummerow <arne.kumero@gmail.com>
|
|
6
|
+
License-Expression: PolyForm-Noncommercial-1.0.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/ArneKummerow/ai2pixelart
|
|
8
|
+
Project-URL: Repository, https://github.com/ArneKummerow/ai2pixelart
|
|
9
|
+
Project-URL: Model weights, https://huggingface.co/arnekummerow/ai2pixelart
|
|
10
|
+
Keywords: pixel-art,image-restoration,image-to-image,retro,gamedev
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: numpy
|
|
25
|
+
Requires-Dist: scipy
|
|
26
|
+
Requires-Dist: scikit-image
|
|
27
|
+
Requires-Dist: pillow
|
|
28
|
+
Requires-Dist: click
|
|
29
|
+
Provides-Extra: neural
|
|
30
|
+
Requires-Dist: torch; extra == "neural"
|
|
31
|
+
Requires-Dist: safetensors; extra == "neural"
|
|
32
|
+
Requires-Dist: huggingface_hub; extra == "neural"
|
|
33
|
+
Provides-Extra: gen
|
|
34
|
+
Requires-Dist: ai2pixelart[neural]; extra == "gen"
|
|
35
|
+
Requires-Dist: diffusers; extra == "gen"
|
|
36
|
+
Requires-Dist: transformers; extra == "gen"
|
|
37
|
+
Requires-Dist: accelerate; extra == "gen"
|
|
38
|
+
Requires-Dist: peft; extra == "gen"
|
|
39
|
+
Provides-Extra: dev
|
|
40
|
+
Requires-Dist: ai2pixelart[neural]; extra == "dev"
|
|
41
|
+
Requires-Dist: pytest; extra == "dev"
|
|
42
|
+
Dynamic: license-file
|
|
43
|
+
|
|
44
|
+
# ai2pixelart
|
|
45
|
+
|
|
46
|
+
Turn AI-generated *pseudo* pixel art (wobbly grids, mixed-color cells, way
|
|
47
|
+
too many shades) into pixel-perfect, palette-clean pixel art — without
|
|
48
|
+
losing the single-pixel details that carry the design.
|
|
49
|
+
|
|
50
|
+
## Setup
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install ai2pixelart # classical cleaner only (no GPU, no torch)
|
|
54
|
+
pip install "ai2pixelart[neural]" # + run the trained networks
|
|
55
|
+
pip install "ai2pixelart[gen]" # + full data-engine / training stack
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Or with conda for development:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
conda env create -f environment.yml # creates env "ai2pixelart"
|
|
62
|
+
conda activate ai2pixelart
|
|
63
|
+
pip install -e ".[dev]"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The classical "Simple" approach needs no GPU; the neural approaches need
|
|
67
|
+
the `neural` extra and download their weights from the Hugging Face Hub
|
|
68
|
+
([arnekummerow/ai2pixelart](https://huggingface.co/arnekummerow/ai2pixelart))
|
|
69
|
+
automatically on first use (cached thereafter — `ai2pixelart models
|
|
70
|
+
download` pre-fetches them for offline use).
|
|
71
|
+
|
|
72
|
+
## Usage
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
ai2pixelart clean input.png -o out.png --preview-scale 8 # classical
|
|
76
|
+
ai2pixelart clean input.png -o out.png --approach robust # neural
|
|
77
|
+
ai2pixelart clean input.png -o out.png --palette "#1e2234,#5ea740,#f0f0f0"
|
|
78
|
+
ai2pixelart inspect my_images/ # no-ground-truth quality table
|
|
79
|
+
ai2pixelart viewer my_images/ # interactive web workspace, port 8412
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`--approach` is `simple` (classical, the default) or a neural model — a name
|
|
83
|
+
under `runs/` (`robust`, `detail`) or a path to a `.safetensors`/`.ckpt`
|
|
84
|
+
file. Run
|
|
85
|
+
`ai2pixelart --help` to see the full command tree (`clean`, `viewer`,
|
|
86
|
+
`inspect`, `eval`, `demo`, plus `data …` and `train …` for the pipeline).
|
|
87
|
+
|
|
88
|
+
## How it works
|
|
89
|
+
|
|
90
|
+
The classical pipeline ("Simple") detects the fake-pixel grid, extracts a
|
|
91
|
+
palette, and votes each cell's color — deterministic, no GPU. The neural
|
|
92
|
+
presets run the same grid/palette proposal, then a small U-Net assigns
|
|
93
|
+
each cell a palette entry; it is structurally incapable of off-palette
|
|
94
|
+
colors and carries learned priors (recovering pure 1-px details,
|
|
95
|
+
collapsing shaded backgrounds) that no local method can. It was trained
|
|
96
|
+
on pairs corrupted by the diffusion stack itself, so inverting AI
|
|
97
|
+
rendering artifacts is exactly what it learned.
|
|
98
|
+
|
|
99
|
+
Two checkpoints are maintained: **Neural Robust** (recommended default)
|
|
100
|
+
and **Neural Detail** (best 1-px retention on very fine grids, the
|
|
101
|
+
fallback when Robust over-corrects).
|
|
102
|
+
|
|
103
|
+
## Limitations
|
|
104
|
+
|
|
105
|
+
- Fake-pixel pitch below ~2 px is undetectable by construction — set the
|
|
106
|
+
pitch manually or use the granularity control.
|
|
107
|
+
- One global grid per image: locally varying pitch is out of scope.
|
|
108
|
+
- Colors missing from a forced palette cannot be assigned well by any
|
|
109
|
+
method.
|
|
110
|
+
|
|
111
|
+
## Documentation
|
|
112
|
+
|
|
113
|
+
- [docs/APPROACH.md](docs/APPROACH.md) — the method: classical pipeline,
|
|
114
|
+
neural design, training-data philosophy, when to use which preset.
|
|
115
|
+
- [docs/VIEWER.md](docs/VIEWER.md) — the interactive workspace viewer.
|
|
116
|
+
- [docs/IMPLEMENTATION.md](docs/IMPLEMENTATION.md) — module layout, data
|
|
117
|
+
engine, retraining recipes, server API.
|
|
118
|
+
- [docs/DEVLOG.md](docs/DEVLOG.md) — the full iteration history: what was
|
|
119
|
+
tried, measured, rejected, and what's left as future work.
|
|
120
|
+
|
|
121
|
+
## License
|
|
122
|
+
|
|
123
|
+
**The pixel art you make with ai2pixelart is yours — sell it, ship it in a
|
|
124
|
+
commercial game, whatever you like.** The noncommercial terms restrict the
|
|
125
|
+
*tool*, not its output. Concretely:
|
|
126
|
+
|
|
127
|
+
- ✅ Use it (even at a company) to produce pixel art, and use that art
|
|
128
|
+
commercially.
|
|
129
|
+
- ✅ Modify and share the code and models for any noncommercial purpose,
|
|
130
|
+
keeping the credit notice.
|
|
131
|
+
- ❌ Commercialize the tool itself — selling it, or offering its
|
|
132
|
+
functionality as a paid/subscription/ad-supported product, service,
|
|
133
|
+
platform, or API — needs a separate license.
|
|
134
|
+
|
|
135
|
+
- **Source code** — [PolyForm Noncommercial License 1.0.0](LICENSE) + output carve-out.
|
|
136
|
+
- **Trained model weights** — [CC BY-NC 4.0](LICENSE-WEIGHTS) + output carve-out.
|
|
137
|
+
|
|
138
|
+
Credit: *ai2pixelart* © 2026 Arne Kummerow. These are source-available, not
|
|
139
|
+
OSI "open source." To build something commercial *on the tool*, get in
|
|
140
|
+
touch about a commercial license.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# ai2pixelart
|
|
2
|
+
|
|
3
|
+
Turn AI-generated *pseudo* pixel art (wobbly grids, mixed-color cells, way
|
|
4
|
+
too many shades) into pixel-perfect, palette-clean pixel art — without
|
|
5
|
+
losing the single-pixel details that carry the design.
|
|
6
|
+
|
|
7
|
+
## Setup
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install ai2pixelart # classical cleaner only (no GPU, no torch)
|
|
11
|
+
pip install "ai2pixelart[neural]" # + run the trained networks
|
|
12
|
+
pip install "ai2pixelart[gen]" # + full data-engine / training stack
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Or with conda for development:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
conda env create -f environment.yml # creates env "ai2pixelart"
|
|
19
|
+
conda activate ai2pixelart
|
|
20
|
+
pip install -e ".[dev]"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The classical "Simple" approach needs no GPU; the neural approaches need
|
|
24
|
+
the `neural` extra and download their weights from the Hugging Face Hub
|
|
25
|
+
([arnekummerow/ai2pixelart](https://huggingface.co/arnekummerow/ai2pixelart))
|
|
26
|
+
automatically on first use (cached thereafter — `ai2pixelart models
|
|
27
|
+
download` pre-fetches them for offline use).
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
ai2pixelart clean input.png -o out.png --preview-scale 8 # classical
|
|
33
|
+
ai2pixelart clean input.png -o out.png --approach robust # neural
|
|
34
|
+
ai2pixelart clean input.png -o out.png --palette "#1e2234,#5ea740,#f0f0f0"
|
|
35
|
+
ai2pixelart inspect my_images/ # no-ground-truth quality table
|
|
36
|
+
ai2pixelart viewer my_images/ # interactive web workspace, port 8412
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`--approach` is `simple` (classical, the default) or a neural model — a name
|
|
40
|
+
under `runs/` (`robust`, `detail`) or a path to a `.safetensors`/`.ckpt`
|
|
41
|
+
file. Run
|
|
42
|
+
`ai2pixelart --help` to see the full command tree (`clean`, `viewer`,
|
|
43
|
+
`inspect`, `eval`, `demo`, plus `data …` and `train …` for the pipeline).
|
|
44
|
+
|
|
45
|
+
## How it works
|
|
46
|
+
|
|
47
|
+
The classical pipeline ("Simple") detects the fake-pixel grid, extracts a
|
|
48
|
+
palette, and votes each cell's color — deterministic, no GPU. The neural
|
|
49
|
+
presets run the same grid/palette proposal, then a small U-Net assigns
|
|
50
|
+
each cell a palette entry; it is structurally incapable of off-palette
|
|
51
|
+
colors and carries learned priors (recovering pure 1-px details,
|
|
52
|
+
collapsing shaded backgrounds) that no local method can. It was trained
|
|
53
|
+
on pairs corrupted by the diffusion stack itself, so inverting AI
|
|
54
|
+
rendering artifacts is exactly what it learned.
|
|
55
|
+
|
|
56
|
+
Two checkpoints are maintained: **Neural Robust** (recommended default)
|
|
57
|
+
and **Neural Detail** (best 1-px retention on very fine grids, the
|
|
58
|
+
fallback when Robust over-corrects).
|
|
59
|
+
|
|
60
|
+
## Limitations
|
|
61
|
+
|
|
62
|
+
- Fake-pixel pitch below ~2 px is undetectable by construction — set the
|
|
63
|
+
pitch manually or use the granularity control.
|
|
64
|
+
- One global grid per image: locally varying pitch is out of scope.
|
|
65
|
+
- Colors missing from a forced palette cannot be assigned well by any
|
|
66
|
+
method.
|
|
67
|
+
|
|
68
|
+
## Documentation
|
|
69
|
+
|
|
70
|
+
- [docs/APPROACH.md](docs/APPROACH.md) — the method: classical pipeline,
|
|
71
|
+
neural design, training-data philosophy, when to use which preset.
|
|
72
|
+
- [docs/VIEWER.md](docs/VIEWER.md) — the interactive workspace viewer.
|
|
73
|
+
- [docs/IMPLEMENTATION.md](docs/IMPLEMENTATION.md) — module layout, data
|
|
74
|
+
engine, retraining recipes, server API.
|
|
75
|
+
- [docs/DEVLOG.md](docs/DEVLOG.md) — the full iteration history: what was
|
|
76
|
+
tried, measured, rejected, and what's left as future work.
|
|
77
|
+
|
|
78
|
+
## License
|
|
79
|
+
|
|
80
|
+
**The pixel art you make with ai2pixelart is yours — sell it, ship it in a
|
|
81
|
+
commercial game, whatever you like.** The noncommercial terms restrict the
|
|
82
|
+
*tool*, not its output. Concretely:
|
|
83
|
+
|
|
84
|
+
- ✅ Use it (even at a company) to produce pixel art, and use that art
|
|
85
|
+
commercially.
|
|
86
|
+
- ✅ Modify and share the code and models for any noncommercial purpose,
|
|
87
|
+
keeping the credit notice.
|
|
88
|
+
- ❌ Commercialize the tool itself — selling it, or offering its
|
|
89
|
+
functionality as a paid/subscription/ad-supported product, service,
|
|
90
|
+
platform, or API — needs a separate license.
|
|
91
|
+
|
|
92
|
+
- **Source code** — [PolyForm Noncommercial License 1.0.0](LICENSE) + output carve-out.
|
|
93
|
+
- **Trained model weights** — [CC BY-NC 4.0](LICENSE-WEIGHTS) + output carve-out.
|
|
94
|
+
|
|
95
|
+
Credit: *ai2pixelart* © 2026 Arne Kummerow. These are source-available, not
|
|
96
|
+
OSI "open source." To build something commercial *on the tool*, get in
|
|
97
|
+
touch about a commercial license.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ai2pixelart"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Turn AI-generated pseudo pixel art into pixel-perfect, palette-clean pixel art"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
authors = [{ name = "Arne Kummerow", email = "arne.kumero@gmail.com" }]
|
|
12
|
+
# Source code only. The trained model weights are licensed separately under
|
|
13
|
+
# CC BY-NC 4.0 (see LICENSE-WEIGHTS) and are not shipped in the wheel.
|
|
14
|
+
license = "PolyForm-Noncommercial-1.0.0"
|
|
15
|
+
license-files = ["LICENSE"]
|
|
16
|
+
keywords = ["pixel-art", "image-restoration", "image-to-image", "retro", "gamedev"]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 4 - Beta",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"Intended Audience :: End Users/Desktop",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Operating System :: OS Independent",
|
|
26
|
+
"Topic :: Multimedia :: Graphics",
|
|
27
|
+
"Topic :: Scientific/Engineering :: Image Processing",
|
|
28
|
+
]
|
|
29
|
+
dependencies = [
|
|
30
|
+
"numpy",
|
|
31
|
+
"scipy",
|
|
32
|
+
"scikit-image",
|
|
33
|
+
"pillow",
|
|
34
|
+
"click",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.optional-dependencies]
|
|
38
|
+
# run the trained networks (inference)
|
|
39
|
+
neural = ["torch", "safetensors", "huggingface_hub"]
|
|
40
|
+
# full data engine + training stack (implies neural)
|
|
41
|
+
gen = ["ai2pixelart[neural]", "diffusers", "transformers", "accelerate", "peft"]
|
|
42
|
+
dev = ["ai2pixelart[neural]", "pytest"]
|
|
43
|
+
|
|
44
|
+
[project.urls]
|
|
45
|
+
Homepage = "https://github.com/ArneKummerow/ai2pixelart"
|
|
46
|
+
Repository = "https://github.com/ArneKummerow/ai2pixelart"
|
|
47
|
+
"Model weights" = "https://huggingface.co/arnekummerow/ai2pixelart"
|
|
48
|
+
|
|
49
|
+
[project.scripts]
|
|
50
|
+
ai2pixelart = "ai2pixelart.cli:main"
|
|
51
|
+
|
|
52
|
+
[tool.setuptools.packages.find]
|
|
53
|
+
where = ["src"]
|
|
54
|
+
|
|
55
|
+
[tool.setuptools.package-data]
|
|
56
|
+
ai2pixelart = ["viewer.html"]
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""No-ground-truth quality assessment of a cleanup result.
|
|
2
|
+
|
|
3
|
+
Real AI images have no ground truth, so these metrics judge a CleanResult
|
|
4
|
+
against the input itself, each targeting one observed failure mode:
|
|
5
|
+
|
|
6
|
+
- grid: `boundary_snr` (gradient mass on cell edges vs interiors — is the
|
|
7
|
+
detected grid where the real fake-pixel boundaries are?) and
|
|
8
|
+
`pitch_consistency` (tile-wise re-estimation vs the global pitch — a
|
|
9
|
+
single global grid on mixed-pitch content is the pipeline's known
|
|
10
|
+
structural limitation, this measures how much an image violates it).
|
|
11
|
+
- palette representation: `cell_fit_mean` / `cell_fit_p95` (ΔE between each
|
|
12
|
+
sampled cell color and the palette entry it received — high values mean
|
|
13
|
+
the palette is too small/flattened for this image, e.g. absorbed shade
|
|
14
|
+
families).
|
|
15
|
+
- pixel-art flatness: `speckle_rate` (isolated single-cell index flips that
|
|
16
|
+
a detail-guarded mode filter would remove) and `shade_flicker` (adjacent
|
|
17
|
+
cells assigned near-duplicate entries <4 ΔE apart — soft-gradient banding
|
|
18
|
+
quantized into mottle; true pixel art places deliberately distinct colors
|
|
19
|
+
next to each other).
|
|
20
|
+
- detail: `detail_survival` (sampled cells that are strong chromatic
|
|
21
|
+
outliers vs their 4-neighborhood must remain outliers in the output — the
|
|
22
|
+
1-px-detail promise, measured without ground truth).
|
|
23
|
+
|
|
24
|
+
All ΔE are CIE76.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import numpy as np
|
|
30
|
+
|
|
31
|
+
from ai2pixelart.grid import estimate_grid
|
|
32
|
+
from ai2pixelart.palette import delta_e, rgb_to_lab
|
|
33
|
+
from ai2pixelart.pipeline import CleanResult
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def boundary_snr(img: np.ndarray, result: CleanResult) -> float | None:
|
|
37
|
+
"""Gradient mass on detected cell boundaries / interior average (per
|
|
38
|
+
axis, averaged). > 1.5 means the grid sits on real edges."""
|
|
39
|
+
if result.grid is None:
|
|
40
|
+
return None
|
|
41
|
+
ratios = []
|
|
42
|
+
for axis, ax_grid in ((0, result.grid.y), (1, result.grid.x)):
|
|
43
|
+
g = np.abs(np.diff(img.astype(np.float64), axis=axis)).sum(axis=2).mean(axis=1 - axis)
|
|
44
|
+
edge = np.round(ax_grid.edges[1:-1]).astype(int) - 1
|
|
45
|
+
edge = edge[(edge >= 0) & (edge < len(g))]
|
|
46
|
+
if not len(edge):
|
|
47
|
+
return None
|
|
48
|
+
on = np.zeros(len(g), dtype=bool)
|
|
49
|
+
on[edge] = True
|
|
50
|
+
if on.all() or not on.any():
|
|
51
|
+
return None
|
|
52
|
+
ratios.append(g[on].mean() / max(g[~on].mean(), 1e-9))
|
|
53
|
+
return float(np.mean(ratios))
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def pitch_consistency(img: np.ndarray, result: CleanResult, tiles: int = 3) -> float | None:
|
|
57
|
+
"""Fraction of image tiles whose locally-estimated pitch agrees (±10%)
|
|
58
|
+
with the global grid, among tiles where estimation succeeds. Low values
|
|
59
|
+
flag mixed-pitch content (out of scope for the single global grid)."""
|
|
60
|
+
if result.grid is None:
|
|
61
|
+
return None
|
|
62
|
+
h, w = img.shape[:2]
|
|
63
|
+
if min(h, w) < tiles * 64:
|
|
64
|
+
return None
|
|
65
|
+
agree = total = 0
|
|
66
|
+
for ty in range(tiles):
|
|
67
|
+
for tx in range(tiles):
|
|
68
|
+
tile = img[ty * h // tiles : (ty + 1) * h // tiles, tx * w // tiles : (tx + 1) * w // tiles]
|
|
69
|
+
g = estimate_grid(tile)
|
|
70
|
+
if g is None:
|
|
71
|
+
continue
|
|
72
|
+
total += 1
|
|
73
|
+
ok_y = abs(g.y.pitch / result.grid.y.pitch - 1) <= 0.1
|
|
74
|
+
ok_x = abs(g.x.pitch / result.grid.x.pitch - 1) <= 0.1
|
|
75
|
+
agree += ok_y and ok_x
|
|
76
|
+
return float(agree / total) if total else None
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def cell_fit(result: CleanResult) -> tuple[float, float]:
|
|
80
|
+
"""(mean, p95) ΔE between sampled cell colors and their palette entry."""
|
|
81
|
+
d = delta_e(rgb_to_lab(result.raw_cells), rgb_to_lab(result.palette[result.indices]))
|
|
82
|
+
return float(d.mean()), float(np.percentile(d, 95))
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _neighbor_stacks(a: np.ndarray) -> np.ndarray:
|
|
86
|
+
"""The 4-neighborhood of each entry, edge-padded: (4, H, W, ...)."""
|
|
87
|
+
pad = np.pad(a, [(1, 1), (1, 1)] + [(0, 0)] * (a.ndim - 2), mode="edge")
|
|
88
|
+
return np.stack([pad[:-2, 1:-1], pad[2:, 1:-1], pad[1:-1, :-2], pad[1:-1, 2:]])
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def speckle_rate(result: CleanResult, tol: float = 3.0) -> float:
|
|
92
|
+
"""Fraction of cells that disagree with a >=5/8 neighborhood majority
|
|
93
|
+
while fitting the majority's entry within `tol` ΔE of their own — i.e.
|
|
94
|
+
quantization speckle a detail-guarded mode filter would remove."""
|
|
95
|
+
from ai2pixelart.pipeline import smooth_indices
|
|
96
|
+
|
|
97
|
+
smoothed = smooth_indices(result.indices, result.raw_cells, result.palette, tol=tol, passes=1)
|
|
98
|
+
return float((smoothed != result.indices).mean())
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def shade_flicker(result: CleanResult, close_de: float = 4.0) -> float:
|
|
102
|
+
"""Fraction of adjacent cell pairs whose (different) palette entries are
|
|
103
|
+
nearly the same color — soft-gradient banding rendered as mottle."""
|
|
104
|
+
lab_pal = rgb_to_lab(result.palette)
|
|
105
|
+
idx = result.indices
|
|
106
|
+
pairs = flicker = 0
|
|
107
|
+
for a, b in ((idx[:, 1:], idx[:, :-1]), (idx[1:, :], idx[:-1, :])):
|
|
108
|
+
differ = a != b
|
|
109
|
+
close = delta_e(lab_pal[a], lab_pal[b]) < close_de
|
|
110
|
+
flicker += int((differ & close).sum())
|
|
111
|
+
pairs += differ.size
|
|
112
|
+
return float(flicker / max(pairs, 1))
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def detail_survival(result: CleanResult, outlier_de: float = 20.0) -> tuple[float, int]:
|
|
116
|
+
"""(survival rate, n details): sampled cells whose color is far from all
|
|
117
|
+
4 neighbors (>= outlier_de) must stay distinct from all 4 neighbors in
|
|
118
|
+
the output."""
|
|
119
|
+
lab_raw = rgb_to_lab(result.raw_cells)
|
|
120
|
+
raw_min = delta_e(_neighbor_stacks(lab_raw), lab_raw[None]).min(axis=0)
|
|
121
|
+
details = raw_min >= outlier_de
|
|
122
|
+
n = int(details.sum())
|
|
123
|
+
if n == 0:
|
|
124
|
+
return 1.0, 0
|
|
125
|
+
lab_out = rgb_to_lab(result.palette)[result.indices]
|
|
126
|
+
out_min = delta_e(_neighbor_stacks(lab_out), lab_out[None]).min(axis=0)
|
|
127
|
+
return float((out_min[details] >= outlier_de * 0.5).mean()), n
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def assess(img: np.ndarray, result: CleanResult) -> dict:
|
|
131
|
+
"""All no-GT metrics for one cleanup run."""
|
|
132
|
+
fit_mean, fit_p95 = cell_fit(result)
|
|
133
|
+
survival, n_details = detail_survival(result)
|
|
134
|
+
return {
|
|
135
|
+
"boundary_snr": _r(boundary_snr(img, result)),
|
|
136
|
+
"pitch_consistency": _r(pitch_consistency(img, result)),
|
|
137
|
+
"cell_fit_mean": round(fit_mean, 2),
|
|
138
|
+
"cell_fit_p95": round(fit_p95, 2),
|
|
139
|
+
"speckle_rate": round(speckle_rate(result), 4),
|
|
140
|
+
"shade_flicker": round(shade_flicker(result), 4),
|
|
141
|
+
"detail_survival": round(survival, 3),
|
|
142
|
+
"n_details": n_details,
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _r(v, nd: int = 2):
|
|
147
|
+
return None if v is None else round(v, nd)
|