1

Exclure les pièces de minifigs et intégrer les visuels de rareté

This commit is contained in:
2025-12-03 11:34:05 +01:00
parent 6dc1f1cac5
commit 39af0d3a8b
8 changed files with 606 additions and 0 deletions

86
lib/plots/part_rarity.py Normal file
View File

@@ -0,0 +1,86 @@
"""Visualisation des pièces les plus rares observées dans les sets filtrés."""
import csv
from pathlib import Path
from typing import List
import matplotlib.pyplot as plt
from matplotlib.offsetbox import AnnotationBbox, OffsetImage
from PIL import Image
from lib.filesystem import ensure_parent_dir
def load_part_rarity(path: Path) -> List[dict]:
"""Charge le CSV des pièces rares."""
rows: List[dict] = []
with path.open() as csv_file:
reader = csv.DictReader(csv_file)
for row in reader:
rows.append(row)
return rows
def format_label(row: dict) -> str:
"""Formate létiquette de laxe vertical."""
return f"{row['part_num']}{row['part_name']}"
def load_part_image(row: dict, resources_dir: Path) -> Image.Image | None:
"""Charge l'image associée à une pièce si elle est disponible."""
path = resources_dir / row["sample_set_id"] / "rare_parts" / f"{row['part_num']}.jpg"
if not path.exists():
return None
return Image.open(path)
def plot_part_rarity(
path: Path,
destination_path: Path,
resources_dir: Path = Path("figures/rebrickable"),
show_images: bool = True,
) -> None:
"""Trace un bar chart horizontal des pièces les plus rares avec leurs visuels."""
rows = load_part_rarity(path)
selected = rows
labels = [format_label(row) for row in selected]
filtered_counts = [int(row["filtered_quantity"]) for row in selected]
other_counts = [int(row["other_sets_quantity"]) for row in selected]
positions = list(range(len(selected)))
fig, ax = plt.subplots(figsize=(13, 0.55 * len(selected) + 1.4))
ax.barh(positions, filtered_counts, color="#1f78b4", label="Sets filtrés")
ax.barh(positions, other_counts, left=filtered_counts, color="#b2df8a", label="Autres sets")
ax.set_yticks(positions)
ax.set_yticklabels(labels)
ax.set_xlabel("Occurrences de la pièce (rechanges incluses)")
ax.grid(axis="x", linestyle="--", alpha=0.4)
ax.legend()
if show_images:
max_count = max((f + o) for f, o in zip(filtered_counts, other_counts)) if selected else 0
pad = max_count * 0.15 if max_count > 0 else 1.0
ax.set_xlim(left=-pad, right=max_count + pad * 0.3)
for row, pos in zip(selected, positions):
image = load_part_image(row, resources_dir)
if image is None:
continue
target_height = 28
ratio = target_height / image.height
resized = image.resize((int(image.width * ratio), target_height))
imagebox = OffsetImage(resized)
ab = AnnotationBbox(
imagebox,
(-pad * 0.45, pos),
xycoords=("data", "data"),
box_alignment=(0.5, 0.5),
frameon=False,
)
ax.add_artist(ab)
fig.subplots_adjust(left=0.42)
fig.tight_layout()
ensure_parent_dir(destination_path)
fig.savefig(destination_path, dpi=150)
plt.close(fig)