From 5352406cbf779e6f1ff3b8fbdc4b778a22dc2ba1 Mon Sep 17 00:00:00 2001 From: Brett Date: Sat, 20 Jul 2024 00:38:39 -0400 Subject: [PATCH] nyah --- CMakeLists.txt | 2 +- include/config.h | 49 ++++++++ include/helper.h | 44 +++++++ include/images.h | 55 +++++++++ lib/blt-gp | 2 +- src/main.cpp | 312 +++++++++++++++++++++++------------------------ 6 files changed, 306 insertions(+), 158 deletions(-) create mode 100644 include/config.h create mode 100644 include/helper.h create mode 100644 include/images.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 4f55395..7152675 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.25) -project(image-gp-6 VERSION 0.0.12) +project(image-gp-6 VERSION 0.0.13) include(FetchContent) diff --git a/include/config.h b/include/config.h new file mode 100644 index 0000000..fe3760e --- /dev/null +++ b/include/config.h @@ -0,0 +1,49 @@ +#pragma once +/* + * Copyright (C) 2024 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 . + */ + +#ifndef IMAGE_GP_6_CONFIG_H +#define IMAGE_GP_6_CONFIG_H + +constexpr size_t log2(size_t n) // NOLINT +{ + return ((n < 2) ? 1 : 1 + log2(n / 2)); +} + +static const blt::u64 SEED = std::random_device()(); +static constexpr blt::size_t IMAGE_SIZE = 128; +static constexpr blt::size_t IMAGE_PADDING = 16; +static constexpr blt::size_t POP_SIZE = 64; +static constexpr blt::size_t CHANNELS = 3; +static constexpr blt::size_t DATA_SIZE = IMAGE_SIZE * IMAGE_SIZE; +static constexpr blt::size_t DATA_CHANNELS_SIZE = DATA_SIZE * CHANNELS; +static constexpr blt::size_t BOX_COUNT = static_cast(log2(IMAGE_SIZE / 2)); +static constexpr float THRESHOLD = 0.3; +static constexpr auto load_image = "../029a_-_Survival_of_the_Idiots_349.jpg"; + +blt::gp::prog_config_t config = blt::gp::prog_config_t() + .set_initial_min_tree_size(4) + .set_initial_max_tree_size(8) + .set_elite_count(2) + .set_max_generations(50) + .set_mutation_chance(1.0) + .set_crossover_chance(1.0) + .set_reproduction_chance(0.5) + .set_pop_size(POP_SIZE) + .set_thread_count(16); + +#endif //IMAGE_GP_6_CONFIG_H diff --git a/include/helper.h b/include/helper.h new file mode 100644 index 0000000..2842b49 --- /dev/null +++ b/include/helper.h @@ -0,0 +1,44 @@ +#pragma once +/* + * Copyright (C) 2024 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 . + */ + +#ifndef IMAGE_GP_6_HELPER_H +#define IMAGE_GP_6_HELPER_H + +template +constexpr static auto make_single(SINGLE_FUNC&& func) +{ + return [func](const full_image_t& a) { + full_image_t img{}; + for (blt::size_t i = 0; i < DATA_CHANNELS_SIZE; i++) + img.rgb_data[i] = func(a.rgb_data[i]); + return img; + }; +} + +template +constexpr static auto make_double(DOUBLE_FUNC&& func) +{ + return [func](const full_image_t& a, const full_image_t& b) { + full_image_t img{}; + for (blt::size_t i = 0; i < DATA_CHANNELS_SIZE; i++) + img.rgb_data[i] = func(a.rgb_data[i], b.rgb_data[i]); + return img; + }; +} + +#endif //IMAGE_GP_6_HELPER_H diff --git a/include/images.h b/include/images.h new file mode 100644 index 0000000..5e39812 --- /dev/null +++ b/include/images.h @@ -0,0 +1,55 @@ +#pragma once +/* + * Copyright (C) 2024 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 . + */ + +#ifndef IMAGE_GP_6_IMAGES_H +#define IMAGE_GP_6_IMAGES_H + +#include + +struct full_image_t +{ + float rgb_data[DATA_SIZE * CHANNELS]{}; + + full_image_t() + { + for (auto& v : rgb_data) + v = 0; + } + + void load(const std::string& path) + { + int width, height, channels; + auto data = stbi_loadf(path.c_str(), &width, &height, &channels, CHANNELS); + + stbir_resize_float_linear(data, width, height, 0, rgb_data, IMAGE_SIZE, IMAGE_SIZE, 0, static_cast(CHANNELS)); + + stbi_image_free(data); + } + + void save(const std::string&) + { + //stbi_write_png(str.c_str(), IMAGE_SIZE, IMAGE_SIZE, CHANNELS, rgb_data, 0); + } + + friend std::ostream& operator<<(std::ostream& str, const full_image_t&) + { + return str; + } +}; + +#endif //IMAGE_GP_6_IMAGES_H diff --git a/lib/blt-gp b/lib/blt-gp index 8e5a3f3..5d72923 160000 --- a/lib/blt-gp +++ b/lib/blt-gp @@ -1 +1 @@ -Subproject commit 8e5a3f3b7c52a361a47199108be082730b1aeddd +Subproject commit 5d72923998fd59cf76a7e777642a9c34e76bfb3d diff --git a/src/main.cpp b/src/main.cpp index 618c0b0..dc0cd99 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -36,22 +36,8 @@ #include "opencv2/imgproc.hpp" #include #include "slr.h" - -constexpr size_t log2(size_t n) // NOLINT -{ - return ((n < 2) ? 1 : 1 + log2(n / 2)); -} - -static const blt::u64 SEED = std::random_device()(); -static constexpr blt::size_t IMAGE_SIZE = 128; -static constexpr blt::size_t IMAGE_PADDING = 16; -static constexpr blt::size_t POP_SIZE = 64; -static constexpr blt::size_t CHANNELS = 3; -static constexpr blt::size_t DATA_SIZE = IMAGE_SIZE * IMAGE_SIZE; -static constexpr blt::size_t DATA_CHANNELS_SIZE = DATA_SIZE * CHANNELS; -static constexpr blt::size_t BOX_COUNT = static_cast(log2(IMAGE_SIZE / 2)); -static constexpr float THRESHOLD = 0.3; -static constexpr auto load_image = "../silly.png"; +#include +#include blt::gfx::matrix_state_manager global_matrices; blt::gfx::resource_manager resources; @@ -86,95 +72,32 @@ inline blt::size_t get_index(blt::size_t x, blt::size_t y) return y * IMAGE_SIZE + x; } -struct full_image_t -{ - float rgb_data[DATA_SIZE * CHANNELS]{}; - - full_image_t() - { - for (auto& v : rgb_data) - v = 0; - } - - void load(const std::string& path) - { - int width, height, channels; - auto data = stbi_loadf(path.c_str(), &width, &height, &channels, CHANNELS); - - stbir_resize_float_linear(data, width, height, 0, rgb_data, IMAGE_SIZE, IMAGE_SIZE, 0, static_cast(CHANNELS)); - - stbi_image_free(data); - } - - void save(const std::string&) - { - //stbi_write_png(str.c_str(), IMAGE_SIZE, IMAGE_SIZE, CHANNELS, rgb_data, 0); - } - - friend std::ostream& operator<<(std::ostream& str, const full_image_t&) - { - return str; - } -}; - std::array generation_images; full_image_t base_image; blt::size_t last_run = 0; -blt::i32 time_between_runs = 100; +blt::i32 time_between_runs = 16; bool is_running = false; -blt::gp::prog_config_t config = blt::gp::prog_config_t() - .set_initial_min_tree_size(4) - .set_initial_max_tree_size(8) - .set_elite_count(2) - .set_max_generations(50) - .set_mutation_chance(1.0) - .set_crossover_chance(1.0) - .set_reproduction_chance(0.5) - .set_pop_size(POP_SIZE) - .set_thread_count(16); - blt::gp::type_provider type_system; blt::gp::gp_program program{type_system, SEED, config}; +std::unique_ptr gp_thread = nullptr; -template -constexpr static auto make_single(SINGLE_FUNC&& func) -{ - return [func](const full_image_t& a) { - full_image_t img{}; - for (blt::size_t i = 0; i < DATA_CHANNELS_SIZE; i++) - img.rgb_data[i] = func(a.rgb_data[i]); - return img; - }; -} - -blt::gp::operation_t add([](const full_image_t& a, const full_image_t& b) { - full_image_t img{}; - for (blt::size_t i = 0; i < DATA_CHANNELS_SIZE; i++) - img.rgb_data[i] = a.rgb_data[i] + b.rgb_data[i]; - return img; -}, "add"); -blt::gp::operation_t sub([](const full_image_t& a, const full_image_t& b) { - full_image_t img{}; - for (blt::size_t i = 0; i < DATA_CHANNELS_SIZE; i++) - img.rgb_data[i] = a.rgb_data[i] - b.rgb_data[i]; - return img; -}, "sub"); -blt::gp::operation_t mul([](const full_image_t& a, const full_image_t& b) { - full_image_t img{}; - for (blt::size_t i = 0; i < DATA_CHANNELS_SIZE; i++) - img.rgb_data[i] = a.rgb_data[i] * b.rgb_data[i]; - return img; -}, "mul"); +blt::gp::operation_t add(make_double(std::plus()), "add"); +blt::gp::operation_t sub(make_double(std::minus()), "sub"); +blt::gp::operation_t mul(make_double(std::multiplies()), "mul"); blt::gp::operation_t pro_div([](const full_image_t& a, const full_image_t& b) { full_image_t img{}; for (blt::size_t i = 0; i < DATA_CHANNELS_SIZE; i++) img.rgb_data[i] = b.rgb_data[i] == 0 ? 0 : (a.rgb_data[i] / b.rgb_data[i]); return img; }, "div"); -blt::gp::operation_t op_sin(make_single((float (*)(float)) &std::sin), "sin"); -blt::gp::operation_t op_cos(make_single((float (*)(float)) &std::cos), "cos"); +blt::gp::operation_t op_sin(make_single([](float a) { + return (std::sin(a) + 1.0f) / 2.0f; +}), "sin"); +blt::gp::operation_t op_cos(make_single([](float a) { + return (std::cos(a) + 1.0f) / 2.0f; +}), "cos"); blt::gp::operation_t op_atan(make_single((float (*)(float)) &std::atan), "atan"); blt::gp::operation_t op_exp(make_single((float (*)(float)) &std::exp), "exp"); blt::gp::operation_t op_abs(make_single((float (*)(float)) &std::abs), "abs"); @@ -215,6 +138,50 @@ blt::gp::operation_t bitwise_xor([](const full_image_t& a, const full_image_t& b return img; }, "xor"); +blt::gp::operation_t dissolve([](const full_image_t& a, const full_image_t& b) { + using blt::mem::type_cast; + full_image_t img{}; + for (blt::size_t i = 0; i < DATA_CHANNELS_SIZE; i++) + { + auto diff = (a.rgb_data[i] - b.rgb_data[i]) / 2.0f; + img.rgb_data[i] = a.rgb_data[i] + diff; + } + return img; +}, "dissolve"); + +blt::gp::operation_t hsv_to_rgb([](const full_image_t& a) { + using blt::mem::type_cast; + full_image_t img{}; + for (blt::size_t i = 0; i < DATA_SIZE; i++) + { + auto h = static_cast(a.rgb_data[i * CHANNELS + 0]) % 360; + auto s = a.rgb_data[i * CHANNELS + 1]; + auto v = a.rgb_data[i * CHANNELS + 2]; + auto c = v * s; + auto x = c * static_cast(1 - std::abs(((h / 60) % 2) - 1)); + auto m = v - c; + + blt::vec3 rgb; + if (h >= 0 && h < 60) + rgb = {c, x, 0.0f}; + else if (h >= 60 && h < 120) + rgb = {x, c, 0.0f}; + else if (h >= 120 && h < 180) + rgb = {0.0f, c, x}; + else if (h >= 180 && h < 240) + rgb = {0.0f, x, c}; + else if (h >= 240 && h < 300) + rgb = {x, 0.0f, c}; + else if (h >= 300 && h < 360) + rgb = {c, 0.0f, x}; + + img.rgb_data[i * CHANNELS] = rgb.x() + m; + img.rgb_data[i * CHANNELS + 1] = rgb.y() + m; + img.rgb_data[i * CHANNELS + 2] = rgb.z() + m; + } + return img; +}, "hsv"); + blt::gp::operation_t lit([]() { full_image_t img{}; auto r = program.get_random().get_float(0.0f, 1.0f); @@ -335,7 +302,7 @@ constexpr float compare_values(float a, float b) struct fractal_stats { - blt::f64 box_size, num_boxes, xy, x2, y2; + blt::f64 r, g, b, total, combined; }; bool in_box(full_image_t& image, blt::size_t channel, blt::size_t box_size, blt::size_t i, blt::size_t j) @@ -353,49 +320,69 @@ bool in_box(full_image_t& image, blt::size_t channel, blt::size_t box_size, blt: return false; } -blt::f64 get_fractal_value(full_image_t& image, blt::size_t channel) +fractal_stats get_fractal_value(full_image_t& image) { - std::array box_data{}; + fractal_stats stats{}; std::array x_data{}; - std::array y_data{}; + std::array boxes_r{}; + std::array boxes_g{}; + std::array boxes_b{}; + std::array boxes_total{}; + std::array boxes_combined{}; for (blt::size_t box_size = IMAGE_SIZE / 2; box_size > 1; box_size /= 2) { - blt::ptrdiff_t num_boxes = 0; + blt::ptrdiff_t num_boxes_r = 0; + blt::ptrdiff_t num_boxes_g = 0; + blt::ptrdiff_t num_boxes_b = 0; + blt::ptrdiff_t num_boxes_total = 0; + blt::ptrdiff_t num_boxes_combined = 0; for (blt::size_t i = 0; i < IMAGE_SIZE; i += box_size) { for (blt::size_t j = 0; j < IMAGE_SIZE; j += box_size) { - if (in_box(image, channel, box_size, i, j)) - num_boxes++; + auto r = in_box(image, 0, box_size, i, j); + auto g = in_box(image, 1, box_size, i, j); + auto b = in_box(image, 2, box_size, i, j); + + if (r) + num_boxes_r++; + if (g) + num_boxes_g++; + if (b) + num_boxes_b++; + if (r && g && b) + num_boxes_combined++; + if (r || g || b) + num_boxes_total++; } } auto x = static_cast(std::log2(box_size)); - auto y = static_cast(num_boxes == 0 ? 0 : std::log2(num_boxes)); - //auto y = static_cast(num_boxes); - box_data[static_cast(std::log2(box_size)) - 1] = {x, y, x * y, x * x, y * y}; + x_data[static_cast(std::log2(box_size)) - 1] = x; - y_data[static_cast(std::log2(box_size)) - 1] = y; - //BLT_DEBUG("%lf vs %lf", x, y); + boxes_r[static_cast(std::log2(box_size)) - 1] = static_cast(num_boxes_r == 0 ? 0 : std::log2(num_boxes_r)); + boxes_g[static_cast(std::log2(box_size)) - 1] = static_cast(num_boxes_g == 0 ? 0 : std::log2(num_boxes_g)); + boxes_b[static_cast(std::log2(box_size)) - 1] = static_cast(num_boxes_b == 0 ? 0 : std::log2(num_boxes_b)); + boxes_total[static_cast(std::log2(box_size)) - 1] = static_cast(num_boxes_combined == 0 ? 0 : std::log2( + num_boxes_combined)); + boxes_combined[static_cast(std::log2(box_size)) - 1] = static_cast(num_boxes_total == 0 ? 0 : std::log2( + num_boxes_total)); } -// fractal_stats total{}; -// for (const auto& b : box_data) -// { -// total.box_size += b.box_size; -// total.num_boxes += b.num_boxes; -// total.xy += b.xy; -// total.x2 += b.x2; -// total.y2 += b.y2; -// } -// -// auto n = static_cast(BOX_COUNT); -// auto b0 = ((total.num_boxes * total.x2) - (total.box_size * total.xy)) / ((n * total.x2) - (total.box_size * total.box_size)); -// auto b1 = ((n * total.xy) - (total.box_size * total.num_boxes)) / ((n * total.x2) - (total.box_size * total.box_size)); -// -// return b1; - slr count{x_data, y_data}; + slr count_r{x_data, boxes_r}; + slr count_g{x_data, boxes_g}; + slr count_b{x_data, boxes_b}; + slr count_total{x_data, boxes_total}; + slr count_combined{x_data, boxes_combined}; + +#define FUNC(f) (-f) + stats.r = FUNC(count_r.beta); + stats.g = FUNC(count_g.beta); + stats.b = FUNC(count_b.beta); + stats.total = FUNC(count_total.beta); + stats.combined = FUNC(count_combined.beta); +#undef FUNC - return count.beta; + return stats; } constexpr auto create_fitness_function() @@ -407,21 +394,17 @@ constexpr auto create_fitness_function() fitness.raw_fitness = 0; for (blt::size_t i = 0; i < DATA_CHANNELS_SIZE; i++) fitness.raw_fitness += compare_values(v.rgb_data[i], base_image.rgb_data[i]); - + fitness.raw_fitness /= (IMAGE_SIZE * IMAGE_SIZE); + auto raw = get_fractal_value(v); + auto fit = std::max(0.0, 1.0 - std::abs(1.35 - raw.combined)); + auto fit2 = std::max(0.0, 1.0 - std::abs(1.35 - raw.total)); + //BLT_DEBUG("Fitness %lf %lf %lf || %lf => %lf (fit: %lf)", raw.r, raw.g, raw.b, raw.total, raw.combined, fit); + if (std::isnan(raw.total) || std::isnan(raw.combined)) + fitness.raw_fitness += 400; + else + fitness.raw_fitness += raw.total + raw.combined + 1.0; - for (blt::size_t channel = 0; channel < CHANNELS; channel++) - { - auto raw = -get_fractal_value(v, channel); - auto fit = 1.0 - std::max(0.0, 1.0 - std::abs(1.35 - raw)); - BLT_DEBUG("Fitness %lf (raw: %lf) for channel %lu", fit, raw, channel); - if (std::isnan(raw)) - fitness.raw_fitness += 400; - else - fitness.raw_fitness += raw; - } - - //BLT_TRACE("Raw fitness: %lf for %ld", fitness.raw_fitness, index); fitness.standardized_fitness = fitness.raw_fitness; fitness.adjusted_fitness = (1.0 / (1.0 + fitness.standardized_fitness)); }; @@ -430,16 +413,16 @@ constexpr auto create_fitness_function() void execute_generation() { BLT_TRACE("------------{Begin Generation %ld}------------", program.get_current_generation()); - BLT_TRACE("Evaluate Fitness"); - BLT_START_INTERVAL("Image Test", "Fitness"); - program.evaluate_fitness(); - BLT_END_INTERVAL("Image Test", "Fitness"); BLT_START_INTERVAL("Image Test", "Gen"); auto sel = blt::gp::select_tournament_t{}; program.create_next_generation(sel, sel, sel); BLT_END_INTERVAL("Image Test", "Gen"); BLT_TRACE("Move to next generation"); program.next_generation(); + BLT_TRACE("Evaluate Fitness"); + BLT_START_INTERVAL("Image Test", "Fitness"); + program.evaluate_fitness(); + BLT_END_INTERVAL("Image Test", "Fitness"); BLT_TRACE("----------------------------------------------"); std::cout << std::endl; } @@ -454,6 +437,26 @@ void print_stats() BLT_INFO("Overall fitness: %lf", stats.overall_fitness.load()); } +std::atomic_bool run_generation = false; + +void run_gp() +{ + BLT_DEBUG("Generate Initial Population"); + static constexpr auto fitness_func = create_fitness_function(); + program.generate_population(type_system.get_type().id(), fitness_func, true); + + while (!program.should_thread_terminate()) + { + if ((run_generation || is_running) && (blt::system::getCurrentTimeMilliseconds() - last_run) > static_cast(time_between_runs)) + { + execute_generation(); + print_stats(); + run_generation = false; + last_run = blt::system::getCurrentTimeMilliseconds(); + } + } +} + void init(const blt::gfx::window_data&) { using namespace blt::gfx; @@ -488,6 +491,8 @@ void init(const blt::gfx::window_data&) builder.add_operator(bitwise_and); builder.add_operator(bitwise_or); builder.add_operator(bitwise_xor); + builder.add_operator(dissolve); + builder.add_operator(hsv_to_rgb); builder.add_operator(lit, true); builder.add_operator(random_val); @@ -500,13 +505,11 @@ void init(const blt::gfx::window_data&) program.set_operations(builder.build()); - BLT_DEBUG("Generate Initial Population"); - static constexpr auto fitness_func = create_fitness_function(); - program.generate_population(type_system.get_type().id(), fitness_func, true); - global_matrices.create_internals(); resources.load_resources(); renderer_2d.create(); + + gp_thread = std::make_unique(run_gp); } void update(const blt::gfx::window_data& data) @@ -522,8 +525,7 @@ void update(const blt::gfx::window_data& data) ImGui::Button("Run Generation"); if (ImGui::IsItemClicked() && !is_running) { - execute_generation(); - print_stats(); + run_generation = true; } ImGui::InputInt("Time Between Runs", &time_between_runs); ImGui::Checkbox("Run", &is_running); @@ -536,14 +538,6 @@ void update(const blt::gfx::window_data& data) ImGui::End(); } - if (is_running && (blt::system::getCurrentTimeMilliseconds() - last_run) > static_cast(time_between_runs)) - { - execute_generation(); - print_stats(); - - last_run = blt::system::getCurrentTimeMilliseconds(); - } - const auto mouse_pos = blt::make_vec2(blt::gfx::calculateRay2D(data.width, data.height, global_matrices.getScale2D(), global_matrices.getView2D(), global_matrices.getOrtho())); @@ -571,7 +565,10 @@ void update(const blt::gfx::window_data& data) if (blt::gfx::mousePressedLastFrame()) { - program.get_current_pop().get_individuals()[i].tree.print(program, std::cout, false); + auto& ind = program.get_current_pop().get_individuals()[i]; + std::cout << "Fitness: " << ind.fitness.adjusted_fitness << " " << ind.fitness.raw_fitness << " "; + ind.tree.print(program, std::cout, false); + std::cout << std::endl; } // if (blt::gfx::mousePressedLastFrame()) @@ -617,13 +614,16 @@ int main() BLT_END_INTERVAL("Image Test", "Main"); base_image.save("input.png"); - for (blt::size_t i = 0; i < CHANNELS; i++) - { - auto v = -get_fractal_value(base_image, i); - BLT_INFO("Base image values per channel: %lf", v); - } + + auto v = get_fractal_value(base_image); + BLT_INFO("Base image values per channel: %lf", v.total); BLT_PRINT_PROFILE("Image Test", blt::PRINT_CYCLES | blt::PRINT_THREAD | blt::PRINT_WALL); + is_running = false; + program.kill(); + if (gp_thread->joinable()) + gp_thread->join(); + return 0; } \ No newline at end of file