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.
Files changed (44) hide show
  1. ai2pixelart-0.1.0/LICENSE +112 -0
  2. ai2pixelart-0.1.0/PKG-INFO +140 -0
  3. ai2pixelart-0.1.0/README.md +97 -0
  4. ai2pixelart-0.1.0/pyproject.toml +56 -0
  5. ai2pixelart-0.1.0/setup.cfg +4 -0
  6. ai2pixelart-0.1.0/src/ai2pixelart/__init__.py +5 -0
  7. ai2pixelart-0.1.0/src/ai2pixelart/autoqa.py +147 -0
  8. ai2pixelart-0.1.0/src/ai2pixelart/cli.py +516 -0
  9. ai2pixelart-0.1.0/src/ai2pixelart/corrupt.py +64 -0
  10. ai2pixelart-0.1.0/src/ai2pixelart/corruptor.py +190 -0
  11. ai2pixelart-0.1.0/src/ai2pixelart/demo.py +83 -0
  12. ai2pixelart-0.1.0/src/ai2pixelart/grid.py +415 -0
  13. ai2pixelart-0.1.0/src/ai2pixelart/imgedit.py +72 -0
  14. ai2pixelart-0.1.0/src/ai2pixelart/metrics.py +95 -0
  15. ai2pixelart-0.1.0/src/ai2pixelart/models.py +247 -0
  16. ai2pixelart-0.1.0/src/ai2pixelart/nndata.py +227 -0
  17. ai2pixelart-0.1.0/src/ai2pixelart/nninfer.py +152 -0
  18. ai2pixelart-0.1.0/src/ai2pixelart/nnmodel.py +76 -0
  19. ai2pixelart-0.1.0/src/ai2pixelart/pairgen.py +253 -0
  20. ai2pixelart-0.1.0/src/ai2pixelart/palette.py +285 -0
  21. ai2pixelart-0.1.0/src/ai2pixelart/pipeline.py +287 -0
  22. ai2pixelart-0.1.0/src/ai2pixelart/rails.py +43 -0
  23. ai2pixelart-0.1.0/src/ai2pixelart/spritegen.py +326 -0
  24. ai2pixelart-0.1.0/src/ai2pixelart/train.py +141 -0
  25. ai2pixelart-0.1.0/src/ai2pixelart/vae.py +62 -0
  26. ai2pixelart-0.1.0/src/ai2pixelart/viewer.html +849 -0
  27. ai2pixelart-0.1.0/src/ai2pixelart/webapp.py +471 -0
  28. ai2pixelart-0.1.0/src/ai2pixelart.egg-info/PKG-INFO +140 -0
  29. ai2pixelart-0.1.0/src/ai2pixelart.egg-info/SOURCES.txt +42 -0
  30. ai2pixelart-0.1.0/src/ai2pixelart.egg-info/dependency_links.txt +1 -0
  31. ai2pixelart-0.1.0/src/ai2pixelart.egg-info/entry_points.txt +2 -0
  32. ai2pixelart-0.1.0/src/ai2pixelart.egg-info/requires.txt +21 -0
  33. ai2pixelart-0.1.0/src/ai2pixelart.egg-info/top_level.txt +1 -0
  34. ai2pixelart-0.1.0/tests/test_autoqa.py +32 -0
  35. ai2pixelart-0.1.0/tests/test_grid.py +84 -0
  36. ai2pixelart-0.1.0/tests/test_metrics.py +54 -0
  37. ai2pixelart-0.1.0/tests/test_models.py +135 -0
  38. ai2pixelart-0.1.0/tests/test_nn.py +372 -0
  39. ai2pixelart-0.1.0/tests/test_palette.py +112 -0
  40. ai2pixelart-0.1.0/tests/test_pipeline.py +179 -0
  41. ai2pixelart-0.1.0/tests/test_rails.py +30 -0
  42. ai2pixelart-0.1.0/tests/test_spritegen.py +47 -0
  43. ai2pixelart-0.1.0/tests/test_vae.py +90 -0
  44. 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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,5 @@
1
+ """ai2pixelart: turn AI-generated pseudo pixel art into pixel-perfect pixel art."""
2
+
3
+ from ai2pixelart.pipeline import CleanResult, clean
4
+
5
+ __all__ = ["clean", "CleanResult"]
@@ -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)