"""Callbacks that can be given to the Evolver class, to be run when a successful change is applied to the genome."""
import csv
from matplotlib.pyplot import savefig
import cv2
from shapevolve.utils import show_image
_display = None
[docs]def default_callback(offspring, changes, loop_index, num_mutation_type_switches, error,
complex_mutation, best_image, genome):
"""The default callback that the Evolver class uses. Currently visual_callback.
:param offspring: The total number of offspring already processed.
:param changes: The total number of changes already applied to the genome.
:param loop_index: The total number of loops already performed.
:param num_mutation_type_switches: The total number of switches between simple and complex mutations so far.
:param error: The error of the current genome compared to the base image.
:param complex_mutation: Whether complex mutations are being applied or not.
:param best_image: The best image so far.
:param genome: The genome for the best image so far.
:type offspring: int
:type changes: int
:type loop_index: int
:type num_mutation_type_switches: int
:type error: float
:type complex_mutation: bool
:type best_image: ndarray
:type genome: Genome
"""
visual_callback(offspring, changes, loop_index, num_mutation_type_switches, error,
complex_mutation, best_image, genome)
# noinspection PyUnusedLocal,PyUnusedLocal,PyUnusedLocal,PyUnusedLocal,PyUnusedLocal
[docs]def visual_callback(offspring, changes, loop_index, num_mutation_type_switches, error,
complex_mutation, best_image, genome):
"""A simple callback that uses matplotlib to provide a live update of the image on the screen.
:param offspring: The total number of offspring already processed.
:param changes: The total number of changes already applied to the genome.
:param loop_index: The total number of loops already performed.
:param num_mutation_type_switches: The total number of switches between simple and complex mutations so far.
:param error: The error of the current genome compared to the base image.
:param complex_mutation: Whether complex mutations are being applied or not.
:param best_image: The best image so far.
:param genome: The genome for the best image so far.
:type offspring: int
:type changes: int
:type loop_index: int
:type num_mutation_type_switches: int
:type error: float
:type complex_mutation: bool
:type best_image: ndarray
:type genome: Genome
"""
_visualize(best_image, genome.adjusters, changes)
[docs]def quiet_visual_callback(offspring, changes, loop_index, num_mutation_type_switches, error,
complex_mutation, best_image, genome):
"""The same thing as visual_callback, but this only runs every 50 generations.
:param offspring: The total number of offspring already processed.
:param changes: The total number of changes already applied to the genome.
:param loop_index: The total number of loops already performed.
:param num_mutation_type_switches: The total number of switches between simple and complex mutations so far.
:param error: The error of the current genome compared to the base image.
:param complex_mutation: Whether complex mutations are being applied or not.
:param best_image: The best image so far.
:param genome: The genome for the best image so far.
:type offspring: int
:type changes: int
:type loop_index: int
:type num_mutation_type_switches: int
:type error: float
:type complex_mutation: bool
:type best_image: ndarray
:type genome: Genome
"""
if changes % 50 == 0:
visual_callback(offspring, changes, loop_index, num_mutation_type_switches, error,
complex_mutation, best_image, genome)
# noinspection PyUnusedLocal,PyUnusedLocal
[docs]def verbose_callback(offspring, changes, loop_index, num_mutation_type_switches, error,
complex_mutation, best_image, genome):
"""A callback that prints the status of the evolution into standard output.
:param offspring: The total number of offspring already processed.
:param changes: The total number of changes already applied to the genome.
:param loop_index: The total number of loops already performed.
:param num_mutation_type_switches: The total number of switches between simple and complex mutations so far.
:param error: The error of the current genome compared to the base image.
:param complex_mutation: Whether complex mutations are being applied or not.
:param best_image: The best image so far.
:param genome: The genome for the best image so far.
:type offspring: int
:type changes: int
:type loop_index: int
:type num_mutation_type_switches: int
:type error: float
:type complex_mutation: bool
:type best_image: ndarray
:type genome: Genome
"""
print(f"Total offspring: {offspring}")
print(f"Total changes: {changes}")
print(f"Total loop iterations: {loop_index}")
print(f"Total mutation type switches: {num_mutation_type_switches}")
print(f"Current error: {error}")
extreme = "Yes" if complex_mutation else "No"
print(f"Is currently in a complex mutation: {extreme}")
[docs]def quiet_verbose_callback(offspring, changes, loop_index, num_mutation_type_switches, error,
complex_mutation, best_image, genome):
"""The same thing as verbose_callback, but this only runs every 50 generations.
:param offspring: The total number of offspring already processed.
:param changes: The total number of changes already applied to the genome.
:param loop_index: The total number of loops already performed.
:param num_mutation_type_switches: The total number of switches between simple and complex mutations so far.
:param error: The error of the current genome compared to the base image.
:param complex_mutation: Whether complex mutations are being applied or not.
:param best_image: The best image so far.
:param genome: The genome for the best image so far.
:type offspring: int
:type changes: int
:type loop_index: int
:type num_mutation_type_switches: int
:type error: float
:type complex_mutation: bool
:type best_image: ndarray
:type genome: Genome
"""
if changes % 50 == 0:
verbose_callback(offspring, changes, loop_index, num_mutation_type_switches, error,
complex_mutation, best_image, genome)
def _visualize(best_image, adjusters, changes):
"""Displays an updated image in a matplotlib window.
:param best_image: The image to be displayed.
:param adjusters: Adjusters to be reversed for the image.
:param changes: The current generation number.
:type best_image: ndarray
:type adjusters: List[Dict[str, Callable]]
:type changes: int
"""
global _display
if _display is None:
_display = show_image(best_image, changes, adjusters=adjusters)
else:
show_image(best_image, changes, display=_display, adjusters=adjusters)
[docs]class GenomeSaver:
"""A class that defines a callback where genomes are saved to files."""
def __init__(self, genome_root, frequency=1):
"""Constructs a genome saver class that takes a given root file path.
:param genome_root: The file path where genomes should be saved.
:param frequency: The frequency at which files will be saved.
:type genome_root: str
:type frequency: int
"""
self.root = genome_root
self.frequency = frequency
# noinspection PyUnusedLocal
[docs] def callback(self, offspring, changes, loop_index, num_mutation_type_switches, error,
complex_mutation, best_image, genome):
"""A callback that saves genomes to a filepath.
:param offspring: The total number of offspring already processed.
:param changes: The total number of changes already applied to the genome.
:param loop_index: The total number of loops already performed.
:param num_mutation_type_switches: The total number of switches between simple and complex mutations so far.
:param error: The error of the current genome compared to the base image.
:param complex_mutation: Whether complex mutations are being applied or not.
:param best_image: The best image so far.
:param genome: The genome for the best image so far.
:type offspring: int
:type changes: int
:type loop_index: int
:type num_mutation_type_switches: int
:type error: float
:type complex_mutation: bool
:type best_image: ndarray
:type genome: Genome
"""
if changes % self.frequency == 0:
genome.save_genome(self.root + f"_gen{changes:04d}.genome")
[docs]class CSVLogger:
"""A class that defines callbacks which record statistics into a CSV file."""
def __init__(self, csv_filepath, frequency=1):
"""A constructor for the class that stores a path to a CSV file.
:param csv_filepath: The filepath of the CSV file that statistics will be written to.
:param frequency: The frequency at which stats will be saved.
:type csv_filepath: str
:type frequency: int
"""
self.csv_filepath = csv_filepath
self.frequency = frequency
with open(csv_filepath, 'w', newline='') as file: # Write the headers for the CSV file.
writer = csv.writer(file)
writer.writerow(["offspring", "generation", "loop_index", "error"])
# noinspection PyUnusedLocal
[docs] def callback(self, offspring, changes, loop_index, num_mutation_type_switches, error,
complex_mutation, best_image, genome):
"""A callback that stores offspring, generation, loop index, and error into the class's CSV file.
:param offspring: The total number of offspring already processed.
:param changes: The total number of changes already applied to the genome.
:param loop_index: The total number of loops already performed.
:param num_mutation_type_switches: The total number of switches between simple and complex mutations so far.
:param error: The error of the current genome compared to the base image.
:param complex_mutation: Whether complex mutations are being applied or not.
:param best_image: The best image so far.
:param genome: The genome for the best image so far.
:type offspring: int
:type changes: int
:type loop_index: int
:type num_mutation_type_switches: int
:type error: float
:type complex_mutation: bool
:type best_image: ndarray
:type genome: Genome
"""
if changes % self.frequency == 0:
with open(self.csv_filepath, 'a', newline='') as file:
writer = csv.writer(file)
writer.writerow([offspring, changes, loop_index, error])
[docs]class ImageSaver:
"""A class that defines a callback where images built during evolution are saved."""
def __init__(self, image_root, frequency=1):
"""A constructor for the class that defines a root filepath for saved images.
:param image_root: The root filepath for saved images.
:param frequency: The frequency at which files will be saved.
:type image_root: str
:type frequency: int
"""
self.root = image_root
self.frequency = frequency
[docs] def callback(self, offspring, changes, loop_index, num_mutation_type_switches, error,
complex_mutation, best_image, genome):
"""A callback that saves the best image so far into a png file.
:param offspring: The total number of offspring already processed.
:param changes: The total number of changes already applied to the genome.
:param loop_index: The total number of loops already performed.
:param num_mutation_type_switches: The total number of switches between simple and complex mutations so far.
:param error: The error of the current genome compared to the base image.
:param complex_mutation: Whether complex mutations are being applied or not.
:param best_image: The best image so far.
:param genome: The genome for the best image so far.
:type offspring: int
:type changes: int
:type loop_index: int
:type num_mutation_type_switches: int
:type error: float
:type complex_mutation: bool
:type best_image: ndarray
:type genome: Genome
"""
if changes % self.frequency == 0:
if not cv2.imwrite(self.root + f"_img{changes:04d}.png", best_image):
raise FileNotFoundError("The path given to ImageSaver was not valid.")
[docs]class HighQualityImageSaver(ImageSaver):
"""A subclass of ImageSaver that scales images to the original resolution before saving them."""
[docs] def callback(self, offspring, changes, loop_index, num_mutation_type_switches, error,
complex_mutation, best_image, genome):
"""A callback that scales an image before providing it to the equivalent method in ImageSaver.
:param offspring: The total number of offspring already processed.
:param changes: The total number of changes already applied to the genome.
:param loop_index: The total number of loops already performed.
:param num_mutation_type_switches: The total number of switches between simple and complex mutations so far.
:param error: The error of the current genome compared to the base image.
:param complex_mutation: Whether complex mutations are being applied or not.
:param best_image: The best image so far.
:param genome: The genome for the best image so far.
:type offspring: int
:type changes: int
:type loop_index: int
:type num_mutation_type_switches: int
:type error: float
:type complex_mutation: bool
:type best_image: ndarray
:type genome: Genome
"""
image = genome.render_scaled_image()
ImageSaver.callback(self, offspring, changes, loop_index, num_mutation_type_switches, error,
complex_mutation, image, genome)
[docs]class MatplotImageSaver(ImageSaver):
"""A class that defines a callback where images shown by matplotlib during evolution are saved. Must be used with a variant of visual_callback in the same list."""
[docs] def callback(self, offspring, changes, loop_index, num_mutation_type_switches, error,
complex_mutation, best_image, genome):
"""A callback that saves the best image so far into a png file.
:param offspring: The total number of offspring already processed.
:param changes: The total number of changes already applied to the genome.
:param loop_index: The total number of loops already performed.
:param num_mutation_type_switches: The total number of switches between simple and complex mutations so far.
:param error: The error of the current genome compared to the base image.
:param complex_mutation: Whether complex mutations are being applied or not.
:param best_image: The best image so far.
:param genome: The genome for the best image so far.
:type offspring: int
:type changes: int
:type loop_index: int
:type num_mutation_type_switches: int
:type error: float
:type complex_mutation: bool
:type best_image: ndarray
:type genome: Genome
"""
if changes % self.frequency == 0:
savefig(self.root + f"_fig{changes:04d}.png")