"""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