pythontkintermultiprocessingpython-multiprocessing

why my code with multiprocessing of tkinter and PIL is not working?


I tried implementing this but it still doesn't work It asks me refer myself to the safe importing of main module, with: if name == "main": But even with what I tried it still wouldn't work and I kept getting errors

Here is the new code with the implementation of the answere (not sure i did it right) :

import multiprocessing 
import tkinter as tk
from PIL import Image, ImageTk
import math, time

def process_tile(_x, _y, tile):
    # (do processing on the tile)
    pastTile = tile
    def non_lerp(a: float, b: float, t: float) -> float:
        """Interpolation non linéaire entre a et b en fonction du temps t"""
        return ((1 - t) * a)/1.5 + (t * b)/10
    global lightposes
    for x in range(_x):                                # on parcourt les pixels en colonne
        for y in range(_y):                            # on parcourt les pixels en ligne
            r = tile.getpixel((x, y))[0]
            v = tile.getpixel((x, y))[1]
            b = tile.getpixel((x, y))[2]
            g = int((tile.getpixel((x,y))[0]+tile.getpixel((x,y))[1]+tile.getpixel((x,y))[2])/3)
            total_light_intensites = []
            if not(r == 0 and b == 0 and v == 0):
                for i in range(len(lightposes)):
                    distance = math.sqrt((lightposes[i][0]-x)**2+(lightposes[i][1]-y)**2)
                    if distance <= lightposes[i][2]:
                        r+=lightposes[i][4][0]
                        v+=lightposes[i][4][1]
                        b+=lightposes[i][4][2]
                        pointlight_intensite = 0.0001
                        if((distance/lightposes[i][2])+(lightposes[i][3])<=1):
                            pointlight_intensite = non_lerp(1,0.1,(distance/lightposes[i][2])+(lightposes[i][3])) 
                        total_light_intensites.append(pointlight_intensite)

                red_color,green_color,blue_color = 0,0,0
                for i in range(len(total_light_intensites)):
                    red_color += int(total_light_intensites[i]*(r*g)/200)
                    green_color += int(total_light_intensites[i]*(v*g)/200)
                    blue_color += int(total_light_intensites[i]*(b*g)/200)
                final_pixel_color = (red_color,green_color,blue_color)
                tile.putpixel((x,y),final_pixel_color)
    if pastTile != tile:
        print(pastTile, tile)
    return (_x, _y, tile)

                
fenetre=tk.Tk()     # on créé la fenêtre
largeur=1000        # on définit les dimensions de la fenêtre
hauteur=600

toile = tk.Canvas(fenetre, width=largeur, height=hauteur)   # on crée une "toile" dans la fen^tre dans laquelle on pourra dessiner    
bouton = tk.Button(fenetre, text='Quitter', command = fenetre.destroy)  # on crée un bouton pour quitter le jeu
toile.pack()        # on place la toile dans la fenêtre
bouton.pack()       # on place le bouton dans la fenêtre

fenetre.geometry('1000x700')
fenetre.configure(background='#7ace54')

fond1 = Image.open("img/fond.png")              # on ouvre l'image à modifier
fond1 = fond1.resize((largeur, hauteur))        # on redimensionne l'image par rapport à la taille de la fenêtre
fond2 = fond1.copy()                            # on créé une copie de cette image
fond3 = ImageTk.PhotoImage(fond2, master = toile)
# on les ajoutes à la toile
main_image = toile.create_image(largeur/2,hauteur/2, image = fond3)

light_calculations= [main_image, fond3, fond2, fond1]

#--------------light calculations-----------------
lightposes=[] #1st = xpose, 2nd = ypose, 3rd = size, 4th = intensity (inversed), 5th = colour
#illumination globale
lightposes.append([500, 300, 1000, 0.85, [2,20,10]])

lightposes.append([1400, -200, 2050, 0.42, [0,170,60]])
lightposes.append([200, -1200, 1900, 0.01, [0,12,40]])


startTime = time.time()


with multiprocessing.Pool(4) as p:
    jobs = []
    for x in range(0, largeur, 64):
        for y in range(0, hauteur, 64):
            job = [x, y, fond2.crop((x, y, x + 64, y + 64))]
            jobs.append(job)
    for x, y, result in p.map(process_tile, jobs):
        # (paste the result back in the image at x/y
        light_calculations[2].paste(result, (x, y))
    # (update the photoimage with the current result)
    light_calculations[1] = ImageTk.PhotoImage(light_calculations[2], master = toile)
    toile.itemconfigure(light_calculations[0], image = light_calculations[1])
        
print(time.time()-startTime)

fenetre.mainloop()  # permet à la fenêtre "d'écouter" les évènements en boucle

error:

Traceback (most recent call last):
File "<string>", line 1, in <module>
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.11_3.11.2544.0_x64__qbz5n2kfra8p0\Lib\multiprocessing\spawn.py", line 122, in spawn_main        
    exitcode = _main(fd, parent_sentinel)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.11_3.11.2544.0_x64__qbz5n2kfra8p0\Lib\multiprocessing\spawn.py", line 131, in _main
    prepare(preparation_data)
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.11_3.11.2544.0_x64__qbz5n2kfra8p0\Lib\multiprocessing\spawn.py", line 246, in prepare
    _fixup_main_from_path(data['init_main_from_path'])
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.11_3.11.2544.0_x64__qbz5n2kfra8p0\Lib\multiprocessing\spawn.py", line 297, in _fixup_main_from_p
ath
    main_content = runpy.run_path(main_path,
                ^^^^^^^^^^^^^^^^^^^^^^^^^
File "<frozen runpy>", line 291, in run_path
File "<frozen runpy>", line 98, in _run_module_code
File "<frozen runpy>", line 88, in _run_code
File "d:\NSI\Cupidon\test multiprocess.py", line 77, in <module>
    with multiprocessing.Pool(4) as p:
        ^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.11_3.11.2544.0_x64__qbz5n2kfra8p0\Lib\multiprocessing\context.py", line 119, in Pool
    return Pool(processes, initializer, initargs, maxtasksperchild,
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.11_3.11.2544.0_x64__qbz5n2kfra8p0\Lib\multiprocessing\pool.py", line 215, in __init__
    self._repopulate_pool()
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.11_3.11.2544.0_x64__qbz5n2kfra8p0\Lib\multiprocessing\pool.py", line 306, in _repopulate_pool   
    return self._repopulate_pool_static(self._ctx, self.Process,
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.11_3.11.2544.0_x64__qbz5n2kfra8p0\Lib\multiprocessing\pool.py", line 329, in _repopulate_pool_st
atic
    w.start()
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.11_3.11.2544.0_x64__qbz5n2kfra8p0\Lib\multiprocessing\process.py", line 121, in start
    self._popen = self._Popen(self)
                ^^^^^^^^^^^^^^^^^
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.11_3.11.2544.0_x64__qbz5n2kfra8p0\Lib\multiprocessing\context.py", line 336, in _Popen    return Popen(process_obj)
        ^^^^^^^^^^^^^^^^^^
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.11_3.11.2544.0_x64__qbz5n2kfra8p0\Lib\multiprocessing\popen_spawn_win32.py", line 46, in __init_
_
    prep_data = spawn.get_preparation_data(process_obj._name)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.11_3.11.2544.0_x64__qbz5n2kfra8p0\Lib\multiprocessing\spawn.py", line 164, in get_preparation_da
ta
    _check_not_importing_main()
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.11_3.11.2544.0_x64__qbz5n2kfra8p0\Lib\multiprocessing\spawn.py", line 140, in _check_not_importi
ng_main
    raise RuntimeError('''
RuntimeError:
        An attempt has been made to start a new process before the
        current process has finished its bootstrapping phase.

        This probably means that you are not using fork to start your
        child processes and you have forgotten to use the proper idiom
        in the main module:

            if __name__ == '__main__':
                freeze_support()
                ...

        The "freeze_support()" line can be omitted if the program
        is not going to be frozen to produce an executable.

        To fix this issue, refer to the "Safe importing of main module"
        section in https://docs.python.org/3/library/multiprocessing.html

Thanks for the help and I wish you a great day!


Solution

  • TypeError: cannot pickle '_tkinter.tkapp' object

    Pickling is the Python serialization method; all objects that are sent to multiprocessing subprocesses need to be pickleable.

    You're sending over light_calculations, which includes a tkinter canvas and a tkinter PhotoImage – those aren't pickleable (to simplify, since they refer to tkinter objects that are only valid in a single process).

    To fix this, you'll have to change things up so you're not sending anything Tkinter-related to those child processes. (Furthermore, attempting to putpixel on an object in the child process would not change it in the parent process, anyway; the children get copies of the objects.)

    If you know the subprocesses don't need the entire image to do their calculations, I would suggest sending tiles of e.g. 64x64 pixels in and receiving the same. That makes it very easy to compose the final image in your parent process. This can be done easily with a multiprocessing.Pool() and map()ing (or imaping or imap_unordereding!) over tiles, e.g.

    def process_tile(x, y, tile):
        ... # (do processing on the tile)
        return (x, y, tile)
    
    
    with multiprocessing.Pool(4) as p:
        jobs = []
        for x in range(0, width, 64):
            for y in range(0, height, 64):
                job = [x, y, fond2.crop((x, y, x + 64, y + 64)]
        for x, y, result in p.map(process_tile, jobs):
            ... # (paste the result back in the image at x/y)
            ... # (update the photoimage with the current result)
    

    EDIT: All in all, to adapt your edited question:

    def process_tile(job):
        x_offset, y_offset, tile, lightposes = job
        width, height = tile.size
    
        for tile_x in range(width):  # on parcourt les pixels en colonne
            for tile_y in range(height):  # on parcourt les pixels en ligne
                x = x_offset + tile_x
                y = y_offset + tile_y
                r, v, b = tile.getpixel((tile_x, tile_y))
                g = (r + v + b) / 3
                total_light_intensites = []
    
                for light_x, light_y, max_distance, intensity, light_color in lightposes:
                    distance = math.sqrt((light_x - x) ** 2 + (light_y - y) ** 2)
                    if distance <= max_distance:
                        r += light_color[0]
                        v += light_color[1]
                        b += light_color[2]
                        pointlight_intensite = 0.0001
                        if (distance / max_distance) + (intensity) <= 1:
                            pointlight_intensite = non_lerp(1, 0.1, (distance / max_distance) + (intensity))
                        total_light_intensites.append(pointlight_intensite)
    
                red_color, green_color, blue_color = 0, 0, 0
                for intensity in total_light_intensites:
                    red_color += int(intensity * (r * g) / 200)
                    green_color += int(intensity * (v * g) / 200)
                    blue_color += int(intensity * (b * g) / 200)
                final_pixel_color = (red_color, green_color, blue_color)
                tile.putpixel((tile_x, tile_y), final_pixel_color)
        return (x_offset, y_offset, tile)
    
    # ...
    
        with multiprocessing.Pool(4) as p:
            jobs = []
            for x in range(0, largeur, 64):
                for y in range(0, hauteur, 64):
                    job = [x, y, fond2.crop((x, y, x + 64, y + 64)), lightposes]
                    jobs.append(job)