/* * * Copyright (C) 2025 Brett Terpstra * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include "opencv2/imgcodecs.hpp" #include "opencv2/imgproc.hpp" #include using namespace blt::gp; bool is_nan(const float f) { return std::isnan(f) || std::isinf(f) || std::isinf(-f); } float filter_nan(const float f, const float failure = 0.0f) { if (is_nan(f)) return failure; return f; } std::vector to_cv2(const image_t& a) { std::vector converted_data(IMAGE_SIZE); for (const auto& [o, v] : blt::in_pairs(converted_data, a.get_data().data)) o = static_cast(v) / static_cast(std::numeric_limits::max()); return converted_data; } image_t from_cv2(const std::vector& a) { image_t ret; for (const auto& [o, v] : blt::in_pairs(ret.get_data().data, a)) o = static_cast(v * static_cast(std::numeric_limits::max())); return ret; } bool use_gamma_correction = false; std::array programs; prog_config_t config{}; std::vector> images; std::vector> images_red; std::vector> images_green; std::vector> images_blue; std::array reference_image; std::vector average_fitness; std::vector best_fitness; std::vector worst_fitness; std::vector overall_fitness; std::array, 3> mean_vec; std::array, 3> variance_vec; template void fitness_func(const tree_t& tree, fitness_t& fitness, const blt::size_t index) { auto image = tree.get_evaluation_ref(); auto& data = image->get_data(); for (blt::size_t x = 0; x < IMAGE_DIMENSIONS; ++x) { for (blt::size_t y = 0; y < IMAGE_DIMENSIONS; ++y) { switch (Channel) { case 0: images_red[index][(y * IMAGE_DIMENSIONS + x)] = data.get(x, y); break; case 1: images_green[index][(y * IMAGE_DIMENSIONS + x)] = data.get(x, y); break; case 2: images_blue[index][(y * IMAGE_DIMENSIONS + x)] = data.get(x, y); break; default: break; } // images[index][(y * IMAGE_DIMENSIONS + x) * 3 + Channel] = data.get(x, y); const auto our = static_cast(data.get(x, y)) / static_cast(std::numeric_limits::max()); const auto theirs = reference_image[Channel].get(x, y); const auto gamma_ours = std::pow(our, 1.0f / 2.2f); // const auto gamma_theirs = std::pow(theirs, 1.0f / 2.2f); if (use_gamma_correction) { const auto diff = gamma_ours - theirs; fitness.raw_fitness += static_cast((diff * diff)); } else { const auto diff = our - theirs; fitness.raw_fitness += static_cast((diff * diff)); } } } fitness.set_normal(static_cast(std::sqrt(fitness.raw_fitness))); // fitness.raw_fitness = static_cast(std::sqrt(fitness.raw_fitness)); // fitness.standardized_fitness = fitness.raw_fitness; // fitness.adjusted_fitness = -fitness.standardized_fitness; } template void setup_operations(gp_program* program) { static operation_t op_image_x([]() { constexpr auto mul = std::numeric_limits::max() / static_cast(IMAGE_DIMENSIONS - 1); image_t ret{}; for (blt::u32 x = 0; x < IMAGE_DIMENSIONS; ++x) { for (blt::u32 y = 0; y < IMAGE_DIMENSIONS; ++y) ret.get_data().get(x, y) = y * mul; } return ret; }); static operation_t op_image_y([]() { constexpr auto mul = std::numeric_limits::max() / static_cast(IMAGE_DIMENSIONS - 1); image_t ret{}; for (blt::u32 x = 0; x < IMAGE_DIMENSIONS; ++x) { for (blt::u32 y = 0; y < IMAGE_DIMENSIONS; ++y) ret.get_data().get(x, y) = x * mul; } return ret; }); static auto op_image_random = operation_t([program]() { image_t ret{}; for (auto& v : ret.get_data().data) v = program->get_random().get_u32(0, std::numeric_limits::max()); return ret; }); static auto op_image_noise = operation_t([program]() { image_t ret{}; for (auto& v : ret.get_data().data) v = program->get_random().get_u32(0, std::numeric_limits::max()); return ret; }).set_ephemeral(); static auto op_image_ephemeral = operation_t([program]() { image_t ret{}; const auto value = program->get_random().get_u32(0, std::numeric_limits::max()); for (auto& v : ret.get_data().data) v = value; return ret; }).set_ephemeral(); // static operation_t op_image_blend([](const image_t a, const image_t b, const float f) { // const auto blend = std::min(std::max(f, 0.0f), 1.0f); // const auto beta = 1.0f - blend; // image_t ret{}; // const cv::Mat src1{IMAGE_DIMENSIONS, IMAGE_DIMENSIONS, CV_32F, a.as_void_const()}; // const cv::Mat src2{IMAGE_DIMENSIONS, IMAGE_DIMENSIONS, CV_32F, b.as_void_const()}; // cv::Mat dst{IMAGE_DIMENSIONS, IMAGE_DIMENSIONS, CV_32F, ret.get_data().data.data()}; // addWeighted(src1, blend, src2, beta, 0.0, dst); // return ret; // }, "blend_image"); static operation_t op_image_sin([](const image_t a) { constexpr auto limit = static_cast(std::numeric_limits::max()); image_t ret{}; for (const auto& [i, v] : blt::enumerate(std::as_const(a.get_data().data))) ret.get_data().data[i] = static_cast(((std::sin((v / limit) * blt::PI) + 1.0) / 2.0f) * limit); return ret; }, "sin_image"); static operation_t op_image_sin_off([](const image_t a, const image_t b) { constexpr auto limit = static_cast(std::numeric_limits::max()); image_t ret{}; for (const auto& [i, v, off] : blt::in_pairs(std::as_const(a.get_data().data), std::as_const(b.get_data().data)).enumerate().flatten()) ret.get_data().data[i] = static_cast(((std::sin((v / limit) * blt::PI * (off / (limit / 4))) + 1.0) / 2.0f) * limit); return ret; }, "sin_image_off"); static operation_t op_image_cos([](const image_t a) { constexpr auto limit = static_cast(std::numeric_limits::max()); image_t ret{}; for (const auto& [i, v] : blt::enumerate(std::as_const(a.get_data().data))) ret.get_data().data[i] = static_cast(((std::cos((v / limit) * blt::PI * 2) + 1.0) / 2.0f) * limit); return ret; }, "cos_image"); static operation_t op_image_cos_off([](const image_t a, const image_t b) { constexpr auto limit = static_cast(std::numeric_limits::max()); image_t ret{}; for (const auto& [i, v, off] : blt::in_pairs(std::as_const(a.get_data().data), std::as_const(b.get_data().data)).enumerate().flatten()) ret.get_data().data[i] = static_cast(((std::cos((v / limit) * blt::PI * (off / (limit / 2))) + 1.0) / 2.0f) * limit); return ret; }, "cos_image_off"); static operation_t op_image_log([](const image_t a) { constexpr auto limit = static_cast(std::numeric_limits::max()); image_t ret{}; for (const auto& [i, v] : blt::enumerate(std::as_const(a.get_data().data))) { if (v == 0) ret.get_data().data[i] = 0; else ret.get_data().data[i] = static_cast(std::log(v / limit) * limit); } return ret; }, "log_image"); static operation_t op_image_exp([](const image_t a) { constexpr auto limit = static_cast(std::numeric_limits::max()); image_t ret{}; for (const auto& [i, v] : blt::enumerate(std::as_const(a.get_data().data))) ret.get_data().data[i] = static_cast(std::exp(v / limit) * limit); return ret; }, "exp_image"); static operation_t op_image_abs([](const image_t a) { image_t ret{}; for (const auto& [i, v] : blt::enumerate(std::as_const(a.get_data().data))) ret.get_data().data[i] = std::numeric_limits::max() - v; return ret; }, "abs_image"); static operation_t op_image_mod([](const image_t a, const image_t b) { image_t ret{}; for (const auto& [i, av, bv] : blt::in_pairs(std::as_const(a.get_data().data), std::as_const(b.get_data().data)).enumerate().flatten()) ret.get_data().data[i] = bv == 0 ? 0 : av % bv; return ret; }, "mod_image"); static operation_t op_image_or([](const image_t a, const image_t b) { image_t ret{}; for (const auto& [i, av, bv] : blt::in_pairs(std::as_const(a.get_data().data), std::as_const(b.get_data().data)).enumerate().flatten()) ret.get_data().data[i] = av | bv; return ret; }, "bit_or_image"); static operation_t op_image_and([](const image_t a, const image_t b) { image_t ret{}; for (const auto& [i, av, bv] : blt::in_pairs(std::as_const(a.get_data().data), std::as_const(b.get_data().data)).enumerate().flatten()) ret.get_data().data[i] = av & bv; return ret; }, "bit_and_image"); static operation_t op_image_xor([](const image_t a, const image_t b) { image_t ret{}; for (const auto& [i, av, bv] : blt::in_pairs(std::as_const(a.get_data().data), std::as_const(b.get_data().data)).enumerate().flatten()) ret.get_data().data[i] = av ^ bv; return ret; }, "bit_xor_image"); static operation_t op_image_not([](const image_t a) { image_t ret{}; for (const auto& [i, av] : blt::enumerate(std::as_const(a.get_data().data)).flatten()) ret.get_data().data[i] = ~av; return ret; }, "bit_not_image"); static operation_t op_image_srgb([](const image_t a) { constexpr auto limit = static_cast(std::numeric_limits::max()); image_t ret{}; for (const auto& [i, av] : blt::enumerate(std::as_const(a.get_data().data)).flatten()) ret.get_data().data[i] = static_cast(std::pow(av / limit, 1.0/2.2) * limit); return ret; }, "srgb_image"); static operation_t op_image_linear([](const image_t a) { struct f { static float srgb_to_linear(const float v) noexcept { return (v <= 0.04045f) ? (v / 12.92f) : std::pow((v + 0.055f) / 1.055f, 2.4f); } }; constexpr auto limit = static_cast(std::numeric_limits::max()); image_t ret{}; for (const auto& [i, av] : blt::enumerate(std::as_const(a.get_data().data)).flatten()) ret.get_data().data[i] = static_cast(f::srgb_to_linear(static_cast(av) / limit) * limit); return ret; }, "srgb_image"); static operation_t op_image_gt([](const image_t a, const image_t b) { image_t ret{}; for (const auto& [i, av, bv] : blt::in_pairs(std::as_const(a.get_data().data), std::as_const(b.get_data().data)).enumerate().flatten()) ret.get_data().data[i] = av > bv ? av : bv; return ret; }, "gt_image"); static operation_t op_image_lt([](const image_t a, const image_t b) { image_t ret{}; for (const auto& [i, av, bv] : blt::in_pairs(std::as_const(a.get_data().data), std::as_const(b.get_data().data)).enumerate().flatten()) ret.get_data().data[i] = av < bv ? av : bv; return ret; }, "lt_image"); static operation_t op_image_grad([](const image_t a, const image_t b) { image_t out{}; for (const auto& [i, av, bv] : blt::in_pairs(std::as_const(a.get_data().data), std::as_const(b.get_data().data)).enumerate().flatten()) { const auto p = static_cast(i) / static_cast(IMAGE_SIZE); const auto pi = 1 - p; out.get_data().data[i] = static_cast(av * p + bv * pi); } return out; }, "grad_image"); static operation_t op_image_perlin([](const image_t a) { constexpr auto limit = static_cast(std::numeric_limits::max()); image_t ret{}; for (const auto& [i, out, bv] : blt::in_pairs(ret.get_data().data, std::as_const(a.get_data().data)).enumerate().flatten()) { constexpr auto AND = IMAGE_DIMENSIONS - 1; const double y = (static_cast(i) / IMAGE_DIMENSIONS) / static_cast(IMAGE_DIMENSIONS); const double x = static_cast(i & AND) / static_cast(IMAGE_DIMENSIONS); out = static_cast(stb_perlin_noise3(static_cast(x), static_cast(y), static_cast(bv / (limit * 0.1)), 0, 0, 0) * limit); } return ret; }, "perlin_image"); static auto op_image_2d_perlin_eph = operation_t([program]() { constexpr auto limit = static_cast(std::numeric_limits::max()); image_t ret{}; const auto variety = program->get_random().get_float(1.5, 255); const auto x_warp = program->get_random().get_i32(0, 255); const auto y_warp = program->get_random().get_i32(0, 255); const auto z_warp = program->get_random().get_i32(0, 255); const auto offset_x = program->get_random().get_float(1.0 / 64.0f, 16.0f); const auto offset_y = program->get_random().get_float(1.0 / 64.0f, 16.0f); for (const auto& [i, out] : blt::enumerate(ret.get_data().data)) { constexpr auto AND = IMAGE_DIMENSIONS - 1; const double y = (static_cast(i) / IMAGE_DIMENSIONS) / static_cast(IMAGE_DIMENSIONS); const double x = static_cast(i & AND) / static_cast(IMAGE_DIMENSIONS); out = static_cast(stb_perlin_noise3(static_cast(x) * offset_x, static_cast(y) * offset_y, variety, x_warp, y_warp, z_warp) * limit); } return ret; }, "perlin_image_eph").set_ephemeral(); static auto op_image_2d_perlin_oct = operation_t([program]() { constexpr auto limit = static_cast(std::numeric_limits::max()); image_t ret{}; const auto rand = program->get_random().get_float(0, 255); const auto octaves = program->get_random().get_i32(2, 8); const auto gain = program->get_random().get_float(0.1f, 0.9f); const auto lac = program->get_random().get_float(1.5f, 6.f); const auto offset = program->get_random().get_float(1.0 / 255.0f, 16.0f); for (const auto& [i, out] : blt::enumerate(ret.get_data().data)) { constexpr auto AND = IMAGE_DIMENSIONS - 1; const double y = (static_cast(i) / IMAGE_DIMENSIONS) / static_cast(IMAGE_DIMENSIONS); const double x = static_cast(i & AND) / static_cast(IMAGE_DIMENSIONS); out = static_cast(stb_perlin_fbm_noise3(static_cast(x * offset), static_cast(y * offset), rand, lac, gain, octaves) * limit); } return ret; }, "perlin_image_eph_oct").set_ephemeral(); static operation_t op_passthrough([](const image_t& a) { image_t ret{}; std::memcpy(ret.get_data().data.data(), a.get_data().data.data(), IMAGE_SIZE_BYTES); return ret; }, "passthrough"); static operation_t op_erode([program](const image_t a) { const auto erosion_size = program->get_random().get_i32(3, 12); std::vector converted_data = to_cv2(a); std::vector output_data(IMAGE_SIZE); const cv::Mat src{IMAGE_DIMENSIONS, IMAGE_DIMENSIONS, CV_32F, converted_data.data()}; cv::Mat dst{IMAGE_DIMENSIONS, IMAGE_DIMENSIONS, CV_32F, output_data.data()}; const cv::Mat element = cv::getStructuringElement(cv::MORPH_ERODE, cv::Size(erosion_size, erosion_size)); cv::erode(src, dst, element); return from_cv2(output_data); }, "erode_image"); static operation_t op_dilate([program](const image_t a) { const auto dilate_size = program->get_random().get_i32(3, 12); std::vector converted_data = to_cv2(a); std::vector output_data(IMAGE_SIZE); const cv::Mat src{IMAGE_DIMENSIONS, IMAGE_DIMENSIONS, CV_32F, converted_data.data()}; cv::Mat dst{IMAGE_DIMENSIONS, IMAGE_DIMENSIONS, CV_32F, output_data.data()}; const cv::Mat element = cv::getStructuringElement(cv::MORPH_DILATE, cv::Size(dilate_size, dilate_size)); cv::dilate(src, dst, element); return from_cv2(output_data); }, "dilate_image"); static operation_t op_band_pass([program](const image_t a) { auto input = to_cv2(a); std::vector output_data(IMAGE_SIZE); const cv::Mat src{IMAGE_DIMENSIONS, IMAGE_DIMENSIONS, CV_32F, input.data()}; cv::Mat dst{IMAGE_DIMENSIONS, IMAGE_DIMENSIONS, CV_32F, output_data.data()}; const auto sigmaLow = program->get_random().get_float(0.5f, 2.0f); const auto sigmaHigh = program->get_random().get_float(3.f, 12.f); const auto size = program->get_random().get_i32(1,5) * 2 + 1; cv::Mat low{IMAGE_DIMENSIONS, IMAGE_DIMENSIONS, CV_32F}, high{IMAGE_DIMENSIONS, IMAGE_DIMENSIONS, CV_32F}; cv::GaussianBlur(src, low, cv::Size(size, size), sigmaLow); cv::GaussianBlur(src, high, cv::Size(size, size), sigmaHigh); cv::Mat dog = high - low; cv::normalize(dog, dog, 0, 1, cv::NORM_MINMAX); dog.copyTo(dst); return from_cv2(output_data); }, "band_pass"); operator_builder builder{}; builder.build(op_image_ephemeral, make_add(), make_sub(), make_mul(), make_div(), op_image_x, op_image_y, op_image_sin, op_image_gt, op_image_lt, op_image_cos, op_image_log, op_image_exp, op_image_or, op_image_and, op_image_xor, op_band_pass, op_image_perlin, op_image_noise, op_image_random, op_image_2d_perlin_eph, op_image_not, op_image_srgb, op_image_grad, op_image_2d_perlin_oct, op_erode, op_dilate, op_image_abs, op_image_mod, op_image_linear); // builder.build(op_thresh, op_image_2d_perlin_oct); program->set_operations(builder.grab()); } void setup_gp_system(const blt::size_t population_size) { reference_image = image_storage_t::from_file("../silly.png"); config.set_pop_size(population_size); config.set_elite_count(2); config.set_thread_count(0); config.set_reproduction_chance(0); // config.set_crossover_chance(0); // config.set_mutation_chance(0); // config.set_reproduction_chance(0); const auto rand = std::random_device()(); BLT_INFO("Random Seed: {}", rand); for (auto& program : programs) { program = new gp_program{rand, config}; } setup_operations(programs[0]); setup_operations(programs[1]); setup_operations(programs[2]); images.resize(population_size); images_red.resize(population_size); images_green.resize(population_size); images_blue.resize(population_size); static auto sel = select_tournament_t{}; for (const auto program : programs) program->generate_initial_population(program->get_typesystem().get_type().id()); programs[0]->setup_generational_evaluation(fitness_func<0>, sel, sel, sel); programs[1]->setup_generational_evaluation(fitness_func<1>, sel, sel, sel); programs[2]->setup_generational_evaluation(fitness_func<2>, sel, sel, sel); } void run_step() { BLT_TRACE("------------\\{Begin Generation {}}------------", programs[0]->get_current_generation()); BLT_TRACE("Creating next generation"); for (const auto program : programs) program->create_next_generation(); BLT_TRACE("Move to next generation"); for (const auto program : programs) program->next_generation(); BLT_TRACE("Evaluate Fitness"); for (const auto program : programs) program->evaluate_fitness(); for (const auto [i, program] : blt::enumerate(programs)) { auto& cur = program->get_current_pop(); auto mean = std::accumulate(cur.begin(), cur.end(), 0.0, [](const double a, const individual_t& b) { return a + b.fitness.adjusted_fitness; }) / static_cast(cur.get_individuals().size()); auto variance = std::accumulate(cur.begin(), cur.end(), 0.0, [mean](const double a, const individual_t& b) { const auto amount = (b.fitness.adjusted_fitness - mean); return a + amount * amount; }) / static_cast(cur.get_individuals().size()); mean_vec[i].push_back(static_cast(mean)); variance_vec[i].push_back(static_cast(variance)); if (i == 0) BLT_TRACE("Channel Red"); else if (i == 1) BLT_TRACE("Channel Green"); else BLT_TRACE("Channel Blue"); BLT_TRACE(" Program has variance of {}", variance); if (program->get_random().choice()) { const auto amount = static_cast(static_cast(cur.get_individuals().size()) * 0.1); for (size_t j = 0; j < amount; j++) { static grow_generator_t gen; const auto index = cur.get_individuals().size() - 1 - j; cur.get_individuals()[index].tree.regen(gen, program->get_typesystem().get_type().id(), 6, 10); } } program->evaluate_fitness(); } for (const auto [i, program] : blt::enumerate(programs)) { const auto& stats = program->get_population_stats(); if (i == 0) BLT_TRACE("Channel Red"); else if (i == 1) BLT_TRACE("Channel Green"); else BLT_TRACE("Channel Blue"); const auto avg = stats.average_fitness.load(std::memory_order_relaxed); const auto best = stats.best_fitness.load(std::memory_order_relaxed); const auto worst = stats.worst_fitness.load(std::memory_order_relaxed); const auto overall = stats.overall_fitness.load(std::memory_order_relaxed); average_fitness.push_back(static_cast(avg)); best_fitness.push_back(static_cast(best)); worst_fitness.push_back(static_cast(worst)); overall_fitness.push_back(static_cast(overall)); BLT_TRACE("\tAvg Fit: {:0.6f}, Best Fit: {:0.6f}, Worst Fit: {:0.6f}, Overall Fit: {:0.6f}", stats.average_fitness.load(std::memory_order_relaxed), stats.best_fitness.load(std::memory_order_relaxed), stats.worst_fitness.load(std::memory_order_relaxed), stats.overall_fitness.load(std::memory_order_relaxed)); } BLT_TRACE("----------------------------------------------"); } bool should_terminate() { return programs[0]->should_terminate() || programs[1]->should_terminate() || programs[2]->should_terminate(); } std::array& get_image(const blt::size_t index) { for (const auto& [i, image_red, image_green, image_blue] : blt::zip(images_red[index], images_green[index], images_blue[index]).enumerate(). flatten()) { images[index][i * 3] = image_red; images[index][i * 3 + 1] = image_green; images[index][i * 3 + 2] = image_blue; } return images[index]; } void cleanup() { for (const auto program : programs) delete program; } const std::array& get_reference_image() { return reference_image; } std::array to_gl_image(const std::array& image) { std::array image_data{}; for (blt::size_t x = 0; x < IMAGE_DIMENSIONS; ++x) { for (blt::size_t y = 0; y < IMAGE_DIMENSIONS; ++y) { // image_data[(x * IMAGE_DIMENSIONS + y) * 3 + 0] = std::pow(image[0].get(x, y), 1.0f / 2.2f); // image_data[(x * IMAGE_DIMENSIONS + y) * 3 + 1] = std::pow(image[1].get(x, y), 1.0f / 2.2f); // image_data[(x * IMAGE_DIMENSIONS + y) * 3 + 2] = std::pow(image[2].get(x, y), 1.0f / 2.2f); image_data[(x * IMAGE_DIMENSIONS + y) * 3 + 0] = image[0].get(x, y); image_data[(x * IMAGE_DIMENSIONS + y) * 3 + 1] = image[1].get(x, y); image_data[(x * IMAGE_DIMENSIONS + y) * 3 + 2] = image[2].get(x, y); } } return image_data; } blt::u32 get_generation() { return programs[0]->get_current_generation(); } void set_population_size(const blt::u32 size) { if (size > images.size()) images.resize(size); if (size > images_red.size()) images_red.resize(size); if (size > images_green.size()) images_green.resize(size); if (size > images_blue.size()) images_blue.resize(size); config.set_pop_size(size); for (const auto program : programs) program->set_config(config); reset_programs(); } void reset_programs() { for (const auto program : programs) program->reset_program(program->get_typesystem().get_type().id()); } std::array get_best_image_index() { std::array best_index{}; for (const auto [slot, program] : blt::zip(best_index, programs)) slot = program->get_best_indexes<1>()[0]; return best_index; } void regenerate_image(blt::size_t index, float& image_storage, blt::i32 width, blt::i32 height) {} std::tuple&, const std::vector&, const std::vector&, const std::vector&> get_fitness_history() { return {average_fitness, best_fitness, worst_fitness, overall_fitness}; } std::array get_populations() { return {&programs[0]->get_current_pop(), &programs[1]->get_current_pop(), &programs[2]->get_current_pop()}; } void set_use_gamma_correction(bool use) { use_gamma_correction = true; } std::pair, 3>&, std::array, 3>&> get_mean_and_variance() { return {mean_vec, variance_vec}; }