155 lines
5.8 KiB
Python
155 lines
5.8 KiB
Python
"""Tests des frises chronologiques de minifigs par personnage."""
|
|
|
|
from pathlib import Path
|
|
|
|
from PIL import Image
|
|
|
|
from lib.plots.minifig_character_collages import build_character_collages, stack_collages
|
|
from lib.rebrickable.minifig_character_sets import build_character_sets, group_by_character
|
|
from lib.rebrickable.resources import sanitize_name
|
|
|
|
|
|
def create_image(path: Path, color: tuple[int, int, int], size: tuple[int, int]) -> None:
|
|
"""Crée une image unie pour les besoins de tests."""
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
Image.new("RGB", size, color).save(path)
|
|
|
|
|
|
def test_build_character_sets_and_collages(tmp_path: Path) -> None:
|
|
"""Construit les apparitions par personnage puis les frises associées."""
|
|
minifigs_rows = [
|
|
{"set_num": "1000-1", "known_character": "Alice", "fig_num": "fig-1", "part_num": "p1"},
|
|
{"set_num": "1000-1", "known_character": "Alice", "fig_num": "fig-1", "part_num": "p1"},
|
|
{"set_num": "1001-1", "known_character": "Bob", "fig_num": "fig-2", "part_num": "p2"},
|
|
{"set_num": "1002-1", "known_character": "Bob", "fig_num": "fig-3", "part_num": "p3"},
|
|
{"set_num": "1004-1", "known_character": "Claire Dearing", "fig_num": "fig-5", "part_num": "p5"},
|
|
{"set_num": "1003-1", "known_character": "Figurant", "fig_num": "fig-4", "part_num": "p4"},
|
|
]
|
|
sets_lookup = {
|
|
"1000-1": {"set_id": "1000", "year": "2020", "in_collection": "true"},
|
|
"1001-1": {"set_id": "1001", "year": "2021", "in_collection": "false"},
|
|
"1002-1": {"set_id": "1002", "year": "2022", "in_collection": "false"},
|
|
"1003-1": {"set_id": "1003", "year": "2023", "in_collection": "false"},
|
|
"1004-1": {"set_id": "1004", "year": "2024", "in_collection": "true"},
|
|
}
|
|
|
|
character_sets = build_character_sets(minifigs_rows, sets_lookup, excluded_characters=["Figurant"])
|
|
assert character_sets == [
|
|
{
|
|
"known_character": "Alice",
|
|
"set_num": "1000-1",
|
|
"set_id": "1000",
|
|
"year": "2020",
|
|
"fig_num": "fig-1",
|
|
"in_collection": "true",
|
|
"part_num": "p1",
|
|
},
|
|
{
|
|
"known_character": "Bob",
|
|
"set_num": "1001-1",
|
|
"set_id": "1001",
|
|
"year": "2021",
|
|
"fig_num": "fig-2",
|
|
"in_collection": "false",
|
|
"part_num": "p2",
|
|
},
|
|
{
|
|
"known_character": "Bob",
|
|
"set_num": "1002-1",
|
|
"set_id": "1002",
|
|
"year": "2022",
|
|
"fig_num": "fig-3",
|
|
"in_collection": "false",
|
|
"part_num": "p3",
|
|
},
|
|
{
|
|
"known_character": "Claire Dearing",
|
|
"set_num": "1004-1",
|
|
"set_id": "1004",
|
|
"year": "2024",
|
|
"fig_num": "fig-5",
|
|
"in_collection": "true",
|
|
"part_num": "p5",
|
|
},
|
|
]
|
|
|
|
resources_dir = tmp_path / "resources"
|
|
for entry in character_sets:
|
|
image_path = resources_dir / entry["set_id"] / sanitize_name(entry["known_character"]) / "minifig.jpg"
|
|
if "Claire" in entry["known_character"]:
|
|
continue
|
|
create_image(image_path, (120, 80, 60), (10, 10))
|
|
|
|
destination_dir = tmp_path / "output"
|
|
missing_minifigs = {
|
|
str(resources_dir / "1004" / sanitize_name("Claire Dearing") / "minifig.jpg"),
|
|
}
|
|
generated = build_character_collages(
|
|
group_by_character(character_sets),
|
|
resources_dir,
|
|
destination_dir,
|
|
missing_paths=missing_minifigs,
|
|
image_filename="minifig.jpg",
|
|
image_height=20,
|
|
label_height=10,
|
|
spacing=2,
|
|
)
|
|
|
|
assert len(generated) == 3
|
|
alice = Image.open(destination_dir / f"{sanitize_name('Alice')}.png")
|
|
bob = Image.open(destination_dir / f"{sanitize_name('Bob')}.png")
|
|
claire = Image.open(destination_dir / f"{sanitize_name('Claire Dearing')}.png")
|
|
assert alice.size == (20, 30)
|
|
assert bob.size == (42, 30)
|
|
assert claire.size == (20, 30)
|
|
assert claire.getpixel((5, 10)) == (220, 220, 220)
|
|
|
|
# Variante sur les têtes : utilise un autre fichier et un autre manque.
|
|
heads_destination = tmp_path / "output_heads"
|
|
head_missing = {
|
|
str(resources_dir / "1001" / sanitize_name("Bob") / "head.jpg"),
|
|
str(resources_dir / "1002" / sanitize_name("Bob") / "head.jpg"),
|
|
str(resources_dir / "1004" / sanitize_name("Claire Dearing") / "head.jpg"),
|
|
}
|
|
head_image = resources_dir / "1000" / sanitize_name("Alice") / "head.jpg"
|
|
create_image(head_image, (10, 120, 60), (8, 12))
|
|
head_collages = build_character_collages(
|
|
{"Bob": group_by_character(character_sets)["Bob"]},
|
|
resources_dir,
|
|
heads_destination,
|
|
missing_paths=head_missing,
|
|
image_filename="head.jpg",
|
|
image_height=16,
|
|
label_height=8,
|
|
spacing=2,
|
|
)
|
|
assert len(head_collages) == 1
|
|
bob_head = Image.open(heads_destination / f"{sanitize_name('Bob')}.png")
|
|
assert bob_head.size == (34, 24)
|
|
assert any(pixel == (220, 220, 220) for pixel in bob_head.getdata())
|
|
|
|
|
|
def test_stack_collages_with_filter(tmp_path: Path) -> None:
|
|
"""Assemble une grille verticale en filtrant les frises autorisées."""
|
|
source_dir = tmp_path / "collages"
|
|
source_dir.mkdir()
|
|
img1 = source_dir / "Alice.png"
|
|
img2 = source_dir / "Bob.png"
|
|
Image.new("RGB", (10, 6), (100, 100, 100)).save(img1)
|
|
Image.new("RGB", (14, 8), (120, 120, 120)).save(img2)
|
|
|
|
destination = tmp_path / "stacked.png"
|
|
stack_collages(
|
|
source_dir,
|
|
destination,
|
|
spacing=2,
|
|
allowed_stems={"Bob"},
|
|
name_panel_width=4,
|
|
labels_by_stem={"Bob": "Bob"},
|
|
)
|
|
|
|
result = Image.open(destination)
|
|
assert result.size == (18, 8)
|
|
assert result.getpixel((0, 0)) == (255, 255, 255)
|
|
assert result.getpixel((6, 0)) == (120, 120, 120)
|