Prérequis Python et Algèbre Linéaire pour la 3D

Où en êtes-vous en Python et en algèbre linéaire ? Faites le test en dix minutes, puis révisez exactement ce qui vous manque avant d'aborder la 3D.
Bases
Test : 10 min · Lecture : 45 min
Python, NumPy, algèbre linéaire

Commencez par le test

20 questions, 10 minutes, votre niveau tout de suite

Dix questions de Python, dix d'algèbre linéaire. À la fin, chaque réponse fausse vous renvoie à la section qui l'explique : vous ne révisez que ce qui vous manque. Le repère de confort est de 16/20 : le test est informatif, il ne conditionne pas l'inscription.

Scan d'intérieur segmenté sans supervision : chaque segment porte une couleur distincte
Où mènent ces bases : un scan d'intérieur de 5 364 551 points découpé en 4 563 segments. Tableaux NumPy, normales, matrices de transformation : chaque étape repose sur ce que couvre cet article.

1. Installer Python : votre laboratoire de travail

La première étape est de disposer d'un environnement Python propre et reproductible. Miniconda est la référence pour la data science et le calcul scientifique, car il gère nativement les dépendances compilées (C++, CUDA, Fortran).

installation.sh
# 1. Télécharger Miniconda (Linux/macOS/Windows)
# https://docs.conda.io/en/latest/miniconda.html

# 2. Créer un environnement dédié
conda create -n prereq-3d python=3.12 -y
conda activate prereq-3d

# 3. Installer les bibliothèques essentielles
pip install numpy matplotlib
Conseil : Préférez Miniconda (léger, ~80 Mo) à Anaconda (lourd, ~4 Go). Vous installerez uniquement les paquets dont vous avez besoin, ce qui réduit les conflits de dépendances.

Vérifiez que tout fonctionne en lançant python --version dans votre terminal. Vous devez obtenir Python 3.12.x. Passons maintenant aux briques fondamentales du langage.

2. Les fondamentaux Python en 30 minutes

Inutile de lire un livre de 500 pages. Pour la 3D, vous avez besoin de maîtriser un sous-ensemble précis du langage. Voici l'essentiel, condensé.

2.1 Variables et types

Python est un langage à typage dynamique. Les types fondamentaux pour la 3D sont int, float, str, bool et list.

types_bases.py
# Variables numériques (coordonnées d'un point 3D)
x = 1.5
y = -3.2
z = 0.8

# Chaîne de caractères
label = "bâtiment"

# Booléen
est_classifie = True

# Vérifier le type
print(type(x))   # <class 'float'>
print(type(label)) # <class 'str'>

2.2 Listes et dictionnaires

Les listes stockent des séquences ordonnées. Les dictionnaires associent des clés à des valeurs. En 3D, une liste de coordonnées et un dictionnaire de métadonnées sont omniprésents.

structures.py
# Liste : un point 3D
point = [1.5, -3.2, 0.8]
print(point[0])  # 1.5 (indexation commence à 0)

# Liste de points (nuage simplifié)
nuage = [
    [1.5, -3.2, 0.8],
    [2.1, 0.4, 1.2],
    [-0.3, 1.7, 0.1],
]

# Dictionnaire : métadonnées d'un scan
scan_info = {
    "source": "LiDAR Terrestre",
    "nb_points": 5_000_000,
    "format": "LAZ",
    "crs": "EPSG:2154",
}
print(scan_info["nb_points"])  # 5000000

2.3 Boucles et conditions

Les boucles for parcourent des séquences. Les conditions if/elif/else contrôlent le flux. En 3D, on itère souvent sur des points pour les filtrer ou les transformer.

boucles.py
# Filtrer les points au-dessus de z = 0.5
points_hauts = []
for point in nuage:
    if point[2] > 0.5:
        points_hauts.append(point)

print(f"Points au-dessus de 0.5m : {len(points_hauts)}")

# Compréhension de liste (syntaxe compacte)
points_hauts = [p for p in nuage if p[2] > 0.5]

# La même syntaxe calcule une liste à partir d'une autre
carres = [x**2 for x in range(4)]
print(carres)  # [0, 1, 4, 9]

# Boucle avec index (enumerate)
for i, p in enumerate(nuage):
    print(f"Point {i} : x={p[0]}, y={p[1]}, z={p[2]}")

2.4 Fonctions

Les fonctions encapsulent une logique réutilisable. Dans un pipeline 3D, chaque étape de traitement est typiquement une fonction.

fonctions.py
def distance_3d(p1, p2):
    """Calcule la distance euclidienne entre deux points 3D."""
    dx = p1[0] - p2[0]
    dy = p1[1] - p2[1]
    dz = p1[2] - p2[2]
    return (dx**2 + dy**2 + dz**2) ** 0.5

a = [0, 0, 0]
b = [1, 1, 1]
print(f"Distance : {distance_3d(a, b):.4f} m")
# Distance : 1.7321 m

3. NumPy : le moteur du calcul vectoriel

NumPy est la bibliothèque fondamentale pour le calcul numérique en Python. En 3D, vous manipulez des millions de points : les boucles Python sont trop lentes. NumPy exécute chaque opération en C compilé, sur tout le tableau d'un coup : c'est ce qui rend possible le traitement de millions de points.

3.1 Créer et manipuler des tableaux

numpy_bases.py
import numpy as np

# Créer un array à partir d'une liste
point = np.array([1.5, -3.2, 0.8])
print(point.shape)  # (3,) : vecteur 3D

# Nuage de 5 points (matrice 5x3)
nuage = np.array([
    [1.5, -3.2, 0.8],
    [2.1,  0.4, 1.2],
    [-0.3, 1.7, 0.1],
    [0.0,  0.0, 0.0],
    [3.4, -1.1, 2.5],
])
print(nuage.shape)  # (5, 3) : 5 points, 3 dimensions

# Accéder à toutes les coordonnées Z
z_coords = nuage[:, 2]
print(z_coords)  # [0.8, 1.2, 0.1, 0.0, 2.5]

3.2 Opérations vectorisées et broadcasting

Le broadcasting est la capacité de NumPy à appliquer une opération entre des arrays de formes différentes. C'est ce qui rend le calcul 3D possible sans boucles.

numpy_ops.py
# Translation : déplacer tout le nuage de (+10, +20, +5)
translation = np.array([10, 20, 5])
nuage_deplace = nuage + translation  # Broadcasting automatique !

# Centrer le nuage sur son barycentre
centroid = np.mean(nuage, axis=0)
nuage_centre = nuage - centroid

# Calculer la distance de chaque point à l'origine
distances = np.linalg.norm(nuage, axis=1)
print(distances)  # [3.62, 2.45, 1.73, 0.00, 4.36]

# Filtrage vectorisé : points à moins de 3m de l'origine
masque = distances < 3.0
points_proches = nuage[masque]
print(f"Points proches : {len(points_proches)}")
Conseil : Pensez toujours "vectorisé". Si vous écrivez une boucle for sur un array NumPy, il y a probablement une opération vectorisée plus rapide. La règle d'or : pas de boucle Python sur des données 3D.

4. Lire et écrire des fichiers

En 3D, les données transitent souvent sous forme de fichiers texte (CSV, TXT) ou structurés (JSON). Savoir les lire et les écrire est un prérequis non négociable.

fichiers_io.py
import numpy as np
import json

# ---- CSV : écrire puis relire un nuage de points ----
nuage = np.array([[ 1.5, -3.2, 0.8],
                  [ 2.1,  0.4, 1.2],
                  [-0.3,  1.7, 0.1]])
np.savetxt("points.csv", nuage, delimiter=",", header="x,y,z", comments="")

nuage_lu = np.loadtxt("points.csv", delimiter=",", skiprows=1)
print(f"Chargé {nuage_lu.shape[0]} points")  # Chargé 3 points

# Sauvegarder les seuls points au-dessus de 0,5 m
np.savetxt("points_filtres.csv", nuage_lu[nuage_lu[:, 2] > 0.5],
           delimiter=",", header="x,y,z", comments="")

# ---- JSON : métadonnées de projet ----
config = {"crs": "EPSG:2154", "nb_points": len(nuage_lu), "bbox": [0, 0, 100, 100]}
with open("projet.json", "w") as f:
    json.dump(config, f, indent=2)

with open("projet.json") as f:
    print(json.load(f)["crs"])  # EPSG:2154

5. Visualiser avec Matplotlib

Matplotlib permet de créer des graphiques 2D et 3D. Indispensable pour vérifier visuellement vos données avant de les injecter dans un pipeline de traitement.

visualisation.py
import matplotlib.pyplot as plt
import numpy as np

# Générer un nuage de points aléatoire
np.random.seed(42)
nuage = np.random.randn(200, 3)

# Visualisation 3D
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection='3d')
ax.scatter(nuage[:, 0], nuage[:, 1], nuage[:, 2],
           c=nuage[:, 2], cmap='viridis', s=5)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
ax.set_title('Nuage de Points 3D')
plt.show()

6. Exercice : charger et afficher des coordonnées 3D

Mettez en pratique tout ce que vous venez d'apprendre. L'objectif : créer un fichier CSV de coordonnées, le charger avec NumPy, calculer des statistiques, et afficher le résultat en 3D.

exercice_python.py
import numpy as np
import matplotlib.pyplot as plt

# 1. Créer un fichier CSV de test
np.random.seed(0)
points = np.random.uniform(-10, 10, size=(500, 3))
np.savetxt("nuage_test.csv", points, delimiter=",",
           header="x,y,z", comments="")

# 2. Charger le fichier
data = np.loadtxt("nuage_test.csv", delimiter=",", skiprows=1)
print(f"Nombre de points : {data.shape[0]}")

# 3. Statistiques
print(f"Barycentre : {np.mean(data, axis=0)}")
print(f"Étendue Z  : {data[:, 2].min():.2f} à {data[:, 2].max():.2f}")

# 4. Filtrer : garder seulement les points avec z > 0
data_filtre = data[data[:, 2] > 0]
print(f"Points avec z > 0 : {len(data_filtre)}")

# 5. Visualiser
fig = plt.figure(figsize=(12, 5))

ax1 = fig.add_subplot(121, projection='3d')
ax1.scatter(data[:, 0], data[:, 1], data[:, 2],
            c=data[:, 2], cmap='coolwarm', s=2)
ax1.set_title('Nuage complet')

ax2 = fig.add_subplot(122, projection='3d')
ax2.scatter(data_filtre[:, 0], data_filtre[:, 1], data_filtre[:, 2],
            c=data_filtre[:, 2], cmap='coolwarm', s=2)
ax2.set_title('Filtré (z > 0)')

plt.tight_layout()
plt.show()
Deux vues 3D : les 500 points du nuage, puis les seuls points au-dessus de z = 0
Ce que doit afficher votre script : à gauche les 500 points tirés entre -10 et 10, colorés par leur hauteur ; à droite les 251 points qui ont z > 0.
Objectif atteint : Si vous pouvez exécuter ce script sans erreur et comprendre chaque ligne, vous possédez les bases Python nécessaires. Vérifiez-le avec les questions 1 à 10 du test, puis passons à l'algèbre linéaire.

7. Vecteurs 2D et 3D : la brique élémentaire

Un vecteur est une quantité qui a une direction et une magnitude. En 3D, un vecteur est un triplet (x, y, z). Les opérations sur les vecteurs sont le fondement de toute géométrie computationnelle.

vecteurs.py
import numpy as np

# Définir deux vecteurs 3D
u = np.array([3, 1, 4])
v = np.array([1, 5, -2])

# Addition et soustraction
somme = u + v       # [4, 6, 2]
diff = u - v        # [2, -4, 6]

# Multiplication par un scalaire
double_u = 2 * u    # [6, 2, 8]

# Norme (longueur) du vecteur
norme_u = np.linalg.norm(u)
print(f"||u|| = {norme_u:.4f}")  # 5.0990

# Vecteur unitaire (normalisation)
u_hat = u / norme_u
print(f"Direction de u : {u_hat}")
print(f"Vérification : ||u_hat|| = {np.linalg.norm(u_hat):.1f}")  # 1.0
Opération Notation NumPy Résultat
Addition u + v u + v Vecteur
Soustraction u - v u - v Vecteur
Scalaire k * u k * u Vecteur
Norme ||u|| np.linalg.norm(u) Scalaire

8. Produit scalaire

Le produit scalaire de deux vecteurs donne un nombre (scalaire). Il mesure à quel point deux vecteurs pointent dans la même direction. Son application 3D principale : calculer l'angle entre deux vecteurs.

dot_product.py
import numpy as np

u = np.array([1, 0, 0])  # Axe X
v = np.array([0, 1, 0])  # Axe Y
w = np.array([1, 1, 0])  # Diagonale XY

# Produit scalaire
print(np.dot(u, v))  # 0 : vecteurs perpendiculaires !
print(np.dot(u, w))  # 1

# Angle entre u et w
cos_angle = np.dot(u, w) / (np.linalg.norm(u) * np.linalg.norm(w))
angle_rad = np.arccos(cos_angle)
angle_deg = np.degrees(angle_rad)
print(f"Angle entre u et w : {angle_deg:.1f}°")  # 45.0°
Application 3D : Le produit scalaire sert à déterminer si une surface est face à la caméra (éclairage), à calculer des projections orthogonales, et à mesurer la planéité locale d'un nuage de points.

9. Produit vectoriel et normale à un plan

Le produit vectoriel de deux vecteurs donne un troisième vecteur perpendiculaire aux deux premiers. C'est l'outil fondamental pour calculer la normale d'une surface en 3D.

cross_product.py
import numpy as np

# Trois points définissent un plan
A = np.array([0, 0, 0])
B = np.array([1, 0, 0])
C = np.array([0, 1, 0])

# Deux vecteurs dans le plan
AB = B - A  # [1, 0, 0]
AC = C - A  # [0, 1, 0]

# Normale au plan = produit vectoriel
normale = np.cross(AB, AC)
print(f"Normale : {normale}")  # [0, 0, 1] : pointe vers le haut

# Normaliser pour obtenir un vecteur unitaire
normale_unit = normale / np.linalg.norm(normale)
print(f"Normale unitaire : {normale_unit}")

# Aire du triangle ABC
aire = 0.5 * np.linalg.norm(normale)
print(f"Aire du triangle : {aire} m²")  # 0.5 m²
À gauche, les vecteurs u, v et leur somme ; à droite, le triangle ABC et sa normale
Les valeurs des sections 7 et 9, dessinées. À gauche, u et v se complètent en u + v, diagonale du parallélogramme. À droite, le produit vectoriel de AB et AC donne la normale au plan ABC, perpendiculaire au triangle.
Normales estimées sur un scan d'intérieur, colorées par direction
La même opération à l'échelle d'un vrai nuage : une normale par point, colorée selon sa direction. Le sol ressort d'une seule teinte, chaque mur prend la couleur de son orientation.
Attention : Le produit vectoriel n'existe qu'en 3D. L'ordre des opérandes compte : u x v = -(v x u). Cela détermine le sens de la normale (vers le haut ou vers le bas).

10. Matrices : multiplication, transposée et inverse

Les matrices sont des tableaux 2D de nombres. En 3D, elles servent à représenter des transformations géométriques (rotation, mise à l'échelle) et à résoudre des systèmes d'équations.

matrices.py
import numpy as np

# Créer une matrice 3x3
M = np.array([
    [2, 0, 0],
    [0, 3, 0],
    [0, 0, 1],
])

# Multiplication matrice x vecteur
v = np.array([1, 1, 1])
resultat = M @ v  # Opérateur @ = multiplication matricielle
print(resultat)   # [2, 3, 1] : mise à l'échelle !

# Transposée
print(M.T)

# Inverse (si elle existe)
M_inv = np.linalg.inv(M)
print(M_inv @ M)  # Matrice identité (aux erreurs flottantes près)

# Déterminant
det = np.linalg.det(M)
print(f"Déterminant : {det}")  # 6.0
Opération NumPy Signification Géométrique
Multiplication A @ B Composition de transformations
Transposée A.T Miroir par rapport à la diagonale
Inverse np.linalg.inv(A) Transformation inverse (annuler)
Déterminant np.linalg.det(A) Facteur de changement de volume

11. Transformations 3D : rotation, translation, échelle

Toute manipulation géométrique en 3D se ramène à une combinaison de trois opérations fondamentales. Comprendre comment les appliquer avec des matrices est essentiel pour travailler avec des nuages de points.

11.1 Matrice de rotation

Une rotation autour de l'axe Z d'un angle theta s'exprime par une matrice 3x3. Les rotations autour de X et Y suivent le même principe.

rotations.py
import numpy as np

def rotation_z(theta_deg):
    """Matrice de rotation autour de l'axe Z."""
    theta = np.radians(theta_deg)
    c, s = np.cos(theta), np.sin(theta)
    return np.array([
        [ c, -s, 0],
        [ s,  c, 0],
        [ 0,  0, 1],
    ])

def rotation_x(theta_deg):
    """Matrice de rotation autour de l'axe X."""
    theta = np.radians(theta_deg)
    c, s = np.cos(theta), np.sin(theta)
    return np.array([
        [1,  0,  0],
        [0,  c, -s],
        [0,  s,  c],
    ])

def rotation_y(theta_deg):
    """Matrice de rotation autour de l'axe Y."""
    theta = np.radians(theta_deg)
    c, s = np.cos(theta), np.sin(theta)
    return np.array([
        [ c, 0, s],
        [ 0, 1, 0],
        [-s, 0, c],
    ])

# Rotation de 45° autour de Z
R = rotation_z(45)
point = np.array([1, 0, 0])
point_tourne = R @ point
print(f"Avant : {point}")
print(f"Après : {point_tourne}")  # [0.707, 0.707, 0]

11.2 Translation et changement d'échelle

transformations.py
# Translation : simple addition vectorielle
nuage = np.random.randn(100, 3)
translation = np.array([10, 20, 5])
nuage_deplace = nuage + translation

# Changement d'échelle : multiplication par une matrice diagonale
S = np.diag([2.0, 2.0, 0.5])  # Double en X/Y, moitié en Z
nuage_scale = (S @ nuage.T).T

# Combinaison : d'abord rotation, puis translation
R = rotation_z(30)
nuage_transforme = (R @ nuage.T).T + translation
Concept clé : En production, les transformations sont composées en une seule matrice 4x4 (matrice homogène) qui combine rotation, translation et échelle en une opération. C'est le standard dans tous les logiciels 3D (Blender, Unity, Open3D).

12. Systèmes de coordonnées et projections

En géospatial, les données proviennent de capteurs utilisant différents systèmes de coordonnées. Il est crucial de savoir passer d'un repère à un autre.

Système Type Usage Typique Unité
WGS84 (EPSG:4326) Géographique GPS, données mondiales Degrés
Lambert 93 (EPSG:2154) Projeté France métropolitaine Mètres
UTM Projeté Universel par zones Mètres
Local / Scanner Cartésien LiDAR terrestre, BIM Mètres

Le passage d'un repère local à un repère géoréférencé se fait par une transformation rigide (rotation + translation), souvent via une matrice 4x4 obtenue par recalage de cibles connues.

projection.py
import numpy as np

# Projection orthogonale d'un vecteur u sur un vecteur v
def projection(u, v):
    """Projette u sur v."""
    return (np.dot(u, v) / np.dot(v, v)) * v

u = np.array([3, 4, 0])
v = np.array([1, 0, 0])  # Axe X

proj = projection(u, v)
print(f"Projection de u sur X : {proj}")  # [3, 0, 0]

# Composante perpendiculaire
perp = u - proj
print(f"Composante perpendiculaire : {perp}")  # [0, 4, 0]

13. Exercice : faire tourner un nuage de points

Exercice de synthèse : générez un nuage de points en forme de "L", appliquez une rotation de 90 degrés autour de l'axe Z, puis visualisez le résultat avant/après.

exercice_rotation.py
import numpy as np
import matplotlib.pyplot as plt

np.random.seed(1)  # même tirage pour tout le monde

# 1. Créer un nuage en forme de L
branche_h = np.random.uniform([0, 0, 0], [5, 1, 1], size=(200, 3))
branche_v = np.random.uniform([0, 0, 0], [1, 4, 1], size=(200, 3))
nuage = np.vstack([branche_h, branche_v])

# 2. Matrice de rotation (90° autour de Z)
theta = np.radians(90)
R = np.array([
    [np.cos(theta), -np.sin(theta), 0],
    [np.sin(theta),  np.cos(theta), 0],
    [0,              0,             1],
])

# 3. Appliquer la rotation à chaque point
# nuage.T = (3, 400), R @ nuage.T = (3, 400), .T = (400, 3)
nuage_tourne = (R @ nuage.T).T

# 4. Visualiser avant / après, vus de dessus et à la même échelle :
#    une rotation autour de Z se lit alors directement dans le plan XY
fig = plt.figure(figsize=(12, 6))
for i, (pts, couleur, titre) in enumerate([(nuage, '#8b5cf6', 'Original'),
                                           (nuage_tourne, '#d946ef', 'Après rotation 90° (Z)')]):
    ax = fig.add_subplot(1, 2, i + 1, projection='3d')
    ax.scatter(pts[:, 0], pts[:, 1], pts[:, 2], c=couleur, s=3)
    ax.set_xlim(-5, 5); ax.set_ylim(-5, 5); ax.set_zlim(0, 1)
    ax.view_init(elev=90, azim=-90)
    ax.set_zticks([])  # vu de dessus, l'axe Z n'apporte rien
    ax.set_title(titre)
    ax.set_xlabel('X'); ax.set_ylabel('Y')

plt.tight_layout()
plt.show()

# Vérification : le barycentre a-t-il bien tourné ?
c_avant = np.mean(nuage, axis=0)
c_apres = np.mean(nuage_tourne, axis=0)
print(f"Barycentre avant  : {c_avant}")
print(f"Barycentre après  : {c_apres}")
print(f"Distance conservée : {np.linalg.norm(c_avant):.4f} vs {np.linalg.norm(c_apres):.4f}")
Le nuage en forme de L vu de dessus, avant et après une rotation de 90 degrés autour de Z
Le résultat attendu, vu de dessus : le L tourne de 90° autour de l'axe Z, dans le sens trigonométrique. Ses dimensions ne changent pas, et la distance du barycentre à l'origine non plus (1,9897 dans les deux cas).
Vérification : Une rotation conserve les distances. La norme du barycentre avant et après doit être identique (aux erreurs de virgule flottante près). Si ce n'est pas le cas, votre matrice n'est pas une rotation valide.

14. Le test de positionnement

Ce questionnaire vous permet d'évaluer votre niveau avant de rejoindre la formation. 20 questions, 4 choix, une seule bonne réponse. Répondez honnêtement : il n'y a aucun piège, juste une vérification de vos acquis.

Test de Positionnement

10 questions Python + 10 questions Algèbre Linéaire. Durée estimée : 10 minutes.

Python (Questions 1-10)

1 Quel est le résultat de type(3.14) en Python ?

2 Comment accéder au troisième élément d'une liste L ?

3 Quelle instruction crée un dictionnaire en Python ?

4 Que retourne len([1, 2, 3, 4]) ?

5 Quelle est la sortie de : [x**2 for x in range(4)] ?

6 Quelle bibliothèque utilise-t-on pour charger un fichier CSV sous forme de tableau numérique ?

7 Quel est le résultat de np.array([1,2,3]).shape ?

8 Comment sélectionner toutes les lignes de la deuxième colonne d'un array A de shape (100, 3) ?

9 Que fait np.mean(A, axis=0) sur un array de shape (100, 3) ?

10 Quelle fonction Python permet de définir un bloc de code réutilisable ?

Algèbre Linéaire (Questions 11-20)

11 Quelle est la norme du vecteur (3, 4, 0) ?

12 Quel est le produit scalaire de (1, 0, 0) et (0, 1, 0) ?

13 Que signifie un produit scalaire nul entre deux vecteurs non nuls ?

14 Quel est le produit vectoriel de (1, 0, 0) et (0, 1, 0) ?

15 Quelle est la dimension du résultat de la multiplication d'une matrice (3x3) par un vecteur (3x1) ?

16 Que représente la matrice identité (I) en géométrie 3D ?

17 Si une matrice de rotation R tourne un objet de 30 degrés, que fait R inverse ?

18 Quelle matrice effectue un changement d'échelle qui double toutes les dimensions ?

19 Quelle propriété est conservée par une matrice de rotation ?

20 En NumPy, quel opérateur effectue une multiplication matricielle ?

Votre score

Sur ce sujet : Le programme de la formation, module par module.

Et après les prérequis ?

Couverture du guide « Architecte IA de Systèmes 3D en Production », 2e édition

Ces bases sont le premier maillon. Le guide gratuit Architecte IA de Systèmes 3D montre où elles mènent : du nuage de points brut au Smart Point Cloud et aux agents spatiaux, en 24 chapitres et 172 pages, à lire en ligne ou en PDF.

La formation, elle, reprend chaque maillon sur des projets concrets.

Recevoir le guide gratuit Passer le test (10 min)

Le programme et les tarifs →