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.2.tar.gz (807.0 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.2-py3-none-any.whl (44.6 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: legopic-0.4.2.tar.gz
  • Upload date:
  • Size: 807.0 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.2.tar.gz
Algorithm Hash digest
SHA256 e7179cc874dd80ef386445cbb0299647bb130f76d7db8baaf5d423dab11dbbac
MD5 49eacf3951b2334f6ea2004bb50d11c8
BLAKE2b-256 73a80376b5a4b0f14c6ab0490ffea1407e4d04eccd4204d1f82b6e1f559a50ec

See more details on using hashes here.

Provenance

The following attestation bundles were made for legopic-0.4.2.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.2-py3-none-any.whl.

File metadata

  • Download URL: legopic-0.4.2-py3-none-any.whl
  • Upload date:
  • Size: 44.6 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.2-py3-none-any.whl
Algorithm Hash digest
SHA256 3d6531bd085528eef80070993792908ce1c1d3af00d2ad888e779fef7cebc67d
MD5 0bed5d03c8f1fd3e31b22717fc7d6e4b
BLAKE2b-256 8485a7d96e68882714b540db026c9e63b4c8adf219d824d37911cf7ca21434bd

See more details on using hashes here.

Provenance

The following attestation bundles were made for legopic-0.4.2-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