{ "cells": [ { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [], "source": [ "from PIL import Image, ImageOps\n", "from pathlib import Path\n", "from typing import Optional\n", "from matplotlib import pyplot as plt\n", "import logging" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "TILE_SIZE = 1650 # in pixels\n", "BORDER_SIZE = 200 # in pixels\n", "BORDER_COLOR = (255, 255, 255)\n", "BACKGROUND_COLOR = (255, 255, 255)\n", "IMAGE_DIR = Path(\"board\")\n", "\n", "# Define the layout grid (rows of indices, None for empty)\n", "# fmt: off\n", "layout:list[list[Optional[int]]] = [\n", " [None, None, 0, None, None],\n", " [None, None, 1, None, None],\n", " [None, 5, 2, 4, None],\n", " [None, None, 3, None, None],\n", "]\n", "# fmt: on\n", "\n", "\n", "\n", "# charuco_410x410_3x3_s133_m105_face3_no_12_DICT_7X7_1000\n", "# xxxxxxx_____face_no__DICT_\n", "# 0 1 2 3 4 5 6 7 8\n", "def parse_filename_to_face_idx(filename: str):\n", " parts = filename.split(\"_\")\n", " return int(parts[5][len(\"face\") :])\n", "\n", "\n", "image_pathes = list(IMAGE_DIR.glob(\"*.png\"))\n", "image_indice = map(lambda p: parse_filename_to_face_idx(p.stem), image_pathes)\n", "images = {k: v for k, v in zip(image_indice, image_pathes)}\n", "display(images)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Create blank canvas\n", "rows = len(layout)\n", "cols = len(layout[0])\n", "canvas = Image.new(\"RGB\", (cols * TILE_SIZE, rows * TILE_SIZE), BACKGROUND_COLOR)\n", "\n", "# Paste tiles\n", "for y, row in enumerate(layout):\n", " for x, idx in enumerate(row):\n", " if idx is not None:\n", " path = images.get(idx)\n", " if path is not None:\n", " tile = Image.open(path)\n", " # for the face index 4, rotate the tile 180 degrees\n", " if idx == 4:\n", " tile = tile.rotate(180)\n", " canvas.paste(tile, (x * TILE_SIZE, y * TILE_SIZE))\n", " else:\n", " logging.warning(f\"Missing: {idx}\")\n", "\n", "# Calculate canvas size (before border)\n", "canvas_width = cols * TILE_SIZE\n", "canvas_height = rows * TILE_SIZE\n", "\n", "# Determine target size to make it square after padding\n", "target_size = max(canvas_width, canvas_height)\n", "extra_padding = target_size - canvas_height\n", "top_pad = extra_padding // 2\n", "bottom_pad = extra_padding - top_pad\n", "\n", "# First add vertical padding to center the layout\n", "canvas_with_border = ImageOps.expand(\n", " canvas,\n", " border=(0, top_pad, 0, bottom_pad), # (left, top, right, bottom)\n", " fill=BACKGROUND_COLOR,\n", ")\n", "\n", "plt.imshow(canvas_with_border)\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [], "source": [ "canvas_with_border.save(\"merged_uv_layout.png\")" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.10" } }, "nbformat": 4, "nbformat_minor": 2 }