diff --git a/CMakeLists.txt b/CMakeLists.txt index 623fc33..4f55395 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.25) -project(image-gp-6 VERSION 0.0.11) +project(image-gp-6 VERSION 0.0.12) include(FetchContent) diff --git a/GSab4SWWcAA1TNR.png b/GSab4SWWcAA1TNR.png new file mode 100644 index 0000000..cd122b7 Binary files /dev/null and b/GSab4SWWcAA1TNR.png differ diff --git a/include/slr.h b/include/slr.h new file mode 100644 index 0000000..a0cedbc --- /dev/null +++ b/include/slr.h @@ -0,0 +1,97 @@ +#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_SLR_H +#define IMAGE_GP_6_SLR_H + +template +float mean(const std::array& data) +{ + T x = 0; + for (blt::size_t n = 0; n < sample_size; n++) + { + x = x + data[n]; + } + x = x / sample_size; + return x; +} + +// https://github.com/georgemaier/simple-linear-regression/blob/master/slr.cpp +template +class slr +{ + private: + T WN1 = 0, WN2 = 0, WN3 = 0, WN4 = 0, Sy = 0, Sx = 0; + + public: + T r = 0, rsquared = 0, alpha = 0, beta = 0, x = 0, y = 0; + T yhat = 0, ybar = 0, xbar = 0; + T SSR = 0, SSE = 0, SST = 0; + T residualSE = 0, residualmax = 0, residualmin = 0, residualmean = 0, t = 0; + T SEBeta = 0, sample = 0, residuals[sample_size]{}; + + slr(const std::array& datax, const std::array& datay) + { + //This is the main regression function that is called when a new SLR object is created. + + //Calculate means + sample = sample_size; + xbar = mean(datax); + ybar = mean(datay); + + //Calculate r correlation + for (blt::size_t n = 0; n < sample_size; ++n) + { + WN1 += (datax[n] - xbar) * (datay[n] - ybar); + WN2 += pow((datax[n] - xbar), 2); + WN3 += pow((datay[n] - ybar), 2); + } + WN4 = WN2 * WN3; + r = WN1 / (std::sqrt(WN4)); + + //Calculate alpha and beta + Sy = std::sqrt(WN3 / (sample_size - 1)); + Sx = std::sqrt(WN2 / (sample_size - 1)); + beta = r * (Sy / Sx); + alpha = ybar - beta * xbar; + + //Calculate SSR, SSE, R-Squared, residuals + for (blt::size_t n = 0; n < sample_size; n++) + { + yhat = alpha + (beta * datax[n]); + SSE += std::pow((yhat - ybar), 2); + SSR += std::pow((datay[n] - yhat), 2); + residuals[n] = (datay[n] - yhat); + if (residuals[n] > residualmax) + residualmax = residuals[n]; + if (residuals[n] < residualmin) + residualmin = residuals[n]; + residualmean += std::fabs(residuals[n]); + } + residualmean = (residualmean / sample_size); + SST = SSR + SSE; + rsquared = SSE / SST; //Can also be obtained by r ^ 2 for simple regression (i.e. 1 independent variable) + + //Calculate T-test for Beta + residualSE = std::sqrt(SSR / (sample_size - 2)); + SEBeta = (residualSE / (Sx * std::sqrt(sample_size - 1))); + t = beta / SEBeta; + } +}; + +#endif //IMAGE_GP_6_SLR_H diff --git a/lib/blt-gp b/lib/blt-gp index c7bb4a4..8e5a3f3 160000 --- a/lib/blt-gp +++ b/lib/blt-gp @@ -1 +1 @@ -Subproject commit c7bb4a434b25d3c918cc7908e0b527aa0101b73d +Subproject commit 8e5a3f3b7c52a361a47199108be082730b1aeddd diff --git a/src/main.cpp b/src/main.cpp index 8e7bf47..618c0b0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -35,14 +35,23 @@ #include "opencv2/imgcodecs.hpp" #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 long IMAGE_SIZE = 128; -static constexpr long IMAGE_PADDING = 16; -static constexpr long POP_SIZE = 64; +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"; blt::gfx::matrix_state_manager global_matrices; blt::gfx::resource_manager resources; @@ -72,11 +81,17 @@ inline context get_pop_ctx(blt::size_t i) return ctx; } +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() { + full_image_t() + { for (auto& v : rgb_data) v = 0; } @@ -110,19 +125,30 @@ blt::i32 time_between_runs = 100; bool is_running = false; blt::gp::prog_config_t config = blt::gp::prog_config_t() - .set_initial_min_tree_size(2) - .set_initial_max_tree_size(6) - .set_elite_count(1) + .set_initial_min_tree_size(4) + .set_initial_max_tree_size(8) + .set_elite_count(2) .set_max_generations(50) - .set_mutation_chance(0.8) + .set_mutation_chance(1.0) .set_crossover_chance(1.0) - .set_reproduction_chance(0) + .set_reproduction_chance(0.5) .set_pop_size(POP_SIZE) - .set_thread_count(0); + .set_thread_count(16); blt::gp::type_provider type_system; blt::gp::gp_program program{type_system, SEED, config}; +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++) @@ -147,30 +173,12 @@ blt::gp::operation_t pro_div([](const full_image_t& a, const full_image_t& b) { 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([](const full_image_t& a) { - full_image_t img{}; - for (blt::size_t i = 0; i < DATA_CHANNELS_SIZE; i++) - img.rgb_data[i] = std::sin(a.rgb_data[i]); - return img; -}, "sin"); -blt::gp::operation_t op_cos([](const full_image_t& a) { - full_image_t img{}; - for (blt::size_t i = 0; i < DATA_CHANNELS_SIZE; i++) - img.rgb_data[i] = std::cos(a.rgb_data[i]); - return img; -}, "cos"); -blt::gp::operation_t op_exp([](const full_image_t& a) { - full_image_t img{}; - for (blt::size_t i = 0; i < DATA_CHANNELS_SIZE; i++) - img.rgb_data[i] = std::exp(a.rgb_data[i]); - return img; -}, "exp"); -blt::gp::operation_t op_log([](const full_image_t& a) { - full_image_t img{}; - for (blt::size_t i = 0; i < DATA_CHANNELS_SIZE; i++) - img.rgb_data[i] = a.rgb_data[i] == 0 ? 0 : std::log(a.rgb_data[i]); - return img; -}, "log"); +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_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"); +blt::gp::operation_t op_log(make_single((float (*)(float)) &std::log), "log"); blt::gp::operation_t op_v_mod([](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++) @@ -209,8 +217,15 @@ blt::gp::operation_t bitwise_xor([](const full_image_t& a, const full_image_t& b blt::gp::operation_t lit([]() { full_image_t img{}; - for (auto& i : img.rgb_data) - i = program.get_random().get_float(0.0f, 1.0f); + auto r = program.get_random().get_float(0.0f, 1.0f); + auto g = program.get_random().get_float(0.0f, 1.0f); + auto b = program.get_random().get_float(0.0f, 1.0f); + for (blt::size_t i = 0; i < DATA_SIZE; i++) + { + img.rgb_data[i * CHANNELS] = r; + img.rgb_data[i * CHANNELS + 1] = g; + img.rgb_data[i * CHANNELS + 2] = b; + } return img; }, "lit"); blt::gp::operation_t random_val([]() { @@ -309,6 +324,80 @@ static blt::gp::operation_t op_y_b([]() { return img; }, "y_b"); +constexpr float compare_values(float a, float b) +{ + if (std::isnan(a) || std::isnan(b) || std::isinf(a) || std::isinf(b)) + return IMAGE_SIZE; + auto dist = a - b; + //BLT_TRACE(std::sqrt(dist * dist)); + return std::sqrt(dist * dist); +} + +struct fractal_stats +{ + blt::f64 box_size, num_boxes, xy, x2, y2; +}; + +bool in_box(full_image_t& image, blt::size_t channel, blt::size_t box_size, blt::size_t i, blt::size_t j) +{ + // TODO: this could be made better by starting from the smallest boxes, moving upwards and using the last set of boxes + // instead of pixels, since they contain already computed information about if a box is in foam + for (blt::size_t x = i; x < i + box_size; x++) + { + for (blt::size_t y = j; y < j + box_size; y++) + { + if (image.rgb_data[get_index(x, y) * CHANNELS + channel] > THRESHOLD) + return true; + } + } + return false; +} + +blt::f64 get_fractal_value(full_image_t& image, blt::size_t channel) +{ + std::array box_data{}; + std::array x_data{}; + std::array y_data{}; + for (blt::size_t box_size = IMAGE_SIZE / 2; box_size > 1; box_size /= 2) + { + blt::ptrdiff_t num_boxes = 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 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); + } +// 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}; + + return count.beta; +} + constexpr auto create_fitness_function() { return [](blt::gp::tree_t& current_tree, blt::gp::fitness_t& fitness, blt::size_t index) { @@ -317,17 +406,23 @@ 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); + + for (blt::size_t channel = 0; channel < CHANNELS; channel++) { - auto base = base_image.rgb_data[i]; - auto set = v.rgb_data[i]; - if (std::isnan(set)) - set = 1 - base; - auto dist = set - base; - fitness.raw_fitness += std::sqrt(dist * dist); + 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 / IMAGE_SIZE; + fitness.standardized_fitness = fitness.raw_fitness; fitness.adjusted_fitness = (1.0 / (1.0 + fitness.standardized_fitness)); }; } @@ -370,13 +465,13 @@ void init(const blt::gfx::window_data&) BLT_INFO("Using Seed: %ld", SEED); BLT_START_INTERVAL("Image Test", "Main"); BLT_DEBUG("Setup Base Image"); - base_image.load("../my_pride_flag.png"); + base_image.load(load_image); BLT_DEBUG("Setup Types and Operators"); type_system.register_type(); blt::gp::operator_builder builder{type_system}; - builder.add_operator(perlin); + //builder.add_operator(perlin); builder.add_operator(perlin_terminal); builder.add_operator(add); @@ -385,8 +480,10 @@ void init(const blt::gfx::window_data&) builder.add_operator(pro_div); builder.add_operator(op_sin); builder.add_operator(op_cos); + builder.add_operator(op_atan); builder.add_operator(op_exp); builder.add_operator(op_log); + builder.add_operator(op_abs); builder.add_operator(op_v_mod); builder.add_operator(bitwise_and); builder.add_operator(bitwise_or); @@ -471,6 +568,11 @@ void update(const blt::gfx::window_data& data) if (io.WantCaptureMouse) continue; + + if (blt::gfx::mousePressedLastFrame()) + { + program.get_current_pop().get_individuals()[i].tree.print(program, std::cout, false); + } // if (blt::gfx::mousePressedLastFrame()) // { @@ -515,6 +617,11 @@ 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); + } BLT_PRINT_PROFILE("Image Test", blt::PRINT_CYCLES | blt::PRINT_THREAD | blt::PRINT_WALL);