Skip to main content

🎨 mozyq

mozyq is a Python command-line tool (mzq) that turns a collection of photographs into a mosaic video. Each frame of the video is a mosaic: a grid of small tile images, drawn from your photo collection, arranged so that from a distance they reproduce a "master" image. The video zooms out from a single tile to the full mosaic, then the tile it started on becomes the next master — chaining several mosaics into one continuous animation.

✨ Features

  • 🖼️ Photo mosaics from any folder of images
  • 🎬 Zoom-transition videos that chain multiple mosaics together
  • 🎯 Optimal tile matching via the Hungarian algorithm (scipy.optimize.linear_sum_assignment)
  • 🌈 Perceptual color matching in LAB color space (OpenCV)
  • 📐 Square or rectangular tiles and output
  • 🛠️ Built-in image normalization to prepare a collection

🚀 Quick start

Prerequisites

  • Python 3.10–3.12

  • ffmpeg on your PATH — used to encode frames into a video:

    sudo apt install ffmpeg     # Debian/Ubuntu
    brew install ffmpeg         # macOS
    choco install ffmpeg        # Windows
    

Install

pip install mozyq

Minimal pipeline

# 1. Collect at least grid_size² images in a folder (81 for the default 9×9).
mkdir photos
seq 200 | xargs -I{} -P8 wget -q https://picsum.photos/630 -O photos/{}.jpg

# 2. Normalize them to a uniform size and format.
mzq normalize photos/ normalized/

# 3. Build the mosaic chain from a seed image (tiles come from its folder).
mzq json normalized/0000.jpg mzq.json --grid-size 9

# 4. Render the video frames.
mzq frames mzq.json frames/

# 5. Encode with ffmpeg.
ffmpeg -r 30 -i frames/%04d.jpg -c:v libx264 -pix_fmt yuv420p mosaic.mp4

📚 Commands

Run mzq --help or mzq <command> --help for the authoritative, up-to-date usage.

Supported image extensions everywhere: .jpg .jpeg .png .bmp .tiff .tif .webp.

mzq normalize

mzq normalize IN_FOLDER OUT_FOLDER [OPTIONS]

Reads every image in IN_FOLDER, resizes it to --target-width (preserving aspect ratio), center-crops it to --target-height, converts to RGB, and writes sequentially numbered JPEGs (0000.jpg, 0001.jpg, …) into OUT_FOLDER. Images smaller than --min-width × --min-height, or whose aspect ratio makes them too short to crop to the target height, are skipped.

Option Default Meaning
--min-width 630 Skip inputs narrower than this
--min-height 630 Skip inputs shorter than this
--target-width 630 Output width
--target-height 630 Output height

mzq json

mzq json MASTER [OUTPUT_JSON] [OPTIONS]

Builds a chain of mosaics and writes it to OUTPUT_JSON (default mzq.json). The tiles are taken from the folder containing MASTER. For each transition it solves an optimal tile-to-cell assignment for the current master, then picks a random tile in the result to become the next master — repeating up to --max-transitions times (stopping early if a master repeats).

Argument / option Default Meaning
MASTER — Seed image; its folder supplies the tiles
OUTPUT_JSON mzq.json Output path
--grid-size 9 Tiles per row/column; must be odd
--max-transitions 10 Maximum number of mosaics in the chain

Requires at least grid_size² images in the tile folder (81 for --grid-size 9, 441 for --grid-size 21).

mzq frames

mzq frames MZQ_JSON OUT_FOLDER [OPTIONS]

Reads a JSON file produced by mzq json and renders the zoom animation as sequentially numbered JPEG frames (0000.jpg, 0001.jpg, …) in OUT_FOLDER.

Option Default Meaning
--fpt 60 Frames rendered per transition
--crossfade-frames 60 Frames spent cross-fading the mosaic into the master image at the end of each transition (higher = slower fade)

Feed the frames to ffmpeg to produce the final video:

ffmpeg -r 30 -i OUT_FOLDER/%04d.jpg -c:v libx264 -preset slow -crf 18 -pix_fmt yuv420p mosaic.mp4

mzq json-full

mzq json-full TILE_FOLDER [OUTPUT_JSON] [OPTIONS]

Computes the best tile arrangement for every image in TILE_FOLDER (each image treated as its own master) and writes a {image_stem: [tile_stem, …]} map to OUTPUT_JSON (default full_mzq.json). This is a bulk lookup table, not a video description — mzq frames does not consume it.

Option Default Meaning
--grid-size 16 Tiles per row/column

Requires at least grid_size² images in TILE_FOLDER.

🔧 How it works

  1. Normalize the collection to uniform dimensions.
  2. Vectorize each tile: downscale in LAB color space and flatten to a feature vector.
  3. Split the master into a grid_size × grid_size set of cells.
  4. Assign tiles to cells by minimizing total Euclidean distance in LAB space with the Hungarian algorithm.
  5. Animate: mzq frames renders a cosine-eased zoom out from the start tile to the full mosaic, then cross-fades toward the master image over --crossfade-frames frames at the end of each transition.

Each output frame is the size of the normalized images, so a larger mzq normalize target produces a higher-resolution video.

🐛 Troubleshooting

Message Fix
Need at least N images for GxG grid Add more images, or lower --grid-size
num_tiles must be odd mzq json needs an odd --grid-size (9, 15, 21, …)
No supported image files found Folder has no .jpg/.png/… files
Could not read image from … A file is corrupt or not a real image
ffmpeg: command not found Install ffmpeg (see Prerequisites)

Created with ❤️ by Gilad Kutiel

Metadata

Release files for mozyq 0.0.24

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mozyq 0.0.24
File Size Uploaded Provenance Actions
mozyq-0.0.24.tar.gz 977.0 kB Without provenance Details for mozyq-0.0.24.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for mozyq 0.0.24
File Interpreter ABI Platform Actions
mozyq-0.0.24-py3-none-any.whl Python 3 none any Details for mozyq-0.0.24-py3-none-any.whl

Total release size: 990.2 kB

Release files / mozyq-0.0.24.tar.gz

Download URL mozyq-0.0.24.tar.gz
Size 977.0 kB
Tags Source
SHA-256 checksum
How to use checksums
603fb8ebe44c67e2de11cf8383a754f29b1df9688b2023126a72b296ceb28df0
BLAKE2b-256 checksum
How to use checksums
9e496a84dba0fd39bd5c678e268cfec53ae011a1a90d758a36d682a03e43aa73
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.7.13

Release files / mozyq-0.0.24-py3-none-any.whl

Download URL mozyq-0.0.24-py3-none-any.whl
Size 13.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
eae44f26ab240765ca8c98c4564d3f79b7001e94dacce2ed31650c910ade50f5
BLAKE2b-256 checksum
How to use checksums
16b25814f0da214d96c4d0ad397e9f02afc453141f8ddfab2520f64d5a6f6db2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.7.13

Security for mozyq 0.0.24

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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page