From 7bc81328306311013edbbb72c9ebfec784ad0311 Mon Sep 17 00:00:00 2001 From: weyland0 Date: Sun, 21 Dec 2025 14:49:19 +0300 Subject: [PATCH 1/7] add information in info.json --- tasks/batkov_f_linear_image_filtering/info.json | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 tasks/batkov_f_linear_image_filtering/info.json diff --git a/tasks/batkov_f_linear_image_filtering/info.json b/tasks/batkov_f_linear_image_filtering/info.json new file mode 100644 index 0000000000..c4d3000124 --- /dev/null +++ b/tasks/batkov_f_linear_image_filtering/info.json @@ -0,0 +1,9 @@ +{ + "student": { + "first_name": "Филипп", + "last_name": "Батьков", + "middle_name": "Владиславович", + "group_number": "3823Б1ПР3", + "task_number": "3" + } +} From 9ee2145246922761506e67801bdaec943a963350 Mon Sep 17 00:00:00 2001 From: weyland0 Date: Sun, 21 Dec 2025 14:49:59 +0300 Subject: [PATCH 2/7] set up common.hpp --- .../common/include/common.hpp | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 tasks/batkov_f_linear_image_filtering/common/include/common.hpp diff --git a/tasks/batkov_f_linear_image_filtering/common/include/common.hpp b/tasks/batkov_f_linear_image_filtering/common/include/common.hpp new file mode 100644 index 0000000000..d398838335 --- /dev/null +++ b/tasks/batkov_f_linear_image_filtering/common/include/common.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "task/include/task.hpp" + +namespace batkov_f_linear_image_filtering { + +struct Image { + std::vector data; + size_t width{}; + size_t height{}; + size_t channels{}; +}; + +using InType = Image; +using OutType = Image; +using TestType = std::tuple; +using BaseTask = ppc::task::Task; + +} // namespace batkov_f_linear_image_filtering From afa7aa58547c6663ddca1b7f74d38c563d6a38cf Mon Sep 17 00:00:00 2001 From: weyland0 Date: Sun, 21 Dec 2025 14:50:31 +0300 Subject: [PATCH 3/7] add solution for SEQ version --- .../seq/include/ops_seq.hpp | 26 +++++ .../seq/src/ops_seq.cpp | 109 ++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 tasks/batkov_f_linear_image_filtering/seq/include/ops_seq.hpp create mode 100644 tasks/batkov_f_linear_image_filtering/seq/src/ops_seq.cpp diff --git a/tasks/batkov_f_linear_image_filtering/seq/include/ops_seq.hpp b/tasks/batkov_f_linear_image_filtering/seq/include/ops_seq.hpp new file mode 100644 index 0000000000..08e8e5dcc3 --- /dev/null +++ b/tasks/batkov_f_linear_image_filtering/seq/include/ops_seq.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include + +#include "batkov_f_linear_image_filtering/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace batkov_f_linear_image_filtering { + +class BatkovFLinearImageFilteringSEQ : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + explicit BatkovFLinearImageFilteringSEQ(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + + std::array, 3> kernel_; +}; + +} // namespace batkov_f_linear_image_filtering diff --git a/tasks/batkov_f_linear_image_filtering/seq/src/ops_seq.cpp b/tasks/batkov_f_linear_image_filtering/seq/src/ops_seq.cpp new file mode 100644 index 0000000000..2f1e1d1e9a --- /dev/null +++ b/tasks/batkov_f_linear_image_filtering/seq/src/ops_seq.cpp @@ -0,0 +1,109 @@ +#include "batkov_f_linear_image_filtering/seq/include/ops_seq.hpp" + +#include +#include +#include +#include +#include + +#include "batkov_f_linear_image_filtering/common/include/common.hpp" + +namespace batkov_f_linear_image_filtering { + +namespace { + +std::vector ApplyKernel(const std::array, 3>& kernel, const Image& image, size_t cy, + size_t cx) { + std::vector result(image.channels, 0); + + for (size_t ky = 0; ky < kernel.size(); ky++) { + for (size_t kx = 0; kx < kernel.size(); kx++) { + size_t px = cx + kx - 1; + size_t py = cy + ky - 1; + + px = std::max(0, std::min(px, image.width - 1)); + py = std::max(0, std::min(py, image.height - 1)); + + size_t pixel_index = ((py * image.width) + px) * image.channels; + float kernel_value = kernel.at(ky).at(kx); + + for (size_t ch = 0; ch < image.channels; ++ch) { + result[ch] += static_cast(image.data[pixel_index + ch]) * kernel_value; + } + } + } + + return result; +} + +} // namespace + +BatkovFLinearImageFilteringSEQ::BatkovFLinearImageFilteringSEQ(const InType& in) : kernel_() { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = Image{}; +} + +bool BatkovFLinearImageFilteringSEQ::ValidationImpl() { + return (!GetInput().data.empty()) && (GetInput().width > 0) && (GetInput().height > 0); +} + +bool BatkovFLinearImageFilteringSEQ::PreProcessingImpl() { + float sum = 0.0F; + float sigma = 1.0F; + + for (int col = -1; col <= 1; col++) { + for (int row = -1; row <= 1; row++) { + kernel_.at(col + 1).at(row + 1) = + std::exp(-static_cast((row * row) + (col * col)) / (2.0F * sigma * sigma)); + sum += kernel_.at(col + 1).at(row + 1); + } + } + + for (size_t col = 0; col < 3; col++) { + for (size_t row = 0; row < 3; row++) { + kernel_.at(col).at(row) /= sum; + } + } + + GetOutput().width = GetInput().width; + GetOutput().height = GetInput().width; + GetOutput().channels = GetInput().channels; + GetOutput().data.resize(GetInput().width * GetInput().width * GetInput().channels); + + return true; +} + +bool BatkovFLinearImageFilteringSEQ::RunImpl() { + size_t width = GetInput().width; + size_t height = GetInput().height; + size_t channels = GetInput().channels; + + size_t block_size = 10; + for (size_t by = 0; by < height; by += block_size) { + for (size_t bx = 0; bx < width; bx += block_size) { + size_t end_y = std::min(by + block_size, height); + size_t end_x = std::min(bx + block_size, width); + + for (size_t col = by; col < end_y; col++) { + for (size_t row = bx; row < end_x; row++) { + auto result = ApplyKernel(kernel_, GetInput(), col, row); + + size_t out_index = (col * width + row) * channels; + for (size_t ch = 0; ch < channels; ch++) { + float pixel_val = result[ch]; + GetOutput().data[out_index + ch] = static_cast(std::max(0.0F, std::min(255.0F, pixel_val))); + } + } + } + } + } + + return true; +} + +bool BatkovFLinearImageFilteringSEQ::PostProcessingImpl() { + return true; +} + +} // namespace batkov_f_linear_image_filtering From fcde62798199ea7b6b95dbba2c7ddd07333b2141 Mon Sep 17 00:00:00 2001 From: weyland0 Date: Sun, 21 Dec 2025 23:34:46 +0300 Subject: [PATCH 4/7] create all files --- .../common/include/common.hpp | 10 +- .../mpi/include/ops_mpi.hpp | 22 ++++ .../mpi/src/ops_mpi.cpp | 72 +++++++++++ .../batkov_f_linear_image_filtering/report.md | 0 .../seq/include/ops_seq.hpp | 4 +- .../seq/src/ops_seq.cpp | 63 +++++----- .../settings.json | 7 ++ .../tests/.clang-tidy | 13 ++ .../tests/functional/main.cpp | 112 ++++++++++++++++++ .../tests/performance/main.cpp | 40 +++++++ 10 files changed, 306 insertions(+), 37 deletions(-) create mode 100644 tasks/batkov_f_linear_image_filtering/mpi/include/ops_mpi.hpp create mode 100644 tasks/batkov_f_linear_image_filtering/mpi/src/ops_mpi.cpp create mode 100644 tasks/batkov_f_linear_image_filtering/report.md create mode 100644 tasks/batkov_f_linear_image_filtering/settings.json create mode 100644 tasks/batkov_f_linear_image_filtering/tests/.clang-tidy create mode 100644 tasks/batkov_f_linear_image_filtering/tests/functional/main.cpp create mode 100644 tasks/batkov_f_linear_image_filtering/tests/performance/main.cpp diff --git a/tasks/batkov_f_linear_image_filtering/common/include/common.hpp b/tasks/batkov_f_linear_image_filtering/common/include/common.hpp index d398838335..1e318f753d 100644 --- a/tasks/batkov_f_linear_image_filtering/common/include/common.hpp +++ b/tasks/batkov_f_linear_image_filtering/common/include/common.hpp @@ -10,8 +10,14 @@ namespace batkov_f_linear_image_filtering { +struct Pixel { + uint8_t r{}; + uint8_t g{}; + uint8_t b{}; +}; + struct Image { - std::vector data; + std::vector data; size_t width{}; size_t height{}; size_t channels{}; @@ -19,7 +25,7 @@ struct Image { using InType = Image; using OutType = Image; -using TestType = std::tuple; +using TestType = std::tuple; using BaseTask = ppc::task::Task; } // namespace batkov_f_linear_image_filtering diff --git a/tasks/batkov_f_linear_image_filtering/mpi/include/ops_mpi.hpp b/tasks/batkov_f_linear_image_filtering/mpi/include/ops_mpi.hpp new file mode 100644 index 0000000000..0b7cd39971 --- /dev/null +++ b/tasks/batkov_f_linear_image_filtering/mpi/include/ops_mpi.hpp @@ -0,0 +1,22 @@ +// #pragma once + +// #include "batkov_f_linear_image_filtering/common/include/common.hpp" +// #include "task/include/task.hpp" + +// namespace batkov_f_linear_image_filtering { + +// class BatkovFLinearImageFilteringMPI : public BaseTask { +// public: +// static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { +// return ppc::task::TypeOfTask::kMPI; +// } +// explicit BatkovFLinearImageFilteringMPI(const InType &in); + +// private: +// bool ValidationImpl() override; +// bool PreProcessingImpl() override; +// bool RunImpl() override; +// bool PostProcessingImpl() override; +// }; + +// } // namespace batkov_f_linear_image_filtering diff --git a/tasks/batkov_f_linear_image_filtering/mpi/src/ops_mpi.cpp b/tasks/batkov_f_linear_image_filtering/mpi/src/ops_mpi.cpp new file mode 100644 index 0000000000..1b04605470 --- /dev/null +++ b/tasks/batkov_f_linear_image_filtering/mpi/src/ops_mpi.cpp @@ -0,0 +1,72 @@ +// #include "batkov_f_linear_image_filtering/mpi/include/ops_mpi.hpp" + +// #include + +// #include +// #include + +// #include "batkov_f_linear_image_filtering/common/include/common.hpp" +// #include "util/include/util.hpp" + +// namespace batkov_f_linear_image_filtering { + +// BatkovFLinearImageFilteringMPI::BatkovFLinearImageFilteringMPI(const InType &in) { +// SetTypeOfTask(GetStaticTypeOfTask()); +// GetInput() = in; +// GetOutput() = 0; +// } + +// bool BatkovFLinearImageFilteringMPI::ValidationImpl() { +// return (GetInput() > 0) && (GetOutput() == 0); +// } + +// bool BatkovFLinearImageFilteringMPI::PreProcessingImpl() { +// GetOutput() = 2 * GetInput(); +// return GetOutput() > 0; +// } + +// bool BatkovFLinearImageFilteringMPI::RunImpl() { +// auto input = GetInput(); +// if (input == 0) { +// return false; +// } + +// for (InType i = 0; i < GetInput(); i++) { +// for (InType j = 0; j < GetInput(); j++) { +// for (InType k = 0; k < GetInput(); k++) { +// std::vector tmp(i + j + k, 1); +// GetOutput() += std::accumulate(tmp.begin(), tmp.end(), 0); +// GetOutput() -= i + j + k; +// } +// } +// } + +// const int num_threads = ppc::util::GetNumThreads(); +// GetOutput() *= num_threads; + +// int rank = 0; +// MPI_Comm_rank(MPI_COMM_WORLD, &rank); + +// if (rank == 0) { +// GetOutput() /= num_threads; +// } else { +// int counter = 0; +// for (int i = 0; i < num_threads; i++) { +// counter++; +// } + +// if (counter != 0) { +// GetOutput() /= counter; +// } +// } + +// MPI_Barrier(MPI_COMM_WORLD); +// return GetOutput() > 0; +// } + +// bool BatkovFLinearImageFilteringMPI::PostProcessingImpl() { +// GetOutput() -= GetInput(); +// return GetOutput() > 0; +// } + +// } // namespace batkov_f_linear_image_filtering diff --git a/tasks/batkov_f_linear_image_filtering/report.md b/tasks/batkov_f_linear_image_filtering/report.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tasks/batkov_f_linear_image_filtering/seq/include/ops_seq.hpp b/tasks/batkov_f_linear_image_filtering/seq/include/ops_seq.hpp index 08e8e5dcc3..95ede03a48 100644 --- a/tasks/batkov_f_linear_image_filtering/seq/include/ops_seq.hpp +++ b/tasks/batkov_f_linear_image_filtering/seq/include/ops_seq.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include #include "batkov_f_linear_image_filtering/common/include/common.hpp" #include "task/include/task.hpp" @@ -20,7 +20,7 @@ class BatkovFLinearImageFilteringSEQ : public BaseTask { bool RunImpl() override; bool PostProcessingImpl() override; - std::array, 3> kernel_; + std::vector> kernel_; }; } // namespace batkov_f_linear_image_filtering diff --git a/tasks/batkov_f_linear_image_filtering/seq/src/ops_seq.cpp b/tasks/batkov_f_linear_image_filtering/seq/src/ops_seq.cpp index 2f1e1d1e9a..efda3f8743 100644 --- a/tasks/batkov_f_linear_image_filtering/seq/src/ops_seq.cpp +++ b/tasks/batkov_f_linear_image_filtering/seq/src/ops_seq.cpp @@ -12,9 +12,10 @@ namespace batkov_f_linear_image_filtering { namespace { -std::vector ApplyKernel(const std::array, 3>& kernel, const Image& image, size_t cy, - size_t cx) { - std::vector result(image.channels, 0); +Pixel ApplyKernel(const std::vector>& kernel, const Image& image, size_t cy, size_t cx) { + float r_ch = 0.0F; + float g_ch = 0.0F; + float b_ch = 0.0F; for (size_t ky = 0; ky < kernel.size(); ky++) { for (size_t kx = 0; kx < kernel.size(); kx++) { @@ -24,21 +25,26 @@ std::vector ApplyKernel(const std::array, 3>& kernel px = std::max(0, std::min(px, image.width - 1)); py = std::max(0, std::min(py, image.height - 1)); - size_t pixel_index = ((py * image.width) + px) * image.channels; - float kernel_value = kernel.at(ky).at(kx); + size_t pixel_index = (py * image.width) + px; + float kernel_value = kernel[ky][kx]; - for (size_t ch = 0; ch < image.channels; ++ch) { - result[ch] += static_cast(image.data[pixel_index + ch]) * kernel_value; - } + r_ch += static_cast(image.data[pixel_index].r) * kernel_value; + g_ch += static_cast(image.data[pixel_index].g) * kernel_value; + b_ch += static_cast(image.data[pixel_index].b) * kernel_value; } } + Pixel result; + result.r = static_cast(r_ch); + result.g = static_cast(g_ch); + result.b = static_cast(b_ch); + return result; } } // namespace -BatkovFLinearImageFilteringSEQ::BatkovFLinearImageFilteringSEQ(const InType& in) : kernel_() { +BatkovFLinearImageFilteringSEQ::BatkovFLinearImageFilteringSEQ(const InType& in) { SetTypeOfTask(GetStaticTypeOfTask()); GetInput() = in; GetOutput() = Image{}; @@ -49,27 +55,32 @@ bool BatkovFLinearImageFilteringSEQ::ValidationImpl() { } bool BatkovFLinearImageFilteringSEQ::PreProcessingImpl() { + kernel_.resize(3); + for (auto& v : kernel_) { + v.resize(3); + } + float sum = 0.0F; float sigma = 1.0F; for (int col = -1; col <= 1; col++) { for (int row = -1; row <= 1; row++) { - kernel_.at(col + 1).at(row + 1) = + kernel_[col + 1][row + 1] = std::exp(-static_cast((row * row) + (col * col)) / (2.0F * sigma * sigma)); - sum += kernel_.at(col + 1).at(row + 1); + sum += kernel_[col + 1][row + 1]; } } for (size_t col = 0; col < 3; col++) { for (size_t row = 0; row < 3; row++) { - kernel_.at(col).at(row) /= sum; + kernel_[col][row] /= sum; } } GetOutput().width = GetInput().width; - GetOutput().height = GetInput().width; + GetOutput().height = GetInput().height; GetOutput().channels = GetInput().channels; - GetOutput().data.resize(GetInput().width * GetInput().width * GetInput().channels); + GetOutput().data.resize(GetInput().width * GetInput().height * GetInput().channels); return true; } @@ -77,25 +88,11 @@ bool BatkovFLinearImageFilteringSEQ::PreProcessingImpl() { bool BatkovFLinearImageFilteringSEQ::RunImpl() { size_t width = GetInput().width; size_t height = GetInput().height; - size_t channels = GetInput().channels; - - size_t block_size = 10; - for (size_t by = 0; by < height; by += block_size) { - for (size_t bx = 0; bx < width; bx += block_size) { - size_t end_y = std::min(by + block_size, height); - size_t end_x = std::min(bx + block_size, width); - - for (size_t col = by; col < end_y; col++) { - for (size_t row = bx; row < end_x; row++) { - auto result = ApplyKernel(kernel_, GetInput(), col, row); - - size_t out_index = (col * width + row) * channels; - for (size_t ch = 0; ch < channels; ch++) { - float pixel_val = result[ch]; - GetOutput().data[out_index + ch] = static_cast(std::max(0.0F, std::min(255.0F, pixel_val))); - } - } - } + + for (size_t col = 0; col < height; col++) { + for (size_t row = 0; row < width; row++) { + auto result = ApplyKernel(kernel_, GetInput(), col, row); + GetOutput().data[((col * width) + row)] = result; } } diff --git a/tasks/batkov_f_linear_image_filtering/settings.json b/tasks/batkov_f_linear_image_filtering/settings.json new file mode 100644 index 0000000000..b1a0d52574 --- /dev/null +++ b/tasks/batkov_f_linear_image_filtering/settings.json @@ -0,0 +1,7 @@ +{ + "tasks_type": "processes", + "tasks": { + "mpi": "enabled", + "seq": "enabled" + } +} diff --git a/tasks/batkov_f_linear_image_filtering/tests/.clang-tidy b/tasks/batkov_f_linear_image_filtering/tests/.clang-tidy new file mode 100644 index 0000000000..ef43b7aa8a --- /dev/null +++ b/tasks/batkov_f_linear_image_filtering/tests/.clang-tidy @@ -0,0 +1,13 @@ +InheritParentConfig: true + +Checks: > + -modernize-loop-convert, + -cppcoreguidelines-avoid-goto, + -cppcoreguidelines-avoid-non-const-global-variables, + -misc-use-anonymous-namespace, + -modernize-use-std-print, + -modernize-type-traits + +CheckOptions: + - key: readability-function-cognitive-complexity.Threshold + value: 50 # Relaxed for tests diff --git a/tasks/batkov_f_linear_image_filtering/tests/functional/main.cpp b/tasks/batkov_f_linear_image_filtering/tests/functional/main.cpp new file mode 100644 index 0000000000..625ccecca4 --- /dev/null +++ b/tasks/batkov_f_linear_image_filtering/tests/functional/main.cpp @@ -0,0 +1,112 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "batkov_f_linear_image_filtering/common/include/common.hpp" +#include "batkov_f_linear_image_filtering/mpi/include/ops_mpi.hpp" +#include "batkov_f_linear_image_filtering/seq/include/ops_seq.hpp" +#include "util/include/func_test_util.hpp" +#include "util/include/util.hpp" + +namespace batkov_f_linear_image_filtering { + +class BatkovFRunFuncTestsProcesses3 : public ppc::util::BaseRunFuncTests { + public: + static std::string PrintTestParam(const TestType &test_param) { + std::string p0 = std::get<0>(test_param); + std::string p1 = std::to_string(std::get<1>(test_param)); + std::string p2 = std::to_string(std::get<2>(test_param)); + std::string p3 = std::to_string(std::get<3>(test_param)); + return p0 + "_" + p1 + "x" + p2 + "x" + p3; + } + + protected: + void SetUp() override { + TestType params = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + input_data_.width = std::get<1>(params); + input_data_.height = std::get<2>(params); + input_data_.channels = std::get<3>(params); + + size_t size = input_data_.width * input_data_.height * input_data_.channels; + input_data_.data.resize(size); + + for (size_t i = 0; i < size; ++i) { + input_data_.data[i] = dis_(gen_); + } + } + + static float CalculateMSE(const Image& original, const Image& filtered) { + float sum = 0.0F; + for (size_t i = 0; i < original.data.size(); i++) { + float diff = static_cast(original.data[i]) - static_cast(filtered.data[i]); + sum += diff * diff; + } + + return sum / static_cast(original.data.size()); + } + + static float CalculatePSNR(const Image& original, const Image& filtered) { + float mse = CalculateMSE(original, filtered); + + if (mse < 1e-10) { + return 100.0; + } + + float max_value = 255.0F; + float psnr = 10.0F * std::log10f((max_value * max_value) / mse); + + return psnr; +} + + bool CheckTestOutputData(OutType &output_data) final { + if (input_data_.data.size() != output_data.data.size()) { + return false; + } + + float psnr = CalculatePSNR(input_data_, output_data); + std::cout << "psnr = " << psnr << '\n'; + + return psnr > 40.0F; + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + std::random_device rd_; + std::mt19937 gen_{rd_()}; + std::uniform_int_distribution dis_{0, 255}; + + InType input_data_; +}; + +namespace { + +TEST_P(BatkovFRunFuncTestsProcesses3, ImageSmoothing) { + ExecuteTest(GetParam()); +} + +const std::array kTestParam = { + std::make_tuple("tiny_image", 10, 10, 3), std::make_tuple("small_image", 50, 50, 3), + std::make_tuple("medium_image", 100, 100, 3), std::make_tuple("big_image", 300, 300, 3)}; + +const auto kTestTasksList = std::tuple_cat( + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_batkov_f_linear_image_filtering)); + // ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_batkov_f_image_smoothing)); + +const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); + +const auto kPerfTestName = BatkovFRunFuncTestsProcesses3::PrintFuncTestName; + +INSTANTIATE_TEST_SUITE_P(ImageSmoothingTests, BatkovFRunFuncTestsProcesses3, kGtestValues, kPerfTestName); + +} // namespace + +} // namespace batkov_f_linear_image_filtering \ No newline at end of file diff --git a/tasks/batkov_f_linear_image_filtering/tests/performance/main.cpp b/tasks/batkov_f_linear_image_filtering/tests/performance/main.cpp new file mode 100644 index 0000000000..2d0aff74ce --- /dev/null +++ b/tasks/batkov_f_linear_image_filtering/tests/performance/main.cpp @@ -0,0 +1,40 @@ +// #include + +// #include "batkov_f_linear_image_filtering/common/include/common.hpp" +// #include "batkov_f_linear_image_filtering/mpi/include/ops_mpi.hpp" +// #include "batkov_f_linear_image_filtering/seq/include/ops_seq.hpp" +// #include "util/include/perf_test_util.hpp" + +// namespace batkov_f_linear_image_filtering { + +// class BatkovFRunPerfTestProcesses3 : public ppc::util::BaseRunPerfTests { +// const int kCount_ = 100; +// InType input_data_{}; + +// void SetUp() override { +// input_data_ = kCount_; +// } + +// bool CheckTestOutputData(OutType &output_data) final { +// return input_data_ == output_data; +// } + +// InType GetTestInputData() final { +// return input_data_; +// } +// }; + +// TEST_P(BatkovFRunPerfTestProcesses3, RunPerfModes) { +// ExecuteTest(GetParam()); +// } + +// const auto kAllPerfTasks = +// ppc::util::MakeAllPerfTasks(PPC_SETTINGS_batkov_f_linear_image_filtering); + +// const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); + +// const auto kPerfTestName = BatkovFRunPerfTestProcesses3::CustomPerfTestName; + +// INSTANTIATE_TEST_SUITE_P(RunModeTests, BatkovFRunPerfTestProcesses3, kGtestValues, kPerfTestName); + +// } // namespace batkov_f_linear_image_filtering From abb16c433a54200d90bd10165dcff164e64892e6 Mon Sep 17 00:00:00 2001 From: weyland0 Date: Mon, 22 Dec 2025 00:13:19 +0300 Subject: [PATCH 5/7] add laplace variance as a job completion detector --- .../common/include/common.hpp | 3 +- .../seq/src/ops_seq.cpp | 3 +- .../tests/functional/main.cpp | 91 +++++++++++-------- 3 files changed, 56 insertions(+), 41 deletions(-) diff --git a/tasks/batkov_f_linear_image_filtering/common/include/common.hpp b/tasks/batkov_f_linear_image_filtering/common/include/common.hpp index 1e318f753d..bc25bc0341 100644 --- a/tasks/batkov_f_linear_image_filtering/common/include/common.hpp +++ b/tasks/batkov_f_linear_image_filtering/common/include/common.hpp @@ -20,12 +20,11 @@ struct Image { std::vector data; size_t width{}; size_t height{}; - size_t channels{}; }; using InType = Image; using OutType = Image; -using TestType = std::tuple; +using TestType = std::tuple; using BaseTask = ppc::task::Task; } // namespace batkov_f_linear_image_filtering diff --git a/tasks/batkov_f_linear_image_filtering/seq/src/ops_seq.cpp b/tasks/batkov_f_linear_image_filtering/seq/src/ops_seq.cpp index efda3f8743..6d654eb346 100644 --- a/tasks/batkov_f_linear_image_filtering/seq/src/ops_seq.cpp +++ b/tasks/batkov_f_linear_image_filtering/seq/src/ops_seq.cpp @@ -79,8 +79,7 @@ bool BatkovFLinearImageFilteringSEQ::PreProcessingImpl() { GetOutput().width = GetInput().width; GetOutput().height = GetInput().height; - GetOutput().channels = GetInput().channels; - GetOutput().data.resize(GetInput().width * GetInput().height * GetInput().channels); + GetOutput().data.resize(GetInput().width * GetInput().height); return true; } diff --git a/tasks/batkov_f_linear_image_filtering/tests/functional/main.cpp b/tasks/batkov_f_linear_image_filtering/tests/functional/main.cpp index 625ccecca4..490492be4e 100644 --- a/tasks/batkov_f_linear_image_filtering/tests/functional/main.cpp +++ b/tasks/batkov_f_linear_image_filtering/tests/functional/main.cpp @@ -1,7 +1,6 @@ #include #include -#include #include #include #include @@ -22,8 +21,7 @@ class BatkovFRunFuncTestsProcesses3 : public ppc::util::BaseRunFuncTests(test_param); std::string p1 = std::to_string(std::get<1>(test_param)); std::string p2 = std::to_string(std::get<2>(test_param)); - std::string p3 = std::to_string(std::get<3>(test_param)); - return p0 + "_" + p1 + "x" + p2 + "x" + p3; + return p0 + "_" + p1 + "x" + p2; } protected: @@ -31,48 +29,66 @@ class BatkovFRunFuncTestsProcesses3 : public ppc::util::BaseRunFuncTests(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); input_data_.width = std::get<1>(params); input_data_.height = std::get<2>(params); - input_data_.channels = std::get<3>(params); - size_t size = input_data_.width * input_data_.height * input_data_.channels; + size_t size = input_data_.width * input_data_.height; input_data_.data.resize(size); for (size_t i = 0; i < size; ++i) { - input_data_.data[i] = dis_(gen_); + input_data_.data[i].r = dis_(gen_); + input_data_.data[i].b = dis_(gen_); + input_data_.data[i].g = dis_(gen_); } + + preprocess_blur_value_ = CalcLaplacianVariance(input_data_); } - static float CalculateMSE(const Image& original, const Image& filtered) { - float sum = 0.0F; - for (size_t i = 0; i < original.data.size(); i++) { - float diff = static_cast(original.data[i]) - static_cast(filtered.data[i]); - sum += diff * diff; + static float CalcLaplacianVariance(const Image &image) { + std::vector gray(image.width * image.height); + + const auto &data = image.data; + size_t width = image.width; + size_t height = image.height; + + for (size_t i = 0; i < width * height; i++) { + auto r = static_cast(data[i].r); + auto g = static_cast(data[i].g); + auto b = static_cast(data[i].b); + + gray[i] = (0.299F * r) + (0.587F * g) + (0.114F * b); } - - return sum / static_cast(original.data.size()); - } - static float CalculatePSNR(const Image& original, const Image& filtered) { - float mse = CalculateMSE(original, filtered); - - if (mse < 1e-10) { - return 100.0; + std::vector laplacian(width * height, 0.0F); + for (size_t y_px = 1; y_px < height - 1; y_px++) { + for (size_t x_px = 1; x_px < width - 1; x_px++) { + size_t idx = (y_px * width) + x_px; + + float value = -gray[((y_px - 1) * width) + x_px] - gray[(y_px * width) + (x_px - 1)] + (4.0F * gray[idx]) - + gray[(y_px * width) + (x_px + 1)] - gray[((y_px + 1) * width) + x_px]; + + laplacian[idx] = value; + } } - - float max_value = 255.0F; - float psnr = 10.0F * std::log10f((max_value * max_value) / mse); - - return psnr; -} - bool CheckTestOutputData(OutType &output_data) final { - if (input_data_.data.size() != output_data.data.size()) { - return false; + float mean = 0.0F; + for (size_t i = 0; i < width * height; i++) { + mean += laplacian[i]; + } + mean /= static_cast(width * height); + + float variance = 0.0F; + for (size_t i = 0; i < width * height; i++) { + float diff = laplacian[i] - mean; + variance += diff * diff; } + variance /= static_cast(width * height); - float psnr = CalculatePSNR(input_data_, output_data); - std::cout << "psnr = " << psnr << '\n'; + return variance; + } + + bool CheckTestOutputData(OutType &output_data) final { + float post_process_blur_value = CalcLaplacianVariance(output_data); - return psnr > 40.0F; + return (preprocess_blur_value_ / post_process_blur_value) > 2.0F; } InType GetTestInputData() final { @@ -84,6 +100,7 @@ class BatkovFRunFuncTestsProcesses3 : public ppc::util::BaseRunFuncTests dis_{0, 255}; + float preprocess_blur_value_ = 0.0F; InType input_data_; }; @@ -94,12 +111,12 @@ TEST_P(BatkovFRunFuncTestsProcesses3, ImageSmoothing) { } const std::array kTestParam = { - std::make_tuple("tiny_image", 10, 10, 3), std::make_tuple("small_image", 50, 50, 3), - std::make_tuple("medium_image", 100, 100, 3), std::make_tuple("big_image", 300, 300, 3)}; + std::make_tuple("tiny_image", 10, 10), std::make_tuple("small_image", 50, 50), + std::make_tuple("medium_image", 100, 100), std::make_tuple("big_image", 300, 300)}; -const auto kTestTasksList = std::tuple_cat( - ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_batkov_f_linear_image_filtering)); - // ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_batkov_f_image_smoothing)); +const auto kTestTasksList = std::tuple_cat(ppc::util::AddFuncTask( + kTestParam, PPC_SETTINGS_batkov_f_linear_image_filtering)); +// ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_batkov_f_image_smoothing)); const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); @@ -109,4 +126,4 @@ INSTANTIATE_TEST_SUITE_P(ImageSmoothingTests, BatkovFRunFuncTestsProcesses3, kGt } // namespace -} // namespace batkov_f_linear_image_filtering \ No newline at end of file +} // namespace batkov_f_linear_image_filtering From 14c3ae99a20dcdf42215f302e7d6092c4dd8c883 Mon Sep 17 00:00:00 2001 From: weyland0 Date: Thu, 25 Dec 2025 12:14:12 +0300 Subject: [PATCH 6/7] add the solution --- .../common/include/common.hpp | 13 +- .../mpi/include/ops_mpi.hpp | 36 +- .../mpi/src/ops_mpi.cpp | 266 ++++++++---- .../batkov_f_linear_image_filtering/report.md | 386 ++++++++++++++++++ .../seq/include/ops_seq.hpp | 4 +- .../seq/src/ops_seq.cpp | 82 ++-- .../tests/functional/main.cpp | 44 +- .../tests/performance/main.cpp | 141 +++++-- 8 files changed, 775 insertions(+), 197 deletions(-) diff --git a/tasks/batkov_f_linear_image_filtering/common/include/common.hpp b/tasks/batkov_f_linear_image_filtering/common/include/common.hpp index bc25bc0341..84af2855e7 100644 --- a/tasks/batkov_f_linear_image_filtering/common/include/common.hpp +++ b/tasks/batkov_f_linear_image_filtering/common/include/common.hpp @@ -10,21 +10,18 @@ namespace batkov_f_linear_image_filtering { -struct Pixel { - uint8_t r{}; - uint8_t g{}; - uint8_t b{}; -}; - struct Image { - std::vector data; + std::vector data; size_t width{}; size_t height{}; + size_t channels{}; }; using InType = Image; using OutType = Image; -using TestType = std::tuple; +using TestType = std::tuple; using BaseTask = ppc::task::Task; +using Kernel = std::vector>; +using ImageData = std::vector; } // namespace batkov_f_linear_image_filtering diff --git a/tasks/batkov_f_linear_image_filtering/mpi/include/ops_mpi.hpp b/tasks/batkov_f_linear_image_filtering/mpi/include/ops_mpi.hpp index 0b7cd39971..d1ddb3c978 100644 --- a/tasks/batkov_f_linear_image_filtering/mpi/include/ops_mpi.hpp +++ b/tasks/batkov_f_linear_image_filtering/mpi/include/ops_mpi.hpp @@ -1,22 +1,24 @@ -// #pragma once +#pragma once -// #include "batkov_f_linear_image_filtering/common/include/common.hpp" -// #include "task/include/task.hpp" +#include "batkov_f_linear_image_filtering/common/include/common.hpp" +#include "task/include/task.hpp" -// namespace batkov_f_linear_image_filtering { +namespace batkov_f_linear_image_filtering { -// class BatkovFLinearImageFilteringMPI : public BaseTask { -// public: -// static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { -// return ppc::task::TypeOfTask::kMPI; -// } -// explicit BatkovFLinearImageFilteringMPI(const InType &in); +class BatkovFLinearImageFilteringMPI : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + explicit BatkovFLinearImageFilteringMPI(const InType &in); -// private: -// bool ValidationImpl() override; -// bool PreProcessingImpl() override; -// bool RunImpl() override; -// bool PostProcessingImpl() override; -// }; + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; -// } // namespace batkov_f_linear_image_filtering + Kernel kernel_; +}; + +} // namespace batkov_f_linear_image_filtering diff --git a/tasks/batkov_f_linear_image_filtering/mpi/src/ops_mpi.cpp b/tasks/batkov_f_linear_image_filtering/mpi/src/ops_mpi.cpp index 1b04605470..e5e6b7a6b1 100644 --- a/tasks/batkov_f_linear_image_filtering/mpi/src/ops_mpi.cpp +++ b/tasks/batkov_f_linear_image_filtering/mpi/src/ops_mpi.cpp @@ -1,72 +1,194 @@ -// #include "batkov_f_linear_image_filtering/mpi/include/ops_mpi.hpp" - -// #include - -// #include -// #include - -// #include "batkov_f_linear_image_filtering/common/include/common.hpp" -// #include "util/include/util.hpp" - -// namespace batkov_f_linear_image_filtering { - -// BatkovFLinearImageFilteringMPI::BatkovFLinearImageFilteringMPI(const InType &in) { -// SetTypeOfTask(GetStaticTypeOfTask()); -// GetInput() = in; -// GetOutput() = 0; -// } - -// bool BatkovFLinearImageFilteringMPI::ValidationImpl() { -// return (GetInput() > 0) && (GetOutput() == 0); -// } - -// bool BatkovFLinearImageFilteringMPI::PreProcessingImpl() { -// GetOutput() = 2 * GetInput(); -// return GetOutput() > 0; -// } - -// bool BatkovFLinearImageFilteringMPI::RunImpl() { -// auto input = GetInput(); -// if (input == 0) { -// return false; -// } - -// for (InType i = 0; i < GetInput(); i++) { -// for (InType j = 0; j < GetInput(); j++) { -// for (InType k = 0; k < GetInput(); k++) { -// std::vector tmp(i + j + k, 1); -// GetOutput() += std::accumulate(tmp.begin(), tmp.end(), 0); -// GetOutput() -= i + j + k; -// } -// } -// } - -// const int num_threads = ppc::util::GetNumThreads(); -// GetOutput() *= num_threads; - -// int rank = 0; -// MPI_Comm_rank(MPI_COMM_WORLD, &rank); - -// if (rank == 0) { -// GetOutput() /= num_threads; -// } else { -// int counter = 0; -// for (int i = 0; i < num_threads; i++) { -// counter++; -// } - -// if (counter != 0) { -// GetOutput() /= counter; -// } -// } - -// MPI_Barrier(MPI_COMM_WORLD); -// return GetOutput() > 0; -// } - -// bool BatkovFLinearImageFilteringMPI::PostProcessingImpl() { -// GetOutput() -= GetInput(); -// return GetOutput() > 0; -// } - -// } // namespace batkov_f_linear_image_filtering +#include "batkov_f_linear_image_filtering/mpi/include/ops_mpi.hpp" + +#include + +#include +#include +#include +#include +#include + +#include "batkov_f_linear_image_filtering/common/include/common.hpp" + +namespace batkov_f_linear_image_filtering { + +namespace { + +float ApplyKernel(const Kernel &kernel, const Image &image, size_t row, size_t col, size_t ch) { + const size_t width = image.width; + const size_t height = image.height; + const size_t channels = image.channels; + + float sum = 0.0F; + + for (size_t ky = 0; ky < 3; ky++) { + for (size_t kx = 0; kx < 3; kx++) { + size_t py = row + ky - 1; + size_t px = col + kx - 1; + + py = std::max(py, 0); + py = std::min(py, height - 1); + px = std::max(px, 0); + px = std::min(px, width - 1); + + auto index = (((py * width) + px) * channels) + ch; + sum += static_cast(image.data[index]) * kernel[ky][kx]; + } + } + + return sum; +} + +void CopyBlockData(const Image &image, Image &block, size_t start_row) { + for (size_t row = 0; row < block.height; row++) { + size_t global_row = start_row + row; + + for (size_t col = 0; col < image.width; col++) { + for (size_t ch = 0; ch < image.channels; ch++) { + size_t local_index = ((row * image.width + col) * image.channels) + ch; + size_t global_index = ((global_row * image.width + col) * image.channels) + ch; + + block.data[local_index] = image.data[global_index]; + } + } + } +} + +void ProcessBlock(const Image &block, Image &result_block, const Kernel &kernel, size_t start_row, size_t local_start) { + for (size_t row = 0; row < result_block.height; row++) { + size_t row_in_block = row + (start_row - local_start); + + for (size_t col = 0; col < block.width; col++) { + for (size_t ch = 0; ch < block.channels; ch++) { + float val = ApplyKernel(kernel, block, row_in_block, col, ch); + size_t index = ((row * block.width + col) * block.channels) + ch; + result_block.data[index] = static_cast(std::clamp(val, 0.0F, 255.0F)); + } + } + } +} + +void CopyBlockToOutput(const Image &result_block, Image &output, size_t start_row) { + for (size_t row = 0; row < result_block.height; row++) { + for (size_t col = 0; col < result_block.width; col++) { + for (size_t ch = 0; ch < result_block.channels; ch++) { + size_t output_index = (((start_row + row) * result_block.width + col) * result_block.channels) + ch; + size_t block_index = ((row * result_block.width + col) * result_block.channels) + ch; + output.data[output_index] = result_block.data[block_index]; + } + } + } +} + +void GatherResultsFromProcesses(size_t mpi_size, size_t rows_per_process, size_t remainder, const Image &result_block, + size_t start_row, Image &output) { + CopyBlockToOutput(result_block, output, start_row); + + for (size_t proc = 1; proc < mpi_size; proc++) { + size_t p_start = (proc * rows_per_process) + std::min(proc, remainder); + size_t p_end = p_start + rows_per_process + (proc < remainder ? 1 : 0); + size_t p_rows = p_end - p_start; + size_t p_data_size = output.width * p_rows * output.channels; + + std::vector recv_buffer(p_data_size); + MPI_Recv(recv_buffer.data(), static_cast(p_data_size), MPI_UNSIGNED_CHAR, static_cast(proc), 0, + MPI_COMM_WORLD, MPI_STATUS_IGNORE); + + for (size_t row = 0; row < p_rows; row++) { + for (size_t col = 0; col < output.width; col++) { + for (size_t ch = 0; ch < output.channels; ch++) { + size_t output_index = (((p_start + row) * output.width + col) * output.channels) + ch; + size_t recv_buf_index = ((row * output.width + col) * output.channels) + ch; + output.data[output_index] = recv_buffer[recv_buf_index]; + } + } + } + } +} + +} // namespace + +BatkovFLinearImageFilteringMPI::BatkovFLinearImageFilteringMPI(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = Image(); +} + +bool BatkovFLinearImageFilteringMPI::ValidationImpl() { + return (!GetInput().data.empty()) && (GetInput().width > 0) && (GetInput().height > 0); +} + +bool BatkovFLinearImageFilteringMPI::PreProcessingImpl() { + kernel_ = {{1.0F / 16.0F, 2.0F / 16.0F, 1.0F / 16.0F}, + {2.0F / 16.0F, 4.0F / 16.0F, 2.0F / 16.0F}, + {1.0F / 16.0F, 2.0F / 16.0F, 1.0F / 16.0F}}; + + return true; +} + +bool BatkovFLinearImageFilteringMPI::RunImpl() { + int int_rank = 0; + int int_size = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &int_rank); + MPI_Comm_size(MPI_COMM_WORLD, &int_size); + + const auto rank = static_cast(int_rank); + const auto size = static_cast(int_size); + + auto &input = GetInput(); + size_t width = input.width; + size_t height = input.height; + size_t channels = input.channels; + + const size_t kernel_size = 3; + const size_t half = kernel_size / 2; + + size_t rows_per_process = height / size; + size_t remainder = height % size; + size_t start_row = (rank * rows_per_process) + std::min(rank, remainder); + size_t end_row = start_row + rows_per_process + (rank < remainder ? 1 : 0); + + size_t local_start = (start_row > half) ? start_row - half : 0; + size_t local_end = (end_row + half < height) ? end_row + half : height; + + Image block; + block.width = width; + block.height = local_end - local_start; + block.channels = channels; + block.data.resize(block.width * block.height * block.channels); + CopyBlockData(input, block, local_start); + + Image result_block; + result_block.width = width; + result_block.height = end_row - start_row; + result_block.channels = channels; + result_block.data.resize(result_block.width * result_block.height * result_block.channels); + ProcessBlock(block, result_block, kernel_, start_row, local_start); + + Image result; + result.width = width; + result.height = height; + result.channels = channels; + result.data.resize(width * height * channels); + + if (rank == 0) { + GatherResultsFromProcesses(size, rows_per_process, remainder, result_block, start_row, result); + } else { + MPI_Send(result_block.data.data(), static_cast(result_block.data.size()), MPI_UNSIGNED_CHAR, 0, 0, + MPI_COMM_WORLD); + } + + MPI_Barrier(MPI_COMM_WORLD); + + MPI_Bcast(result.data.data(), static_cast(result.data.size()), MPI_UNSIGNED_CHAR, 0, MPI_COMM_WORLD); + GetOutput() = std::move(result); + + MPI_Barrier(MPI_COMM_WORLD); + return true; +} + +bool BatkovFLinearImageFilteringMPI::PostProcessingImpl() { + return true; +} + +} // namespace batkov_f_linear_image_filtering diff --git a/tasks/batkov_f_linear_image_filtering/report.md b/tasks/batkov_f_linear_image_filtering/report.md index e69de29bb2..5172cc19c1 100644 --- a/tasks/batkov_f_linear_image_filtering/report.md +++ b/tasks/batkov_f_linear_image_filtering/report.md @@ -0,0 +1,386 @@ +# Линейная фильтрация изображений (блочное разбиение). Ядро Гаусса 3x3. + +- **Студент**: Батьков Филипп Владиславович, группа 3823Б1ПР3 +- **Технология**: SEQ | MPI +- **Вариант**: 28 + +## 1. Введение + +Цель работы — разработать и реализовать линейной фильтраци изображений двумя способами: + +- **последовательная (SEQ)** реализация на одном процессе; +- **параллельная (MPI)** реализация с блочным распараллеливанием изображения. + +Дополнительно необходимо: + +- реализовать вспомогательную инфраструктуру для генерации случайного и детерминированного шума, а также для построения гауссовых ядер; +- реализовать детектор "размытия" изображения на основе дисперсии лапласиана для проверки корректности фильтрации; +- написать функциональные и производительные тесты и сравнить поведение SEQ и MPI реализаций на реальных изображениях. + +## 2. Постановка задачи + +На вход алгоритма подаётся сгенерируемое программно цветное изображение. + +Типы входных и выходных данных: + +```cpp +struct Image { + std::vector data; + size_t width; + size_t height; + size_t channels; +}; + +using InType = Image; // исходное изображение +using OutType = Image; // сглаженное изображение +``` + +Структура `Image` содержит: + +- размеры изображения `width`, `height`; +- количество каналов `channels` (1, 3 или 4); +- вектор байтов `std::vector data`, хранящий пиксели в формате (RGB[A]). + +Задача: по входному изображению построить новое изображение, на которое применён **оператор фильтрации** с фиксированным ядром 3×3. Полученное изображение должно быть более чистым, что проверяется при помощи дисперсии Лапласиана. + +## 3. Базовый алгоритм (последовательная версия) + +Последовательная реализация находится в `seq/src/ops_seq.cpp`, класс `BatkovFLinearImageFilteringSEQ`. + +### 3.1. Формирование гауссова ядра + +Гауссово ядро размером `3×3` задается прямым образом в функции `PreProcessingImpl`: + +```cpp +kernel_ = {{1.0F / 16.0F, 2.0F / 16.0F, 1.0F / 16.0F}, + {2.0F / 16.0F, 4.0F / 16.0F, 2.0F / 16.0F}, + {1.0F / 16.0F, 2.0F / 16.0F, 1.0F / 16.0F}}; +``` + +Это классическое дискретное гауссово ядро, нормированное так, чтобы сумма коэффициентов была равна 1. + +### 3.2. Схема свёртки + +Алгоритм последовательного сглаживания (`BatkovFLinearImageFilteringSEQ::RunImpl`) выполняет двумерную свёртку исходного изображения с гауссовым ядром: + +- извлекаются размеры `width`, `height`, количество каналов `channels` и массив пикселей `img_data`; +- создаётся выходной буфер `temp` размером `width * height * channels`; +- для каждого пикселя `(row, col)` и каждого канала `ch` вычисляется новое значение как взвешенная сумма соседних пикселей в окрестности `3×3`: + +```cpp +for (size_t row = 0; row < height; row++) { +for (size_t col = 0; col < width; col++) { + for (size_t ch = 0; ch < channels; ch++) { + float val = ApplyKernel(kernel_, GetInput(), row, col, ch); + size_t index = (((row * width) + col) * channels) + ch; + GetOutput().data[index] = static_cast(std::clamp(val, 0.0F, 255.0F)); + } +} +} +``` + +Границы обрабатываются по схеме **зеркального отражения**: индексы, выходящие за границу, прижимаются к диапазону `[0, width-1]` / `[0, height-1]`. + +## 4. Схема распараллеливания (MPI) + +Параллельная реализация описана в `mpi/src/ops_mpi.cpp`, класс `BatkovFLinearImageFilteringMPI`. + +### 4.1. Общая идея + +Распараллеливание выполняется на блоки с определнным количеством строк: + +- все процессы знают размеры изображения и входные данные (`GetInput()` одинаков на всех рангах); +- по вертикали (`height`) изображение делится на блоки строк между процессами (почти равные по количеству строк, остаток раздается первым процессам); +- каждый процесс обрабатывает свои строки, но для корректной свёртки с ядром 3×3 ему нужны также **дополнительные строки сверху и снизу** ("halo"-область); +- после локальной обработки каждый процесс отправляет свой фрагмент результата процессу 0, который собирает полное изображение и затем рассылает его всем процессам. + +### 4.2. Определение ранга и размеров + +В начале `RunImpl` выполняется стандартная инициализация ранга и количетсва процессов MPI: + +```cpp +int int_rank = 0; +int int_size = 0; +MPI_Comm_rank(MPI_COMM_WORLD, &int_rank); +MPI_Comm_size(MPI_COMM_WORLD, &int_size); + +const auto rank = static_cast(int_rank); +const auto size = static_cast(int_size); + +auto &input = GetInput(); +size_t width = input.width; +size_t height = input.height; +size_t channels = input.channels; +``` + +Затем читаются параметры входного изображения `width`, `height`, `channels` и ссылка на исходные данные. + +### 4.3. Разбиение на блоки + +Количество строк на процесс и диапазон строк для текущего ранга: + +```cpp +size_t rows_per_process = height / size; +size_t remainder = height % size; + +size_t start_row = rank * rows_per_process + std::min(rank, remainder); +size_t end_row = start_row + rows_per_process + (rank < remainder ? 1 : 0); +``` + +Такой подход гарантирует, что первые `remainder` процессов получат на одну строку больше. + +### 4.4. Halo-область для свёртки + +Чтобы корректно посчитать свёртку вблизи горизонтальных границ локального блока, каждый процесс расширяет свой диапазон за счёт соседних строк: + +```cpp +const size_t kernel_size = 3; +const size_t half = kernel_size / 2; + +size_t local_start = (start_row > half) ? start_row - half : 0; +size_t local_end = (end_row + half < height) ? end_row + half : height; +size_t local_height = local_end - local_start; +``` + +### 4.5. Копирование локальной области + +Функция `CopyBlockData` копирует необходимые строки исходного изображения в новый блок: + +```cpp +Image block; +block.width = width; +block.height = local_end - local_start; +block.channels = channels; +block.data.resize(block.width * block.height * block.channels); +CopyBlockData(input, block, local_start); +``` + +Каждая строка `global_row` из диапазона `[local_start, local_end)` копируется в соответствующую строку локального блока. + +### 4.6. Локальная свёртка + +Функция `ProcessBlock` выполняет свёртку только для строк одного блока `[start_row, end_row)`, используя halo-строки из ранее скопированного `block`: + +```cpp +Image result_block; +result_block.width = width; +result_block.height = end_row - start_row; +result_block.channels = channels; +result_block.data.resize(result_block.width * result_block.height * result_block.channels); +ProcessBlock(block, result_block, kernel_, start_row, local_start); +``` + +Внутри функции логика свёртки полностью аналогична последовательной версии, но индексы `row_in_block` сдвинуты на `local_start`. + +### 4.7. Сборка результата на процессе 0 + +Процесс 0 собирает фрагменты от всех процессов с помощью вспомогательных функций: + +```cpp +Image result; +result.width = width; +result.height = height; +result.channels = channels; +result.data.resize(width * height * channels); + +if (rank == 0) { + GatherResultsFromProcesses(size, rows_per_process, remainder, result_block, start_row, result); +} else { + MPI_Send(result_block.data.data(), static_cast(result_block.data.size()), MPI_UNSIGNED_CHAR, 0, 0, + MPI_COMM_WORLD); +} +``` + +Функция `GatherResultsFromProcesses` на процессе 0: + +- сначала копирует локальный блок процесса 0 в `result` (`CopyBlockToOutput`); +- затем в цикле по `proc = 1 .. size-1` принимает фрагменты через `MPI_Recv` и раскладывает их на нужные позиции в `result`. + +### 4.8. Рассылка результата всем процессам + +Чтобы функциональные тесты могли проверять результат на каждом процессе, итоговое изображение рассылается всем рангам через `MPI_Bcast`: + +```cpp +MPI_Barrier(MPI_COMM_WORLD); +MPI_Bcast(result.data.data(), static_cast(result.data.size()), MPI_UNSIGNED_CHAR, 0, MPI_COMM_WORLD); +GetOutput() = std::move(result); +MPI_Barrier(MPI_COMM_WORLD); +``` + +Таким образом, после окончания `RunImpl` **все процессы** содержат идентичный результат сглаживания в `GetOutput()`. + +## 5. Детали реализации и структура проекта + +Структура каталога задачи: + +```text +tasks/batkov_f_batkov_f_linear_image_filteringimage_smoothing/ +├── common +│ └── include +│ └── common.hpp +├── info.json +├── mpi +│ ├── include +│ │ └── ops_mpi.hpp +│ └── src +│ └── ops_mpi.cpp +├── seq +│ ├── include +│ │ └── ops_seq.hpp +│ └── src +│ └── ops_seq.cpp +├── settings.json +└── tests + ├── functional + │ └── main.cpp + └── performance + └── main.cpp +``` + +Основные классы: + +- `BatkovFLinearImageFilteringSEQ` — последовательная реализация; +- `BatkovFLinearImageFilteringMPI` — MPI-реализация с блочным разбиением; +- `BatkovFRunFuncTestsProcesses3` — функциональные тесты (сравнение с детектором сглаженности); +- `BatkovFRunPerfTestProcesses3` — тесты производительности. + +Функция `CalcLaplacianVariance` используется в обоих тестах для проверки, что изображение действительно стало более чистым. + +```cpp +static float CalcLaplacianVariance(const Image& image) +{ + std::vector gray(image.width * image.height); + + const auto &data = image.data; + size_t width = image.width; + size_t height = image.height; + size_t channels = image.channels; + + if (channels == 1) { + for (size_t i = 0; i < width * height; i++) { + gray[i] = static_cast(data[i]); + } + } else { + for (size_t i = 0; i < width * height; i++) { + size_t idx = i * channels; + auto r = static_cast(data[idx + 0]); + auto g = static_cast(data[idx + 1]); + auto b = static_cast(data[idx + 2]); + + gray[i] = (0.299F * r) + (0.587F * g) + (0.114F * b); + } + } + + std::vector laplacian(width * height, 0.0F); + for (size_t y_px = 1; y_px < height - 1; y_px++) { + for (size_t x_px = 1; x_px < width - 1; x_px++) { + size_t idx = (y_px * width) + x_px; + + float value = -gray[((y_px - 1) * width) + x_px] - gray[(y_px * width) + (x_px - 1)] + (4.0F * gray[idx]) - + gray[(y_px * width) + (x_px + 1)] - gray[((y_px + 1) * width) + x_px]; + + laplacian[idx] = value; + } + } + + float mean = 0.0F; + for (size_t i = 0; i < width * height; i++) { + mean += laplacian[i]; + } + mean /= static_cast(width * height); + + float variance = 0.0F; + for (size_t i = 0; i < width * height; i++) { + float diff = laplacian[i] - mean; + variance += diff * diff; + } + variance /= static_cast(width * height); + + return variance; +} +``` + +Алгоритм: + +1. Перевод изображения в **градации серого** либо берётся единственный канал, либо используется стандартная формула (Y = 0.299 R + 0.587 G + 0.114 B). +2. Вычисление **лапласиана** в каждой внутренней точке (5-точечный шаблон): + +```cpp +float value = -gray[(y-1)*width + x] - gray[y*width + (x-1)] + + 4.0F * gray[idx] + - gray[y*width + (x+1)] - gray[(y+1)*width + x]; +``` + +3. Подсчёт средней и дисперсии значений лапласиана по всему изображению. + +## 6. Экспериментальная среда + +| Компонент | Значение | +|-----------|----------------------------------------| +| CPU | Apple M2 (8 ядер) | +| RAM | 16 GB | +| ОС | macOS 15.3.1 | +| Компилятор| g++ (через CMake), стандарт C++20 | +| MPI | mpirun (Open MPI) 5.0.8 | + +Тестовые данные: + +1. **Функциональные тесты** (`tests/functional/main.cpp`): + - изображения с шумом разного размера генерируются случайным образом; + - для каждого изображения задаётся начальное значение размытия `preprocess_blur_value_`; + - для каждого теста запускаются обе реализации: SEQ и MPI; + - в конце еще раз подсчитывается значение размытия и сравнивается с начальным. + +2. **Тесты производительности** (`tests/performance/main.cpp`): + - изображение с детерминированным шумом генерируется на основе хеша; + - тестовый фреймворк `BaseRunPerfTests` автоматически прогоняет SEQ и MPI-версии в различных режимах запуска (в т.ч. `task_run` и `pipeline`) и для разного числа процессов. + +## 7. Результаты и обсуждение + +### 7.1. Корректность + +- Функциональные тесты проверяют, что результат сглаживания удовлетворяет критерию `(preprocess_blur_value_ / post_process_blur_value) > 2.0F`. +- Для каждого тестового изображения по результатам работы SEQ и MPI реализаций детектор размытия выдаёт одинаковый ответ, что говорит о **функциональной эквивалентности** алгоритмов. + +### 7.2. Производительность + +**pipeline:** + +| Mode | Count | Time, s | Speedup | Efficiency | +| ---- | ----- | ------- | ------- | ---------- | +| SEQ | 1 | 0.3637 | 1.00 | N/A | +| MPI | 1 | 0.4688 | 0.78 | 78.0% | +| MPI | 2 | 0.2799 | 1.30 | 65.0% | +| MPI | 4 | 0.1823 | 1.99 | 49.8% | +| MPI | 8 | 0.2522 | 1.44 | 18.0% | + +**task_run:** + +| Mode | Count | Time, s | Speedup | Efficiency | +| ---- | ----- | ------- | ------- | ---------- | +| SEQ | 1 | 0.3629 | 1.00 | N/A | +| MPI | 1 | 0.4571 | 0.79 | 79.3% | +| MPI | 2 | 0.2742 | 1.32 | 66.2% | +| MPI | 4 | 0.1830 | 1.98 | 49.6% | +| MPI | 8 | 0.3299 | 1.10 | 13.7% | + +- При запуске на одном процессе MPI-реализация ожидаемо медленнее SEQ-за счёт накладных расходов на инициализацию MPI и обмен данными. +- При увеличении числа процессов наблюдается уменьшение времени выполнения MPI-версии до определённого предела: строковое разбиение хорошо масштабируется по числу процессов, пока +коммуникационные расходы не начинают преобладать. +- Эффективность распараллеливания сильно зависит от размеров изображения: чем больше пикселей обрабатывает каждый процесс, тем лучше соотношение "вычисления/коммуникации". + +## 8. Заключение + +В рамках работы реализованы: + +1. **Последовательный алгоритм** гауссова сглаживания изображения с ядром 5×5, корректно обрабатывающий границы кадра. +2. **Параллельная MPI-реализация**, использующая разбиение по строкам и halo-область для точного воспроизведения результата свёртки на каждом процессе. +3. **Функциональные и производительные тесты**, демонстрирующие корректность и исследующие поведение алгоритма при разных режимах запуска. + +MPI-реализация показывает выигрыш по времени при достаточно больших изображениях и числе процессов, однако эффект ограничивается ростом накладных расходов на синхронизацию и передачу блоков изображения. Тем не менее, предложенный подход легко масштабируется и может быть расширен для более сложных фильтров (большие ядра, последовательность нескольких свёрток) и трёхмерных изображений. + +## 10. Источники + +1. [Материалы курса](https://learning-process.github.io/parallel_programming_course/ru/common_information/report.html) +2. [Документация Open MPI](https://www.open-mpi.org/doc/) +3. [MPI стандарт](https://www.mpi-forum.org/) diff --git a/tasks/batkov_f_linear_image_filtering/seq/include/ops_seq.hpp b/tasks/batkov_f_linear_image_filtering/seq/include/ops_seq.hpp index 95ede03a48..3ae8a915f1 100644 --- a/tasks/batkov_f_linear_image_filtering/seq/include/ops_seq.hpp +++ b/tasks/batkov_f_linear_image_filtering/seq/include/ops_seq.hpp @@ -1,7 +1,5 @@ #pragma once -#include - #include "batkov_f_linear_image_filtering/common/include/common.hpp" #include "task/include/task.hpp" @@ -20,7 +18,7 @@ class BatkovFLinearImageFilteringSEQ : public BaseTask { bool RunImpl() override; bool PostProcessingImpl() override; - std::vector> kernel_; + Kernel kernel_; }; } // namespace batkov_f_linear_image_filtering diff --git a/tasks/batkov_f_linear_image_filtering/seq/src/ops_seq.cpp b/tasks/batkov_f_linear_image_filtering/seq/src/ops_seq.cpp index 6d654eb346..830763b716 100644 --- a/tasks/batkov_f_linear_image_filtering/seq/src/ops_seq.cpp +++ b/tasks/batkov_f_linear_image_filtering/seq/src/ops_seq.cpp @@ -1,10 +1,8 @@ #include "batkov_f_linear_image_filtering/seq/include/ops_seq.hpp" #include -#include #include #include -#include #include "batkov_f_linear_image_filtering/common/include/common.hpp" @@ -12,39 +10,34 @@ namespace batkov_f_linear_image_filtering { namespace { -Pixel ApplyKernel(const std::vector>& kernel, const Image& image, size_t cy, size_t cx) { - float r_ch = 0.0F; - float g_ch = 0.0F; - float b_ch = 0.0F; +float ApplyKernel(const Kernel &kernel, const Image &image, size_t row, size_t col, size_t ch) { + const size_t width = image.width; + const size_t height = image.height; + const size_t channels = image.channels; - for (size_t ky = 0; ky < kernel.size(); ky++) { - for (size_t kx = 0; kx < kernel.size(); kx++) { - size_t px = cx + kx - 1; - size_t py = cy + ky - 1; + float sum = 0.0F; - px = std::max(0, std::min(px, image.width - 1)); - py = std::max(0, std::min(py, image.height - 1)); + for (size_t ky = 0; ky < 3; ky++) { + for (size_t kx = 0; kx < 3; kx++) { + size_t py = row + ky - 1; + size_t px = col + kx - 1; - size_t pixel_index = (py * image.width) + px; - float kernel_value = kernel[ky][kx]; + py = std::max(py, 0); + py = std::min(py, height - 1); + px = std::max(px, 0); + px = std::min(px, width - 1); - r_ch += static_cast(image.data[pixel_index].r) * kernel_value; - g_ch += static_cast(image.data[pixel_index].g) * kernel_value; - b_ch += static_cast(image.data[pixel_index].b) * kernel_value; + auto index = (((py * width) + px) * channels) + ch; + sum += static_cast(image.data[index]) * kernel[ky][kx]; } } - Pixel result; - result.r = static_cast(r_ch); - result.g = static_cast(g_ch); - result.b = static_cast(b_ch); - - return result; + return sum; } } // namespace -BatkovFLinearImageFilteringSEQ::BatkovFLinearImageFilteringSEQ(const InType& in) { +BatkovFLinearImageFilteringSEQ::BatkovFLinearImageFilteringSEQ(const InType &in) { SetTypeOfTask(GetStaticTypeOfTask()); GetInput() = in; GetOutput() = Image{}; @@ -55,31 +48,14 @@ bool BatkovFLinearImageFilteringSEQ::ValidationImpl() { } bool BatkovFLinearImageFilteringSEQ::PreProcessingImpl() { - kernel_.resize(3); - for (auto& v : kernel_) { - v.resize(3); - } - - float sum = 0.0F; - float sigma = 1.0F; - - for (int col = -1; col <= 1; col++) { - for (int row = -1; row <= 1; row++) { - kernel_[col + 1][row + 1] = - std::exp(-static_cast((row * row) + (col * col)) / (2.0F * sigma * sigma)); - sum += kernel_[col + 1][row + 1]; - } - } - - for (size_t col = 0; col < 3; col++) { - for (size_t row = 0; row < 3; row++) { - kernel_[col][row] /= sum; - } - } + kernel_ = {{1.0F / 16.0F, 2.0F / 16.0F, 1.0F / 16.0F}, + {2.0F / 16.0F, 4.0F / 16.0F, 2.0F / 16.0F}, + {1.0F / 16.0F, 2.0F / 16.0F, 1.0F / 16.0F}}; GetOutput().width = GetInput().width; GetOutput().height = GetInput().height; - GetOutput().data.resize(GetInput().width * GetInput().height); + GetInput().channels = GetInput().channels; + GetOutput().data.resize(GetInput().width * GetInput().height * GetInput().channels); return true; } @@ -87,11 +63,15 @@ bool BatkovFLinearImageFilteringSEQ::PreProcessingImpl() { bool BatkovFLinearImageFilteringSEQ::RunImpl() { size_t width = GetInput().width; size_t height = GetInput().height; - - for (size_t col = 0; col < height; col++) { - for (size_t row = 0; row < width; row++) { - auto result = ApplyKernel(kernel_, GetInput(), col, row); - GetOutput().data[((col * width) + row)] = result; + size_t channels = GetInput().channels; + + for (size_t row = 0; row < height; row++) { + for (size_t col = 0; col < width; col++) { + for (size_t ch = 0; ch < channels; ch++) { + float val = ApplyKernel(kernel_, GetInput(), row, col, ch); + size_t index = (((row * width) + col) * channels) + ch; + GetOutput().data[index] = static_cast(std::clamp(val, 0.0F, 255.0F)); + } } } diff --git a/tasks/batkov_f_linear_image_filtering/tests/functional/main.cpp b/tasks/batkov_f_linear_image_filtering/tests/functional/main.cpp index 490492be4e..f1275aa586 100644 --- a/tasks/batkov_f_linear_image_filtering/tests/functional/main.cpp +++ b/tasks/batkov_f_linear_image_filtering/tests/functional/main.cpp @@ -11,7 +11,6 @@ #include "batkov_f_linear_image_filtering/mpi/include/ops_mpi.hpp" #include "batkov_f_linear_image_filtering/seq/include/ops_seq.hpp" #include "util/include/func_test_util.hpp" -#include "util/include/util.hpp" namespace batkov_f_linear_image_filtering { @@ -21,7 +20,8 @@ class BatkovFRunFuncTestsProcesses3 : public ppc::util::BaseRunFuncTests(test_param); std::string p1 = std::to_string(std::get<1>(test_param)); std::string p2 = std::to_string(std::get<2>(test_param)); - return p0 + "_" + p1 + "x" + p2; + std::string p3 = std::to_string(std::get<3>(test_param)); + return p0 + "_" + p1 + "x" + p2 + "x" + p3; } protected: @@ -29,14 +29,13 @@ class BatkovFRunFuncTestsProcesses3 : public ppc::util::BaseRunFuncTests(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); input_data_.width = std::get<1>(params); input_data_.height = std::get<2>(params); + input_data_.channels = std::get<3>(params); - size_t size = input_data_.width * input_data_.height; + size_t size = input_data_.width * input_data_.height * input_data_.channels; input_data_.data.resize(size); for (size_t i = 0; i < size; ++i) { - input_data_.data[i].r = dis_(gen_); - input_data_.data[i].b = dis_(gen_); - input_data_.data[i].g = dis_(gen_); + input_data_.data[i] = dis_(gen_); } preprocess_blur_value_ = CalcLaplacianVariance(input_data_); @@ -48,13 +47,21 @@ class BatkovFRunFuncTestsProcesses3 : public ppc::util::BaseRunFuncTests(data[i].r); - auto g = static_cast(data[i].g); - auto b = static_cast(data[i].b); - - gray[i] = (0.299F * r) + (0.587F * g) + (0.114F * b); + if (channels == 1) { + for (size_t i = 0; i < width * height; i++) { + gray[i] = static_cast(data[i]); + } + } else { + for (size_t i = 0; i < width * height; i++) { + size_t idx = i * channels; + auto r = static_cast(data[idx + 0]); + auto g = static_cast(data[idx + 1]); + auto b = static_cast(data[idx + 2]); + + gray[i] = (0.299F * r) + (0.587F * g) + (0.114F * b); + } } std::vector laplacian(width * height, 0.0F); @@ -106,23 +113,24 @@ class BatkovFRunFuncTestsProcesses3 : public ppc::util::BaseRunFuncTests kTestParam = { - std::make_tuple("tiny_image", 10, 10), std::make_tuple("small_image", 50, 50), - std::make_tuple("medium_image", 100, 100), std::make_tuple("big_image", 300, 300)}; + std::make_tuple("tiny_image", 10, 10, 3), std::make_tuple("small_image", 50, 50, 3), + std::make_tuple("medium_image", 100, 100, 3), std::make_tuple("big_image", 300, 300, 3)}; const auto kTestTasksList = std::tuple_cat(ppc::util::AddFuncTask( - kTestParam, PPC_SETTINGS_batkov_f_linear_image_filtering)); -// ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_batkov_f_image_smoothing)); + kTestParam, PPC_SETTINGS_batkov_f_linear_image_filtering), + ppc::util::AddFuncTask( + kTestParam, PPC_SETTINGS_batkov_f_linear_image_filtering)); const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); const auto kPerfTestName = BatkovFRunFuncTestsProcesses3::PrintFuncTestName; -INSTANTIATE_TEST_SUITE_P(ImageSmoothingTests, BatkovFRunFuncTestsProcesses3, kGtestValues, kPerfTestName); +INSTANTIATE_TEST_SUITE_P(LinearFilteringTests, BatkovFRunFuncTestsProcesses3, kGtestValues, kPerfTestName); } // namespace diff --git a/tasks/batkov_f_linear_image_filtering/tests/performance/main.cpp b/tasks/batkov_f_linear_image_filtering/tests/performance/main.cpp index 2d0aff74ce..6ea69f04a9 100644 --- a/tasks/batkov_f_linear_image_filtering/tests/performance/main.cpp +++ b/tasks/batkov_f_linear_image_filtering/tests/performance/main.cpp @@ -1,40 +1,125 @@ -// #include +#include -// #include "batkov_f_linear_image_filtering/common/include/common.hpp" -// #include "batkov_f_linear_image_filtering/mpi/include/ops_mpi.hpp" -// #include "batkov_f_linear_image_filtering/seq/include/ops_seq.hpp" -// #include "util/include/perf_test_util.hpp" +#include +#include +#include -// namespace batkov_f_linear_image_filtering { +#include "batkov_f_linear_image_filtering/common/include/common.hpp" +#include "batkov_f_linear_image_filtering/mpi/include/ops_mpi.hpp" +#include "batkov_f_linear_image_filtering/seq/include/ops_seq.hpp" +#include "util/include/perf_test_util.hpp" -// class BatkovFRunPerfTestProcesses3 : public ppc::util::BaseRunPerfTests { -// const int kCount_ = 100; -// InType input_data_{}; +namespace batkov_f_linear_image_filtering { -// void SetUp() override { -// input_data_ = kCount_; -// } +class BatkovFRunPerfTestProcesses3 : public ppc::util::BaseRunPerfTests { + InType input_data_; + float preprocess_blur_value_ = 0.0F; -// bool CheckTestOutputData(OutType &output_data) final { -// return input_data_ == output_data; -// } + void SetUp() override { + size_t width = 7680; + size_t height = 4320; + size_t channels = 3; -// InType GetTestInputData() final { -// return input_data_; -// } -// }; + input_data_.width = width; + input_data_.height = height; + input_data_.channels = channels; -// TEST_P(BatkovFRunPerfTestProcesses3, RunPerfModes) { -// ExecuteTest(GetParam()); -// } + input_data_.data.resize(width * height * channels); -// const auto kAllPerfTasks = -// ppc::util::MakeAllPerfTasks(PPC_SETTINGS_batkov_f_linear_image_filtering); + for (size_t col = 0; col < width; col += channels) { + for (size_t row = 0; row < height; row += channels) { + unsigned int hash_r = 2654435761U ^ (row * 73856093U); + unsigned int hash_g = 2654435761U ^ (col * 19349663U); + unsigned int hash_b = 2654435761U ^ ((row + col) * 83492791U); -// const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); + hash_r = (hash_r ^ (hash_r >> 13)) * 2654435761U; + hash_g = (hash_g ^ (hash_g >> 13)) * 2654435761U; + hash_b = (hash_b ^ (hash_b >> 13)) * 2654435761U; -// const auto kPerfTestName = BatkovFRunPerfTestProcesses3::CustomPerfTestName; + size_t index = (col * height) + row; + input_data_.data[index + 0] = static_cast((hash_r >> 8) & 0xFF); + input_data_.data[index + 1] = static_cast((hash_g >> 8) & 0xFF); + input_data_.data[index + 2] = static_cast((hash_b >> 8) & 0xFF); + } + } -// INSTANTIATE_TEST_SUITE_P(RunModeTests, BatkovFRunPerfTestProcesses3, kGtestValues, kPerfTestName); + preprocess_blur_value_ = CalcLaplacianVariance(input_data_); + } -// } // namespace batkov_f_linear_image_filtering + static float CalcLaplacianVariance(const Image &image) { + std::vector gray(image.width * image.height); + + const auto &data = image.data; + size_t width = image.width; + size_t height = image.height; + size_t channels = image.channels; + + if (channels == 1) { + for (size_t i = 0; i < width * height; i++) { + gray[i] = static_cast(data[i]); + } + } else { + for (size_t i = 0; i < width * height; i++) { + size_t idx = i * channels; + auto r = static_cast(data[idx + 0]); + auto g = static_cast(data[idx + 1]); + auto b = static_cast(data[idx + 2]); + + gray[i] = (0.299F * r) + (0.587F * g) + (0.114F * b); + } + } + + std::vector laplacian(width * height, 0.0F); + for (size_t y_px = 1; y_px < height - 1; y_px++) { + for (size_t x_px = 1; x_px < width - 1; x_px++) { + size_t idx = (y_px * width) + x_px; + + float value = -gray[((y_px - 1) * width) + x_px] - gray[(y_px * width) + (x_px - 1)] + (4.0F * gray[idx]) - + gray[(y_px * width) + (x_px + 1)] - gray[((y_px + 1) * width) + x_px]; + + laplacian[idx] = value; + } + } + + float mean = 0.0F; + for (size_t i = 0; i < width * height; i++) { + mean += laplacian[i]; + } + mean /= static_cast(width * height); + + float variance = 0.0F; + for (size_t i = 0; i < width * height; i++) { + float diff = laplacian[i] - mean; + variance += diff * diff; + } + variance /= static_cast(width * height); + + return variance; + } + + bool CheckTestOutputData(OutType &output_data) final { + float post_process_blur_value = CalcLaplacianVariance(output_data); + + return (preprocess_blur_value_ / post_process_blur_value) > 2.0F; + } + + InType GetTestInputData() final { + return input_data_; + } +}; + +TEST_P(BatkovFRunPerfTestProcesses3, RunPerfModes) { + ExecuteTest(GetParam()); +} + +const auto kAllPerfTasks = + ppc::util::MakeAllPerfTasks( + PPC_SETTINGS_batkov_f_linear_image_filtering); + +const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); + +const auto kPerfTestName = BatkovFRunPerfTestProcesses3::CustomPerfTestName; + +INSTANTIATE_TEST_SUITE_P(RunModeTests, BatkovFRunPerfTestProcesses3, kGtestValues, kPerfTestName); + +} // namespace batkov_f_linear_image_filtering From 1887cc682142b898622185908b5ecc4fb639fcc7 Mon Sep 17 00:00:00 2001 From: weyland0 Date: Thu, 25 Dec 2025 14:27:31 +0300 Subject: [PATCH 7/7] fix missed header --- tasks/batkov_f_linear_image_filtering/tests/functional/main.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tasks/batkov_f_linear_image_filtering/tests/functional/main.cpp b/tasks/batkov_f_linear_image_filtering/tests/functional/main.cpp index f1275aa586..d6398b0675 100644 --- a/tasks/batkov_f_linear_image_filtering/tests/functional/main.cpp +++ b/tasks/batkov_f_linear_image_filtering/tests/functional/main.cpp @@ -11,6 +11,7 @@ #include "batkov_f_linear_image_filtering/mpi/include/ops_mpi.hpp" #include "batkov_f_linear_image_filtering/seq/include/ops_seq.hpp" #include "util/include/func_test_util.hpp" +#include "util/include/util.hpp" namespace batkov_f_linear_image_filtering {