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 regenerate_midis(self, generate_txt: bool = False): """ Regenerate MIDI files from internally stored sequences. :param generate_txt: Parameter that controls the generation of .txt files that contain music21.stream data. :return: """ self.__clear_files(self.__regen_path) pf = "Regenerating MIDI files to " + self.__regen_path + ":" end = len(self.__sequences) for progress, sequence in enumerate(self.__sequences): print_progress(progress, end, prefix=pf, suffix=sequence) stream_to_file( sequence, self.__regen_path + "regenerated_" + str(progress) + ".mid") if generate_txt: file = open( self.__regen_path + "regenerated_" + str(progress) + ".txt", "a") for element in sequence[0]: file.write("{" + str(element.offset) + "}" + " - " + str(element) + "\n") file.close() print_progress(progress + 1, end, prefix=pf, suffix=sequence)
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 __clear_files(self, clear_path): """ Clear physical files in given directory. :param clear_path: Describes a directory that has to be purged. :return: """ clear_files = listdir(clear_path) pf = "Removing files in " + clear_path + ":" end = len(clear_files) for progress, file in enumerate(clear_files): print_progress(progress, end, prefix=pf, suffix=file) remove(clear_path + file) print_progress(progress + 1, end, prefix=pf, suffix=file)
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 prepare_data(self): """ Prepares MIDIs, changes them to music21 streams that are then processed and added onto tensorflow tensors. Method starts with calling internal MidiSplitter that divides MIDIs into instrumental parts. These parts, in order to be readable by music21 toolkit, have to physically exist on hard drive. Split MIDIs contain data on time signatures and tempos. MIDI programs (instruments) that are not fully supported by music21 module, therefore are stored as comments in music21.instrument.Instrument() classes found outside in a dictionary that is then used to reassign these programs when corresponding streams are created. These comments become useful in later parts of input data creation process. :return: """ # Input files are split into multiple instrument tracks and saved as separate MIDIs. input_files = listdir(self.__input_path) pf = "Splitting MIDI files:" end = len(input_files) for progress, file in enumerate(input_files): print_progress(progress, end, prefix=pf, suffix=file) self.__splitter.split_midi(file) print_progress(progress + 1, end, prefix=pf, suffix=file) # Split files are turned into music21 sequences. split_files = listdir(self.__split_path) pf = "Assigning instruments for parts:" end = len(split_files) for progress, file in enumerate(split_files): print_progress(progress, end, prefix=pf, suffix=file) sequence = mu.converter.parse(self.__split_path + file, quantizePost=True) # Processed sequence is now reassigned its instrument that have potentially lost during conversion. self.__reassign_program(sequence, file) self.__sequences.append(sequence) print_progress(progress + 1, end, prefix=pf, suffix=file)
def prepare_data(self, subsequence_length: int = 16, subsequence_width: int = 6): """ Final training data preparations. Final lists are created that can be converted into tensorflow.Tensor classes. Function iterates through loaded sequences, extracts necessary parameters and creates measures from these sequences. These measures are then transformed into two-dimensional lists of size [subsequence_length + 1, subsequence_width]. Subsequence_length describes how many notes can be stored in one measure (+1 is for tag_line that sits in the beginning of every subsequence and gives more information concerning the measure structure). :param subsequence_length: :param subsequence_width: :return: """ # data is reset self.__input = [] self.__target = [] if subsequence_width >= TAG_LINE_LEN: highest_width_loss = 0 highest_length_loss = 0 pf = "Splitting parts to subsequences: " end = len(self.__sequences) for progress, sequence in enumerate(self.__sequences): print_progress(progress, end, prefix=pf, suffix=sequence) # divide each sequence into measures that are defined by time signature measures = sequence[0].makeMeasures() # saving tempo (dynamically changing tempos are not supported) boundaries = sequence[0].metronomeMarkBoundaries() if len(boundaries) == 1: metronome = float(boundaries[0][2].number) uniform = True else: metronome = 0.0 uniform = False # saving initial time signature time_signature = sequence[0].timeSignature # saving tag line values instrument values instrument = sequence[0].getInstrument() is_drum = float( instrument.editorial.comments[0].is_drum == "True") program = float(instrument.editorial.comments[0].true_program) # subsequences are unique to each midi track, # these same subsequences will be divided into training and target data # (subsequent subsequences) subsequences = [] for measure in measures: # in case if the object is not a measure, it's discarded if type(measure) is not mu.stream.Measure: measures.remove(measure) continue # deleting empty measure and skipping to the next one if measure.notes.highestOffset == 0.0: measures.remove(measure) continue # creating subsequence and measures dictionary for each measure # dictionary is created and cleared but used only if uniform is False subsequence = [] metronomes = {} # detecting changes in time signatures if measure.timeSignature is not None: time_signature = measure.timeSignature # tag_line is a list composed of "instruction values" # that are found at the very beginning of every input vector tag_line = [ float(measure.offset), program, is_drum, float(time_signature.numerator), float(time_signature.denominator) ] tag_line += ([0.0] * (subsequence_width - TAG_LINE_LEN)) subsequence.append(tag_line) # extracting metronome boundaries for each measure # as they have different offsets compared to the initial metronome boundaries check if uniform is False: boundaries = measure.metronomeMarkBoundaries() for boundary in boundaries: metronomes[float(boundary[0])] = boundary[2].number # adding elements to subsequence for element in measure.notes.sorted: offset = float(element.offset) if uniform is False: key = min(metronomes.keys(), key=lambda x: abs(x - offset)) metronome = metronomes[key] length = float(element.quarterLength) line = [offset, metronome, length] # adding cord pitches to width for pitch in element.pitches: line.append(float(pitch.midi)) # zero-padding width diff = subsequence_width - len(line) if diff > 0: for i in range(diff): line.append(0.0) if diff < 0: if len(line) > highest_width_loss: highest_width_loss = len(line) for i in range(abs(diff)): line.pop() subsequence.append(line) # zero-padding length diff = subsequence_length + 1 - len(subsequence) if diff > 0: for i in range(diff): subsequence.append([0.0] * subsequence_width) elif diff < 0: if len(subsequence) - 1 > highest_length_loss: highest_length_loss = len(subsequence) - 1 for i in range(abs(diff)): subsequence.pop() subsequences.append(subsequence) # assigning input and target data to subsequent subsequences expected_offset = 0.0 past_subsequence = None for subsequence in subsequences: tag_line = subsequence[0] current_offset = tag_line[0] if current_offset == expected_offset and past_subsequence is not None: self.__input.append(past_subsequence) self.__target.append(subsequence) past_subsequence = subsequence expected_offset = current_offset + (tag_line[3] / tag_line[4] * 4) print_progress(progress + 1, end, prefix=pf, suffix=sequence) if highest_width_loss > 0: print("Subsequence width (" + str(subsequence_width) + ") may result in data losses. " "Minimum width of " + str(highest_width_loss) + " is recommended") if highest_length_loss > 0: print("Subsequence length (" + str(subsequence_length) + ") may result in data losses. " "Minimum length of " + str(highest_length_loss) + " is recommended") else: print("Subsequence width (" + str(subsequence_width) + ") cannot be smaller than " + str(TAG_LINE_LEN))
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()