Calificación:
  • 0 voto(s) - 0 Media
  • 1
  • 2
  • 3
  • 4
  • 5
Problema con Multiprocessing.Process
#1
Hola gente, estoy diseñando una GUI con Tkinter en python 2.7 para un proyecto ya desarrollado y que esta funcionando. El proyecto realiza un fotomosaico a partir de un banco de imágenes, lo realiza a través de la libreria PIL.

El script al trabajar con procesamiento de imágenes utiliza la librería multiprocessing para utilizar todos los núcleos disponibles del CPU (o al menos eso creo). 

El problema surge cuando después de modificar el código para obtener los datos desde TKinter, obtengo un error que me clava el script pero no genera ninguna advertencia ni nada por consola. Por las pruebas que estuve haciendo, el error (al parecer) esta en los argumentos de la llamada de ejecución del proceso. Quisiera por favor si pueden, darle una mirada y darme su opinión...

El script falla llegado a este punto:

Process(target=build_mosaic, args=(result_queue, all_tile_data_large, original_img_large, tamanio)).start()

###Los valores para prueba que estoy utilizando para correr el script son= Recortar Mosaicos: 50, Resolucion Mosaicos: 4, Agrandamiento: 6

ahi se cuelga y para cerrar la interfaz windows me pide finalizar python.

 me siento bastante frustrado con esto porquehace bastante tiempo intento hacerlo funcionar sin resultados  Sad .  Muchas gracias desde ya... 

Código:
# -*- coding: utf-8 -*-
"""
Created on Thu Mar 09 19:59:07 2017

@author: Daniel
"""

#Los valores para prueba que estoy utilizando son Recortar Mosaicos: 50, Resolucion Mosaicos: 4, Agrandamiento: 6

import sys, os, tkFileDialog, unicodedata
from multiprocessing import Process, Queue, cpu_count
from Tkinter import *
from PIL import Image
WORKER_COUNT = max(cpu_count() - 1, 1) #selecciona el maximo entre 1 y 1 menos el numero de procesador
EOQ_VALUE = None #inicializa una variable como un objeto del tipo None (nada)    

class TileProcessor:
   def __init__(self, tiles_directory, tamanio, ENLARGEMENT, TILE_BLOCK_SIZE):
       self.tiles_directory = tiles_directory
       self.tamanio=tamanio
       self.ENLARGEMENT=ENLARGEMENT
       self.TILE_BLOCK_SIZE=TILE_BLOCK_SIZE

   def __process_tile(self, tile_path):
       try:
           img = Image.open(tile_path)
           # los mosaicos deben ser cuadrados, entonces seleciona el minimo entre largo y ancho y los recorta
           w = img.size[0]
           h = img.size[1]
           min_dimension = min(w, h)
           w_crop = (w - min_dimension) / 2
           h_crop = (h - min_dimension) / 2
           img = img.crop((w_crop, h_crop, w - w_crop, h - h_crop))

           large_tile_img = img.resize((self.tamanio, self.tamanio), Image.ANTIALIAS)
           small_tile_img = img.resize((self.tamanio/self.TILE_BLOCK_SIZE, self.tamanio/self.TILE_BLOCK_SIZE), Image.ANTIALIAS)

           return (large_tile_img.convert('RGB'), small_tile_img.convert('RGB'))
       except:
           return (None, None)

   def get_tiles(self):
       large_tiles = []
       small_tiles = []
       #i=1
       print 'Leyendo mosaicos de \'%s\'...' % (self.tiles_directory, )
       #print (tamanio)
       # se buscan los mosaicos recursivamente en las subcarpetas de la direccion
       for root, subFolders, files in os.walk(self.tiles_directory):
           for tile_name in files:
               tile_path = os.path.join(root, tile_name)
               large_tile, small_tile = self.__process_tile(tile_path)
               if large_tile:
                   large_tiles.append(large_tile)
                   small_tiles.append(small_tile)
       
       print 'Se procesaron %s mosaicos.' % (len(large_tiles),)
       

       return (large_tiles, small_tiles)

class TargetImage:
   def __init__(self, image_path, ENLARGEMENT, TILE_BLOCK_SIZE, tamanio):
       self.image_path = image_path
       self.ENLARGEMENT=ENLARGEMENT
       self.TILE_BLOCK_SIZE=TILE_BLOCK_SIZE
       self.tamanio=tamanio
   def get_data(self):
       print 'Procesando imagen principal...'
       img = Image.open(self.image_path)
       w = img.size[0] * self.ENLARGEMENT  #tamaño ancho
       h = img.size[1]    * self.ENLARGEMENT #tamaño alto
       large_img = img.resize((w, h), Image.ANTIALIAS) #retorna una copia cambiada de tamaño y filtrada con antialias
       w_diff = (w % self.tamanio)/2
       h_diff = (h % self.tamanio)/2
       print()
       # si es necesario recorta la imagen ligeramente para utilizar un numero entero de mosaicos horizontales y verticales
       if w_diff or h_diff:
           large_img = large_img.crop((w_diff, h_diff, w - w_diff, h - h_diff)) #recorta la imagen de cada lado es decir 4 datos necesarios
       small_img = large_img.resize((w/self.TILE_BLOCK_SIZE, h/self.TILE_BLOCK_SIZE), Image.ANTIALIAS)
       image_data = (large_img.convert('RGB'), small_img.convert('RGB'))
       print 'Imagen principal procesada.'
       return image_data

class TileFitter:
   def __init__(self, tiles_data):
       self.tiles_data = tiles_data

   def __get_tile_diff(self, t1, t2, bail_out_value):
       diff = 0
       for i in range(len(t1)):
           #diff += (abs(t1[i][0] - t2[i][0]) + abs(t1[i][1] - t2[i][1]) + abs(t1[i][2] - t2[i][2]))
           diff += ((t1[i][0] - t2[i][0])**2 + (t1[i][1] - t2[i][1])**2 + (t1[i][2] - t2[i][2])**2)
           if diff > bail_out_value:
               # we know already that this isnt going to be the best fit, so no point continuing with this tile
               return diff
       return diff

   def get_best_fit_tile(self, img_data):
       best_fit_tile_index = None
       min_diff = sys.maxint
       tile_index = 0

       # go through each tile in turn looking for the best match for the part of the image represented by 'img_data'
       for tile_data in self.tiles_data:
           diff = self.__get_tile_diff(img_data, tile_data, min_diff)
           if diff < min_diff:
               min_diff = diff
               best_fit_tile_index = tile_index
           tile_index += 1

       return best_fit_tile_index

def fit_tiles(work_queue, result_queue, tiles_data):
   # this function gets run by the worker processes, one on each CPU core
   tile_fitter = TileFitter(tiles_data)

   while True:
       try:
           img_data, img_coords = work_queue.get(True)
           if img_data == EOQ_VALUE:
               break
           tile_index = tile_fitter.get_best_fit_tile(img_data)
           result_queue.put((img_coords, tile_index))
       except KeyboardInterrupt:
           pass

   # let the result handler know that this worker has finished everything
   result_queue.put((EOQ_VALUE, EOQ_VALUE))

class ProgressCounter:
   def __init__(self, total):
       self.total = total
       self.counter = 0

   def update(self):
       self.counter += 1
       sys.stdout.write("Progress: %s%% %s" % (100 * self.counter / self.total, "\r"))
       sys.stdout.flush();

class MosaicImage:
   def __init__(self, original_img, tamanio):
       self.tamanio=tamanio
       print(tamanio)
       self.image = Image.new(original_img.mode, original_img.size)
       self.x_tile_count = original_img.size[0] / self.tamanio
       self.y_tile_count = original_img.size[1] / self.tamanio
       self.total_tiles  = self.x_tile_count * self.y_tile_count
       print('ajam..')
   def add_tile(self, tile_data, coords):
       img = Image.new('RGB', (self.tamanio, self.tamanio))
       img.putdata(tile_data)
       self.image.paste(img, coords)

   def save(self, path):
       self.image.save(path)

def build_mosaic(result_queue, all_tile_data_large, original_img_large, tamanio):
   mosaic = MosaicImage(original_img_large, tamanio)

   active_workers = WORKER_COUNT
   print('fuera')
   while True:
       try:
           img_coords, best_fit_tile_index = result_queue.get()

           if img_coords == EOQ_VALUE:
               print('dentro')
               active_workers -= 1
               if not active_workers:
                   break
           else:
               print('else')
               tile_data = all_tile_data_large[best_fit_tile_index]
               mosaic.add_tile(tile_data, img_coords)

       except KeyboardInterrupt:
           pass

   mosaic.save(salida)
   print '\nFinalizado, la imagen de salida se encuentra en', salida

def terminal(original_img, tiles, tamanio, TILE_BLOCK_SIZE): # terminal determina la parte visual del programa en la terminal de windows
   print 'Construyendo imagen, Ctrl+C para abortar...'
   tamanio=tamanio
   original_img_large, original_img_small = original_img
   tiles_large, tiles_small = tiles

   mosaic = MosaicImage(original_img_large, tamanio)

   all_tile_data_large = map(lambda tile : list(tile.getdata()), tiles_large)
   all_tile_data_small = map(lambda tile : list(tile.getdata()), tiles_small)

   work_queue   = Queue(WORKER_COUNT)    
   result_queue = Queue()
   print('aqui')
   try:
       print('hola hola')
       # start the worker processes that will build the mosaic image

#todo funciona perfecto hasta este punto... luego de esta orden de ejecutar este proceso el script falla...
       Process(target=build_mosaic, args=(result_queue, all_tile_data_large, original_img_large, tamanio)).start()
       print('hola')
       # start the worker processes that will perform the tile fitting
       for n in range(WORKER_COUNT):
           Process(target=fit_tiles, args=(work_queue, result_queue, all_tile_data_small)).start()

       progress = ProgressCounter(mosaic.x_tile_count * mosaic.y_tile_count)
       print('hola 2')
       for x in range(mosaic.x_tile_count):
           for y in range(mosaic.y_tile_count):
               large_box = (x * tamanio, y * tamanio, (x + 1) * tamanio, (y + 1) * tamanio)
               small_box = (x * tamanio/TILE_BLOCK_SIZE, y * tamanio/TILE_BLOCK_SIZE, (x + 1) * tamanio/TILE_BLOCK_SIZE, (y + 1) * tamanio/TILE_BLOCK_SIZE)
               work_queue.put((list(original_img_small.crop(small_box).getdata()), large_box))
               progress.update()

   except:
       print '\nGuardando imagen parcial, espere...'

   finally:
       print('aqui tambien')
       # put these special values onto the queue to let the workers know they can terminate
       for n in range(WORKER_COUNT):
           work_queue.put((EOQ_VALUE, EOQ_VALUE))

def mosaic(): # definicion del metodo mosaic
   tamanio   = int(entero1.get())# alto y ancho de los mosaicos en pixeles
   TILE_MATCH_RES = int(entero2.get())    # tile matching resolution (higher values give better fit but require more processing)
   ENLARGEMENT    = int(entero3.get())    # la imagen mosaico sera esta cantidad de veces mas grande (en largo y ancho) que la imagen original    
   TILE_BLOCK_SIZE = tamanio / max(min(TILE_MATCH_RES, tamanio), 1) #tamaño del bloque final
   os.remove('C:\Users\Daniel Doyharzabal\Desktop\Imagenes\principal.pyc')
   
   salida = 'mosaico.jpeg' #da le nombre al archivo que se crea
   
   path=tkFileDialog.askdirectory()
   label_6.config(text=path)
   archivo=tkFileDialog.askopenfilename()
   label_7.config(text=archivo)
   tiles_path=path #"C:/ImageOutput"
   img_path=archivo #"C:/nene.jpg"
   print(tiles_path)
   print(img_path)
   tiles_data = TileProcessor(tiles_path, tamanio, ENLARGEMENT, TILE_BLOCK_SIZE).get_tiles() # utiliza la direccion de los mosaicos de entrada para iniciar el procesamiento
   image_data = TargetImage(img_path, ENLARGEMENT, TILE_BLOCK_SIZE, tamanio).get_data()  #
   terminal(image_data, tiles_data, tamanio, TILE_BLOCK_SIZE)
   
if __name__ == '__main__': #verifica si el modulo ha sido importado o si es ejecutado como modulo principal (main)

   root=Tk()
   root.title("Fotomosaico con Python")
   root.geometry('650x400+200+200')

   label_1= Label(root, text="Recortar Mosaicos")
   label_2= Label(root, text="Resolucion Mosaicos")
   label_3= Label(root, text="Agrandamiento")
   label_4= Label(root, text="Direccion de Mosaicos")
   label_5= Label(root, text="Direccion de Imagen")
   label_6= Label(root, text="...")
   label_7= Label(root, text="...")
   label_8= Label(root, text="...")
       
   x=StringVar()
   y=StringVar()
   z=StringVar()

   entero1= Entry(root, textvariable='x')
   entero2= Entry(root, textvariable='y')
   entero3= Entry(root, textvariable='z')

   label_1.grid(row=0, column=0, sticky=E)
   label_2.grid(row=1, column=0, sticky=E)
   label_3.grid(row=2, column=0, sticky=E)
   label_4.grid(row=3, column=0, sticky=E)
   label_5.grid(row=4, column=0, sticky=E)
   label_6.grid(row=3, column=1, sticky=W) #direccion mosaicos
   label_7.grid(row=4, column=1, sticky=W) #direccion imagen
     
   entero1.grid(row=0, column=1)
   entero2.grid(row=1, column=1)
   entero3.grid(row=2, column=1)

   boton_3=Button(root, text="Aceptar", command= mosaic )
   boton_3.grid(column= 4, row=5)


   root.mainloop() #ejecuta la GUI


else :
   print ('fallo')
Responder
#2
Hola Daniel. Corrí tu programa con una imagen de prueba y, al parecer, en la función build_mosaic no hay ninguna variable llamada salida, por lo que Python lanzará una excepción del tipo NameError.

Por lo que veo has definido salida = 'mosaico.jpeg' dentro de la función mosaic, por lo que no será visible fuera de ese entorno. Deberías pasarla como argumento al proceso.

Saludos.
Responder
#3
(09-05-2017, 05:14 PM)Francisco escribió: Hola Daniel. Corrí tu programa con una imagen de prueba y, al parecer, en la función build_mosaic no hay ninguna variable llamada salida, por lo que Python lanzará una excepción del tipo NameError.

Por lo que veo has definido salida = 'mosaico.jpeg' dentro de la función mosaic, por lo que no será visible fuera de ese entorno. Deberías pasarla como argumento al proceso.

Saludos.

Hola Francisco!, gracias por responder tan rápido.. corregí lo de la variable... tenias razón... de todas maneras el error me sigue saltando...

Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "C:\Python27\lib\multiprocessing\forking.py", line 381, in main
    self = load(from_parent)
  File "C:\Python27\lib\pickle.py", line 1384, in load
    return Unpickler(file).load()
  File "C:\Python27\lib\pickle.py", line 864, in load
    dispatch[key](self)
  File "C:\Python27\lib\pickle.py", line 886, in load_eof
    raise EOFError
EOFError


Despues de cortar con Ctrl+C la ejecucion me tira ese error... que no termino de entender..
Responder
#4
El problema es que detener un programa con múltiples procesos no es una tarea sencilla. El resultado al presionar CTRL + C puede ser muy variado, dependiendo del código que se esté ejecutando en ese momento. El módulo multiprocessing hace uso de pickle para, por ejemplo, enviar los argumentos de tu programa principal a los subprocesos.

Ya que estás usando Tkinter, te propongo que te olvides de la consola y muestres todo el progreso en la ventana. Además, podés proveer un botón para detener el procesamiento y así evitar usar CTRL + C que lanzará la excepción KeyboardInterrupt y te será bastante difícil poder manejarla.

Te recomiendo que leas un artículo que escribí hace un tiempo sobre comunicación entre procesos vía multiprocessing. La mejor forma de terminar un proceso de forma agradable es enviar una "señal" indicándole que debe detenerse. Cuando tu proceso obtenga la señal, simplemente tendrías que utilizar break para salir del bucle.

La API también provee una función poco recomendada, terminate, que hará que el proceso se cierre abruptamente y podría provocar errores.

Saludos.
Responder


Salto de foro:


Usuarios navegando en este tema: 1 invitado(s)