Source code for shapevolve.genome

"""The Genome class represents a shape sequence and its various properties, from which an image can be built."""

import pickle

import numpy as np

from shapevolve.utils import unadjust


[docs]class MismatchedGenomeError(Exception): """An error that occurs when two genomes do not share the same properties with one another.""" pass
[docs]class Genome: """This class represents a shape sequence and its various properties, from which an image can be built.""" def __init__(self, sequence, ratio, height, width, background_color, adjusters, palette, draw): """Constructs a genome based on certain properties. :param sequence: A sequence of Gene objects that represent the shapes in the image. :param ratio: The ratio between the evolution image dimensions and the original image dimensions. :param height: The height of the image. :param width: The width of the image. :param background_color: The background color of the image. :param adjusters: Reversible image adjusters that can be applied and unapplied to the image. :param palette: A list of RGB color tuples that colors are referenced from. :param draw: A function that is used to draw an image according to a Gene object. :type sequence: List[Gene] :type ratio: float :type height: int :type width: int :type background_color: Tuple[int, int, int] :type adjusters: List[Dict[str, Callable]] :type palette: List[Tuple[int, int, int]] :type draw: Callable """ self.sequence = sequence self.ratio = ratio self.height = height self.width = width self.background_color = background_color self.adjusters = adjusters self.palette = palette self.draw = draw
[docs] def render_scaled_image(self, after_unadjustment=False, fill_gaps=True): """Renders a scaled version of an image according to the genome's ratio field. :param after_unadjustment: Whether the image has already been un-adjusted. :param fill_gaps: Whether gaps should be filled in the scaled image. :type after_unadjustment: bool :type fill_gaps: bool :return: The scaled image. :rtype: ndarray """ scaled_height = round(self.height / self.ratio) scaled_width = round(self.width / self.ratio) scaled_sequence = self.scale_sequence(fill_gaps) scaled_image = self.render_image(scaled_height, scaled_width, scaled_sequence, after_unadjustment) return scaled_image
[docs] def scale_sequence(self, fill_gaps): """Generates a gene sequence with each gene scaled by the genome's ratio field. :param fill_gaps: Whether the gene's radius should be adjusted to fill new gaps in the scaled image. :type fill_gaps: bool :return: The new Gene sequence. :rtype: List[Gene] """ return [gene.get_scaled_version(self.ratio, fill_gaps) for gene in self.sequence]
[docs] def render_raw_image(self, after_unadjustment=False): """Renders the image without any scaling. :param after_unadjustment: Whether the image has already been un-adjusted. :type after_unadjustment: bool :return: The rendered image. :rtype: ndarray """ return self.render_image(self.height, self.width, self.sequence, after_unadjustment)
[docs] def render_image(self, height, width, sequence, after_unadjustment): """Renders an image according to certain parameters. :param height: The height of the desired image. :param width: The width of the desired image. :param sequence: The gene sequence to draw on the image. :param after_unadjustment: Whether the image has already been un-adjusted. :type height: int :type width: int :type sequence: List[Gene] :type after_unadjustment: bool :return: The rendered image. :rtype: ndarray """ image = np.zeros((height, width, 3), np.uint8) image[:] = self.background_color for gene in sequence: self.draw(image, gene, self.palette) if not after_unadjustment: image = unadjust(image, self.adjusters) return image
[docs] def save_genome(self, filename): """Saves the genome to a file using pickle. :param filename: The filename to save the genome to. :type filename: str """ with open(filename, 'wb') as genomeFile: pickle.dump(self, genomeFile)
[docs]def is_compatible(genome1, genome2): """Check whether two genomes could describe the same base image. :param genome1: Genome 1, to be compared. :param genome2: Genome 2, to be compared. :type genome1: Genome :type genome2: Genome :return: Whether they are matched. :rtype: bool :raises: MismatchedGenomeError: If the genomes do not match. """ gene_checks = ['max_radius', 'min_radius', 'height', 'width', 'num_colors'] genome_checks = ['ratio', 'height', 'width', 'background_color', 'adjusters', 'palette', 'draw'] for gene1, gene2 in zip(genome1.sequence, genome2.sequence): gene1_dict = vars(gene1) gene2_dict = vars(gene2) for check in gene_checks: if gene1_dict[check] != gene2_dict[check]: raise MismatchedGenomeError(f"Genome match was inconsistent: {check} field was incorrect. " f"Please make sure the source image matches the genome you are " f"trying to load.") if len(genome1.sequence) != len(genome2.sequence): raise MismatchedGenomeError(f"Genome match was inconsistent: gene sequences had different lengths. " f"Please make sure the source image matches the genome you are " f"trying to load.") genome1dict = vars(genome1) genome2dict = vars(genome2) for check in genome_checks: if genome1dict[check] != genome2dict[check]: raise MismatchedGenomeError(f"Genome match was inconsistent: {check} field was incorrect. " f"Please make sure the source image matches the genome you are " f"trying to load.") return True
[docs]def load_genome(filename): """Loads a pickled Genome file. :param filename: The filename of the genome to be loaded. :type filename: str :return: The loaded genome. :rtype: Genome """ with open(filename, 'rb') as genomeFile: genome = pickle.load(genomeFile) return genome