Skip to main content

Convert images to Lego mosaic patterns with perceptual color matching

Project description

legopic

Convert images to LEGO mosaic patterns with perceptual color matching.

CI codecov PyPI version Downloads Python License Checked with mypy Ruff


Installation

pip install legopic

Quick Start

Basic Conversion

from legopic import ConversionSession, ConvertConfig, Palette, load_image

# Load image and palette
image = load_image("photo.jpg")
palette = Palette.from_set(31197)  # Andy Warhol's Marilyn Monroe set

# Create session and convert
session = ConversionSession(image, palette, canvas_size=(48, 48))
session.convert()

# Access result
print(f"Similarity score: {session.similarity_score:.2f}")
rgb_array = session.canvas.to_array()  # numpy array (48, 48, 3)

for row in session.canvas.cells:
    for cell in row:
        print(f"({cell.x}, {cell.y}): {cell.color.name}")

Using All Standard LEGO Colors

from legopic import ConversionSession, Palette, load_image

# Load all 41 standard (opaque) LEGO colors
palette = Palette.from_set()  # No set_id = all standard colors

image = load_image("photo.jpg")
session = ConversionSession(image, palette, (48, 48))
session.convert()

Using a Custom Palette

from legopic import ConversionSession, Palette, Color, load_image

# Create a custom palette
palette = Palette([
    Color((255, 0, 0), "Red"),
    Color((0, 255, 0), "Green"),
    Color((0, 0, 255), "Blue"),
    Color((255, 255, 255), "White"),
    Color((0, 0, 0), "Black"),
])

image = load_image("photo.jpg")
session = ConversionSession(image, palette, canvas_size=(48, 48))
session.convert()

Different Downsampling Methods

from legopic import ConversionSession, ConvertConfig, Palette, load_image

image = load_image("photo.jpg")
palette = Palette.from_set(31198)  # The Beatles

session = ConversionSession(image, palette, (48, 48))

# Use different methods via ConvertConfig
config = ConvertConfig(method='match_then_mode')  # Best for sharp edges
session.convert(config)

# Re-convert with different method
session.reconvert(ConvertConfig(method='mean_then_match'))  # Best for gradients

Interactive Editing Workflow

from legopic import ConversionSession, ConvertConfig, Palette, Color, load_image

image = load_image("photo.jpg")
palette = Palette.from_set(31197)
session = ConversionSession(image, palette, (48, 48))

# Initial conversion
session.convert(ConvertConfig(method='match_then_mode'))
print(f"Initial similarity: {session.similarity_score:.2f}")

# Pin a cell and change its color
custom_blue = Color((0, 100, 200), "Custom Blue")
session.pin(5, 10, custom_blue)  # Pin cell at (5, 10) to custom blue

# Bulk swap a color throughout the canvas
old_red = Color((179, 0, 6), "Red")
new_orange = Color((255, 126, 20), "Orange")
count = session.swap_color(old_red, new_orange)
print(f"Swapped {count} cells from red to orange")

# Re-convert with a different method, preserving pinned cells
session.reconvert(ConvertConfig(method='mean_then_match'), keep_pins=True)

# Get pinned cells
pinned = session.get_pinned_cells()
print(f"Pinned cells: {pinned}")

Inventory-Limited Conversion

from legopic import ConversionSession, ConvertConfig, Palette, load_image

image = load_image("photo.jpg")
palette = Palette.from_set(31197)  # Has specific element counts

session = ConversionSession(image, palette, (48, 48))

# Enable inventory limits - cells fall back to next-best color when preferred runs out
config = ConvertConfig(
    method='match_then_mode',
    limit_inventory=True,
    algorithm='priority_greedy'  # Fast heuristic
)
session.convert(config)

Exporting for Building Guide

from legopic import ConversionSession, Palette, load_image

image = load_image("photo.jpg")
palette = Palette.from_set(31197)
session = ConversionSession(image, palette, (48, 48))
session.convert()

# Bill of Materials
bom = session.get_bill_of_materials()
for entry in bom:
    status = "✓" if entry.in_palette else "⚠ custom"
    print(f"{entry.color.name}: {entry.count_needed} tiles {status}")

# Grid data for rendering
grid = session.get_grid_data()
for y, row in enumerate(grid):
    for x, cell_data in enumerate(row):
        print(f"({x},{y}): {cell_data.color.name}, ΔE={cell_data.delta_e:.1f}")

# Similarity map (identify problem areas)
sim_map = session.get_similarity_map()
max_delta_e = max(max(row) for row in sim_map)
print(f"Worst color match: ΔE={max_delta_e:.1f}")

Exporting to External Platforms

Export your parts list directly to BrickLink or Rebrickable for easy ordering:

from legopic import ConversionSession, Palette, load_image

image = load_image("photo.jpg")
palette = Palette.from_set(31197)
session = ConversionSession(image, palette, (48, 48))
session.convert()

# Export to BrickLink XML (for wanted list upload)
xml = session.export_bricklink_xml()
with open("wanted_list.xml", "w") as f:
    f.write(xml)

# Export to Rebrickable CSV (for parts list import)
csv = session.export_rebrickable_csv()
with open("parts_list.csv", "w") as f:
    f.write(csv)

The exports include platform-specific color IDs so you can directly upload to:

Available LEGO Sets

The package includes data for official LEGO Art sets:

Set ID Name Canvas Size
31197 Andy Warhol's Marilyn Monroe 48×48
31198 The Beatles 48×48
31202 Disney's Mickey Mouse 48×48
31203 World Map 128×80
31204 Elvis Presley "The King" 48×48
21226 Art Project – Create Together 48×48
from legopic.data import list_available_sets, get_set_dimensions

# List all sets
for set_id, name in list_available_sets():
    width, height = get_set_dimensions(set_id)
    print(f"{set_id}: {name} ({width}×{height})")

Features

Color Matching

Uses the Delta E (CIE2000) perceptual color distance metric via basic_colormath, which accounts for non-linearities in human vision perception.

Downsampling Methods

Method Description Best For
mean_then_match Average pixel colors, then match to palette Smooth gradients
match_then_mean Match each pixel, then average results Balanced approach
match_then_mode Match each pixel, take most common (default) Sharp edges, distinct colors

Dimension Validation

For uniform stride downsampling, image and canvas dimensions must satisfy:

image_width // canvas_width == image_height // canvas_height

This ensures every canvas cell maps to image pixels with a uniform block size.

Valid examples:

  • Image 100×100 → Canvas 10×10 ✓
  • Image 100×91 → Canvas 10×10 ✓ (last row has 1 pixel)

Invalid examples:

  • Image 100×90 → Canvas 10×10 ✗ (height stride differs)
  • Image 92×101 → Canvas 10×10 ✗ (width stride differs)

About LEGO Mosaics

What Are LEGO Mosaics?

LEGO mosaics are pixel-art style creations built using 1×1 round tiles (commonly called "studs" or "dots"). Each tile represents a single pixel, and when arranged on a baseplate, they form a cohesive image — similar to pointillism or cross-stitch patterns.

The official LEGO Art line (sets like 31197, 31198, etc.) popularized this technique, offering curated color palettes and building instructions for iconic portraits and artwork.

The 1×1 Round Tile (Element 98138)

The primary building block for LEGO mosaics is the 1×1 round plate with design ID 98138. This element:

  • Has a smooth, circular top surface
  • Sits flat on baseplates and standard LEGO bricks
  • Comes in 40+ official colors
  • Creates the characteristic "dotted" mosaic appearance

Where to Buy LEGO Tiles

Once you've designed your mosaic with legopic, you'll need to source the actual bricks. Here are the main marketplaces:

Marketplace Description
BrickLink The largest secondary LEGO marketplace. Search by element ID (98138) to find tiles in any color.
BrickOwl Alternative marketplace with competitive pricing and international sellers.
LEGO Pick-a-Brick Official LEGO store. Limited color selection but guaranteed authenticity.

Pro tip: Use session.export_bricklink_xml() to generate a ready-to-upload wanted list for BrickLink, or session.export_rebrickable_csv() for Rebrickable. No manual counting needed!

Contributing New Colors & Sets

The LEGO color palette evolves over time, and new Art sets are released regularly. We welcome community contributions!

If you'd like to add:

  • New colors — Add entries to src/legopic/data/colors.csv
  • New sets — Add set info to sets.csv and element mappings to elements.csv

See the Contributing section for validation rules and submit a PR. The CI will automatically verify data integrity.

API Reference

Main API

Class Description
ConversionSession(image, palette, canvas_size) Main workflow manager for conversion
ConvertConfig(method, limit_inventory, algorithm) Soft parameters for conversion

Models

Class Description
Color(rgb, name=None) RGB color with optional name
Element(element_id, design_id, variant_id, count=None) LEGO element variant for inventory tracking
Cell(color, x=None, y=None) Single pixel/block with position
Image(array) Input image wrapper
Canvas(width, height) Output mosaic grid
Palette(colors) Collection of available colors with element variants
BOMEntry Bill of materials entry for export
CellData Cell data for grid export

ConversionSession Methods

Method Description
convert(config=None) Run initial conversion
reconvert(config=None, keep_pins=True) Re-convert preserving pinned cells
pin(x, y, new_color=None) Pin a cell, optionally changing its color
unpin(x, y) Unpin a cell
swap_color(old, new, pin=True) Bulk swap all cells of one color
get_pinned_cells() Get list of (x, y) pinned coordinates
get_bill_of_materials() Get BOM for building guide
get_grid_data() Get 2D cell data for rendering
get_similarity_map() Get per-cell Delta E values
export_bricklink_xml() Export BOM as BrickLink XML wanted list
export_rebrickable_csv() Export BOM as Rebrickable CSV parts list

ConversionSession Properties

Property Description
image Source image (read-only)
palette Available colors (read-only)
canvas_size Target dimensions (read-only)
canvas Current conversion result
config Current configuration
similarity_score Average Delta E across all cells

Palette Methods

Method Description
Palette.from_set(set_id=None, standard_only=True) Load from LEGO set or all colors
palette.colors List of unique Color objects
palette.elements List of all Element objects
palette.get_elements_for_color(color) Get element variants for a color

Canvas Methods

Method Description
Canvas.from_set(set_id) Create empty canvas with set dimensions
canvas.get_cell(x, y) Get cell at coordinates
canvas.to_array() Convert to numpy RGB array

Utility Functions

Function Description
load_image(source) Load from file path or URL
downsize(image, palette, width, height, method) Low-level resize with color matching
match_color(targets, palette) Raw color matching utility
export_bricklink_xml(bom) Export BOM list to BrickLink XML format
export_rebrickable_csv(bom) Export BOM list to Rebrickable CSV format

Development

Setup

# Clone the repository
git clone https://github.com/zl3311/lego_image_converter.git
cd lego_image_converter

# Install with uv (recommended)
uv sync --all-groups

# Or with pip
pip install -e ".[dev]"

Testing

# Run all tests
uv run pytest

# Run with coverage
uv run pytest --cov=legopic --cov-report=term-missing

Code Quality

# Lint
uv run ruff check src/ tests/

# Format
uv run ruff format src/ tests/

# Type check
uv run mypy src/legopic/

Contributing New Colors or Sets

When adding new LEGO colors or sets via PR, the CI automatically validates data integrity. The following checks run on every PR:

colors.csv Validation

Check Description
Unique element_id Primary key must be unique
No exact duplicates Same (design_id, name, r, g, b, variant_id) not allowed
Consistent RGB per name Same color name must have same RGB values
Valid RGB range Values must be integers in [0, 255]
Valid is_standard Must be "true" or "false"
Valid variant_id Must be a positive integer

sets.csv Validation

Check Description
Unique set_id Primary key must be unique
Unique names Set names should not duplicate
Valid dimensions Width and height must be positive integers
Dimension limits Canvas cannot exceed 1024×1024 studs

elements.csv Validation

Check Description
Unique pairs (set_id, element_id) must be unique
Valid element_id Must exist in colors.csv
Valid set_id Must exist in sets.csv
Valid count Must be a positive integer

Cross-file Consistency

Check Description
Sets have elements Every set must have at least one element
Consistent design_id Must match between colors.csv and elements.csv

Run these checks locally before submitting:

uv run pytest tests/unit/test_data_integrity.py -v

Background

This project was born from a passion for LEGO Art and the desire to create custom mosaic portraits.

Originally created for LEGO Art Project 21226 — a collaborative set designed for creative freedom — this package helps you prototype LEGO mosaic designs by:

  1. Converting any image to the constrained color palette of available LEGO tiles
  2. Downsizing intelligently to your target canvas dimensions (e.g., 48×48 studs)
  3. Using perceptually accurate color matching via the CIEDE2000 algorithm

Whether you're recreating a family photo, a pet portrait, or pixel art, legopic lets you preview exactly how your design will look before ordering hundreds of tiles. No more guesswork — just load an image, pick a palette, and see the result instantly.

Why Perceptual Color Matching?

Simple RGB distance doesn't account for how humans actually perceive color. Two colors might be mathematically similar but look completely different to our eyes. legopic uses the Delta E (CIE2000) metric, which models human vision to find the closest perceptual match — resulting in mosaics that look right, not just mathematically correct.

License

MIT License - see LICENSE for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

legopic-0.4.3.tar.gz (807.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

legopic-0.4.3-py3-none-any.whl (44.8 kB view details)

Uploaded Python 3

File details

Details for the file legopic-0.4.3.tar.gz.

File metadata

  • Download URL: legopic-0.4.3.tar.gz
  • Upload date:
  • Size: 807.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for legopic-0.4.3.tar.gz
Algorithm Hash digest
SHA256 187e11f9877af0cfcd8af6c53be65af849c7e9d26d604f6f04bc2cc3ebf49241
MD5 00253840c0aa74d6aa29acacc5c01422
BLAKE2b-256 0044dd222cc7364b6c5dd1b79d42df4c604e4ff6f7d673f34da3426b7969b4cf

See more details on using hashes here.

Provenance

The following attestation bundles were made for legopic-0.4.3.tar.gz:

Publisher: release.yml on zl3311/lego_image_converter

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file legopic-0.4.3-py3-none-any.whl.

File metadata

  • Download URL: legopic-0.4.3-py3-none-any.whl
  • Upload date:
  • Size: 44.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for legopic-0.4.3-py3-none-any.whl
Algorithm Hash digest
SHA256 239fc792f9eac1088da2b757f3837329cd84b584415d0b676c7546ff8363b31a
MD5 f085152c1ef4af6664f5c26d1e3255eb
BLAKE2b-256 72bf9df9415d8d58e87a1ba85e74c3000e79008e75ae4c077c5fda9f596822ae

See more details on using hashes here.

Provenance

The following attestation bundles were made for legopic-0.4.3-py3-none-any.whl:

Publisher: release.yml on zl3311/lego_image_converter

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page