Metadata-Version: 2.4
Name: mosaicraft
Version: 0.4.2
Summary: Perceptual photomosaic generator — Oklab color matching, MKL optimal transport, Hungarian placement, and Oklch tile-pool expansion
Project-URL: Homepage, https://github.com/hinanohart/mosaicraft
Project-URL: Documentation, https://github.com/hinanohart/mosaicraft#readme
Project-URL: Repository, https://github.com/hinanohart/mosaicraft
Project-URL: Issues, https://github.com/hinanohart/mosaicraft/issues
Project-URL: Changelog, https://github.com/hinanohart/mosaicraft/blob/main/CHANGELOG.md
Author: mosaicraft contributors
License: MIT License
        
        Copyright (c) 2026 mosaicraft contributors
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: art,color-transfer,computer-vision,hungarian-algorithm,image-processing,mosaic,oklab,oklch,optimal-transport,perceptual-color,photomosaic
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Multimedia :: Graphics
Classifier: Topic :: Scientific/Engineering :: Image Processing
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: numpy>=1.23.5
Requires-Dist: opencv-python>=4.8.1.78
Requires-Dist: scikit-image>=0.21
Requires-Dist: scipy>=1.13.1
Provides-Extra: dev
Requires-Dist: bandit>=1.7; extra == 'dev'
Requires-Dist: build>=1.0; extra == 'dev'
Requires-Dist: mypy>=1.5; extra == 'dev'
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Requires-Dist: twine>=4.0; extra == 'dev'
Provides-Extra: faiss
Requires-Dist: faiss-cpu>=1.13.0; extra == 'faiss'
Description-Content-Type: text/markdown

# mosaicraft

**A Python photomosaic generator that runs every step of the pipeline in a perceptual colour space, and makes every cell of the output a distinct photograph.**

[![PyPI version](https://img.shields.io/pypi/v/mosaicraft.svg)](https://pypi.org/project/mosaicraft/)
[![Python](https://img.shields.io/pypi/pyversions/mosaicraft.svg)](https://pypi.org/project/mosaicraft/)
[![CI](https://github.com/hinanohart/mosaicraft/actions/workflows/ci.yml/badge.svg)](https://github.com/hinanohart/mosaicraft/actions/workflows/ci.yml)
[![License](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/hinanohart/mosaicraft/blob/main/LICENSE)
[![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://github.com/astral-sh/ruff)

![Zoom in on the mosaic - every square is one animal photograph](https://raw.githubusercontent.com/hinanohart/mosaicraft/main/docs/images/zoom_detail.jpg)

*The whole image is a mosaic of Vermeer's *Girl with a Pearl Earring*; zoom into any square and it's a different animal photograph.*

`mosaicraft` rebuilds a target image as a grid of smaller tile photographs. Unlike most photomosaic libraries — which match mean colour in RGB/HSV and reuse the same tile dozens of times — `mosaicraft` runs **Oklab** for perceptual colour matching, **MKL optimal transport** for per-tile colour transfer, and **Hungarian 1:1 assignment** so every cell is a distinct photograph. Laplacian pyramid blending is also available (set `--preset ultra` when you want the cell seams smoothed away at the cost of some tile sharpness).

```bash
pip install mosaicraft
mosaicraft generate photo.jpg --tiles ./tiles --output mosaic.jpg
```

## The tile pool

![Animal close-up tile pool](https://raw.githubusercontent.com/hinanohart/mosaicraft/main/docs/images/tiles_sample.jpg)

*A sample from the curated animal close-up pool (5,000 photographs, CC BY 2.0 / Open Images V7). Hungarian 1:1 assignment picks exactly one cell for each photograph.*

## Installation

```bash
pip install mosaicraft                # PyPI
pip install "mosaicraft[faiss]"       # with FAISS for huge tile pools
```

Requires Python 3.10+, NumPy ≥ 1.23, OpenCV ≥ 4.6, SciPy ≥ 1.10, scikit-image ≥ 0.20. No GPU required.

## Quick start

```bash
mosaicraft generate photo.jpg --tiles ./tiles --output mosaic.jpg -n 5000
```

```python
from mosaicraft import MosaicGenerator
MosaicGenerator(tile_dir="./tiles").generate("photo.jpg", "mosaic.jpg", target_tiles=5000)
```

## Why Oklab / Why MKL

**Why Oklab?** CIELAB was calibrated on small colour differences; it underestimates perceptual distance for the large jumps a photomosaic routinely makes. Oklab ([Björn Ottosson, 2020](https://bottosson.github.io/posts/oklab/)) was rebuilt on modern colour-difference data and is noticeably more uniform on the saturated regions a photomosaic spends most of its compute budget matching. Dropping it into the cost function is free and visibly improves matches on saturated photos.

**Why MKL optimal transport?** Reinhard color transfer matches the first and second moments of the LAB distribution. MKL ([Pitié et al., 2007](https://www.researchgate.net/publication/220056262)) matches the full covariance, so the *shape* of the tile's color distribution is preserved as its statistics slide toward the target cell. Details survive; averages don't win.

## Reproducing the figures

The zoom-detail and tile-pool images above are regenerated by `python scripts/generate_readme_figures.py` after running `scripts/download_demo_assets.py` (one-time, ~450 MB). The tile pool comes from the **Open Images V7** dataset (Google, 2022); to refresh it, run `python scripts/curate_oiv7_animals.py` (downloads ~180 MB of metadata into `.cache/oiv7/`, HEAD-validates every retained URL, and rejects rows whose `Rotation` flag is non-zero so sideways Flickr thumbnails do not enter the pool). The curator filters by exact-match `License == https://creativecommons.org/licenses/by/2.0/` so the pool contains only CC BY 2.0 images — no CC BY-SA, no CC BY-NC, no public-domain mixing. Per-image Title, Author, Source (landing URL), and License are surfaced in `docs/assets/ATTRIBUTION.md` (auto-generated by `scripts/generate_attribution.py` per Creative Commons TASL practice) and the file is enforced in CI by `scripts/verify_attribution.py`.

## Testing

```bash
pip install -e ".[dev]"
pytest                        # unit + pipeline + CLI tests
ruff check src tests          # lint
bandit -r src -ll             # security scan
```

## Contributing

Bug reports, feature requests, and pull requests are welcome. See [CONTRIBUTING.md](https://github.com/hinanohart/mosaicraft/blob/main/CONTRIBUTING.md) for the development workflow. Security issues: see [SECURITY.md](https://github.com/hinanohart/mosaicraft/blob/main/SECURITY.md).

## Verification (sigstore)

Releases from **v_next_** (released after 2026-05-16) include a sigstore keyless signature bundle
(`.sigstore` per artifact) attached to the GitHub Release.

### Verify a PyPI install

```bash
pip download <pkg-name>==<version> --no-deps -d ./verify
python -m sigstore verify github \
    --cert-identity 'https://github.com/hinanohart/mosaicraft/.github/workflows/release.yml@refs/tags/v<version>' \
    --cert-oidc-issuer 'https://token.actions.githubusercontent.com' \
    ./verify/*.whl ./verify/*.tar.gz
```

The corresponding `.sigstore` bundles can be downloaded from the GitHub Release page.

### Historic releases (pre-2026-05-16)

Earlier releases were published without sigstore bundles. Re-installing those versions
provides no cryptographic provenance — pin to a current release if assurance matters.

## License and image credits

MIT License for human use. See [LICENSE](https://github.com/hinanohart/mosaicraft/blob/main/LICENSE).

**AI/ML training opt-out**: this repository is opted out of AI/ML training, fine-tuning, and embedding generation — see [ai.txt](./ai.txt). Training use requires separately negotiated written permission.

Image credits:

- **Target painting** — Johannes Vermeer, *Girl with a Pearl Earring* (c. 1665), public domain, via [Wikimedia Commons](https://commons.wikimedia.org/).
- **Tile pool** — 5,000 animal close-up photographs from the [Open Images V7](https://storage.googleapis.com/openimages/web/index.html) dataset, restricted to images licensed under [CC BY 2.0](https://creativecommons.org/licenses/by/2.0/) (no share-alike, no non-commercial, no public-domain mixing). Curated by `scripts/curate_oiv7_animals.py` from a hand-picked list of 80 leaf classes in the Open Images Animal subtree (Cat, Dog, Fox, Owl, Tiger, etc.), with HEAD-probe URL validation to drop dead Flickr links and a Rotation-flag filter so the un-rotated Flickr thumbnails arrive upright. Each photograph is attributed to its photographer in `docs/assets/ATTRIBUTION.md` (auto-generated from `animal_tiles_index.json`, ~3,500 distinct Flickr accounts, with title + source URL per Creative Commons TASL practice) as required by CC BY 2.0. Photographers may withdraw their original uploads — the `MANIFEST.json` SHA256 check reports any URL that subsequently 404s.

## References

mosaicraft stands on the following classic and modern work:

- Björn Ottosson, *A perceptual color space for image processing* (2020, blog). Oklab.
- Pitié, F. et al., *The linear Monge-Kantorovitch linear colour mapping for example-based colour transfer* (IET-CVMP 2007). MKL.
- Reinhard, E. et al., *Color transfer between images* (IEEE CGA 2001).
- Zhang, R. et al., *The Unreasonable Effectiveness of Deep Features as a Perceptual Metric* (CVPR 2018). LPIPS.
- Wang, Z. et al., *Image quality assessment: from error visibility to structural similarity* (IEEE TIP 2004). SSIM.
- Tesfaldet, M. et al., *Convolutional Photomosaic Generation via Multi-Scale Perceptual Losses* (ECCVW 2018). Multi-scale perceptual loss for photomosaic quality assessment.
- Burt, P. & Adelson, E., *A multiresolution spline with application to image mosaics* (ACM ToG 1983). Laplacian pyramid blending.
- Kuhn, H. W., *The Hungarian method for the assignment problem* (Naval Research Logistics 1955).
