I'm designing a GUI with Tkinter in python 2.7 for a project that is working ( link ). The project makes a photomosaic from a bank of images, it is done through the Pillow library.
The script when working with image processing uses the multiprocessing library to use all the available CPU cores (or at least I understand that).
The problem arises when after modifying the code to obtain the data from TKinter through the widgets of Entry and others, I get an error that nails the script but does not generate any warning or anything by console. By the tests that I was doing, the error (apparently) is in the arguments of the execution call of the process. I would like please, if you can, take a look and give me your opinion ...
The values that I am using for the test of the script are:
Crop Mosaic = 50 Mosaic Resolution = 4 Enlargement = 8
The script fails at this point:
Process(target=build_mosaic, args=(result_queue, all_tile_data_large, original_img_large, tamanio)).start()
there it hangs up and to close the windows interface it asks me to finish python.
I leave the entire script to see if you can give me a hand.
# -*- coding: utf-8 -*-
"""
Created on Thu Mar 09 19:59:07 2017
@author: Daniel
"""
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)
salida = 'mosaico.jpeg' #da le nombre al archivo que se crea
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')
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')
I feel quite frustrated with this because I tried to make it work without results for quite some time. Thank you very much from now ...