diff --git a/CMakeLists.txt b/CMakeLists.txt index f24afe7..61b55d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,28 +1,36 @@ cmake_minimum_required(VERSION 3.25) -project(image-gp-6 VERSION 0.0.27) +project(image-gp-6 VERSION 0.0.28) include(FetchContent) option(ENABLE_ADDRSAN "Enable the address sanitizer" OFF) option(ENABLE_UBSAN "Enable the ub sanitizer" OFF) option(ENABLE_TSAN "Enable the thread data race sanitizer" OFF) +option(ENABLE_NATIVE_SSE "Enable native ASM generation" ON) +option(DEBUG_LEVEL "Enable debug features which prints extra information to the console, might slow processing down. [0, 3)" 0) + +if (${ENABLE_NATIVE_SSE}) + add_compile_options(-march=native) +endif () set(CMAKE_CXX_STANDARD 17) add_subdirectory(lib/blt-gp) add_subdirectory(lib/blt-graphics) -find_package( OpenCV REQUIRED ) +find_package(OpenCV REQUIRED) include_directories(include/) include_directories(lib/stb) include_directories(lib/ThreatExchange) -include_directories( ${OpenCV_INCLUDE_DIRS} ) +include_directories(${OpenCV_INCLUDE_DIRS}) file(GLOB_RECURSE PROJECT_BUILD_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp") add_executable(image-gp-6 ${PROJECT_BUILD_FILES}) +target_compile_definitions(image-gp-6 PRIVATE BLT_DEBUG_LEVEL=${DEBUG_LEVEL}) + target_compile_options(image-gp-6 PRIVATE -Wall -Wextra -Wpedantic -Wno-comment) target_link_options(image-gp-6 PRIVATE -Wall -Wextra -Wpedantic -Wno-comment) diff --git a/hannah.png b/hannah.png new file mode 100644 index 0000000..8c71b40 Binary files /dev/null and b/hannah.png differ diff --git a/include/config.h b/include/config.h index c1ee93d..38a0b77 100644 --- a/include/config.h +++ b/include/config.h @@ -36,6 +36,8 @@ inline constexpr blt::u64 u64_size_min = 1; inline constexpr blt::u64 u64_size_max = 9; inline constexpr float THRESHOLD = 0.5; inline constexpr auto load_image = "../GSab4SWWcAA1TNR.png"; +//inline constexpr auto load_image = "../hannah.png"; +inline constexpr blt::size_t MAX_ARG_C = 8; inline blt::gp::image_crossover_t image_crossover; inline blt::gp::image_mutation_t image_mutation; diff --git a/include/custom_transformer.h b/include/custom_transformer.h index c5a5830..bbfe034 100644 --- a/include/custom_transformer.h +++ b/include/custom_transformer.h @@ -63,8 +63,8 @@ namespace blt::gp enum class mutation_operator : blt::i32 { EXPRESSION, // Generate a new random expression - ADJUST, // adjust the value of the type. - FUNC, // Change node into a different function. Args will be generated / removed. + ADJUST, // adjust the value of the type. (if it is a function it will mutate it to a different one) + //FUNC, // Change node into a different function. Args will be generated / removed. SUB_FUNC, // subexpression becomes argument to new random function. Other args are generated. JUMP_FUNC, // subexpression becomes this new node. Other arguments discarded. COPY, // node can become copy of another subexpression. @@ -87,7 +87,7 @@ namespace blt::gp static constexpr std::array mutation_operator_chances = aggregate_array( 0.01, // EXPRESSION 0.11, // ADJUST - 0.05, // FUNC + //0.05, // FUNC 0.01, // SUB_FUNC 0.1, // JUMP_FUNC 0.05 // COPY diff --git a/include/image_operations.h b/include/image_operations.h index c3fa721..8552ab4 100644 --- a/include/image_operations.h +++ b/include/image_operations.h @@ -46,7 +46,7 @@ inline blt::gp::operation_t op_atan(make_single((float (*)(float)) &std::atan), inline blt::gp::operation_t op_exp(make_single((float (*)(float)) &std::exp), "exp"); inline blt::gp::operation_t op_abs(make_single((float (*)(float)) &std::abs), "abs"); inline blt::gp::operation_t op_log(make_single((float (*)(float)) &std::log), "log"); -inline blt::gp::operation_t op_round(make_single([](float f) {return std::round(f * 255.0f) / 255.0f;}), "round"); +inline blt::gp::operation_t op_round(make_single([](float f) { return std::round(f * 255.0f) / 255.0f; }), "round"); inline 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++) @@ -107,17 +107,17 @@ inline blt::gp::operation_t band_pass([](const full_image_t& a, float fa, float cv::Mat src(IMAGE_SIZE, IMAGE_SIZE, CV_32FC3, const_cast(a.rgb_data)); full_image_t img{}; std::memcpy(img.rgb_data, a.rgb_data, DATA_CHANNELS_SIZE * sizeof(float)); - + cv::Mat dst{IMAGE_SIZE, IMAGE_SIZE, CV_32FC3, img.rgb_data}; if (size % 2 == 0) size++; - + auto min = fa < fb ? fa : fb; auto max = fa > fb ? fa : fb; - + auto low = cv::getGaussianKernel(static_cast(size), min * ((static_cast(size) - 1) * 0.5 - 1) + 0.8, CV_32F); auto high = cv::getGaussianKernel(static_cast(size), max * ((static_cast(size) - 1) * 0.5 - 1) + 0.8, CV_32F); - + auto func = high - low; cv::Mat funcY; cv::transpose(func, funcY); @@ -177,13 +177,13 @@ inline blt::gp::operation_t median_blur([](const full_image_t& a, blt::u64 size) return img; }, "median_blur"); -inline blt::gp::operation_t bilateral_filter([](const full_image_t& a, blt::u64 size) { - cv::Mat src(IMAGE_SIZE, IMAGE_SIZE, CV_32FC3, const_cast(a.rgb_data)); +inline blt::gp::operation_t bilateral_filter([](const full_image_t& a, blt::u64 size, float color, float space) { full_image_t img{}; + cv::Mat src(IMAGE_SIZE, IMAGE_SIZE, CV_32FC3, const_cast(a.rgb_data)); cv::Mat dst{IMAGE_SIZE, IMAGE_SIZE, CV_32FC3, img.rgb_data}; if (size % 2 == 0) size++; - cv::bilateralFilter(src, dst, static_cast(size), static_cast(size) * 2, static_cast(size) / 2.0); + cv::bilateralFilter(src, dst, static_cast(size), color * static_cast(size) * 2.0, space * static_cast(size) * 2.0); return img; }, "bilateral_filter"); @@ -269,7 +269,7 @@ inline blt::gp::operation_t perlin_warped([](const full_image_t& u, const full_i for (blt::size_t i = 0; i < DATA_CHANNELS_SIZE; i++) { auto ctx = get_ctx(i); - img.rgb_data[i] = perlin_noise((ctx.x + + u.rgb_data[i]) / IMAGE_SIZE, (ctx.y + v.rgb_data[i]) / IMAGE_SIZE, + img.rgb_data[i] = perlin_noise((ctx.x + +u.rgb_data[i]) / IMAGE_SIZE, (ctx.y + v.rgb_data[i]) / IMAGE_SIZE, static_cast(i % CHANNELS) / CHANNELS); } return img; @@ -399,7 +399,10 @@ void create_image_operations(blt::gp::operator_builder& builder) builder.add_operator(hsv_to_rgb); builder.add_operator(gaussian_blur); builder.add_operator(median_blur); + // idk when it got enabled but this works on 4.10 +#if CV_VERSION_MAJOR >= 4 && CV_VERSION_MINOR >= 10 builder.add_operator(bilateral_filter); +#endif builder.add_operator(high_pass); bool state = false; diff --git a/lib/blt-gp b/lib/blt-gp index 64e8d71..9f7c647 160000 --- a/lib/blt-gp +++ b/lib/blt-gp @@ -1 +1 @@ -Subproject commit 64e8d71468c632d17e01750083f1e5ebd5236910 +Subproject commit 9f7c647952ab41a59d8b7d0a196fb3dc3147d23f diff --git a/src/custom_transformer.cpp b/src/custom_transformer.cpp index 9032ff6..9c84bd1 100644 --- a/src/custom_transformer.cpp +++ b/src/custom_transformer.cpp @@ -24,12 +24,20 @@ namespace blt::gp { + template + static blt::u8* get_thread_pointer_for_size(blt::size_t bytes) + { + static thread_local blt::expanding_buffer buffer; + if (bytes > buffer.size()) + buffer.resize(bytes); + return buffer.data(); + } + blt::expected image_crossover_t::apply(gp_program& program, const tree_t& p1, const tree_t& p2) { return crossover_t::apply(program, p1, p2); } - tree_t image_mutation_t::apply(gp_program& program, const tree_t& p) { // child tree @@ -44,9 +52,10 @@ namespace blt::gp { if (!program.get_random().choice(node_mutation_chance)) continue; + + // select an operator to apply auto selected_point = static_cast(mutation_operator::COPY); auto choice = program.get_random().get_double(); - for (const auto& [index, value] : blt::enumerate(mutation_operator_chances)) { if (index == 0) @@ -75,7 +84,188 @@ namespace blt::gp { // this is going to be evil >:3 const auto& node = ops[c_node]; - if (node.is_value) + if (!node.is_value) + { + BLT_TRACE("Running adjust on function"); + auto& current_func_info = program.get_operator_info(ops[c_node].id); + operator_id random_replacement = program.get_random().select( + program.get_type_non_terminals(current_func_info.return_type.id)); + auto& replacement_func_info = program.get_operator_info(random_replacement); + + struct child_t + { + blt::ptrdiff_t start; + // one past the end + blt::ptrdiff_t end; + }; + + // cache memory used for offset data. + thread_local static std::vector children_data; + children_data.clear(); + + while (children_data.size() < current_func_info.argument_types.size()) + { + auto current_point = children_data.size(); + child_t prev{}; + if (current_point == 0) + { + // first child. + prev = {static_cast(c_node + 1), + c.find_endpoint(program, static_cast(c_node + 1))}; + children_data.push_back(prev); + continue; + } else + prev = children_data[current_point - 1]; + child_t next = {prev.end, c.find_endpoint(program, prev.end)}; + children_data.push_back(next); + } + + BLT_TRACE("%ld vs %ld, replacement will be size %ld", children_data.size(), current_func_info.argument_types.size(), + replacement_func_info.argument_types.size()); + + for (const auto& [index, val] : blt::enumerate(replacement_func_info.argument_types)) + { + // need to generate replacement. + if (index < current_func_info.argument_types.size() && val.id != current_func_info.argument_types[index].id) + { + BLT_TRACE_STREAM << "Replacing tree argument from type " + << program.get_typesystem().get_type(current_func_info.argument_types[index]).name() << " to type " + << program.get_typesystem().get_type(val).name() << "\n"; + // TODO: new config? + auto tree = config.generator.get().generate( + {program, val.id, config.replacement_min_depth, config.replacement_max_depth}); + blt::size_t total_bytes_after = 0; + blt::size_t total_bytes_for = 0; + + auto& child = children_data[index]; + for (blt::ptrdiff_t i = child.start; i < child.end; i++) + { + if (ops[i].is_value) + total_bytes_for += stack_allocator::aligned_size(ops[i].type_size); + } + for (blt::size_t i = child.end; i < ops.size(); i++) + { + if (ops[i].is_value) + total_bytes_after += stack_allocator::aligned_size(ops[i].type_size); + } + BLT_TRACE("Size for %ld size after: %ld", total_bytes_for, total_bytes_after); + + auto after_ptr = get_thread_pointer_for_size(total_bytes_after); + vals.copy_to(after_ptr, total_bytes_after); + vals.pop_bytes(static_cast(total_bytes_after + total_bytes_for)); + + blt::size_t total_child_bytes = 0; + for (const auto& v : tree.get_operations()) + { + if (v.is_value) + total_child_bytes += stack_allocator::aligned_size(v.type_size); + } + + BLT_TRACE("Copying %ld bytes back into stack", total_child_bytes); + + vals.copy_from(tree.get_values(), total_child_bytes); + vals.copy_from(after_ptr, total_bytes_after); + + ops.erase(ops.begin() + child.start, ops.begin() + child.end); + ops.insert(ops.begin() + child.start, tree.get_operations().begin(), tree.get_operations().end()); + + // shift over everybody after. + for (auto& new_child : blt::iterate(children_data.begin() + static_cast(index), children_data.end())) + { + // remove the old tree size, then add the new tree size to get the correct positions. + new_child.start = + new_child.start - (child.end - child.start) + static_cast(tree.get_operations().size()); + new_child.end = + new_child.end - (child.end - child.start) + static_cast(tree.get_operations().size()); + } + child.end = static_cast(child.start + tree.get_operations().size()); + +#if BLT_DEBUG_LEVEL >= 2 + blt::size_t found_bytes = vals.size().total_used_bytes; + blt::size_t expected_bytes = std::accumulate(ops.begin(), + ops.end(), 0ul, + [](const auto& v1, const auto& v2) { + if (v2.is_value) + return v1 + stack_allocator::aligned_size(v2.type_size); + return v1; + }); + if (found_bytes != expected_bytes) + { + BLT_WARN("Found bytes %ld vs Expected Bytes %ld", found_bytes, expected_bytes); + BLT_ABORT("Amount of bytes in stack doesn't match the number of bytes expected for the operations"); + } +#endif + } + } + + if (current_func_info.argc.argc > replacement_func_info.argc.argc) + { + BLT_TRACE("TOO MANY ARGS"); + // too many args + blt::size_t end_index = children_data.back().end; + blt::size_t start_index = children_data[replacement_func_info.argc.argc].start; + blt::size_t total_bytes_for = 0; + blt::size_t total_bytes_after = 0; + for (blt::size_t i = start_index; i < end_index; i++) + { + if (ops[i].is_value) + total_bytes_for += stack_allocator::aligned_size(ops[i].type_size); + } + for (blt::size_t i = end_index; i < ops.size(); i++) + { + if (ops[i].is_value) + total_bytes_after += stack_allocator::aligned_size(ops[i].type_size); + } + auto* data = get_thread_pointer_for_size(total_bytes_after); + vals.copy_to(data, total_bytes_after); + vals.pop_bytes(static_cast(total_bytes_after + total_bytes_for)); + vals.copy_from(data, total_bytes_after); + ops.erase(ops.begin() + static_cast(start_index), ops.begin() + static_cast(end_index)); + } else if (current_func_info.argc.argc == replacement_func_info.argc.argc) + { + BLT_TRACE("JUST ENOUGH ARGS"); + // exactly enough args + // return types should have been replaced if needed. this part should do nothing? + } else + { + BLT_TRACE("NOT ENOUGH ARGS"); + // not enough args + blt::size_t total_bytes_after = 0; + blt::size_t start_index = c_node + 1; + if (current_func_info.argc.argc != 0) + start_index = children_data.back().end; + for (blt::size_t i = start_index; i < ops.size(); i++) + { + if (ops[i].is_value) + total_bytes_after += stack_allocator::aligned_size(ops[i].type_size); + } + auto* data = get_thread_pointer_for_size(total_bytes_after); + vals.copy_to(data, total_bytes_after); + vals.pop_bytes(static_cast(total_bytes_after)); + + for (blt::size_t i = current_func_info.argc.argc; i < replacement_func_info.argc.argc; i++) + { + BLT_TRACE("Generating argument %ld", i); + auto tree = config.generator.get().generate( + {program, replacement_func_info.argument_types[i].id, config.replacement_min_depth, + config.replacement_max_depth}); + blt::size_t total_bytes_for = 0; + for (const auto& op : tree.get_operations()) + { + if (op.is_value) + total_bytes_for += stack_allocator::aligned_size(op.type_size); + } + vals.copy_from(tree.get_values(), total_bytes_for); + ops.insert(ops.begin() + static_cast(start_index), tree.get_operations().begin(), + tree.get_operations().end()); + start_index += tree.get_operations().size(); + } + vals.copy_from(data, total_bytes_after); + } + // now finally update the type. + ops[c_node] = {replacement_func_info.function, program.get_typesystem().get_type(replacement_func_info.return_type).size(), + random_replacement, false}; + } else { blt::size_t bytes_from_head = 0; for (auto it = ops.begin() + static_cast(c_node) + 1; it != ops.end(); it++) @@ -93,7 +283,7 @@ namespace blt::gp val = program.get_random().get_u64(val, u64_size_max); else val = program.get_random().get_u64(u64_size_min, val + 1); - } else // is an image + } else if (node.type_size == sizeof(full_image_t))// is an image { auto& val = vals.from(bytes_from_head); auto type = program.get_typesystem().get_type(); @@ -119,12 +309,13 @@ namespace blt::gp value += adjustment.rgb_data[index]; value /= 2.0f; } + } else + { + BLT_ABORT("This type size doesn't exist!"); } } } break; - case mutation_operator::FUNC: - break; case mutation_operator::SUB_FUNC: break; case mutation_operator::JUMP_FUNC: @@ -140,6 +331,44 @@ namespace blt::gp #endif } } + +#if BLT_DEBUG_LEVEL >= 2 + blt::size_t bytes_expected = 0; + auto bytes_size = vals.size().total_used_bytes; + + for (const auto& op : c.get_operations()) + { + if (op.is_value) + bytes_expected += stack_allocator::aligned_size(op.type_size); + } + + if (bytes_expected != bytes_size) + { + BLT_WARN_STREAM << "Stack state: " << vals.size() << "\n"; + BLT_WARN("Child tree bytes %ld vs expected %ld, difference: %ld", bytes_size, bytes_expected, + static_cast(bytes_expected) - static_cast(bytes_size)); + BLT_ABORT("Amount of bytes in stack doesn't match the number of bytes expected for the operations"); + } + auto copy = c; + try + { + auto result = copy.evaluate(nullptr); + blt::black_box(result); + } catch (const std::exception& e) + { + std::cout << "Parent: " << std::endl; + p.print(program, std::cout, false, true); + std::cout << "Child:" << std::endl; + c.print(program, std::cout, false, true); + std::cout << std::endl; + c.print(program, std::cout, true, true); + std::cout << std::endl; + BLT_WARN(e.what()); + throw e; + } +#endif + + BLT_TRACE("- - - - - - - - - Passed and finished - - - - - - - - -"); return c; }