metalgrow
AI-powered image upscaler accelerated on Apple Metal (MPS).
metalgrow runs super-resolution on Apple Silicon GPUs through PyTorch's MPS
backend, with graceful fallbacks to CUDA and CPU. It ships with a bicubic
baseline so the pipeline is runnable end-to-end, and exposes a clean seam for
plugging in a learned backbone (Real-ESRGAN, SwinIR, etc.).
Table of contents
Features
- 🍎 Apple Metal first — PyTorch MPS backend, zero config on Apple Silicon
- 🔁 Portable — automatic fallback to CUDA / CPU when MPS isn't available
- 🧩 Pluggable backbones — bicubic, Real-ESRGAN (x2/x4), SwinIR (x2/x4)
- 🧱 Tiled inference — process arbitrarily large images with feathered overlap blending
- 📂 Batch mode — upscale whole directories or globs with a progress bar
- 🖨️ Print-aware —
--preserve-metadatakeeps grayscale mode, the ICC profile, and a rescaled DPI tag intact - 📰 Descreen —
--descreenremoves halftone screens (moiré) from scanned print before upscaling, leaving text and linework untouched - 📦 Model registry —
metalgrow modelsmanages cached weights with sha256 verification - 🧪 Tested — pytest suite covering the CPU baseline, tiling, batch mode, and registry
Requirements
- Python 3.11+
- macOS with Apple Silicon for MPS acceleration (optional — CPU/CUDA also work)
Install
git clone https://github.com/joaodotwork/metalgrow.git
cd metalgrow
uv venv && source .venv/bin/activate
uv pip install -e .
Or with plain pip:
python -m venv .venv && source .venv/bin/activate
pip install -e .
Usage
CLI
Single file:
metalgrow info
metalgrow upscale input.jpg out.png --scale 2
metalgrow upscale input.jpg out.png --scale 4 --device mps --backbone realesrgan-x4
Whole directory or glob (writes to a target directory, mirroring filenames):
metalgrow upscale ./photos ./photos-upscaled --scale 2 --backbone realesrgan-x2
metalgrow upscale "./photos/*.png" ./out --scale 2 --skip-existing
| Flag | Default | Description |
|---|---|---|
--scale, -s |
2.0 |
Upscale factor (1.01–8.0); learned backbones run their native factor then resample to this exact target |
--device, -d |
auto |
auto | mps | cuda | cpu |
--backbone, -b |
bicubic |
bicubic | realesrgan-x{2,4} | swinir-x{2,4} |
--dtype |
fp32 |
fp32 | fp16 (fp16 is MPS-only and noisier) |
--tile |
backbone hint | Tile size in input px for tiled inference (0 disables) |
--tile-pad |
backbone hint | Context padding per tile edge (covers backbone receptive field) |
--skip-existing |
off | Batch mode: skip outputs that already exist |
--preserve-metadata, -p |
off | Keep grayscale mode, embedded ICC profile, and a rescaled DPI tag (for print / preflight workflows) |
--workers, -j |
4 |
Batch mode: parallel I/O workers (inference stays serial) |
--descreen |
off | Remove halftone screens (moiré) from scanned print before the backbone runs |
--descreen-strength |
1.0 |
Notch depth, 0–1 |
Descreen scanned print
Newspaper and magazine scans carry the printer's halftone dot screen, which
SR backbones sharpen into moiré. --descreen removes it first. Detection is
local and only touches regions that actually contain a dot lattice — type,
rules and blank paper come back unchanged. See
ADR 0002 for how it works.
# descreen + upscale, keeping DPI / ICC / grayscale for print
metalgrow upscale scan.tif out.tif --descreen -p --scale 1.5 -b swinir-x2
# descreen only, no resize
metalgrow descreen scan.tif clean.tif -p
Manage cached model weights
metalgrow models list
metalgrow models download realesrgan-x4
metalgrow models rm realesrgan-x4
Weights cache to ~/.cache/metalgrow/ by default; override with the
METALGROW_CACHE_DIR env var. Every download is sha256-verified.
See docs/models.md for a quality / speed / memory
comparison (with benchmark numbers) and guidance on which backbone to pick,
or docs/usage.md for the full CLI / library reference.
Library
from PIL import Image
from metalgrow import Upscaler
upscaler = Upscaler(backbone="realesrgan-x2", device="auto") # auto | mps | cuda | cpu
result = upscaler.upscale(
Image.open("input.jpg").convert("RGB"),
scale=2.0,
tile=256, # optional — backbone has sensible defaults
tile_pad=16,
)
result.save("out.png")
Project layout
src/metalgrow/
device.py # device auto-selection (MPS → CUDA → CPU)
upscaler.py # Upscaler class + tiled inference with overlap blending
batch.py # directory / glob batch mode
descreen.py # local FFT notch filter for halftone screens
metadata.py # ICC / DPI / grayscale capture + reapply
weights.py # weight registry, sha256 verification, cache management
cli.py # typer CLI (upscale, descreen, info, models)
backbones/ # bicubic, realesrgan, swinir; plugin registry
tests/
docs/
usage.md # CLI recipes, library API, env vars, pitfalls
models.md # backbone comparison, benchmark snapshot, tiling
benchmarks.md # full (device × backbone × scale) tables
architecture.md # module layout, data flow, extension points
Roadmap
- Integrate a learned SR backbone (Real-ESRGAN default)
- Tiled inference for large images
- Batch / directory mode
- Model registry + weight download
- Second backbone family (SwinIR)
- GitHub Actions CI (lint + test on macOS + Linux)
- Benchmarks: MPS vs CPU (CUDA pending GPU runner)
- v0.1.0 release
Contributing
Issues and PRs are welcome. Please run ruff and pytest before opening a PR.
License
MIT — see LICENSE.
Made with 🔩 on Apple Silicon.
Metadata
Release files for metalgrow 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| metalgrow-0.2.0.tar.gz | 111.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| metalgrow-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 140.7 kB
Release files / metalgrow-0.2.0.tar.gz
| Download URL | metalgrow-0.2.0.tar.gz |
|---|---|
| Size | 111.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
44641e82e43dd86d81d3bb048f2f923664b7953f9b81e3046be9a40896e9c882
|
|
BLAKE2b-256 checksum How to use checksums |
923eb2cecb10ea450e147c99955d147b0348b2d5cd9e17e735e19de866f73501
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 2, 2026.
Transparency logRelease files / metalgrow-0.2.0-py3-none-any.whl
| Download URL | metalgrow-0.2.0-py3-none-any.whl |
|---|---|
| Size | 29.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1a93d542affc0b00aca2da46ef502782bee70392e6851ee9824f113bf10dbd50
|
|
BLAKE2b-256 checksum How to use checksums |
695c1bbef44c1ea4aaaced1553adef3eebc0dfdc35ed49adad61d73182f88cc2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 2, 2026.
Transparency log