Étiquette : fractales

Projets

Un Explorateur de fractales en Python

Plongez dans des mondes visuels éblouissants créés à partir de simples équations mathématiques, les fractales. Découvrez des fractales emblématiques telles que Julia et Mandelbrot, personnalisez votre expérience avec des palettes de couleurs. Embarquez pour un voyage captivant au cœur de l’art mathématique !

Les prémices du projet

Tout débute durant mon année de seconde. En math, j’ai utilisé le script Mandelbrot initialement présent dans la calculatrice Numworks. Je me suis amusé à analyser le script et grâce à ça et à mon père, je suis tombé dans les fractales. 

Ensuite, j’ai commencé la NSI en première et j’ai réalisé mon premier tutoriel sur ledit script. Et en fin d’année, j’ai modifié ce script afin de faire de plus belles couleurs.

Cette année, j’ai eu envie d’aller bien plus loin, l’idée vient du fait que pour  réaliser de belles images de fractale, j’utilise des explorateurs de fractales trouvables facilement sur internet pour trouver des coordonnées intéressantes. Mais l’expression le dit bien, on n’est jamais mieux servi que par soi-même, et j’ai donc choisi pour mon dernier projet de lycée de faire mon propre explorateur de fractale.

Le début du projet

Je suis parti du script Julia que j’avais fait durant mon projet de fin d’année de première, vous pourrez retrouver l’explication du script ici (lien vers le tuto Julia palette). Ainsi, nous partons de ce script : 

from kandinsky import*
def julia(N_iteration):
    palette = []
    xmax = 2
    xmin = -2
    ymax = 1.3875
    ymin = -1.387
    r = 255
    g = 255
    b = 255
    for j in range(0,128):
        b = 255 -2 * j
        palette.append([r,g,b])
    for j in range(128,256):
        r = 255 -2 * (j-128)
        g = 255 -2 * (j-128)
        palette.append([r,g,b])
    for x in range(320):
        for y in range(222):
            i = 0
            z = complex(xmin+(xmax-xmin)*x/320+(ymax-(ymax-ymin)*y/222)*1J)
            c = complex(0.36,0.36)
            while i < N_iteration and abs(z) < 2:
                i = i + 1
                z = z*z+c
            couleur = palette[int(255*i/N_iteration)]
            col = color(couleur[0],couleur[1],couleur[2])
            set_pixel(x,y,col)

La logique

I – optimisation

Pour faire mon explorateur, je veux générer une nouvelle fractale à chaque zoom, dézoome, déplacement, sur la droite, la gauche, le haut, le bas en modifiant les valeurs des coins de l’image. Mais si je suis cette logique, le script Julia pose déjà un problème. En effet, le script met des dizaines de secondes à fabriquer une seule image, vous conviendrez qu’un zoom en deux minutes, c’est un peu lent non ? Étrangement, un tutoriel a été écrit durant les vacances de Noël 2023 et bizarrement ce script présente exactement ce dont j’ai besoin ici.

Ainsi, j’ai un script qui me permet de générer des images en .png de fractale rapidement. Maintenant, j’ai remplacé la bibliothèque Kandinsky par Pillow et j’ai entièrement modifié les calculs de la fractale pour utiliser NumPy. Nous avons donc ce script :

from PIL import Image
import time
import tkinter as tk
from tkinter import PhotoImage
import keyboard
import numpy as np

global xmax, xmin, ymax, ymin, réso_x, réso_y
#valeurs de base
xmax = 2
xmin = -2
ymax = 1.3875
ymin = -1.387
réso_x = 30720 
réso_y = 21312

def julia_PIL(N_iteration = 100):
    palette = []
    r = 255
    g = 255
    b = 255
    for j in range(0, 128):
        b = 255 - 2 * j
        palette.append((r, g, b))
    for j in range(128, 256):
        r = 255 - 2 * (j - 128)
        g = 255 - 2 * (j - 128)
        palette.append((r, g, b))

    palette = np.array(palette, dtype=np.uint8)

    # Créer une grille de coordonnées complexes
    x = np.linspace(xmin, xmax, réso_x)
    y = np.linspace(ymin, ymax, réso_y)
    X, Y = np.meshgrid(x, y)
    Z = X + 1J * Y

    # Initialiser une matrice d'indices pour la palette
    indices_matrix = np.zeros((réso_y, réso_x), dtype=int)

    for i in range(N_iteration):
        print(i)
        # Mettre à jour les pixels
        mask = np.logical_and(i < N_iteration, np.abs(Z) < 2)
        indices_matrix[mask] = np.round(255 * i / N_iteration).astype(int)

        # Mettre à jour Z pour les pixels actifs
        Z[mask] = Z[mask] ** 2 + complex(0.36, 0.36)

    # Créer une image à partir de la matrice d'indices
    img_array = palette[indices_matrix]

    # Créer une image PIL à partir de l'array
    img = Image.fromarray(img_array, 'RGB')

    # Sauvegarder l'image
    img.save('fractales_images/test.png')

II – Affichage de l’image 

Pour afficher l’image, j’utilise la fonction Pillow mixé à Tkinter. Pillow génère une image en .png et l’enregistre dans un dossier et à chaque nouvelle génération la nouvelle image remplace l’ancienne. 

Tkinter lui génère une fenêtre de la même résolution que l’image générée par Pillow. Ensuite, il va chercher dans le dossier cette image faite par Pillow et l’affiche dans la fenêtre précédemment créée.

def fractal_builder(N_iteration = 100,nom_img='Explorer_image/Image.png'):
  """
  script de génération de la fractale
  """
    # Créer une image à partir de la matrice des indices
    img_array = palette[indices_matrix]

    # Créer une image à partir de l'array
    img = Image.fromarray(img_array, 'RGB')

    # Sauvegarder l'image
    img.save(nom_img)    

def afficher_image():
    img = PhotoImage(file=chemin_image)
    # stocke l'image dans la variable img
    canvas.create_image(0, 0, anchor=tk.NW, image=img)
    #affiche l'image

III – déplacement et zoom 

Afin de me déplacer et de zoomer, je vais utiliser la bibliothèque keyboard. Elle me permet de détecter quand j’appuie sur les touches de mon clavier. Je crée donc une fonction “explorer” qui s’occupe des déplacements et de leur logique. Je vais ainsi modifier les valeurs des coordonnées des coins de l’image que je prends de la fractale puis la générer avec ses nouvelles valeurs dans la fenêtre de Tkinter.

def explorer(action):
    global xmax, xmin, ymax, ymin,img,nbr_img    
    x = xmax - xmin
    y = ymax - ymin
    
    if keyboard.is_pressed('up'):# zoom avant
        xmax = xmax - (x/20)
        xmin = xmin - (x/20)*-1
        ymax = ymax - (y/20)
        ymin = ymin - (y/20)*-1
        fractal_builder()
        afficher_image()      
    elif keyboard.is_pressed('down'):# zoom arrière
        xmax = xmax + (x/20)
        xmin = xmin + (x/20)*-1
        ymax = ymax + (y/20)
        ymin = ymin + (y/20)*-1
        fractal_builder()
        afficher_image()
    elif keyboard.is_pressed('z'): # déplacement haut
        ymax = ymax - (y/40)
        ymin = ymin - (y/40)
        fractal_builder()
        afficher_image()
    elif keyboard.is_pressed('s'): # déplacement bas
        ymax = ymax + (y/40)
        ymin = ymin + (y/40)
        fractal_builder()
        afficher_image()
    elif keyboard.is_pressed('q'): # déplacement gauche
        xmax = xmax - (x/40)
        xmin = xmin - (x/40)
        fractal_builder()
        afficher_image()
    elif keyboard.is_pressed('d'): # déplacement droite
        xmax = xmax + (x/40)
        xmin = xmin + (x/40)
        fractal_builder()
        afficher_image()

IV – Menu, accueil

Au début du projet, ma volonté était qu’à la fin, j’ai réussi à faire un explorateur de fractaleS. Pouvoir explorer des fractales comme Julia et Mandelbrot que je maîtrise déjà et peut-être même d’autres.

Mais pour faire ça il faut que l’utilisateur puisse choisir dans un menu quelle fractale veut-il explorer et faire un menu, quand j’ai commencé le projet été pour moi le plus dur à réaliser.

Durant les vacances de février 2024 il nous a été demandé de faire un gestionnaire de base de données, d’ailleurs voici ce que j’ai réalisé (lien vers le projet). Mais quel rapport avec des fractales ? Eh bien le fait que ce projet utilise Tkinter pour réaliser un menu, exactement ce que cherche à faire et surtout, c’est simple ! (même si honnêtement, on ne dirait pas).

Parallèlement à ce projet de base de données, j’ai donc piqué des bouts de mon code pour le mettre dans mon explorateur et en seulement quelques minutes, j’ai un menu ! Il ne sert à rien et il est moche (et il le restera, je suis pas designer).

Faire une fenêtre avec Tkinter est plutôt simple à comprendre, on crée des variables qui vont être chaque élément de la page, texte, menu déroulant, champs pour écrire, etc. Pour les construire, Tkinter nous offre des fonctions comme Label() pour les textes, Entry() pour les champs d’écritures entre autres. Il ne nous reste qu’à trouver sur internet les paramètres à mettre à l’intérieur (comme text = “le texte que l’on veut afficher” pour un texte).

titre_acceuil = tk.Label(cadre_acceuil, text="Bienvenue sur cet explorateur de fractales", font=("Consolas", 20), bg="#C2C2C2", fg="black")
soustitre_acceuil = tk.Label(cadre_acceuil, text="Choisissez les paramètres de votre fractale", font=("Consolas", 15), bg="#C2C2C2", fg="black")
titre_acceuil.pack(pady=10)
soustitre_acceuil.place(x=130,y=50)

#création de la liste déroulante de choix des palettes
desc_liste_deroul_palette = tk.Label(cadre_acceuil, text="Choisissez la palette de couleur que vous voulez utiliser", font=("Consolas", 12), bg="#C2C2C2", fg="black")
desc_liste_deroul_palette.place(x=15,y=100)
liste_choix = ["Blanc - Jaune - Noir","Blanc - Noir"] # élément de la liste déroulante
variable_palette = tk.StringVar() # élément initial
variable_palette.set("Blanc - Noir")
liste_deroulante_palette = ttk.Combobox(cadre_acceuil, textvariable=variable_palette, values=liste_choix)
liste_deroulante_palette.place(x=15,y=125)
select = liste_deroulante_palette.get() # sélection de l'élément choisi dans la liste

#création de la liste déroulante de choix de la fractale
desc_liste_deroul_fractale = tk.Label(cadre_acceuil, text="Choisissez la fractale que vous voulez voir", font=("Consolas", 12), bg="#C2C2C2", fg="black")
desc_liste_deroul_fractale.place(x=15,y=145)
liste_choix = ["Julia","Mandelbrot"] # élément de la liste déroulante
variable_fractal = tk.StringVar() # élément intiaux
variable_fractal.set("Julia")
liste_deroulante_fractal = ttk.Combobox(cadre_acceuil, textvariable=variable_fractal, values=liste_choix)
liste_deroulante_fractal.place(x=15,y=170)

# création du champ de texte pour choisir la valeur de c
desc1_chmp_val_julia = tk.Label(cadre_acceuil, text="Si vous avez choisi de générer une fractale de Julia :", font=("Consolas", 12), bg="#C2C2C2", fg="black")
desc2_chmp_val_julia = tk.Label(cadre_acceuil, text="Choisissez la valeur de la constante c (où laisser par défaut), sachant c est un complexe", font=("Consolas", 10), bg="#C2C2C2", fg="black")
desc1_chmp_val_julia.place(x=15,y=195)
desc2_chmp_val_julia.place(x=25,y=220)
variable_r = tk.StringVar()
variable_r.set("0.36")
chmp_str_c_r_julia = tk.Entry(cadre_acceuil, textvariable=variable_r, font=("Helvetica",12), bg="#ffffff", fg="black", width=5)
chmp_str_c_r_julia.place(x=40,y=245)
variable_i = tk.StringVar()
variable_i.set("0.36")
chmp_str_c_i_julia = tk.Entry(cadre_acceuil, textvariable=variable_i, font=("Helvetica",12), bg="#ffffff", fg="black", width=5)
chmp_str_c_i_julia.place(x=100,y=245)

#création de la liste déroulante de choix de la résolution
desc_liste_deroul_resolution = tk.Label(cadre_acceuil, text="Choisissez la résolution de l'explorateurr", font=("Consolas", 12), bg="#C2C2C2", fg="black")
desc_liste_deroul_resolution.place(x=15,y=270)
liste_choix = ["320x222","480x333","720x555"]
variable_resolution = tk.StringVar()
variable_resolution.set("320x222")
liste_deroulante_resolution = ttk.Combobox(cadre_acceuil, textvariable=variable_resolution, values=liste_choix)
liste_deroulante_resolution.place(x=15,y=295)

V – Menu, faire devenir utile l’accueil

Maintenant qu’on a un superbe menu, rendons le utile. Pour cela, je crée des fonctions pour sélectionner la palette, la résolution, la fractale etc que j’affecte à une grande fonction start que j’affecte elle au bouton qui lance l’explorateur. Les fonctions de sélection récupèrent ce qui est écrit dans les champs de textes, menu déroulant pour pouvoir donner c’est paramètre à la fonction mère, start().

start (), elle initialise toutes les variables utiles à l’explorateur, supprime la fenêtre du menu, crée la fenêtre de l’explorateur, initialise l’explorateur avec la première image et lance les dernières fonctions nécessaires comme explorer().

def selection_reso(): # fonction de sélection de la résolution utilisée
    global réso_x,réso_y
    if liste_deroulante_resolution.get() == "320x222":
        réso_x = 320
        réso_y = 222
    if liste_deroulante_resolution.get() == "480x333":
        réso_x = 480
        réso_y = 333
    if liste_deroulante_resolution.get() == "720x555":
        réso_x = 720
        réso_y = 555

def selection_palette(): # fonction de sélection de la palette utilisée
    global palette
    palette = []
    select = liste_deroulante_palette.get()
    if select == "Blanc - Jaune - Noir":
        r, g, b = 255, 255, 255
        for j in range(0, 128):
            b = 255 - 2 * j
            palette.append((r, g, b))
        for j in range(128, 256):
            r = 255 - 2 * (j - 128)
            g = 255 - 2 * (j - 128)
            palette.append((r, g, b))
    else :
        palette = [[i,i,i] for i in range(255)]

def start(): # fonction de lancement de l'exploration
    global img,chemin_image,canvas,choix_fract,compl_r,compl_i, réso_x, réso_y
    selection_palette()
    # sélèction de la fractale
    if liste_deroulante_fractal.get() == "Julia":
        choix_fract = 0
    if liste_deroulante_fractal.get() == "Mandelbrot":
        choix_fract = 1
    selection_reso()
    compl_r,compl_i = chmp_str_c_r_julia.get(),chmp_str_c_i_julia.get() # sélection des valeurs de c
    
    fenetre.destroy() #supprime la fenêtre de l'accueil
    cadre_acceuil_explo = tk.Tk() # crée la fenètre de l'explorateur
    cadre_acceuil_explo.title("Affichage d'une Image")
    
    chemin_image = "Explorer_image/Image.png"
    # Charge l'image
    img = PhotoImage(file=chemin_image)
    # Crée un widget Canvas pour afficher l'image
    canvas = tk.Canvas(cadre_acceuil_explo, width=réso_x, height=réso_y)
    canvas.pack()
    # initialisation de la 1ère fractale
    fractal_builder()
    afficher_image()
    cadre_acceuil_explo.mainloop()

# création et placement du bouton de lancement de l'explorateur
bouton_start_explo = tk.Button(cadre_acceuil, text="Commencer l'exploration", font=("Consolas",15), bg="white", fg="black", command = start)
bouton_start_explo.place(x=230,y=495)

VI – Menu, prévisualisation

Actuellement, j’ai donc un menu qui me permet d’explorer 2 fractales différentes avec x palette différente et une infinité de fractales de Julia. Mais pendant que je joue avec mon script, je me rends compte que c’est lourd de devoir mettre les paramètres, lancer l’exploration, se rendre compte que ce n’est pas ce qu’on veut, donc on relance le script et ainsi de suite… Une idée me vient alors en tête : “pouvoir prévisualiser la fractale qu’on génère dans le menu, ce serait bien non ?” et voilà un nouvel objectif et pas des moindres, il me demande de mettre plusieurs variables en global pour y accéder, d’utiliser les fonctions de sélection, etc.

Pour réaliser cette fonction, on fait la même chose que la fonction start(). Mais cette fois, on ne supprime ni ne crée aucune fenêtre, on vient juste placer un canvas, l’image de la fractale que nous voulons prévisualiser au bon endroit avec les bons paramètres.

def previsu():
    global palette, choix_fract,compl_r,compl_i, réso_x, réso_y
    #sélèction des diffèrents paramètres
    réso_x, réso_y = 240,167
    selection_palette()
    if liste_deroulante_fractal.get() == "Julia":
        choix_fract = 0
    if liste_deroulante_fractal.get() == "Mandelbrot":
        choix_fract = 1
    compl_r,compl_i = chmp_str_c_r_julia.get(),chmp_str_c_i_julia.get()
    
    fractal_builder() # génération d'une fractale avec ces paramètres
    prévi = PhotoImage(file="Explorer_image/Image.png") # récupération de l'image
    # création du cardre et placement de l'image dans le cadre
    label_image = tk.Label(cadre_acceuil, image=prévi)
    label_image.place(x=245,y=310)
    label_image.mainloop()

VII – Menu, tutoriel

La dernière barrière entre mon explorateur et l’utilisateur est ainsi ma logique. En effet, les touches que j’ai choisies pour mon explorateur ne sont sûrement pas les meilleures, donc je rajoute une nouvelle page à mon menu pour y écrire un petit guide des touches. Je profite de ça pour ajouter deux nouvelles fonctions. La première fonction récupère les coordonnées de la fractale qu’on voit pour les nerds. La seconde, plus complexe, prend des “screenshot” de ce qu’on voit dans l’explorateur. En réalité, elle génère une fractale avec les mêmes paramètres que celle que l’on voit dans l’explorateur, mais change son nom au moment de l’enregistrer.

#création du cadre de tutoriel dans la fenêtre de l'accueil
cadre_acceuil = tk.Frame(fenetre, bg="#C2C2C2",heigh = 555,width=720)
cadre_acceuil.pack_propagate(False) 
cadre_tuto = tk.Frame(fenetre, bg="#C2C2C2",heigh = 555,width=720)
cadre_tuto.pack_propagate(False) 
# création et placement du texte du tuto
titre_tuto = tk.Label(cadre_tuto, text="Guide des Touches", font=("Consolas", 20), bg="#C2C2C2", fg="black")
soustitre_tuto = tk.Label(cadre_tuto, text="Vous vous sentez un peu  perdu ?", font=("Consolas", 15), bg="#C2C2C2", fg="black")
titre_tuto.pack()
soustitre_tuto.pack(pady=10)
texte_tuto = tk.Label(cadre_tuto, text="z - aller vers le haut \ns - aller vers le bas \nq - aller à droite \nd - aller à gauche \n\nflèche du haut - zoom avant \nflèche du bas - zoom arrière \n\nm - renvoie les coordonnées de \n    où vous êtes dans la fractale \n\nc - sauvegarde un PNG de ce que \n    vous voyez dans le dossier Explorer_image \n", font=("Consolas", 13), bg="#C2C2C2", fg="black")
texte_tuto.pack()
fin = tk.Label(cadre_tuto, text="Amusez-vous !", font=("Consolas", 15), bg="#C2C2C2", fg="black")
fin.pack(pady=20)

#nouvelles fonctions ajoutées à l'explorateur
def explorer(action):
    global xmax, xmin, ymax, ymin,img,nbr_img
    x = xmax - xmin
    y = ymax - ymin
    """
    différentes fonctions de déplacements
    """
    elif keyboard.is_pressed('m'): # récupération de nos coordonnées (pour les nerds)
        print("xmax = ",xmax)
        print("xmin = ",xmin)
        print("ymax = ",ymax)
        print("ymin = ",ymin)
    elif keyboard.is_pressed('c'): # prend un screenshot de la fractale
        print("Image enregistrée !")
        nbr_img +=1
        fractal_builder(100,'Explorer_image/Image_saved'+str(nbr_img)+'.png')
        afficher_image()
        print(nbr_img)

Conclusion

En conclusion, ce projet ma permis d’utiliser la plupart de mes compétences. Il m’a suivi durant la moitié de mon année de terminale.

Galerie

Quelques exemples de fractale que vous pourrez à votre tour explorer :

Script complet

Voici le script final en .7z, il vous suffit de l’extraire et vous pourrez directement commencer à explorer !

vous pouvez également voir les futures mise à jour sur GitHub ici.

Tutoriels

Optimiser son code python

Vous cherchez à rendre l’affichage d’une image plus rapide ? où simplement, vous trouvez votre code un peu long à se faire ? Ici, nous nous plongerons dans l’art de perfectionner nos codes pour en maximiser l’efficacité. Vous trouverez ici des moyens de mieux utiliser certaines bibliothèques ou des alternatives à celle-ci.

Teaser du tutoriel

Kandinsky

Vous avez peut-être déjà remarqué que Kandinsky, bibliothèque utile pour dessiner en python, est très lent sur ordinateur. Mais l’avez-vous bien paramétré ? Et êtes-vous satisfait de sa rapidité ? Regardons ensemble comment y remédier.

Comment bien utiliser Kandinsky

Tout d’abord, regardons comment vous avez et pourrez paramétrer l’émulateur de Kandinsky. Pour vérifier ça, ouvrez votre IDE, lancez votre script.

vous partez donc de cela :

Ensuite suivez, ces indications :

tout d’abord allez dans le menu « Options » :

D’ici, vous pouvez :

  • avec l’onglet « OS », choisir l’os utilisé.
  • avec l’onglet « Model », choisir une version de la calculatrice Numworks, ici émulé.
  • avec l’onglet « Zoom », choisir la taille de la fenêtre.

Ici pour accélérer notre script, nous utiliserons l’OS « PC ».

Et voilà, déjà, votre script va bien plus vite sur votre ordinateur. Il est important de savoir que ce n’affecte pas la vitesse du script sur la calculatrice, mais seulement ici sur votre ordinateur.

Pillow, Kandinsky en plus rapide (et plus encore)

Vous trouvez encore votre script lent à l’exécution ? Alors voici la bibliothèque PIL. Avant de commencer, il est important de rappeler que Pillow n’est pas disponible sur Numworks là où l’est Kandinsky.

Comparatif de vitesse entre Pillow et Kandinsky

Pour analyser la vitesse de mes deux options, je vais utiliser le script disponible sur le tuto ici qui génère un « Julia set », et je le modifie pour qu’il utilise Pillow plutôt que Kandinsky.

Afin de chronométrer le temps de génération, j’ai rajouté la bibliothèque Time :

#si le script utilise Pillow
from PIL import Image
#si le script utilise Kandinsky
#import Kandinsky
import time

def julia_PIL(N_iteration):
    start_time = time.time()  # Enregistrez le temps de début

    # création de la palette de couleurs
    palette = []
    xmax = 2
    xmin = -2
    ymax = 1.3875
    ymin = -1.387
    r = 255
    g = 255
    b = 255
    for j in range(0, 128):
        b = 255 - 2 * j
        palette.append((r, g, b))
    for j in range(128, 256):
        r = 255 - 2 * (j - 128)
        g = 255 - 2 * (j - 128)
        palette.append((r, g, b))

    # Création de l'image avec Pillow (rien a mettre si on utilise Kandinsky)
    img = Image.new('RGB', (320, 222))

    #generation de la fractale
    for x in range(320):
        for y in range(222):
            i = 0
            z = complex(xmin + (xmax - xmin) * x / 320 + (ymax - (ymax - ymin) * y / 222) * 1J)
            c = complex(0.36, 0.36)
            while i < N_iteration and abs(z) < 2:
                i = i + 1
                z = z * z + c
            couleur = palette[int(255 * i / N_iteration)]

            # Avec Pillow : Définir la couleur du pixel dans l'image aux coordonnées (x,y)
            img.putpixel((x, y), couleur)
            # Avec Kandinsky : même but que Pillow
            #col = color(couleur[0],couleur[1],couleur[2])
            #set_pixel(x,y,col)

    # Affichage de l'image (rien a mettre si on utilise Kandinsky)
    img.show()

    end_time = time.time()  # Enregistrez le temps de fin
    elapsed_time = end_time - start_time
    print("Temps d'exécution : ",elapsed_time," secondes")

Ici, le script utilisant Kandinsky prend 3,25 secondes, celui avec Pillow prend 4,40 secondes quand je génère une fractale avec 1000 itérations. Mais il est plus lent ?!! en fait non, car Pillow, avec son .show() (ligne 40), doit ouvrir une nouvelle fenêtre photo, ce qui prend longtemps. Si à la place, on enregistre l’image et qu’on ne l’affiche pas, le script prend 0,39 seconde à s’exécuter. On rajoute donc ceci à notre script au même endroit que .show().

img.save('Julia.png')

L’image générée est enregistrée dans le même répertoire que le script, sous le nom « Julia.png » (le nom est modifiable à souhait).

Utilisations

De mon côté, j’ai utilisé le script modifié grâce à Pillow. Ainsi, je peux générer des images a très grand format et j’ai donc réalisé ceci, un « Julia set » en 8k en 211,25 secondes (ce qui est impossible avec Kandinsky, car la bibliothèque est limitée en 320p par 222p) :

En outre, je peux l’utiliser par exemple pour faire un explorateur de fractale sur des images plus petites que de la 8k quand même.

Si vous avez besoin de plus d’information, voici la documentation complète de Pillow, ici.

NumPy

NumPy est une bibliothèque python très utile dans le calcul de tableaux ou de matrices pour accélérer ces calculs ou simplifier la lecture du code grâce à ses fonctions directement implémentée comme la multiplication matricielle. C’est une bibliothèque très utilisée par exemple pour :

  • Calcul Scientifique : NumPy est largement utilisé dans le domaine scientifique pour la modélisation mathématique, les simulations numériques et l’analyse de données.
  • Apprentissage Automatique : De nombreuses bibliothèques d’apprentissage automatique, comme scikit-learn, utilisent NumPy pour la manipulation des données et les opérations numériques.
  • Traitement des Images et du Son : NumPy est souvent utilisé dans le traitement des images et des signaux sonores en raison de ses capacités à travailler avec des tableaux multidimensionnels.
  • Analyse de Données : Les analystes de données et les scientifiques des données utilisent NumPy pour effectuer des opérations numériques efficaces sur de grands ensembles de données.

Pourquoi utiliser les tableaux de NumPy plutôt que de simples listes ?

Les listes normales en Python sont des structures de données de base qui peuvent contenir des éléments de types différents. Elles offrent une grande souplesse, mais peuvent être moins performantes pour les opérations numériques sur de grands ensembles de données. D’un autre côté, les tableaux de NumPy (ou array) sont des structures de données spécialisées pour les calculs numériques qui offrent des performances optimisées.

En outre, voici quelques exemples montrant les différences entre les deux :

  • Type d’Éléments :
    • Liste : Peut contenir des éléments de différents types (entiers, chaînes de caractères, etc.).
    • Array : Contient des éléments d’un type de données homogène. Les Array sont typés, ce qui signifie que tous les éléments doivent être du même type.
  • Performance :
    • Liste : Moins efficace pour les opérations numériques sur de grands ensembles de données en raison de sa flexibilité.
    • Array : Conçu pour les calculs numériques, offrant des opérations vectorielles efficaces et des performances optimisées.
  • Fonctions et Opérations :
    • Liste : Offre des fonctionnalités de base pour la manipulation de données, mais peut nécessiter des boucles explicites pour certaines opérations numériques.
    • Array : Fournit une vaste gamme de fonctions optimisées pour les opérations numériques, y compris des opérations vectorielles, des fonctions mathématiques et des fonctions de statistiques.
  • Taille Dynamique :
    • Liste : La taille d’une liste peut changer dynamiquement en ajoutant ou en supprimant des éléments.
    • Array : La taille d’un Array est fixée à la création. Pour ajouter ou supprimer des éléments, un nouveau tableau doit être créé.
  • Syntaxe :
    • Liste : Définie avec des crochets, par exemple, ma_liste = [1, 2, 3]
    • Array : Créé avec la fonction numpy.array(), par exemple, mon_tableau = numpy.array([1, 2, 3])

Démonstration

Imaginons que nous devons faire des calculs sur des éléments d’une liste d’integer :

liste = [i for i in range(10000000)]
resultat = []
for nb in liste :
    resultat.append(nb ** 2)

Ici, on élève chaque élément de notre liste au carré, ce script prend 3,42 seconde. Avec NumPy, on peut faire ceci :

import numpy as np

arr = np.array([i for i in range(10000000)])
# Applique une fonction pour élever chaque élément de la liste au carré
result = np.square(arr)

Ici, le script prend 1,15 seconde, NumPy est utile pour gérer des tableaux de valeurs très grandes et appliquer des règles de calculs sur ces tableaux. Les array sont un nouveau type d’objet qui correspondent aux listes et matrices.

Fonctions natives intéressantes

NumPy offre un grand nombre de fonctions qui pourront simplifier la lecture de votre code et l’accélérer :

  • Produits de tous les éléments :
import numpy as np

liste = [1, 2, 3, 4, 5]

produit = np.prod(liste)
# renvoie donc avec un print ou un return 120
  • moyenne et écart-type :
import numpy as np

liste = [1, 2, 3, 4, 5]

moyenne = np.mean(liste)
# renvoie donc avec un print ou un return 3.0
ecart_type = np.std(liste)
# renvoie donc avec un print ou un return 1.4142135623730951
  • Chercher des éléments suivants une condition et liste des éléments unique de la liste
import numpy as np

#va renvoyer tous les éléments du tableau supérieur à 2 dans une nouvelle liste
liste_sat = [1, 2, 3, 4, 5]
elements_satisfaisants = np.extract(np.array(liste_sat) > 2, liste_sat)
# renvoie donc avec un print ou un return [3 4 5]

#va renvoyer tous les éléments uniques du tableau et leur nombre d'apparitions dans une nouvelle liste
liste_uni = [1, 2, 2, 3, 4, 4, 5]
elements_uniques, comptages = np.unique(liste_uni, return_counts=True)
# renvoie donc avec un print ou un return [1 2 3 4 5] [1 2 1 2 1]

Si vous avez besoin de plus d’information, voici la documentation complète de NumPy, ici.