def process_and_save_files(self): """ process files in self.files, according to its width and height :return: None """ print('Processing and saving files ...') start_time = time.time() self.structure.remove_existing_files() self.structure.make_folders() self.images = [] total = len(self.files) files_chunks = [] start = 0 while start < total - 1: end = start + settings.MAX_CACHE_PROCESSED_IMAGES files_chunks.append(self.files[start:end]) start = end total_chunks = len(files_chunks) for chunk_index, chunk in enumerate(files_chunks): processed_images = [] total_files = len(chunk) for file_index, file in enumerate(chunk): database_item = DatabaseImageItem(file) processed_images.append(database_item) self.images.append( ImageItem(database_item, self.width, self.height)) utilities.print_progress(file_index + 1, total_files, curr_chunk=chunk_index + 1, total_chunks=total_chunks) utilities.save(processed_images, self.structure.get_list_name(chunk_index)) utilities.print_done(time.time() - start_time)
def load(folder, width, height): print('Loading database from {}'.format(folder)) database_structure = utilities.get_database_structure(folder) database = ImageDatabase(width, height, folder) start_time = time.time() chunks = database_structure.get_list_names() total = len(chunks) print('Loading {} chunks from {}'.format( total, database_structure.image_folder)) database.images = [] for index, chunk in enumerate(chunks): database.images += [ ImageItem(image, database.width, database.height) for image in utilities.load(chunk) ] utilities.print_progress(index + 1, total) if settings.MAX_CHUNKS_USE and settings.MAX_CHUNKS_USE == index + 1: print('Reached limit for max chunks use') break utilities.print_done(time.time() - start_time) return database
def create_database(self): print('Creating database | {} | '.format(self.database_path), end='') # remove existing database if any if os.path.isdir(self.database_path): shutil.rmtree(self.database_path) os.mkdir(self.database_path) pool = Pool() file_size = len(self.files) database_item_generator = functools.partial( DatabaseItem, width=settings.DATABASE_IMAGE_WIDTH, height=settings.DATABASE_IMAGE_HEIGHT) print('total {} '.format(file_size)) data_paths = [] cache_images = [] cache_images_size = 0 print(' >>> waiting for arrival of chunks ...', end='') for index, items in enumerate( pool.imap_unordered(database_item_generator, self.files)): cache_images.append(items) # database item cache_images_size += 1 if (cache_images_size >= settings.DATABASE_CHUNK_SIZE) or index + 1 == file_size: data_path = self.get_save_image_path() data_paths.append(data_path) utilities.save(cache_images, data_path) cache_images = [] cache_images_size = 0 utilities.print_progress(index + 1, file_size) database_meta = { 'number of files': file_size, 'chunk size': settings.DATABASE_CHUNK_SIZE, 'files': self.files, 'paths': data_paths, 'image width': settings.DATABASE_IMAGE_WIDTH, 'image height': settings.DATABASE_IMAGE_HEIGHT, 'chunk count': self.save_image_count, 'file type': settings.DATABASE_FILE_TYPE, 'database path': self.database_path, 'meta file path': self.database_meta_file } # save meta utilities.save(database_meta, self.database_meta_file) utilities.print_done()
def main(): parser = argparse.ArgumentParser(description='build image from images') parser.add_argument('-src', '--source', help='the image to stimulate') parser.add_argument('-s', '--size', type=int, help='the size of each pieces') parser.add_argument( '-f', '--folder', help='the folder containing images used to stimulate the source') parser.add_argument( '-d', '--dest', help='the base name of the output file, not including extension') parser.add_argument('-r', '--repeat', action='store_true', help='allow build with repeating images') parser.add_argument('-fa', '--factor', type=int, help='result size factor compared to original') args = parser.parse_args() input_file = args.source database_folder = args.folder src = Image.open(input_file).convert('RGB') source, background = make_from(src, database_folder, args.size, args.factor, use_repeat=args.repeat) print('Blending & saving images ... ') folder = args.dest if not os.path.isdir(folder): os.mkdir(folder) background_file = folder + '/background_{}.jpg' background.save( background_file.format('repeat' if args.repeat else 'no_repeat')) output_file = folder + '/{}.jpg' blend_range = 10 for index, blend_percent in enumerate(range(0, blend_range)): blend_percent = blend_percent / blend_range image = Image.blend(source, background, blend_percent) image.save(output_file.format(blend_percent)) utilities.print_progress(index + 1, blend_range) utilities.print_done(folder)
def _build_without_repeat(source, background, matcher, chunk_indexes, method): _build_ = functools.partial(_match_one, background=background, source=source, matcher=matcher, use_repeat=False, method=method) total = len(chunk_indexes) for index, chunk_index in enumerate(chunk_indexes): _build_(chunk_index) utilities.print_progress(index + 1, total) utilities.print_done() return background
def make_from(source, folder, size, factor, use_repeat=True): src_width, src_height = source.size width = int(src_width * factor) height = int(src_height * factor) source = source.resize((width, height)) pieces_required = math.ceil(width / size) * math.ceil(height / size) print('=' * 50) print('factor:', factor) print('result dimension:', width, height) print('total pieces:', pieces_required) print('repeat:', use_repeat) print('algorithm:', settings.COLOR_DIFF_METHOD) print('=' * 50) if size < 1 or size < 1: raise ValueError( 'width or height for each small piece of images is less than 1px: {} {} < {}' .format(width, height, size)) database = _load_database(folder, size, use_repeat, pieces_required) database.process_images() print('=' * 50) print('Database size:', database.size) print('=' * 50) chunk_count = 0 background = Image.new(source.mode, source.size, 'black') print('building image from database ...') start_time = time.time() for h in range(0, height, size): for w in range(0, width, size): btmx = w + size btmy = h + size if btmx > width: btmx = width if btmy > height: btmy = height curr_chunk = source.crop((w, h, btmx, btmy)) best_match = database.find_closest( curr_chunk, use_repeat, method=settings.COLOR_DIFF_METHOD) background.paste(best_match, (w, h)) chunk_count += 1 utilities.print_progress(chunk_count, pieces_required) utilities.print_done(time.time() - start_time) return source, background
def update_existing_database(self): print('Updating existing database') meta_dict = utilities.load(self.database_meta_file) files_to_add = [ file for file in self.files if file not in meta_dict['files'] ] total_files = len(files_to_add) if total_files == 0: print('There is no new images found ... [ done ]') return database_width = meta_dict['image width'] database_height = meta_dict['image height'] max_chunk_size = meta_dict['chunk size'] chunk_count = meta_dict['chunk count'] file_type = meta_dict['file type'] database_path = meta_dict['database path'] cache_images = [] num_of_cache_images = 0 data_paths = [] for index, file in enumerate(files_to_add): cache_images.append( DatabaseItem(file, database_width, database_height)) num_of_cache_images += 1 if num_of_cache_images >= max_chunk_size: chunk_count += 1 data_path = database_path + os.path.sep + chunk_count + file_type utilities.save(cache_images, data_path) data_paths.append(data_path) cache_images = [] num_of_cache_images = 0 utilities.print_progress(index + 1, total_files) utilities.print_done() # save remaining cached images if cache_images: # if not empty chunk_count += 1 data_path = database_path + os.path.sep + chunk_count + file_type utilities.save(cache_images, data_path) data_paths.append(data_path) meta_dict['number of files'] += len(files_to_add) meta_dict['files'] += files_to_add meta_dict['paths'] += data_paths meta_dict['chunk count'] += chunk_count utilities.save(meta_dict, meta_dict['meta file path'])
def _build(source, matcher, step, use_repeat, method): source_width, source_height = source.size total_chunks = math.ceil(source_width / step) * math.ceil( source_height / step) if matcher.size < total_chunks and not use_repeat: raise ValueError( 'Database does not have enough images: {} < {}'.format( matcher.size, total_chunks)) print('') # new line print('Gathering chunks | {}'.format(total_chunks)) curr_chunk_num = 0 chunk_indexes = [] for upper in range(0, source_height, step): for left in range(0, source_width, step): right = left + step lower = upper + step if right > source_width: right = source_width if lower > source_height: lower = source_height chunk_indexes.append((left, upper, right, lower)) # curr_chunk = source.crop((left, upper, right, lower)) # chunks.append(curr_chunk) # best_match = matcher.find_closest(curr_chunk, method=method) # if not use_repeat: # matcher.remove(best_match) # background.paste(best_match.image, (left, upper)) curr_chunk_num += 1 utilities.print_progress(curr_chunk_num, total_chunks) utilities.print_done() background = Image.new(source.mode, source.size, 'black') print('building image | {} | {} x {} | [{}] -> {} '.format( method, background.width, background.height, matcher.size, total_chunks)) if use_repeat: return _build_with_repeat(source, background, matcher, chunk_indexes, method) else: return _build_without_repeat(source, background, matcher, chunk_indexes, method)
def generate_color_space(self): if not self.rgb_image_dict: raise ValueError('Please call process_images first') total = len(self.rgb_image_dict) print('Generating color space | {}'.format(total)) start_time = time.time() color_space = [[[[] for _ in range(256)] for _ in range(256)] for _ in range(256)] # 256 x 256 x 256 arrays for index, item in enumerate(self.rgb_image_dict.items()): r, g, b = item[0] color_space[r][g][b].append(item[1]) utilities.print_progress(index + 1, total) self.images = None utilities.print_done(time.time() - start_time) start_time = time.time() print('cleaning color space ...', end='') self.color_space = utilities.remove_empty(color_space) print(' [ done ] => {0:.2f}s'.format(time.time() - start_time))
def _build_with_repeat(source, background, matcher, chunk_indexes, method): _build_ = functools.partial(_match_one, background=background, source=source, matcher=matcher, use_repeat=True, method=method) # This is actually slower # pool = Pool() # total = len(chunk_indexes) # for index, _ in enumerate(pool.imap_unordered(_build_, chunk_indexes, chunksize=1)): # utilities.print_progress(index + 1, total) # utilities.print_done() # return background total = len(chunk_indexes) for index, chunk_index in enumerate(chunk_indexes): _build_(chunk_index) utilities.print_progress(index + 1, total) utilities.print_done() return background
def check_and_load_database(self): # do some checking first if not os.path.isdir(self.database_path): raise ValueError('Database does not exists') if not os.path.isfile(self.database_meta_file): raise ValueError('Corrupted database: missing meta file') meta_dict = utilities.load(self.database_meta_file) database_chunk_size = meta_dict['chunk size'] if database_chunk_size != settings.DATABASE_CHUNK_SIZE: raise ValueError( 'Existing database has different chunk size signature | database {} | program {}' .format(database_chunk_size, settings.DATABASE_CHUNK_SIZE)) if settings.DATABASE_IMAGE_WIDTH != meta_dict['image width']: raise ValueError( 'Existing database has different image width signature | database {} | program {}' .format(settings.DATABASE_IMAGE_WIDTH, meta_dict['image width'])) if settings.DATABASE_IMAGE_HEIGHT != meta_dict['image height']: raise ValueError( 'Existing database has different image height signature | database {} | program {}' .format(settings.DATABASE_IMAGE_HEIGHT, meta_dict['image height'])) # now process the database num_of_database_images = meta_dict['number of files'] num_of_files_found = len(self.files) if num_of_database_images != num_of_files_found: if num_of_database_images == 0: self.create_and_load_database() return while True: print('Files found: {} | Database size: {}'.format( num_of_files_found, num_of_database_images)) print('[1] use existing database') print('[2] create new database') print('[3] update existing database') res = input('Please enter a number:') if res == '1': break elif res == '2': self.create_and_load_database() return elif res == '3': self.update_existing_database() meta_dict = utilities.load( self.database_meta_file) # update for below use break print('Please provide your answer as \'y\' or \'n\'') images_list_paths = meta_dict['paths'] images_list_size = len(images_list_paths) print('Loading database | {} | chunks {} | size per chunk {}'.format( self.database_path, images_list_size, meta_dict['chunk size'])) self.loaded_image_items = [] for index, images_path in enumerate(images_list_paths): try: database_items = utilities.load(images_path) except AttributeError as e: raise ValueError( 'Database Object has different signature, did you change your code structure? | {}' .format(e)) self.loaded_image_items += [ ImageItem(item, self.width, self.height) for item in database_items ] utilities.print_progress(index + 1, images_list_size) utilities.print_done()