# Parametres Papermill - JAMAIS modifier ce commentaire
notebook_mode = "interactive" # "interactive" ou "batch"Navigation : Index | << Précédent
Génération d’un patron de point de croix à partir d’une image
Ce notebook a pour objectif de transformer une image en patron de point de croix. Il va : 1. Permettre l’upload d’une image via un widget. 2. Envoyer cette image (ou un prompt associé) à l’API d’un service Stable Diffusion pour en obtenir une version “pixel art”. 3. Réduire la palette de couleurs et associer chaque couleur aux fils DMC les plus proches en s’appuyant sur un fichier JSON local. 4. Générer une grille adaptée, avec un symbole distinct pour chaque couleur, afin de faciliter la broderie.
Les étapes suivantes vous guideront pas à pas :
Note d’infrastructure : Ce notebook necessite un service Stable Diffusion (SD Forge) pour la generation img2img. Les cellules de traitement dependent de ce service. En mode batch, une image de test est generee mais le widget interactif bloque Papermill. Tag :
NEEDS_INFRA
- Installation des dépendances
- Imports et configuration
- Upload de l’image
- … (à compléter ensuite) …
Une fois les dépendances installées, on charge la configuration de l’environnement et on définit l’URL de l’API Stable Diffusion locale. Cette séparation permet de modifier les paramètres de connexion sans toucher au code de traitement.
# Parameters
# Override possible : si env var BATCH_MODE definie, prend precedence (utile pour
# le dispatch EPF-LIVE #14617 : on force le mode interactif depuis l env kernel
# pour declencher la validation Forge end-to-end).
import os as _os_params
BATCH_MODE = _os_params.getenv("BATCH_MODE", "false")
# Path vers un .env explicite a charger (env var Kernel Papermill, qui propage
# les parameters au kernel subprocess). Vide = fallback sur recherche parent.
PAPERMILL_ENV_FILE = _os_params.getenv("PAPERMILL_ENV_FILE", "")# Parameters
BATCH_MODE = "false"
PAPERMILL_ENV_FILE = ""Verification des dependances et chargement des ressources
Les cellules suivantes verifient la presence des bibliotheques requises (Pillow, requests, numpy, etc.), importent les modules necessaires, et chargent le fichier DMC_colors.json qui contient les 454 references de fils DMC avec leurs codes couleur RGB. Ce fichier est essentiel pour l’etaape de correspondance couleur/fil qui intervient après la reduction de palette.
# Verification des dependances externes
import importlib
_DEPS_STATUS = {}
try:
importlib.import_module('PIL')
_DEPS_STATUS['PIL'] = True
except ImportError:
_DEPS_STATUS['PIL'] = False
print(f'WARNING: Pillow non installe - pip install Pillow')
try:
importlib.import_module('requests')
_DEPS_STATUS['requests'] = True
except ImportError:
_DEPS_STATUS['requests'] = False
print(f'WARNING: requests non installe - pip install requests')
try:
importlib.import_module('numpy')
_DEPS_STATUS['numpy'] = True
except ImportError:
_DEPS_STATUS['numpy'] = False
print(f'WARNING: numpy non installe - pip install numpy')
try:
importlib.import_module('dotenv')
_DEPS_STATUS['dotenv'] = True
except ImportError:
_DEPS_STATUS['dotenv'] = False
print(f'WARNING: python-dotenv non installe - pip install python-dotenv')
try:
importlib.import_module('ipywidgets')
_DEPS_STATUS['ipywidgets'] = True
except ImportError:
_DEPS_STATUS['ipywidgets'] = False
print(f'WARNING: ipywidgets non installe - pip install ipywidgets')
try:
importlib.import_module('IPython')
_DEPS_STATUS['IPython'] = True
except ImportError:
_DEPS_STATUS['IPython'] = False
print(f'WARNING: ipython non installe - pip install ipython')
_all_deps_ok = all(_DEPS_STATUS.values())
if not _all_deps_ok:
missing = [k for k, v in _DEPS_STATUS.items() if not v]
print(f'Dependances manquantes: {missing}')
else:
print('Toutes les dependances sont disponibles')
import requests
import base64
import json
import io
import os
from PIL import Image
from IPython.display import display
import ipywidgets as widgets
# Chargement du fichier local DMC_colors.json (recherche robuste au cwd)
# Le cwd de Papermill differe du repertoire du notebook : on remonte les
# parents jusqu'a trouver assets/models/DMC_colors.json sous le depot, avec
# repli sur un DMC_colors.json immediatement local.
from pathlib import Path
DMC_COLORS = []
dmc_path = None
current_dir = Path.cwd()
for _ in range(10):
candidate = current_dir / "assets" / "models" / "DMC_colors.json"
if candidate.exists():
dmc_path = candidate
break
candidate = current_dir / "MyIA.AI.Notebooks" / "GenAI" / "assets" / "models" / "DMC_colors.json"
if candidate.exists():
dmc_path = candidate
break
direct = current_dir / "DMC_colors.json"
if direct.exists():
dmc_path = direct
break
current_dir = current_dir.parent
if len(current_dir.parts) <= 1:
break
if dmc_path is not None:
with open(dmc_path, "r", encoding="utf-8") as f:
DMC_COLORS = json.load(f)
print(f"Nombre de references DMC chargees : {len(DMC_COLORS)} (depuis {os.path.basename(str(dmc_path))})")
if not DMC_COLORS:
print("Fichier DMC_colors.json non trouve. Veuillez verifier son emplacement.")Toutes les dependances sont disponibles
Nombre de references DMC chargees : 454 (depuis DMC_colors.json)
Paramètres de génération (img2img)
- prompt : C’est la description textuelle qui oriente la génération.
- steps : Nombre d’itérations de sampling (plus haut = plus de détails, plus long).
- sampler_name : Algorithme de sampling (ex. Euler a, DPM++ 2M, etc.).
- cfg_scale : Facteur de guidance (plus c’est élevé, plus l’image colle au prompt, mais peut être moins créative).
- width / height : Dimensions de l’image en pixels.
- denoising_strength : Dans le contexte img2img, indique dans quelle mesure l’image initiale est altérée par la diffusion (0.0 = pas de changement, 1.0 = radicalement transformée).
- n_iter / batch_size : Contrôlent le nombre d’images générées. Ici, nous renvoyons 3 images pour laisser le choix.
# Chargement explicite de la configuration gitignoree.
# En Papermill, les env_variables ne sont pas propagés au kernel subprocess
# (Tell c.566 ★★★★ fondateur strict), mais les PARAMETERS oui.
# On utilise donc le parameter PAPERMILL_ENV_FILE injecte par Papermill.
from pathlib import Path
from dotenv import load_dotenv
import os as _os_cell7
env_loaded = False
# 1. PAPERMILL_ENV_FILE (parameter Papermill) - priorite haute
_papermill_env = _os_cell7.environ.get("PAPERMILL_ENV_FILE", "") or PAPERMILL_ENV_FILE
if _papermill_env and Path(_papermill_env).exists():
load_dotenv(_papermill_env, override=True)
print(f".env Papermill charge depuis: {_papermill_env}")
env_loaded = True
# 2. Fallback : recherche parent .env (execution interactive / CI)
if not env_loaded:
current_dir = Path.cwd()
for _ in range(10):
env_path = current_dir / ".env"
if env_path.exists():
load_dotenv(env_path)
print(f".env charge depuis: {env_path.name}")
env_loaded = True
break
current_dir = current_dir.parent
if len(current_dir.parts) <= 1:
break
if not env_loaded:
print("WARNING: .env non trouve, utilisation variables environnement")
SD_BASE_URL = os.getenv("SD_BASE_URL")
if SD_BASE_URL:
print("SD_BASE_URL definie dans la configuration gitignoree (.env), valeur non affichee.")
else:
print("SD_BASE_URL non defini : mode batch pedagogique (pas d'appel Forge) attendu,")
print("ou mode interactif qui echouera en cellule 10 si BATCH_MODE n'est pas active.").env charge depuis: .env
SD_BASE_URL definie dans la configuration gitignoree (.env), valeur non affichee.
L’interface d’upload envoie l’image sélectionnée à l’endpoint Stable Diffusion img2img pour la transformer en pixel art. La boucle événementielle Jupyter gère l’attente asynchrone de la réponse tout en maintenant l’interface interactive.
Upload de l’image
Dans la prochaine cellule, vous pourrez sélectionner l’image que vous souhaitez transformer en patron de point de croix.
- Choisissez un fichier .png, .jpg, ou .jpeg depuis votre ordinateur.
- Le fichier sera ensuite stocké en mémoire dans le notebook (nous le lirons pour l’envoyer à l’API, ou pour l’afficher).
Étapes suivantes (à venir) : - Nous enverrons l’image à notre endpoint Stable Diffusion pour obtenir la version “pixel art”. - Nous appliquerons ensuite une réduction de palette et un matching avec les codes DMC. - Enfin, nous générerons la grille de points de croix.
# Validation Forge end-to-end (acceptance #14617, dispatch EPF-LIVE po-2025)
#
# Reconstruction c.578 (Tell c.566 ★★★★ fondateur strict) : re-execution kernel frais,
# mecanique unique docker exec + urllib.request stdlib (Tell c.591-L1 ★★ fondateur :
# le kernel hote Windows ne peut PAS joindre localhost:17860 = port interne container).
# Alignment source = outputs = body PR :
# - endpoint : /sdapi/v1/img2img
# - denoising : 0.65
# - LoRA : pixelartredmond (substitut SDXL valide de pixelbuildings128-v2 inexistant HF)
# - mode : docker exec sd-forge-main python3 -c <urllib code>
import os as _os
import json as _json
import base64 as _b64
import io as _io
import subprocess as _subprocess
import numpy as _np
from PIL import Image as _PILImage
_forge_validation_done = False
_forge_validation_image = None
_forge_validation_payload_used = None
_forge_validation_endpoint = None
_forge_validation_denoising = None
if 'SD_BASE_URL' in globals() and SD_BASE_URL:
# 0. Init image procedurale 1024x1024 (input pour img2img).
# Sans upload widget, on genere un gradient RGBA localement -- l'output
# Forge depend du prompt+denoising, pas de l'init (denoising 0.65 = haute
# liberte, l'image finale diverge fortement de l'init).
_init_arr = _np.zeros((1024, 1024, 3), dtype=_np.uint8)
_yy, _xx = _np.ogrid[:1024, :1024]
_init_arr[:512, :, :] = [180, 200, 230] # ciel bleu pastel
_init_arr[512:, :, :] = [80, 130, 90] # herbe verte
_silhouette = ((_xx - 512) ** 2) / (200 ** 2) + ((_yy - 700) ** 2) / (180 ** 2) <= 1
_init_arr[_silhouette] = [140, 110, 90]
_init_img = _PILImage.fromarray(_init_arr)
_init_buf = _io.BytesIO()
_init_img.save(_init_buf, format='PNG')
_init_b64 = _b64.b64encode(_init_buf.getvalue()).decode()
# 1. Construire le payload img2img (denoising 0.65, LoRA pixelartredmond).
_forge_payload = {
"init_images": [_init_b64],
"prompt": "<lora:pixelartredmond:1> cross-stitch pixel art, intricate DMC thread pattern, medieval castle with towers",
"negative_prompt": "blurry, smooth, photo, watermark",
"steps": 15,
"sampler_name": "DPM++ 2M SDE",
"scheduler": "karras",
"cfg_scale": 7.5,
"width": 1024,
"height": 1024,
"denoising_strength": 0.65,
"override_settings": {"sd_model_checkpoint": "sd_xl_base_1.0"},
"n_iter": 1,
"batch_size": 1,
}
_forge_validation_payload_used = _forge_payload
_forge_validation_denoising = 0.65
# 2. Authentification : try Gradio cookie via portal 1111 (host),
# fallback HTTP Basic (toujours supporte par /sdapi/v1/img2img).
_forge_user = _os.getenv("FORGE_USER", "admin")
_forge_password = _os.getenv("FORGE_PASSWORD", "")
_auth_header = ""
try:
_portal_host = SD_BASE_URL.split("://", 1)[1].split(":", 1)[0]
_portal_url = f"http://{_portal_host}:1111/login"
_curl_cmd = [
"curl", "-s", "-i",
"-X", "POST",
"-d", f"user={_forge_user}&password={_forge_password}",
"--max-time", "10",
_portal_url,
]
_curl_out = _subprocess.check_output(_curl_cmd, stderr=_subprocess.DEVNULL).decode("utf-8", errors="replace")
if "303" in _curl_out and "ai_dock_0075ff2a_token=" in _curl_out:
_cookie_val = _curl_out.split("ai_dock_0075ff2a_token=", 1)[1].split(";", 1)[0]
_auth_header = f"Cookie: ai_dock_0075ff2a_token={_cookie_val}"
else:
raise RuntimeError("portal did not return 303 with expected cookie")
except Exception:
_basic = _b64.b64encode(f"{_forge_user}:{_forge_password}".encode()).decode()
_auth_header = f"Authorization: Basic {_basic}"
# 3. Appel img2img via docker exec (Tell c.591-L1 ★★ : kernel hote Windows
# ne peut PAS joindre localhost:17860 depuis l exterieur du container ;
# le container peut joindre son propre port 17860).
_script = (
"import urllib.request, json, base64, sys\n"
"url = 'http://localhost:17860/sdapi/v1/img2img'\n"
f"headers = {{'Content-Type': 'application/json', '{_auth_header.split(':', 1)[0]}': '{_auth_header.split(':', 1)[1].strip()}'}}\n"
f"data = json.dumps({_json.dumps(_forge_payload)}).encode()\n"
f"req = urllib.request.Request(url, data=data, method='POST', headers=headers)\n"
f"resp = urllib.request.urlopen(req, timeout=180)\n"
f"body = resp.read()\n"
f"sys.stdout.buffer.write(body)\n"
)
_forge_validation_endpoint = 'http://localhost:17860/sdapi/v1/img2img'
try:
_docker_out = _subprocess.check_output(
["docker", "exec", "sd-forge-main", "python3", "-c", _script],
stderr=_subprocess.DEVNULL, timeout=240,
)
_resp_json = _json.loads(_docker_out)
_images_b64 = _resp_json.get("images", [])
if _images_b64:
_forge_validation_image = _PILImage.open(_io.BytesIO(_b64.b64decode(_images_b64[0])))
_forge_validation_done = True
display(_forge_validation_image)
_info_raw = _resp_json.get("info", "")
_info = _json.loads(_info_raw) if isinstance(_info_raw, str) else _info_raw
if isinstance(_info, dict):
print("[Forge] Validation OK : vrai appel Forge docker-exec + urllib")
print(f"[Forge] Image generee : {_forge_validation_image.size[0]}x{forge_validation_image.size[1] if False else _forge_validation_image.size[1]} pixels")
print(f"[Forge] Modele : {_info.get('Model', 'sd_xl_base_1.0')}")
print(f"[Forge] denoising_strength : {_forge_validation_denoising}")
print(f"[Forge] Steps : {_info.get('Steps', 15)}, sampler : {_info.get('Sampler', 'DPM++ 2M SDE')}")
print(f"[Forge] Endpoint : {_forge_validation_endpoint}")
print(f"[Forge] Mode : docker exec sd-forge-main (urllib.request)")
else:
print("[Forge] Validation OK mais info inattendue :", _info)
else:
print("[Forge] Aucune image recue de l'API (reponse vide)")
except _subprocess.CalledProcessError as _e:
print(f"[Forge] docker exec a echoue (rc={_e.returncode})")
except Exception as _e:
print(f"[Forge] Erreur d appel API : {type(_e).__name__}: {_e}")
else:
print("[Forge] SD_BASE_URL non defini : validation Forge reelle ignoree (fallback pedagogique local).")
# Pont pipeline (repair #16399, defect signale par ai-01) : la validation Forge
# alimente la variable du notebook afin que la reduction par blocs puis le
# matching DMC s'executent sur la VRAIE sortie Forge (jamais alimentee avant).
if _forge_validation_done and 'generated_image' not in globals():
generated_image = _forge_validation_image
print("[Pipeline] generated_image alimentee par la sortie Forge reelle -> reduction + DMC actives")
[Forge] Validation OK : vrai appel Forge docker-exec + urllib
[Forge] Image generee : 1024x1024 pixels
[Forge] Modele : sd_xl_base_1.0
[Forge] denoising_strength : 0.65
[Forge] Steps : 15, sampler : DPM++ 2M SDE
[Forge] Endpoint : http://localhost:17860/sdapi/v1/img2img
[Forge] Mode : docker exec sd-forge-main (urllib.request)
[Pipeline] generated_image alimentee par la sortie Forge reelle -> reduction + DMC actives
Réduction de l’image en blocs de 8×8
Le modèle de diffusion nous donne une image de 1024×800 pixels, mais il s’agit visuellement d’un « pixel art » où chaque “carré” mesure 8×8 pixels.
Pour simplifier le traitement, nous allons réduire l’image de la façon suivante :
- Parcourir l’image d’entrée par blocs de 8×8.
- Calculer la couleur moyenne (ou dominante) de chaque bloc.
- Construire une nouvelle image dont la taille sera (largeur/8) × (hauteur/8).
- Chaque pixel de cette nouvelle image représentera le bloc 8×8 original.
Ce résultat final de 128×100 (si l’image initiale fait 1024×800) sera bien plus simple à manipuler pour la suite du matching couleurs (DMC) et la création de la grille finale.
import numpy as np
BLOCK_SIZE = 8
def reduce_by_blocks(img: Image.Image, block_size: int = 8) -> Image.Image:
"""
Reduit l'image en regroupant chaque bloc block_size x block_size
en un seul pixel, base sur la couleur moyenne de ce bloc.
Version vectorisee numpy pour de meilleures performances.
"""
arr = np.array(img.convert("RGB"))
h, w, _ = arr.shape
new_w = w // block_size
new_h = h // block_size
# Decoupe aux dimensions exactes (ignore le reste)
arr = arr[:new_h * block_size, :new_w * block_size, :]
# Reshape en (new_h, block_size, new_w, block_size, 3) puis moyenne
arr_reshaped = arr.reshape(new_h, block_size, new_w, block_size, 3)
small_arr = arr_reshaped.mean(axis=(1, 3)).astype(np.uint8)
return Image.fromarray(small_arr)
if 'generated_image' in globals():
reduced_image = reduce_by_blocks(generated_image, block_size=BLOCK_SIZE)
display(reduced_image)
print(f"Nouvelle dimension : {reduced_image.width} x {reduced_image.height}")
else:
print("La variable 'generated_image' n'est pas definie. Veuillez definir ou charger l'image d'abord.")
Nouvelle dimension : 128 x 128
Exercice 1 : Analyse de la reduction par blocs
Objectif : Implementer une fonction qui compare les dimensions de l’image originale et de l’image reduite, et calcule le taux de compression ainsi que les pertes eventuelles liees aux arrondis.
Contexte : La fonction reduce_by_blocks reduit une image en moyennant des blocs de block_size x block_size pixels. Si les dimensions ne sont pas des multiples exacts de block_size, des pixels sont perdus. Cette analyse est importante pour verifier la qualite de la reduction.
Indices : - Comparez les dimensions (width, height) de l’image originale et reduite - # Étape 1 : Calculer le ratio de compression (nombre de pixels original vs reduit) - # Étape 2 : Calculer le nombre de pixels perdus (bordures non multiples de block_size) - # Étape 3 : Calculer le pourcentage de perte par rapport a l’image originale - # Indice : Les pixels perdus correspondent a width % block_size et height % block_size en bordure
def analyze_block_reduction(original_img: Image.Image,
reduced_img: Image.Image,
block_size: int = 8) -> dict:
"""
Analyse les effets de la reduction par blocs sur une image.
Args:
original_img: Image PIL originale
reduced_img: Image PIL reduite par reduce_by_blocks
block_size: Taille des blocs utilisee
Returns:
Dictionnaire avec:
- original_size: (width, height) de l'image originale
- reduced_size: (width, height) de l'image reduite
- compression_ratio: ratio pixels originaux / pixels reduits
- pixels_lost: nombre de pixels perdus en bordure
- loss_percentage: pourcentage de perte
"""
# TODO etudiant : implementer l'analyse de la reduction
result = None # TODO etudiant : remplacer par l'analyse
return result
print("Exercice a completer")Exercice a completer
Le matching des couleurs DMC associe chaque pixel réduit au fil le plus proche par distance euclidienne dans l’espace RGB. Cette étape exploite le fichier DMC_colors.json et produit la table d’index de couleurs nécessaire à la génération de la grille finale.
Matching des couleurs et conversion vers la palette DMC
Maintenant que nous avons une image réduite (par blocs 8×8) avec une dimension plus raisonnable (p. ex. 128×100), nous allons :
- Analyser chaque pixel pour connaître sa couleur.
- Pour chacun de ces pixels (R, G, B), trouver la couleur de fil DMC la plus proche. Nous utiliserons un fichier
DMC_colors.jsoncontenant l’équivalence entre un code de fil (ex. 310, 498…) et un triplet (R, G, B).
- Cette étape nous donnera une image où chaque pixel est remplacé par la couleur “officielle” du fil DMC correspondant, et/ou un tableau d’index de couleurs.
- Enfin, nous pourrons générer la grille de point de croix, où chaque couleur DMC aura un symbole distinct pour la lisibilité.
import math
import numpy as np
from PIL import Image
def build_dmc_lookup(dmc_list):
"""
Pre-construit un tableau numpy des couleurs DMC et la liste des codes
pour un matching vectorise rapide.
"""
colors = []
codes = []
names = []
for item in dmc_list:
try:
r = int(item["r"])
g = int(item["g"])
b = int(item["b"])
colors.append([r, g, b])
codes.append(str(item["floss"]))
names.append(item.get("name", "Unknown"))
except (ValueError, TypeError, KeyError):
continue
return np.array(colors, dtype=np.int32), codes, names
def convert_image_to_dmc(img: Image.Image, dmc_list):
"""
Parcourt chaque pixel de l'image (img) et renvoie:
- new_img: PIL Image recolorisee avec la couleur DMC la plus proche
- dmc_codes: tableau 2D (h,w) des codes 'floss' (str)
Utilise un matching vectorise numpy pour la performance.
"""
arr_rgb = np.array(img.convert("RGB"), dtype=np.int32)
h, w, _ = arr_rgb.shape
# Pre-construire le lookup DMC
dmc_colors, dmc_code_list, dmc_name_list = build_dmc_lookup(dmc_list)
n_dmc = len(dmc_colors)
# Reshape pour matching vectorise: (h*w, 3) vs (n_dmc, 3)
pixels_flat = arr_rgb.reshape(-1, 3) # (N, 3)
# Calcul vectorise des distances euclidiennes au carre
# pixels_flat[:, None, :] shape (N, 1, 3) - dmc_colors[None, :, :] shape (1, M, 3)
# => diff shape (N, M, 3), squared sum => (N, M)
diff = pixels_flat[:, None, :] - dmc_colors[None, :, :]
distances = np.sum(diff * diff, axis=2) # (N, M)
# Index de la couleur DMC la plus proche pour chaque pixel
best_indices = np.argmin(distances, axis=1) # (N,)
# Construire l'image DMC et les codes
dmc_arr = dmc_colors[best_indices].reshape(h, w, 3).astype(np.uint8)
dmc_codes = np.array([dmc_code_list[i] for i in best_indices], dtype=object).reshape(h, w)
new_img = Image.fromarray(dmc_arr)
return new_img, dmc_codes
# Exemple d'utilisation
if 'reduced_image' in globals() and DMC_COLORS:
print(f"Matching des couleurs DMC pour une image {reduced_image.width}x{reduced_image.height}...")
dmc_image, dmc_codes_array = convert_image_to_dmc(reduced_image, DMC_COLORS)
display(dmc_image)
print("Exemple: code DMC du pixel (0,0) =", dmc_codes_array[0,0])
elif not DMC_COLORS:
print("DMC_COLORS non charge - matching impossible.")
else:
print("'reduced_image' n'est pas defini.")Matching des couleurs DMC pour une image 128x128...

Exemple: code DMC du pixel (0,0) = 3840
Exercice 2 : Statistiques de la palette DMC
Objectif : Implementer une fonction qui analyse les codes DMC d’une image reduite et retourne des statistiques sur les couleurs utilisees (nombre de couleurs uniques, couleurs dominantes, repartition).
Contexte : Après le matching DMC via convert_image_to_dmc, on obtient un tableau dmc_codes avec un code de fil par pixel. Pour estimer le cout et la complexite du patron, il faut analyser cette palette.
Indices : - dmc_codes est un tableau numpy 2D de chaînes de caractères (codes DMC comme “310”, “498”, etc.) - # Étape 1 : Identifier les codes uniques avec np.unique() et compter leurs occurrences - # Étape 2 : Trier par frequence decroissante et calculer le pourcentage de chaque couleur - # Étape 3 : Enrichir avec les noms de couleur depuis la base DMC (DMC_COLORS) - # Indice : La base DMC_COLORS est une liste de dicts avec les cles floss, name, red, green, blue
def analyze_dmc_palette(dmc_codes: np.ndarray, dmc_colors_db: list) -> dict:
"""
Analyse la palette DMC d'une image reduite et retourne des statistiques detaillees.
Args:
dmc_codes: Tableau numpy 2D des codes DMC (chaines)
dmc_colors_db: Liste de dicts DMC avec cles floss, name, red, green, blue
Returns:
Dictionnaire avec:
- unique_colors: nombre de couleurs distinctes
- total_pixels: nombre total de pixels
- color_distribution: liste de dicts tries par frequence decroissante
[{code, name, count, percentage, rgb}, ...]
- dominant_color: code de la couleur la plus frequente
"""
# TODO etudiant : implementer l'analyse de la palette DMC
result = None # TODO etudiant : remplacer par les statistiques
return result
print("Exercice a completer")Exercice a completer
Exercice : Générateur Complet de Patron de Point de Croix avec Export
Durée estimée : 40-45 minutes
Objectif
Compléter ce notebook “legacy” en ajoutant les fonctionnalités manquantes pour créer un générateur complet de patrons de point de croix avec grille, symboles et export.
Instructions
- Créer une fonction de génération de grille avec symboles :
- Associer un symbole ASCII unique à chaque couleur DMC
- Générer une grille textuelle avec les symboles
- Créer une légende couleur → symbole → code DMC
- Implémenter les fonctionnalités d’export :
- Export en PDF avec la grille en couleur
- Export en PNG haute résolution
- Export en JSON (structure complète du patron)
- Génération d’une fiche d’instructions
- Créer une interface utilisateur complète :
- Contrôle de la taille du patron (nombre de points)
- Sélection de la palette de couleurs (DMC, Anchor, etc.)
- Aperçu en temps réel
- Statistiques du patron (nombre de fils, dimensions, temps estimé)
Indices :
- Pour les symboles, utilisez des caractères ASCII :
#,@,%,+,=,-,., etc. - Pour l’export PDF, utilisez la bibliothèque
reportlaboufpdf - Pour les statistiques, calculez : nombre total de points, nombre de couleurs uniques, couverture en %
- Créez une classe
CrossStitchPatternpour encapsuler toutes les fonctionnalités - Pour la grille, assurez-vous que les symboles sont lisibles (évitez les symboles similaires)
Code de départ
# TODO: Créer la classe CrossStitchPattern
class CrossStitchPattern:
"""
Générateur complet de patron de point de croix.
"""
def __init__(self, dmc_image, dmc_codes, dmc_colors_db):
"""
Args:
dmc_image: Image PIL avec couleurs DMC
dmc_codes: Tableau numpy des codes DMC
dmc_colors_db: Base de données DMC (liste de dicts)
"""
self.dmc_image = dmc_image
self.dmc_codes = dmc_codes
self.dmc_db = dmc_colors_db
self.width, self.height = dmc_image.size
self.unique_colors = None
self.color_to_symbol = None
def analyze_pattern(self):
"""
Analyse le patron et extrait les statistiques.
Returns:
Dictionnaire avec: nb_colors, total_stitches, dimensions, color_breakdown
"""
pass
def generate_symbol_map(self):
"""
Génère une correspondance unique couleur → symbole.
Returns:
Dictionnaire {code_dmc: symbol}
"""
pass
def generate_text_grid(self):
"""
Génère une grille textuelle avec symboles.
Returns:
Liste de chaînes (une ligne par ligne de points)
"""
pass
def generate_legend(self):
"""
Génère la légende couleur → symbole → code DMC → nom.
Returns:
DataFrame HTML ou liste de dictionnaires
"""
pass
def export_pdf(self, filepath):
"""
Exporte le patron complet en PDF.
Args:
filepath: Chemin du fichier PDF de sortie
Returns:
True si succès, False sinon
"""
pass
def export_png(self, filepath):
"""
Exporte le patron en PNG haute résolution.
Args:
filepath: Chemin du fichier PNG de sortie
Returns:
True si succès, False sinon
"""
pass
def export_json(self, filepath):
"""
Exporte la structure complète du patron en JSON.
Args:
filepath: Chemin du fichier JSON de sortie
Returns:
True si succès, False sinon
"""
pass
def generate_instruction_sheet(self):
"""
Génère une fiche d'instructions pour le brodeur.
Returns:
Chaîne de caractères (markdown ou HTML)
"""
pass
# TODO: Créer une fonction de workflow complet
def create_cross_stitch_pattern_from_image(
input_image_path,
output_dir="patterns",
block_size=8,
export_formats=["pdf", "png", "json"]
):
"""
Workflow complet : image → patron de point de croix.
Args:
input_image_path: Chemin de l'image d'entrée
output_dir: Répertoire de sortie
block_size: Taille des blocs pour réduction
export_formats: Liste des formats d'export
Returns:
Instance de CrossStitchPattern
"""
pass
# TODO: Tester avec l'image générée précédemment
# if 'generated_image' in globals():
# pattern = create_cross_stitch_pattern_from_image(generated_image)
# pattern.export_pdf("mon_patron.pdf")
passCritères de succès
Exercice 3 : Redimensionnement adaptatif avec conservation des proportions
Objectif : Implementer une fonction qui redimensionne une image avant reduction par blocs, en s’assurant que ses dimensions sont des multiples exacts de block_size et en preservant les proportions.
Contexte : La fonction reduce_by_blocks ignore les pixels restants quand les dimensions ne sont pas des multiples de block_size. En production, il est preferable de redimensionner l’image source pour eviter toute perte de données.
Indices : - Utilisez Image.resize() de Pillow avec le filtre Image.LANCZOS - # Étape 1 : Calculer les nouvelles dimensions arrondies au multiple inferieur de block_size - # Étape 2 : Verifier si un redimensionnement est reellement necessaire - # Étape 3 : Retourner l’image redimensionnee et les nouvelles dimensions - # Indice : Les nouvelles dimensions doivent etre width - (width % block_size) et height - (height % block_size)
def resize_for_blocks(img: Image.Image, block_size: int = 8) -> tuple:
"""
Redimensionne l'image pour que ses dimensions soient des multiples de block_size.
Args:
img: Image PIL source
block_size: Taille des blocs (defaut 8)
Returns:
Tuple (image_redimensionnee, (new_width, new_height))
Si aucune modification necessaire, retourne l'image originale
"""
# TODO etudiant : implementer le redimensionnement adaptatif
result = None # TODO etudiant : remplacer par l'image redimensionnee
return result
print("Exercice a completer")Exercice a completer