From 115bd9032ff30a01a00b4f1accdb5723ba719b79 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Apr 2026 13:48:07 +0000 Subject: [PATCH 1/4] Initial plan From 674bd661a4cd257be391c3e32236a961b65ac922 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Apr 2026 14:05:41 +0000 Subject: [PATCH 2/4] Add homework corrections for StachnissLab Modern C++ course (HW1, HW2, HW3) Agent-Logs-Url: https://github.com/zappateinstein/C-_tutorial/sessions/d20adc7e-20b3-4e5e-90d3-f61058e1178c Co-authored-by: zappateinstein <107572402+zappateinstein@users.noreply.github.com> --- .gitignore | 32 ++++ HW1/CMakeLists.txt | 20 +++ HW1/src/CMakeLists.txt | 3 + HW1/src/main.cpp | 24 +++ HW1/src/string_tools.cpp | 34 +++++ HW1/src/string_tools.h | 21 +++ HW1/tests/CMakeLists.txt | 6 + HW1/tests/test_string_tools.cpp | 52 +++++++ HW2/CMakeLists.txt | 20 +++ HW2/data/sample.pgm | 11 ++ HW2/src/CMakeLists.txt | 5 + HW2/src/application1/CMakeLists.txt | 2 + HW2/src/application1/main.cpp | 21 +++ HW2/src/application2/CMakeLists.txt | 2 + HW2/src/application2/main.cpp | 23 +++ HW2/src/application3/CMakeLists.txt | 2 + HW2/src/application3/main.cpp | 23 +++ HW2/src/application4/CMakeLists.txt | 2 + HW2/src/application4/main.cpp | 25 +++ HW2/src/igg_image/CMakeLists.txt | 1 + HW2/src/igg_image/image.cpp | 89 +++++++++++ HW2/src/igg_image/image.h | 56 +++++++ HW2/src/igg_image/io_tools.h | 66 ++++++++ HW2/tests/CMakeLists.txt | 8 + HW2/tests/test_image.cpp | 144 ++++++++++++++++++ HW3/CMakeLists.txt | 20 +++ HW3/data/sample.ppm | 20 +++ HW3/src/CMakeLists.txt | 1 + HW3/src/igg_image/CMakeLists.txt | 5 + HW3/src/igg_image/image.cpp | 62 ++++++++ HW3/src/igg_image/image.h | 48 ++++++ .../igg_image/io_strategies/CMakeLists.txt | 1 + .../igg_image/io_strategies/dummy_strategy.h | 20 +++ .../igg_image/io_strategies/ppm_strategy.cpp | 59 +++++++ .../igg_image/io_strategies/ppm_strategy.h | 15 ++ HW3/src/igg_image/io_strategies/strategy.h | 27 ++++ HW3/src/igg_image/main.cpp | 32 ++++ HW3/tests/CMakeLists.txt | 8 + HW3/tests/test_image.cpp | 115 ++++++++++++++ README.md | 123 ++++++++++++++- 40 files changed, 1247 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 HW1/CMakeLists.txt create mode 100644 HW1/src/CMakeLists.txt create mode 100644 HW1/src/main.cpp create mode 100644 HW1/src/string_tools.cpp create mode 100644 HW1/src/string_tools.h create mode 100644 HW1/tests/CMakeLists.txt create mode 100644 HW1/tests/test_string_tools.cpp create mode 100644 HW2/CMakeLists.txt create mode 100644 HW2/data/sample.pgm create mode 100644 HW2/src/CMakeLists.txt create mode 100644 HW2/src/application1/CMakeLists.txt create mode 100644 HW2/src/application1/main.cpp create mode 100644 HW2/src/application2/CMakeLists.txt create mode 100644 HW2/src/application2/main.cpp create mode 100644 HW2/src/application3/CMakeLists.txt create mode 100644 HW2/src/application3/main.cpp create mode 100644 HW2/src/application4/CMakeLists.txt create mode 100644 HW2/src/application4/main.cpp create mode 100644 HW2/src/igg_image/CMakeLists.txt create mode 100644 HW2/src/igg_image/image.cpp create mode 100644 HW2/src/igg_image/image.h create mode 100644 HW2/src/igg_image/io_tools.h create mode 100644 HW2/tests/CMakeLists.txt create mode 100644 HW2/tests/test_image.cpp create mode 100644 HW3/CMakeLists.txt create mode 100644 HW3/data/sample.ppm create mode 100644 HW3/src/CMakeLists.txt create mode 100644 HW3/src/igg_image/CMakeLists.txt create mode 100644 HW3/src/igg_image/image.cpp create mode 100644 HW3/src/igg_image/image.h create mode 100644 HW3/src/igg_image/io_strategies/CMakeLists.txt create mode 100644 HW3/src/igg_image/io_strategies/dummy_strategy.h create mode 100644 HW3/src/igg_image/io_strategies/ppm_strategy.cpp create mode 100644 HW3/src/igg_image/io_strategies/ppm_strategy.h create mode 100644 HW3/src/igg_image/io_strategies/strategy.h create mode 100644 HW3/src/igg_image/main.cpp create mode 100644 HW3/tests/CMakeLists.txt create mode 100644 HW3/tests/test_image.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..da5f7c1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +# Build directories +HW1/build/ +HW2/build/ +HW3/build/ + +# Compiled binaries +HW1/bin/ +HW2/bin/ +HW3/bin/ + +# CMake generated files +CMakeCache.txt +CMakeFiles/ +cmake_install.cmake +Makefile +CTestTestfile.cmake +Testing/ + +# Static / shared libraries +*.a +*.so +*.so.* + +# Editor / IDE files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# macOS +.DS_Store diff --git a/HW1/CMakeLists.txt b/HW1/CMakeLists.txt new file mode 100644 index 0000000..771a982 --- /dev/null +++ b/HW1/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.1) +project(homework1) + +set(CMAKE_CXX_STANDARD 11) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() + +set(CMAKE_CXX_FLAGS "-Wall -Wextra") +set(CMAKE_CXX_FLAGS_DEBUG "-g") +set(CMAKE_CXX_FLAGS_RELEASE "-O2") + +set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) + +include_directories(${PROJECT_SOURCE_DIR}/src) + +add_subdirectory(src) +enable_testing() +add_subdirectory(tests) diff --git a/HW1/src/CMakeLists.txt b/HW1/src/CMakeLists.txt new file mode 100644 index 0000000..efbd6b7 --- /dev/null +++ b/HW1/src/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library(string_tools string_tools.cpp) +add_executable(hw1_main main.cpp) +target_link_libraries(hw1_main string_tools) diff --git a/HW1/src/main.cpp b/HW1/src/main.cpp new file mode 100644 index 0000000..1759c7a --- /dev/null +++ b/HW1/src/main.cpp @@ -0,0 +1,24 @@ +#include "string_tools.h" + +#include + +int main() { + // Demonstrate string tokenization + const std::string csv_line = "hello,world,modern,cpp,course"; + std::cout << "Tokenizing: \"" << csv_line << "\" by ','" << std::endl; + auto tokens = hw1::Tokenize(csv_line, ','); + for (const auto& token : tokens) { + std::cout << " Token: " << token << std::endl; + } + + // Demonstrate word frequency counting + const std::string text = + "the quick brown fox jumps over the lazy dog the fox"; + std::cout << "\nWord frequencies in: \"" << text << "\"" << std::endl; + auto frequencies = hw1::CountWordFrequency(text); + for (const auto& pair : frequencies) { + std::cout << " \"" << pair.first << "\": " << pair.second << std::endl; + } + + return 0; +} diff --git a/HW1/src/string_tools.cpp b/HW1/src/string_tools.cpp new file mode 100644 index 0000000..199f2fc --- /dev/null +++ b/HW1/src/string_tools.cpp @@ -0,0 +1,34 @@ +#include "string_tools.h" + +#include +#include +#include + +namespace hw1 { + +std::vector Tokenize(const std::string& str, char delimiter) { + std::vector tokens; + std::istringstream stream(str); + std::string token; + while (std::getline(stream, token, delimiter)) { + if (!token.empty()) { + tokens.push_back(token); + } + } + return tokens; +} + +std::vector> CountWordFrequency( + const std::string& str) { + std::map freq_map; + std::istringstream stream(str); + std::string word; + while (stream >> word) { + ++freq_map[word]; + } + std::vector> result(freq_map.begin(), + freq_map.end()); + return result; +} + +} // namespace hw1 diff --git a/HW1/src/string_tools.h b/HW1/src/string_tools.h new file mode 100644 index 0000000..e2035fe --- /dev/null +++ b/HW1/src/string_tools.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +namespace hw1 { + +/// Split a string into tokens separated by a delimiter character. +/// @param str The input string to split. +/// @param delimiter The character used as delimiter. +/// @return A vector of substrings (tokens). +std::vector Tokenize(const std::string& str, char delimiter); + +/// Count the frequency of each word in a string. +/// Words are separated by whitespace. +/// @param str The input string. +/// @return A vector of (word, count) pairs, sorted alphabetically by word. +std::vector> CountWordFrequency( + const std::string& str); + +} // namespace hw1 diff --git a/HW1/tests/CMakeLists.txt b/HW1/tests/CMakeLists.txt new file mode 100644 index 0000000..2c67349 --- /dev/null +++ b/HW1/tests/CMakeLists.txt @@ -0,0 +1,6 @@ +include(CTest) +set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) + +add_executable(test_string_tools test_string_tools.cpp) +target_link_libraries(test_string_tools string_tools) +add_test(NAME TestStringTools COMMAND test_string_tools) diff --git a/HW1/tests/test_string_tools.cpp b/HW1/tests/test_string_tools.cpp new file mode 100644 index 0000000..010eb1b --- /dev/null +++ b/HW1/tests/test_string_tools.cpp @@ -0,0 +1,52 @@ +#include "string_tools.h" + +#include +#include +#include +#include + +void TestTokenize() { + auto tokens = hw1::Tokenize("a,b,c", ','); + assert(tokens.size() == 3); + assert(tokens[0] == "a"); + assert(tokens[1] == "b"); + assert(tokens[2] == "c"); + + // Test with empty parts (consecutive delimiters skipped) + auto tokens2 = hw1::Tokenize("hello world", ' '); + assert(tokens2.size() == 2); + assert(tokens2[0] == "hello"); + assert(tokens2[1] == "world"); + + // Test empty string + auto tokens3 = hw1::Tokenize("", ','); + assert(tokens3.empty()); + + std::cout << "TestTokenize: PASSED" << std::endl; +} + +void TestCountWordFrequency() { + auto freq = hw1::CountWordFrequency("the cat sat on the mat the cat"); + // Expect: cat=2, mat=1, on=1, sat=1, the=3 + assert(freq.size() == 5); + for (const auto& wc : freq) { + if (wc.first == "the") assert(wc.second == 3); + if (wc.first == "cat") assert(wc.second == 2); + if (wc.first == "sat") assert(wc.second == 1); + if (wc.first == "on") assert(wc.second == 1); + if (wc.first == "mat") assert(wc.second == 1); + } + + // Test empty string + auto freq2 = hw1::CountWordFrequency(""); + assert(freq2.empty()); + + std::cout << "TestCountWordFrequency: PASSED" << std::endl; +} + +int main() { + TestTokenize(); + TestCountWordFrequency(); + std::cout << "All HW1 tests passed!" << std::endl; + return 0; +} diff --git a/HW2/CMakeLists.txt b/HW2/CMakeLists.txt new file mode 100644 index 0000000..5ed5930 --- /dev/null +++ b/HW2/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.1) +project(homework2) + +set(CMAKE_CXX_STANDARD 11) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() + +set(CMAKE_CXX_FLAGS "-Wall -Wextra -fPIC") +set(CMAKE_CXX_FLAGS_DEBUG "-g") +set(CMAKE_CXX_FLAGS_RELEASE "-O2") + +set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) + +include_directories(${PROJECT_SOURCE_DIR}/src) + +add_subdirectory(src) +enable_testing() +add_subdirectory(tests) diff --git a/HW2/data/sample.pgm b/HW2/data/sample.pgm new file mode 100644 index 0000000..702e3c5 --- /dev/null +++ b/HW2/data/sample.pgm @@ -0,0 +1,11 @@ +P2 +8 8 +255 +0 18 36 54 72 91 109 127 +18 36 54 72 91 109 127 145 +36 54 72 91 109 127 145 163 +54 72 91 109 127 145 163 182 +72 91 109 127 145 163 182 200 +91 109 127 145 163 182 200 218 +109 127 145 163 182 200 218 236 +127 145 163 182 200 218 236 255 diff --git a/HW2/src/CMakeLists.txt b/HW2/src/CMakeLists.txt new file mode 100644 index 0000000..dca2987 --- /dev/null +++ b/HW2/src/CMakeLists.txt @@ -0,0 +1,5 @@ +add_subdirectory(igg_image) +add_subdirectory(application1) +add_subdirectory(application2) +add_subdirectory(application3) +add_subdirectory(application4) diff --git a/HW2/src/application1/CMakeLists.txt b/HW2/src/application1/CMakeLists.txt new file mode 100644 index 0000000..a9aaf25 --- /dev/null +++ b/HW2/src/application1/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(application1 main.cpp) +target_link_libraries(application1 igg_image) diff --git a/HW2/src/application1/main.cpp b/HW2/src/application1/main.cpp new file mode 100644 index 0000000..c264ba6 --- /dev/null +++ b/HW2/src/application1/main.cpp @@ -0,0 +1,21 @@ +#include "igg_image/image.h" + +#include + +/// Application 1: demonstrates the getter and setter via at(). +int main() { + std::cout << "=== Application 1: getter/setter test ===" << std::endl; + igg::Image img(50, 50); + + // Reading through a const reference exercises the const (getter) overload. + const igg::Image& const_img = img; + std::cout << "Before set: img.at(49,49) = " << const_img.at(49, 49) + << std::endl; + + // Assignment through the non-const overload exercises the setter. + img.at(49, 49) = 50; + + std::cout << "After set: img.at(49,49) = " << const_img.at(49, 49) + << std::endl; + return 0; +} diff --git a/HW2/src/application2/CMakeLists.txt b/HW2/src/application2/CMakeLists.txt new file mode 100644 index 0000000..af79286 --- /dev/null +++ b/HW2/src/application2/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(application2 main.cpp) +target_link_libraries(application2 igg_image) diff --git a/HW2/src/application2/main.cpp b/HW2/src/application2/main.cpp new file mode 100644 index 0000000..5e5caea --- /dev/null +++ b/HW2/src/application2/main.cpp @@ -0,0 +1,23 @@ +#include "igg_image/image.h" + +#include + +/// Application 2: reads a PGM file, draws a diagonal, and writes it back. +int main() { + std::cout << "=== Application 2: read/write PGM ===" << std::endl; + igg::Image img; + if (img.FillFromPgm("../data/lena.ascii.pgm")) { + std::cout << "Image loaded: " << img.rows() << " rows x " << img.cols() + << " cols" << std::endl; + // Draw a white diagonal + int diag = std::min(img.rows(), img.cols()); + for (int i = 0; i < diag; ++i) { + img.at(i, i) = 255; + } + img.WriteToPgm("../data/lena_diagonal.ascii.pgm"); + std::cout << "Written to ../data/lena_diagonal.ascii.pgm" << std::endl; + } else { + std::cerr << "Could not open ../data/lena.ascii.pgm" << std::endl; + } + return 0; +} diff --git a/HW2/src/application3/CMakeLists.txt b/HW2/src/application3/CMakeLists.txt new file mode 100644 index 0000000..3d8af46 --- /dev/null +++ b/HW2/src/application3/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(application3 main.cpp) +target_link_libraries(application3 igg_image) diff --git a/HW2/src/application3/main.cpp b/HW2/src/application3/main.cpp new file mode 100644 index 0000000..895d44c --- /dev/null +++ b/HW2/src/application3/main.cpp @@ -0,0 +1,23 @@ +#include "igg_image/image.h" + +#include +#include + +/// Application 3: loads a PGM image and prints its histogram. +int main() { + std::cout << "=== Application 3: histogram ===" << std::endl; + igg::Image img; + if (img.FillFromPgm("../data/lena.ascii.pgm")) { + std::cout << "Image loaded: " << img.rows() << " rows x " << img.cols() + << " cols" << std::endl; + const int kBins = 10; + std::vector hist = img.ComputeHistogram(kBins); + std::cout << "Histogram (" << kBins << " bins):" << std::endl; + for (int i = 0; i < kBins; ++i) { + std::cout << " bin[" << i << "] = " << hist[i] << std::endl; + } + } else { + std::cerr << "Could not open ../data/lena.ascii.pgm" << std::endl; + } + return 0; +} diff --git a/HW2/src/application4/CMakeLists.txt b/HW2/src/application4/CMakeLists.txt new file mode 100644 index 0000000..74498a2 --- /dev/null +++ b/HW2/src/application4/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(application4 main.cpp) +target_link_libraries(application4 igg_image) diff --git a/HW2/src/application4/main.cpp b/HW2/src/application4/main.cpp new file mode 100644 index 0000000..ad7c87b --- /dev/null +++ b/HW2/src/application4/main.cpp @@ -0,0 +1,25 @@ +#include "igg_image/image.h" + +#include + +/// Application 4: demonstrates downscaling and upscaling. +int main() { + std::cout << "=== Application 4: scaling ===" << std::endl; + igg::Image img; + if (img.FillFromPgm("../data/lena.ascii.pgm")) { + std::cout << "Original size: " << img.rows() << " x " << img.cols() + << std::endl; + img.DownScale(2); + std::cout << "After DownScale(2): " << img.rows() << " x " << img.cols() + << std::endl; + img.WriteToPgm("../data/lena_downscaled.ascii.pgm"); + + img.UpScale(2); + std::cout << "After UpScale(2): " << img.rows() << " x " << img.cols() + << std::endl; + img.WriteToPgm("../data/lena_upscaled.ascii.pgm"); + } else { + std::cerr << "Could not open ../data/lena.ascii.pgm" << std::endl; + } + return 0; +} diff --git a/HW2/src/igg_image/CMakeLists.txt b/HW2/src/igg_image/CMakeLists.txt new file mode 100644 index 0000000..446ae03 --- /dev/null +++ b/HW2/src/igg_image/CMakeLists.txt @@ -0,0 +1 @@ +add_library(igg_image image.cpp) diff --git a/HW2/src/igg_image/image.cpp b/HW2/src/igg_image/image.cpp new file mode 100644 index 0000000..44b8337 --- /dev/null +++ b/HW2/src/igg_image/image.cpp @@ -0,0 +1,89 @@ +#include "image.h" + +namespace igg { + +Image::Image() {} + +Image::Image(int rows, int cols) + : rows_(rows), cols_(cols), data_(rows * cols, 0) {} + +int Image::rows() const { return rows_; } + +int Image::cols() const { return cols_; } + +int& Image::at(int row, int col) { + return data_[row * cols_ + col]; +} + +const int& Image::at(int row, int col) const { + return data_[row * cols_ + col]; +} + +bool Image::FillFromPgm(const std::string& file_name) { + io_tools::ImageData img = io_tools::ReadFromPgm(file_name); + if (img.data.empty()) { return false; } + rows_ = img.rows; + cols_ = img.cols; + max_val_ = img.max_val; + data_.swap(img.data); + return true; +} + +void Image::WriteToPgm(const std::string& file_name) { + io_tools::ImageData img; + img.rows = rows_; + img.cols = cols_; + img.max_val = max_val_; + img.data = data_; + io_tools::WriteToPgm(img, file_name); +} + +std::vector Image::ComputeHistogram(int bins) const { + std::vector hist(bins, 0.0f); + for (const int& pixel : data_) { + // Map pixel in [0, max_val_] to a bin index in [0, bins-1]. + int bin = static_cast( + std::ceil(static_cast(pixel) / static_cast(max_val_) * + static_cast(bins))) - + 1; + if (bin < 0) { bin = 0; } + if (bin >= bins) { bin = bins - 1; } + hist[bin]++; + } + return hist; +} + +void Image::DownScale(int scale) { + int new_rows = rows_ / scale; + int new_cols = cols_ / scale; + std::vector new_data(new_rows * new_cols, 0); + for (int row = 0; row < new_rows; ++row) { + for (int col = 0; col < new_cols; ++col) { + new_data[row * new_cols + col] = data_[row * scale * cols_ + col * scale]; + } + } + rows_ = new_rows; + cols_ = new_cols; + data_.swap(new_data); +} + +void Image::UpScale(int scale) { + int new_rows = rows_ * scale; + int new_cols = cols_ * scale; + std::vector new_data(new_rows * new_cols, 0); + for (int row = 0; row < rows_; ++row) { + for (int col = 0; col < cols_; ++col) { + int val = data_[row * cols_ + col]; + for (int dr = 0; dr < scale; ++dr) { + for (int dc = 0; dc < scale; ++dc) { + new_data[(row * scale + dr) * new_cols + (col * scale + dc)] = val; + } + } + } + } + rows_ = new_rows; + cols_ = new_cols; + data_.swap(new_data); +} + +} // namespace igg diff --git a/HW2/src/igg_image/image.h b/HW2/src/igg_image/image.h new file mode 100644 index 0000000..0d7067c --- /dev/null +++ b/HW2/src/igg_image/image.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include +#include +#include + +#include "io_tools.h" + +namespace igg { + +class Image { + public: + /// Default constructor – creates an empty (0×0) image. + Image(); + + /// Constructor that creates a zero-initialised image with the given size. + Image(int rows, int cols); + + /// Returns the number of rows. + int rows() const; + + /// Returns the number of columns. + int cols() const; + + /// Returns a reference to the pixel at (row, col) — acts as a setter. + int& at(int row, int col); + + /// Returns a const reference to the pixel at (row, col) — acts as a getter. + const int& at(int row, int col) const; + + /// Fills the image from an ASCII PGM file. + /// Returns true on success, false if the file could not be read. + bool FillFromPgm(const std::string& file_name); + + /// Writes the image to an ASCII PGM file. + void WriteToPgm(const std::string& file_name); + + /// Computes a normalised histogram with the given number of bins. + /// Each bin value is the count of pixels falling in that bin. + std::vector ComputeHistogram(int bins) const; + + /// Downscales the image by keeping every `scale`-th pixel. + void DownScale(int scale); + + /// Upscales the image by repeating each pixel `scale` times in both axes. + void UpScale(int scale); + + private: + int rows_ = 0; + int cols_ = 0; + int max_val_ = 255; + std::vector data_; +}; + +} // namespace igg diff --git a/HW2/src/igg_image/io_tools.h b/HW2/src/igg_image/io_tools.h new file mode 100644 index 0000000..7ea21fc --- /dev/null +++ b/HW2/src/igg_image/io_tools.h @@ -0,0 +1,66 @@ +#pragma once + +#include +#include +#include + +namespace igg { +namespace io_tools { + +/// Stores relevant image data for a PGM (greyscale) image. +struct ImageData { + int rows = 0; + int cols = 0; + int max_val = 255; + std::vector data; +}; + +/// Reads a greyscale image from an ASCII PGM file. +/// Returns an ImageData with an empty data vector if the file cannot be opened +/// or is not a valid P2 PGM file. +inline ImageData ReadFromPgm(const std::string& file_name) { + std::ifstream file(file_name); + if (!file.is_open()) { return {}; } + + std::string magic; + file >> magic; + if (magic != "P2") { return {}; } + + // Skip comments + char c; + while (file.get(c) && c != '\n') {} + while (file.peek() == '#') { + std::string comment; + std::getline(file, comment); + } + + ImageData img; + file >> img.cols >> img.rows >> img.max_val; + img.data.resize(img.rows * img.cols); + for (int& pixel : img.data) { + file >> pixel; + } + return img; +} + +/// Writes greyscale image data to an ASCII PGM file. +/// Returns true on success. +inline bool WriteToPgm(const ImageData& image_data, + const std::string& file_name) { + std::ofstream file(file_name); + if (!file.is_open()) { return false; } + file << "P2\n"; + file << image_data.cols << " " << image_data.rows << "\n"; + file << image_data.max_val << "\n"; + for (int row = 0; row < image_data.rows; ++row) { + for (int col = 0; col < image_data.cols; ++col) { + file << image_data.data[row * image_data.cols + col]; + if (col < image_data.cols - 1) { file << " "; } + } + file << "\n"; + } + return true; +} + +} // namespace io_tools +} // namespace igg diff --git a/HW2/tests/CMakeLists.txt b/HW2/tests/CMakeLists.txt new file mode 100644 index 0000000..0f1cdd7 --- /dev/null +++ b/HW2/tests/CMakeLists.txt @@ -0,0 +1,8 @@ +include(CTest) +set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) + +add_executable(test_image test_image.cpp) +target_link_libraries(test_image igg_image) +target_compile_definitions(test_image PRIVATE + CMAKE_SOURCE_DIR="${PROJECT_SOURCE_DIR}") +add_test(NAME TestImage COMMAND test_image) diff --git a/HW2/tests/test_image.cpp b/HW2/tests/test_image.cpp new file mode 100644 index 0000000..bc9f475 --- /dev/null +++ b/HW2/tests/test_image.cpp @@ -0,0 +1,144 @@ +#include "igg_image/image.h" + +#include +#include +#include + +// Path to the sample PGM file relative to the build directory. +// CMake copies (or we rely on) the data directory. +static const char* kSamplePgm = + CMAKE_SOURCE_DIR "/data/sample.pgm"; + +void TestDefaultConstructor() { + igg::Image img; + assert(img.rows() == 0); + assert(img.cols() == 0); + std::cout << "TestDefaultConstructor: PASSED" << std::endl; +} + +void TestSizedConstructor() { + igg::Image img(4, 6); + assert(img.rows() == 4); + assert(img.cols() == 6); + // All pixels should be zero-initialised. + for (int r = 0; r < 4; ++r) + for (int c = 0; c < 6; ++c) + assert(img.at(r, c) == 0); + std::cout << "TestSizedConstructor: PASSED" << std::endl; +} + +void TestAtSetterGetter() { + igg::Image img(3, 3); + img.at(1, 2) = 128; + const igg::Image& cimg = img; + assert(cimg.at(1, 2) == 128); + assert(cimg.at(0, 0) == 0); + std::cout << "TestAtSetterGetter: PASSED" << std::endl; +} + +void TestFillFromPgm() { + igg::Image img; + bool ok = img.FillFromPgm(kSamplePgm); + assert(ok); + assert(img.rows() == 8); + assert(img.cols() == 8); + // Top-left pixel should be 0. + assert(img.at(0, 0) == 0); + // Bottom-right pixel should be 255. + assert(img.at(7, 7) == 255); + std::cout << "TestFillFromPgm: PASSED" << std::endl; +} + +void TestFillFromPgmMissing() { + igg::Image img; + bool ok = img.FillFromPgm("/nonexistent/path.pgm"); + assert(!ok); + assert(img.rows() == 0); + std::cout << "TestFillFromPgmMissing: PASSED" << std::endl; +} + +void TestWriteToPgm() { + igg::Image img(4, 4); + img.at(0, 0) = 10; + img.at(3, 3) = 200; + + const std::string tmp = "/tmp/hw2_test_write.pgm"; + img.WriteToPgm(tmp); + + igg::Image img2; + bool ok = img2.FillFromPgm(tmp); + assert(ok); + assert(img2.rows() == 4); + assert(img2.cols() == 4); + assert(img2.at(0, 0) == 10); + assert(img2.at(3, 3) == 200); + std::cout << "TestWriteToPgm: PASSED" << std::endl; +} + +void TestComputeHistogram() { + // Create a 2x2 image with known values: 0, 64, 192, 255 + igg::Image img(2, 2); + img.at(0, 0) = 0; + img.at(0, 1) = 64; + img.at(1, 0) = 192; + img.at(1, 1) = 255; + + const int bins = 4; + auto hist = img.ComputeHistogram(bins); + assert(static_cast(hist.size()) == bins); + // Total pixel count must equal image size. + float total = 0.0f; + for (float v : hist) total += v; + assert(static_cast(total) == img.rows() * img.cols()); + std::cout << "TestComputeHistogram: PASSED" << std::endl; +} + +void TestDownScale() { + igg::Image img(6, 6); + for (int r = 0; r < 6; ++r) + for (int c = 0; c < 6; ++c) + img.at(r, c) = r * 6 + c; + + img.DownScale(2); + assert(img.rows() == 3); + assert(img.cols() == 3); + // Top-left 2x2 block had values 0,1,6,7 – we keep (0,0) = 0. + assert(img.at(0, 0) == 0); + std::cout << "TestDownScale: PASSED" << std::endl; +} + +void TestUpScale() { + igg::Image img(2, 2); + img.at(0, 0) = 10; + img.at(0, 1) = 20; + img.at(1, 0) = 30; + img.at(1, 1) = 40; + + img.UpScale(2); + assert(img.rows() == 4); + assert(img.cols() == 4); + // Each original pixel should map to a 2x2 block. + assert(img.at(0, 0) == 10); + assert(img.at(0, 1) == 10); + assert(img.at(1, 0) == 10); + assert(img.at(1, 1) == 10); + assert(img.at(0, 2) == 20); + assert(img.at(2, 0) == 30); + assert(img.at(2, 2) == 40); + assert(img.at(3, 3) == 40); + std::cout << "TestUpScale: PASSED" << std::endl; +} + +int main() { + TestDefaultConstructor(); + TestSizedConstructor(); + TestAtSetterGetter(); + TestFillFromPgm(); + TestFillFromPgmMissing(); + TestWriteToPgm(); + TestComputeHistogram(); + TestDownScale(); + TestUpScale(); + std::cout << "All HW2 tests passed!" << std::endl; + return 0; +} diff --git a/HW3/CMakeLists.txt b/HW3/CMakeLists.txt new file mode 100644 index 0000000..9694f27 --- /dev/null +++ b/HW3/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.1) +project(homework3) + +set(CMAKE_CXX_STANDARD 11) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() + +set(CMAKE_CXX_FLAGS "-Wall -Wextra -fPIC") +set(CMAKE_CXX_FLAGS_DEBUG "-g") +set(CMAKE_CXX_FLAGS_RELEASE "-O2") + +set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) + +include_directories(${PROJECT_SOURCE_DIR}/src) + +add_subdirectory(src) +enable_testing() +add_subdirectory(tests) diff --git a/HW3/data/sample.ppm b/HW3/data/sample.ppm new file mode 100644 index 0000000..a89079e --- /dev/null +++ b/HW3/data/sample.ppm @@ -0,0 +1,20 @@ +P3 +# sample 4x4 colour image +4 4 +255 +0 0 128 +0 85 128 +0 170 128 +0 255 128 +85 0 128 +85 85 128 +85 170 128 +85 255 128 +170 0 128 +170 85 128 +170 170 128 +170 255 128 +255 0 128 +255 85 128 +255 170 128 +255 255 128 diff --git a/HW3/src/CMakeLists.txt b/HW3/src/CMakeLists.txt new file mode 100644 index 0000000..e73553b --- /dev/null +++ b/HW3/src/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(igg_image) diff --git a/HW3/src/igg_image/CMakeLists.txt b/HW3/src/igg_image/CMakeLists.txt new file mode 100644 index 0000000..db8313a --- /dev/null +++ b/HW3/src/igg_image/CMakeLists.txt @@ -0,0 +1,5 @@ +add_subdirectory(io_strategies) +add_library(igg_image image.cpp) +target_link_libraries(igg_image io_strategies) +add_executable(hw3_main main.cpp) +target_link_libraries(hw3_main igg_image) diff --git a/HW3/src/igg_image/image.cpp b/HW3/src/igg_image/image.cpp new file mode 100644 index 0000000..3434d12 --- /dev/null +++ b/HW3/src/igg_image/image.cpp @@ -0,0 +1,62 @@ +#include "igg_image/image.h" + +namespace igg { + +Image::Image(const IoStrategy& io_strategy) : io_strategy_(io_strategy) {} + +Image::Image(int rows, int cols, const IoStrategy& io_strategy) + : rows_(rows), cols_(cols), io_strategy_(io_strategy), + data_(rows * cols, Pixel{}) {} + +int Image::rows() const { return rows_; } +int Image::cols() const { return cols_; } + +const Image::Pixel& Image::at(int row, int col) const { + return data_[row * cols_ + col]; +} + +Image::Pixel& Image::at(int row, int col) { + return data_[row * cols_ + col]; +} + +bool Image::ReadFromDisk(const std::string& file_name) { + ImageData img_data = io_strategy_.Read(file_name); + if (img_data.data.empty() || img_data.rows == 0 || img_data.cols == 0) { + return false; + } + rows_ = img_data.rows; + cols_ = img_data.cols; + max_val_ = img_data.max_val; + + const auto& red = img_data.data.at(0); + const auto& green = img_data.data.at(1); + const auto& blue = img_data.data.at(2); + + int pixel_count = rows_ * cols_; + data_.resize(pixel_count); + for (int i = 0; i < pixel_count; ++i) { + data_[i].red = red[i]; + data_[i].green = green[i]; + data_[i].blue = blue[i]; + } + return true; +} + +void Image::WriteToDisk(const std::string& file_name) const { + ImageData img_data; + img_data.rows = rows_; + img_data.cols = cols_; + img_data.max_val = max_val_; + + int pixel_count = rows_ * cols_; + std::vector red(pixel_count), green(pixel_count), blue(pixel_count); + for (int i = 0; i < pixel_count; ++i) { + red[i] = data_[i].red; + green[i] = data_[i].green; + blue[i] = data_[i].blue; + } + img_data.data = {red, green, blue}; + io_strategy_.Write(file_name, img_data); +} + +} // namespace igg diff --git a/HW3/src/igg_image/image.h b/HW3/src/igg_image/image.h new file mode 100644 index 0000000..abe19c0 --- /dev/null +++ b/HW3/src/igg_image/image.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include + +#include "igg_image/io_strategies/strategy.h" + +namespace igg { + +class Image { + public: + /// A pixel holds three colour channels. + struct Pixel { + int red = 0; + int green = 0; + int blue = 0; + }; + + /// Constructs an empty image using the given I/O strategy. + explicit Image(const IoStrategy& io_strategy); + + /// Constructs a zero-filled image with the given dimensions. + Image(int rows, int cols, const IoStrategy& io_strategy); + + int rows() const; + int cols() const; + + /// Returns a const reference to the pixel at (row, col). + const Pixel& at(int row, int col) const; + + /// Returns a reference to the pixel at (row, col). + Pixel& at(int row, int col); + + /// Reads image data from disk using the configured I/O strategy. + bool ReadFromDisk(const std::string& file_name); + + /// Writes image data to disk using the configured I/O strategy. + void WriteToDisk(const std::string& file_name) const; + + private: + int rows_ = 0; + int cols_ = 0; + int max_val_ = 255; + const IoStrategy& io_strategy_; + std::vector data_; +}; + +} // namespace igg diff --git a/HW3/src/igg_image/io_strategies/CMakeLists.txt b/HW3/src/igg_image/io_strategies/CMakeLists.txt new file mode 100644 index 0000000..e24394d --- /dev/null +++ b/HW3/src/igg_image/io_strategies/CMakeLists.txt @@ -0,0 +1 @@ +add_library(io_strategies ppm_strategy.cpp) diff --git a/HW3/src/igg_image/io_strategies/dummy_strategy.h b/HW3/src/igg_image/io_strategies/dummy_strategy.h new file mode 100644 index 0000000..87817b5 --- /dev/null +++ b/HW3/src/igg_image/io_strategies/dummy_strategy.h @@ -0,0 +1,20 @@ +#pragma once + +#include "igg_image/io_strategies/strategy.h" + +namespace igg { + +/// A dummy I/O strategy used for testing. +/// Write() always succeeds; Read() always returns an empty ImageData. +class DummyIoStrategy : public IoStrategy { + public: + bool Write(const std::string& /*file_name*/, + const ImageData& /*data*/) const override { + return true; + } + ImageData Read(const std::string& /*file_name*/) const override { + return {}; + } +}; + +} // namespace igg diff --git a/HW3/src/igg_image/io_strategies/ppm_strategy.cpp b/HW3/src/igg_image/io_strategies/ppm_strategy.cpp new file mode 100644 index 0000000..eb10a32 --- /dev/null +++ b/HW3/src/igg_image/io_strategies/ppm_strategy.cpp @@ -0,0 +1,59 @@ +#include "igg_image/io_strategies/ppm_strategy.h" + +#include +#include +#include +#include +#include + +namespace igg { + +bool PpmIoStrategy::Write(const std::string& file_name, + const ImageData& data) const { + std::ofstream ofs(file_name); + if (!ofs.is_open()) { return false; } + + ofs << "P3\n"; + ofs << data.cols << " " << data.rows << "\n"; + ofs << data.max_val << "\n"; + + const auto& red = data.data.at(0); + const auto& green = data.data.at(1); + const auto& blue = data.data.at(2); + + int pixel_count = data.rows * data.cols; + for (int i = 0; i < pixel_count; ++i) { + ofs << red[i] << " " << green[i] << " " << blue[i]; + // PPM recommends at most 70 characters per line; add a newline per pixel + // for clarity. + ofs << "\n"; + } + return true; +} + +ImageData PpmIoStrategy::Read(const std::string& file_name) const { + std::ifstream ifs(file_name); + if (!ifs.is_open()) { return {}; } + + std::string magic; + ifs >> magic; + if (magic != "P3") { return {}; } + + ImageData img; + // Skip optional comment lines. + std::string line; + std::getline(ifs, line); // consume rest of magic line + while (ifs.peek() == '#') { std::getline(ifs, line); } + + ifs >> img.cols >> img.rows >> img.max_val; + + int pixel_count = img.rows * img.cols; + std::vector red(pixel_count), green(pixel_count), blue(pixel_count); + for (int i = 0; i < pixel_count; ++i) { + ifs >> red[i] >> green[i] >> blue[i]; + } + img.data = {red, green, blue}; + return img; +} + +} // namespace igg diff --git a/HW3/src/igg_image/io_strategies/ppm_strategy.h b/HW3/src/igg_image/io_strategies/ppm_strategy.h new file mode 100644 index 0000000..cdb6cde --- /dev/null +++ b/HW3/src/igg_image/io_strategies/ppm_strategy.h @@ -0,0 +1,15 @@ +#pragma once + +#include "igg_image/io_strategies/strategy.h" + +namespace igg { + +/// I/O strategy for plain PPM (P3) colour images. +class PpmIoStrategy : public IoStrategy { + public: + bool Write(const std::string& file_name, + const ImageData& data) const override; + ImageData Read(const std::string& file_name) const override; +}; + +} // namespace igg diff --git a/HW3/src/igg_image/io_strategies/strategy.h b/HW3/src/igg_image/io_strategies/strategy.h new file mode 100644 index 0000000..a61b65a --- /dev/null +++ b/HW3/src/igg_image/io_strategies/strategy.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +namespace igg { + +/// Stores the raw data of a colour image. +/// `data` holds three colour channels: data[0] = red, data[1] = green, +/// data[2] = blue. Each channel is a flat vector of rows * cols integers. +struct ImageData { + int rows = 0; + int cols = 0; + int max_val = 255; + std::vector> data; +}; + +/// Abstract base class for image I/O strategies. +class IoStrategy { + public: + virtual bool Write(const std::string& file_name, + const ImageData& data) const = 0; + virtual ImageData Read(const std::string& file_name) const = 0; + virtual ~IoStrategy() {} +}; + +} // namespace igg diff --git a/HW3/src/igg_image/main.cpp b/HW3/src/igg_image/main.cpp new file mode 100644 index 0000000..4201e2d --- /dev/null +++ b/HW3/src/igg_image/main.cpp @@ -0,0 +1,32 @@ +#include "igg_image/image.h" +#include "igg_image/io_strategies/ppm_strategy.h" + +#include + +int main() { + std::cout << "=== HW3: colour image with strategy pattern ===" << std::endl; + + igg::PpmIoStrategy ppm_strategy; + + igg::Image img(ppm_strategy); + if (img.ReadFromDisk("../data/sample.ppm")) { + std::cout << "Loaded " << img.rows() << " x " << img.cols() + << " colour image" << std::endl; + + // Make the diagonal red. + int diag = std::min(img.rows(), img.cols()); + for (int i = 0; i < diag; ++i) { + igg::Image::Pixel red_pixel; + red_pixel.red = 255; + red_pixel.green = 0; + red_pixel.blue = 0; + img.at(i, i) = red_pixel; + } + + img.WriteToDisk("../data/output.ppm"); + std::cout << "Written to ../data/output.ppm" << std::endl; + } else { + std::cerr << "Could not open ../data/sample.ppm" << std::endl; + } + return 0; +} diff --git a/HW3/tests/CMakeLists.txt b/HW3/tests/CMakeLists.txt new file mode 100644 index 0000000..0f1cdd7 --- /dev/null +++ b/HW3/tests/CMakeLists.txt @@ -0,0 +1,8 @@ +include(CTest) +set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) + +add_executable(test_image test_image.cpp) +target_link_libraries(test_image igg_image) +target_compile_definitions(test_image PRIVATE + CMAKE_SOURCE_DIR="${PROJECT_SOURCE_DIR}") +add_test(NAME TestImage COMMAND test_image) diff --git a/HW3/tests/test_image.cpp b/HW3/tests/test_image.cpp new file mode 100644 index 0000000..ced95f8 --- /dev/null +++ b/HW3/tests/test_image.cpp @@ -0,0 +1,115 @@ +#include "igg_image/image.h" +#include "igg_image/io_strategies/dummy_strategy.h" +#include "igg_image/io_strategies/ppm_strategy.h" + +#include +#include +#include + +static const char* kSamplePpm = CMAKE_SOURCE_DIR "/data/sample.ppm"; + +void TestDefaultConstructor() { + igg::DummyIoStrategy dummy; + igg::Image img(dummy); + assert(img.rows() == 0); + assert(img.cols() == 0); + std::cout << "TestDefaultConstructor: PASSED" << std::endl; +} + +void TestSizedConstructor() { + igg::DummyIoStrategy dummy; + igg::Image img(3, 4, dummy); + assert(img.rows() == 3); + assert(img.cols() == 4); + // All pixels should be zero. + for (int r = 0; r < 3; ++r) + for (int c = 0; c < 4; ++c) { + assert(img.at(r, c).red == 0); + assert(img.at(r, c).green == 0); + assert(img.at(r, c).blue == 0); + } + std::cout << "TestSizedConstructor: PASSED" << std::endl; +} + +void TestAtSetterGetter() { + igg::DummyIoStrategy dummy; + igg::Image img(2, 2, dummy); + igg::Image::Pixel px; + px.red = 10; px.green = 20; px.blue = 30; + img.at(0, 1) = px; + const igg::Image& cimg = img; + assert(cimg.at(0, 1).red == 10); + assert(cimg.at(0, 1).green == 20); + assert(cimg.at(0, 1).blue == 30); + assert(cimg.at(0, 0).red == 0); + std::cout << "TestAtSetterGetter: PASSED" << std::endl; +} + +void TestReadFromDisk() { + igg::PpmIoStrategy ppm; + igg::Image img(ppm); + bool ok = img.ReadFromDisk(kSamplePpm); + assert(ok); + assert(img.rows() == 4); + assert(img.cols() == 4); + // Top-left pixel: red=0, green=0, blue=128 + assert(img.at(0, 0).red == 0); + assert(img.at(0, 0).green == 0); + assert(img.at(0, 0).blue == 128); + std::cout << "TestReadFromDisk: PASSED" << std::endl; +} + +void TestReadFromDiskMissing() { + igg::PpmIoStrategy ppm; + igg::Image img(ppm); + bool ok = img.ReadFromDisk("/nonexistent/path.ppm"); + assert(!ok); + assert(img.rows() == 0); + std::cout << "TestReadFromDiskMissing: PASSED" << std::endl; +} + +void TestWriteToDisk() { + igg::PpmIoStrategy ppm; + igg::Image img(2, 2, ppm); + igg::Image::Pixel px00; px00.red = 100; px00.green = 150; px00.blue = 200; + img.at(0, 0) = px00; + igg::Image::Pixel px11; px11.red = 50; px11.green = 75; px11.blue = 25; + img.at(1, 1) = px11; + + const std::string tmp = "/tmp/hw3_test_write.ppm"; + img.WriteToDisk(tmp); + + igg::Image img2(ppm); + bool ok = img2.ReadFromDisk(tmp); + assert(ok); + assert(img2.rows() == 2); + assert(img2.cols() == 2); + assert(img2.at(0, 0).red == 100); + assert(img2.at(0, 0).green == 150); + assert(img2.at(0, 0).blue == 200); + assert(img2.at(1, 1).red == 50); + assert(img2.at(1, 1).green == 75); + assert(img2.at(1, 1).blue == 25); + std::cout << "TestWriteToDisk: PASSED" << std::endl; +} + +void TestDummyStrategy() { + igg::DummyIoStrategy dummy; + igg::Image img(dummy); + bool ok = img.ReadFromDisk("any_file.ppm"); + assert(!ok); // DummyIoStrategy returns empty data + assert(dummy.Write("any_file.ppm", {}) == true); + std::cout << "TestDummyStrategy: PASSED" << std::endl; +} + +int main() { + TestDefaultConstructor(); + TestSizedConstructor(); + TestAtSetterGetter(); + TestReadFromDisk(); + TestReadFromDiskMissing(); + TestWriteToDisk(); + TestDummyStrategy(); + std::cout << "All HW3 tests passed!" << std::endl; + return 0; +} diff --git a/README.md b/README.md index de93bb8..a4dffa8 100644 --- a/README.md +++ b/README.md @@ -1 +1,122 @@ -# C-_tutorial \ No newline at end of file +# C++ Tutorial — StachnissLab Homework Corrections + +Solutions to all homework assignments of the **Modern C++ for Computer Vision +and Image Processing** course by Cyrill Stachniss +([StachnissLab](https://www.ipb.uni-bonn.de/), University of Bonn). + +--- + +## Prerequisites + +| Tool | Version | +|------|---------| +| C++ compiler | C++11 or later (g++ / clang++) | +| CMake | ≥ 3.1 | + +--- + +## Repository structure + +``` +. +├── HW1/ # Homework 1 – Basic C++: string tokenisation & word frequency +├── HW2/ # Homework 2 – Greyscale image class with PGM file I/O +└── HW3/ # Homework 3 – Colour image class with Strategy-pattern I/O +``` + +--- + +## HW1 – String operations + +**Topics covered:** `std::string`, `std::vector`, `std::map`, range-based for +loops, function design. + +**Tasks:** +- `Tokenize(str, delimiter)` — splits a string into tokens using a delimiter + character, skipping empty tokens. +- `CountWordFrequency(str)` — counts how often each word appears in a string + and returns an alphabetically sorted list of `(word, count)` pairs. + +### Build & run + +```bash +cd HW1 && mkdir build && cd build +cmake .. +make +./bin/hw1_main +ctest --output-on-failure +``` + +--- + +## HW2 – Greyscale image class (`igg::Image`) + +**Topics covered:** classes, const/non-const overloading, file I/O streams, +`std::vector`, histogram computation, image scaling. + +**Class interface (`igg::Image`):** + +| Method | Description | +|--------|-------------| +| `Image()` | Default constructor (empty image) | +| `Image(rows, cols)` | Constructor — creates a zero-filled image | +| `int rows() const` | Returns number of rows | +| `int cols() const` | Returns number of columns | +| `int& at(row, col)` | Pixel setter (returns mutable reference) | +| `const int& at(row, col) const` | Pixel getter (returns const reference) | +| `bool FillFromPgm(file)` | Reads an ASCII PGM (P2) file | +| `void WriteToPgm(file)` | Writes an ASCII PGM (P2) file | +| `vector ComputeHistogram(bins)` | Computes pixel-count histogram | +| `void DownScale(scale)` | Sub-samples by keeping every `scale`-th pixel | +| `void UpScale(scale)` | Repeats each pixel `scale` times in both dimensions | + +**Four demo applications** are provided in `src/application{1..4}/`: +1. Getter/setter demonstration +2. Read a PGM file, draw a diagonal, write it back +3. Compute and print a 10-bin histogram +4. Downscale and upscale a PGM image + +### Build & run + +```bash +cd HW2 && mkdir build && cd build +cmake .. +make +ctest --output-on-failure +``` + +--- + +## HW3 – Colour image class with Strategy pattern (`igg::Image`) + +**Topics covered:** inheritance, virtual functions, abstract base classes, +the Strategy design pattern, colour images (RGB), PPM file format. + +**Class hierarchy:** + +``` +IoStrategy (abstract base) +├── DummyIoStrategy (always succeeds write; returns empty on read — for tests) +└── PpmIoStrategy (plain PPM P3 format) +``` + +**`igg::Image` interface:** + +| Method | Description | +|--------|-------------| +| `Image(strategy)` | Constructor — binds an I/O strategy | +| `Image(rows, cols, strategy)` | Constructor — creates a zero-filled image | +| `int rows() / int cols()` | Dimension accessors | +| `Pixel& at(row, col)` | Pixel accessor (read/write) | +| `const Pixel& at(row, col) const` | Pixel accessor (read-only) | +| `bool ReadFromDisk(file)` | Reads image via the strategy | +| `void WriteToDisk(file)` | Writes image via the strategy | + +### Build & run + +```bash +cd HW3 && mkdir build && cd build +cmake .. +make +ctest --output-on-failure +``` From 50da8399f95d4734c00ca407ded36602bc377cc0 Mon Sep 17 00:00:00 2001 From: Franck Date: Mon, 13 Apr 2026 21:04:05 +0200 Subject: [PATCH 3/4] Checkpoint from VS Code for cloud agent session --- README.md | 122 --------------------- homework_4/include/homework_4.h | 0 homework_4/tests/CMakeLists.txt | 12 +++ homework_4/tests/test_ipb_algorithm.cpp | 76 +++++++++++++ homework_4/tests/test_main.cpp | 7 ++ homework_4/tests/test_named_vector.cpp | 136 ++++++++++++++++++++++++ 6 files changed, 231 insertions(+), 122 deletions(-) delete mode 100644 README.md create mode 100644 homework_4/include/homework_4.h create mode 100644 homework_4/tests/CMakeLists.txt create mode 100644 homework_4/tests/test_ipb_algorithm.cpp create mode 100644 homework_4/tests/test_main.cpp create mode 100644 homework_4/tests/test_named_vector.cpp diff --git a/README.md b/README.md deleted file mode 100644 index a4dffa8..0000000 --- a/README.md +++ /dev/null @@ -1,122 +0,0 @@ -# C++ Tutorial — StachnissLab Homework Corrections - -Solutions to all homework assignments of the **Modern C++ for Computer Vision -and Image Processing** course by Cyrill Stachniss -([StachnissLab](https://www.ipb.uni-bonn.de/), University of Bonn). - ---- - -## Prerequisites - -| Tool | Version | -|------|---------| -| C++ compiler | C++11 or later (g++ / clang++) | -| CMake | ≥ 3.1 | - ---- - -## Repository structure - -``` -. -├── HW1/ # Homework 1 – Basic C++: string tokenisation & word frequency -├── HW2/ # Homework 2 – Greyscale image class with PGM file I/O -└── HW3/ # Homework 3 – Colour image class with Strategy-pattern I/O -``` - ---- - -## HW1 – String operations - -**Topics covered:** `std::string`, `std::vector`, `std::map`, range-based for -loops, function design. - -**Tasks:** -- `Tokenize(str, delimiter)` — splits a string into tokens using a delimiter - character, skipping empty tokens. -- `CountWordFrequency(str)` — counts how often each word appears in a string - and returns an alphabetically sorted list of `(word, count)` pairs. - -### Build & run - -```bash -cd HW1 && mkdir build && cd build -cmake .. -make -./bin/hw1_main -ctest --output-on-failure -``` - ---- - -## HW2 – Greyscale image class (`igg::Image`) - -**Topics covered:** classes, const/non-const overloading, file I/O streams, -`std::vector`, histogram computation, image scaling. - -**Class interface (`igg::Image`):** - -| Method | Description | -|--------|-------------| -| `Image()` | Default constructor (empty image) | -| `Image(rows, cols)` | Constructor — creates a zero-filled image | -| `int rows() const` | Returns number of rows | -| `int cols() const` | Returns number of columns | -| `int& at(row, col)` | Pixel setter (returns mutable reference) | -| `const int& at(row, col) const` | Pixel getter (returns const reference) | -| `bool FillFromPgm(file)` | Reads an ASCII PGM (P2) file | -| `void WriteToPgm(file)` | Writes an ASCII PGM (P2) file | -| `vector ComputeHistogram(bins)` | Computes pixel-count histogram | -| `void DownScale(scale)` | Sub-samples by keeping every `scale`-th pixel | -| `void UpScale(scale)` | Repeats each pixel `scale` times in both dimensions | - -**Four demo applications** are provided in `src/application{1..4}/`: -1. Getter/setter demonstration -2. Read a PGM file, draw a diagonal, write it back -3. Compute and print a 10-bin histogram -4. Downscale and upscale a PGM image - -### Build & run - -```bash -cd HW2 && mkdir build && cd build -cmake .. -make -ctest --output-on-failure -``` - ---- - -## HW3 – Colour image class with Strategy pattern (`igg::Image`) - -**Topics covered:** inheritance, virtual functions, abstract base classes, -the Strategy design pattern, colour images (RGB), PPM file format. - -**Class hierarchy:** - -``` -IoStrategy (abstract base) -├── DummyIoStrategy (always succeeds write; returns empty on read — for tests) -└── PpmIoStrategy (plain PPM P3 format) -``` - -**`igg::Image` interface:** - -| Method | Description | -|--------|-------------| -| `Image(strategy)` | Constructor — binds an I/O strategy | -| `Image(rows, cols, strategy)` | Constructor — creates a zero-filled image | -| `int rows() / int cols()` | Dimension accessors | -| `Pixel& at(row, col)` | Pixel accessor (read/write) | -| `const Pixel& at(row, col) const` | Pixel accessor (read-only) | -| `bool ReadFromDisk(file)` | Reads image via the strategy | -| `void WriteToDisk(file)` | Writes image via the strategy | - -### Build & run - -```bash -cd HW3 && mkdir build && cd build -cmake .. -make -ctest --output-on-failure -``` diff --git a/homework_4/include/homework_4.h b/homework_4/include/homework_4.h new file mode 100644 index 0000000..e69de29 diff --git a/homework_4/tests/CMakeLists.txt b/homework_4/tests/CMakeLists.txt new file mode 100644 index 0000000..3b917b9 --- /dev/null +++ b/homework_4/tests/CMakeLists.txt @@ -0,0 +1,12 @@ +find_package(Catch2 REQUIRED) +include(CTest) +include(Catch) + +set(TEST_BINARY ${PROJECT_NAME}_test) +add_executable(${TEST_BINARY} + test_named_vector.cpp + test_ipb_algorithm.cpp + test_main.cpp) + +target_link_libraries(${TEST_BINARY} ipb_algorithm Catch2::Catch2) +catch_discover_tests(${TEST_BINARY}) diff --git a/homework_4/tests/test_ipb_algorithm.cpp b/homework_4/tests/test_ipb_algorithm.cpp new file mode 100644 index 0000000..8b488fb --- /dev/null +++ b/homework_4/tests/test_ipb_algorithm.cpp @@ -0,0 +1,76 @@ +// @file test_ipb_algorithm.cpp +// @author Ignacio Vizzo [ivizzo@uni-bonn.de] +// +// Copyright (c) 2020 Ignacio Vizzo, all rights reserved +#include +#include + +#include + +#include "homework_4.h" + +using vector = ipb::named_vector; + +TEST_CASE("ipb::accumulate", "[ipb_algorithm]") { + vector v{"name", {1, 2, 3, 4}}; + REQUIRE(ipb::accumulate(v) == 10); +} + +TEST_CASE("ipb::count", "[ipb_algorithm]") { + vector v{"name", {1, 2, 3, 4}}; + REQUIRE(ipb::count(v, 1) == 1); +} + +TEST_CASE("ipb::all_even", "[ipb_algorithm]") { + vector even_v{"even_vector", {2, 4, 8, 14}}; + REQUIRE(ipb::all_even(even_v)); +} + +TEST_CASE("ipb::clamp", "[ipb_algorithm]") { + vector v{"name", {1, 2, 3, 4}}; + const int kMin = 2; + const int kMax = 3; + ipb::clamp(v, kMin, kMax); + REQUIRE(v.vector() == std::vector{2, 2, 3, 3}); +} + +TEST_CASE("ipb::fill", "[ipb_algorithm]") { + vector v{"name", {1, 2, 3, 4}}; + const int k = -100; + ipb::fill(v, k); + REQUIRE(v.vector() == std::vector(v.vector().size(), k)); +} + +TEST_CASE("ipb::find", "[ipb_algorithm]") { + vector even_v{"even_vector", {2, 4, 8, 14}}; + REQUIRE(ipb::find(even_v, 4)); +} + +TEST_CASE("ipb::print", "[ipb_algorithm]") { + vector v{"name", {1, 2, 3, 4}}; + ipb::print(v); +} + +TEST_CASE("ipb::toupper", "[ipb_algorithm]") { + vector v{"name", {1, 2, 3, 4}}; + ipb::toupper(v); + REQUIRE(v.name() == "NAME"); +} + +TEST_CASE("ipb::sort", "[ipb_algorithm]") { + vector v_unsorted{"unsorted_vector", {12, 4, 0, 1}}; + ipb::sort(v_unsorted); + REQUIRE(v_unsorted.vector() == std::vector{0, 1, 4, 12}); +} + +TEST_CASE("ipb::rotate", "[ipb_algorithm]") { + vector v{"name", {1, 2, 3, 4}}; + ipb::rotate(v, 2); + REQUIRE(v.vector() == std::vector{3, 4, 1, 2}); +} + +TEST_CASE("ipb::reverse", "[ipb_algorithm]") { + vector v_unsorted{"unsorted_vector", {12, 4, 0, 1}}; + ipb::reverse(v_unsorted); + REQUIRE(v_unsorted.vector() == std::vector{1, 0, 4, 12}); +} \ No newline at end of file diff --git a/homework_4/tests/test_main.cpp b/homework_4/tests/test_main.cpp new file mode 100644 index 0000000..97d885f --- /dev/null +++ b/homework_4/tests/test_main.cpp @@ -0,0 +1,7 @@ +// @file test_main.cpp +// @author Ignacio Vizzo [ivizzo@uni-bonn.de] +// +// Copyright (c) 2019 Ignacio Vizzo, all rights reserved +// This tells Catch to provide a main() - only do this in one cpp file +#define CATCH_CONFIG_MAIN +#include \ No newline at end of file diff --git a/homework_4/tests/test_named_vector.cpp b/homework_4/tests/test_named_vector.cpp new file mode 100644 index 0000000..7668763 --- /dev/null +++ b/homework_4/tests/test_named_vector.cpp @@ -0,0 +1,136 @@ +// @file test_ipb_algorithm.cpp +// @author Ignacio Vizzo [ivizzo@uni-bonn.de] +// +// Copyright (c) 2020 Ignacio Vizzo, all rights reserved +#include +#include +#include + +#include +#include + +#include "homework_4.h" + +TEMPLATE_TEST_CASE("ipb::named_vector can be initalized as an std::vector", + "[named_vector]", char, int, std::string, float, double, + (std::tuple)) { + const int kNumElements = 5; + const std::string type_name = boost::core::demangle(typeid(TestType).name()); + const std::string empty_str; + const std::vector std_empty_vector{}; + const std::vector std_vector(kNumElements); + + SECTION("Initialize empty") { + ipb::named_vector templated_vector; + REQUIRE(templated_vector.empty()); + REQUIRE(templated_vector.name().empty()); + REQUIRE(templated_vector.vector().empty()); + REQUIRE(templated_vector.name() == empty_str); + REQUIRE(templated_vector.vector() == std_empty_vector); + } + + SECTION("Initialize with name only") { + ipb::named_vector templated_vector{type_name, {}}; + REQUIRE(templated_vector.empty()); // still considered to be empty + REQUIRE(!templated_vector.name().empty()); + REQUIRE(templated_vector.vector().empty()); + REQUIRE(templated_vector.name() == type_name); + REQUIRE(templated_vector.vector() == std_empty_vector); + } + + SECTION("Full initialization") { + ipb::named_vector templated_vector{type_name, std_vector}; + REQUIRE(!templated_vector.empty()); + REQUIRE(!templated_vector.name().empty()); + REQUIRE(!templated_vector.vector().empty()); + REQUIRE(templated_vector.name() == type_name); + REQUIRE(templated_vector.vector() == std_vector); + } +} + +TEMPLATE_TEST_CASE("ipb::named_vectors can be sized and resized", + "[named_vector]", char, int, std::string, float, double, + (std::tuple)) { + const int kNumElements = 5; + const std::string name = "resizable_vector"; + const std::vector std_vector(kNumElements); + ipb::named_vector v{name, std_vector}; + + REQUIRE(v.size() == kNumElements + name.size()); + REQUIRE(v.capacity() >= kNumElements); + + SECTION("resizing bigger changes size and capacity") { + v.resize(2 * kNumElements); + REQUIRE(v.size() == 2 * kNumElements + name.size()); + REQUIRE(v.capacity() >= 2 * kNumElements); + } + SECTION("resizing smaller changes size but not capacity") { + v.resize(0); + REQUIRE(v.empty()); + REQUIRE(v.capacity() >= kNumElements); + } + SECTION("reserving bigger changes capacity but not size") { + v.reserve(2 * kNumElements); + REQUIRE(v.size() == kNumElements + name.size()); + REQUIRE(v.capacity() >= 2 * kNumElements); + } + SECTION("reserving smaller does not change size or capacity") { + v.reserve(0); + REQUIRE(v.size() == kNumElements + name.size()); + REQUIRE(v.capacity() >= kNumElements); + } +} + +SCENARIO("ipb::named_vector size is consistent", "[named_vector]") { + GIVEN("No arguments") { + ipb::named_vector v; + REQUIRE(v.size() == 0); + REQUIRE(v.empty()); + WHEN("Members are initialized") { + v = {"name", {1, 2, 3, 4}}; + THEN("The size increases") { + REQUIRE(v.size() > 0); + REQUIRE(!v.empty()); + } + } + WHEN("named_vector is constructed") { + v = {"new_name", {1, 2, 3, 4}}; + THEN("The size increases") { + REQUIRE(v.size() > 0); + REQUIRE(!v.empty()); + } + } + } + GIVEN("A completely initialized vector") { + const std::string name{"Nacho"}; + const std::vector std_vector{1, 2, 3, 4}; + ipb::named_vector v{name, std_vector}; + REQUIRE(!v.empty()); + REQUIRE(v.size() == name.size() + std_vector.size()); + } + + GIVEN("A partially initialized vector") { + const std::string name{"Nacho"}; + const std::vector std_vector{1, 2, 3, 4}; + WHEN("No name is provided") { + ipb::named_vector v; + v = {"", std_vector}; + THEN("The named_vector is considered incomplete, therefore, empty") { + REQUIRE(v.empty()); + } + THEN("The size() matches the std::vector::size()") { + REQUIRE(v.size() == std_vector.size()); + } + } + WHEN("No vector is provided") { + ipb::named_vector v; + v = {name, {}}; + THEN("The named_vector is considered incomplete, therefore, empty") { + REQUIRE(v.empty()); + } + THEN("The size() matches the std::string::size()") { + REQUIRE(v.size() == name.size()); + } + } + } +} \ No newline at end of file From 2a479a48695e575dd44ec3a7524d6e846e9c246c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Apr 2026 19:12:21 +0000 Subject: [PATCH 4/4] Implement homework_4: ipb::named_vector class and algorithm functions Agent-Logs-Url: https://github.com/zappateinstein/Cpp_tutorial/sessions/08e96db2-5a78-4544-8454-11afa21a212b Co-authored-by: zappateinstein <107572402+zappateinstein@users.noreply.github.com> --- .gitignore | 2 + homework_4/CMakeLists.txt | 19 ++++ homework_4/include/homework_4.h | 121 ++++++++++++++++++++++++ homework_4/tests/CMakeLists.txt | 2 +- homework_4/tests/test_ipb_algorithm.cpp | 2 +- homework_4/tests/test_main.cpp | 4 +- homework_4/tests/test_named_vector.cpp | 2 +- 7 files changed, 146 insertions(+), 6 deletions(-) create mode 100644 homework_4/CMakeLists.txt diff --git a/.gitignore b/.gitignore index da5f7c1..af3d818 100644 --- a/.gitignore +++ b/.gitignore @@ -2,11 +2,13 @@ HW1/build/ HW2/build/ HW3/build/ +homework_4/build/ # Compiled binaries HW1/bin/ HW2/bin/ HW3/bin/ +homework_4/bin/ # CMake generated files CMakeCache.txt diff --git a/homework_4/CMakeLists.txt b/homework_4/CMakeLists.txt new file mode 100644 index 0000000..53b4365 --- /dev/null +++ b/homework_4/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 3.1) +project(homework_4) + +set(CMAKE_CXX_STANDARD 17) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() + +set(CMAKE_CXX_FLAGS "-Wall -Wextra -fPIC") +set(CMAKE_CXX_FLAGS_DEBUG "-g") +set(CMAKE_CXX_FLAGS_RELEASE "-O2") + +# Header-only library target +add_library(ipb_algorithm INTERFACE) +target_include_directories(ipb_algorithm INTERFACE ${PROJECT_SOURCE_DIR}/include) + +enable_testing() +add_subdirectory(tests) diff --git a/homework_4/include/homework_4.h b/homework_4/include/homework_4.h index e69de29..a4e4a5c 100644 --- a/homework_4/include/homework_4.h +++ b/homework_4/include/homework_4.h @@ -0,0 +1,121 @@ +// @file homework_4.h +// @author Ignacio Vizzo [ivizzo@uni-bonn.de] +// +// Copyright (c) 2020 Ignacio Vizzo, all rights reserved +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace ipb { + +template +class named_vector { + public: + named_vector() = default; + named_vector(const std::string& name, const std::vector& vec) + : name_(name), vec_(vec) {} + + // Accessors + const std::string& name() const { return name_; } + std::string& name() { return name_; } + const std::vector& vector() const { return vec_; } + std::vector& vector() { return vec_; } + + /// Returns true only when BOTH name and vector are non-empty. + bool empty() const { return name_.empty() || vec_.empty(); } + + /// Returns the combined size of the name and the underlying vector. + std::size_t size() const { return name_.size() + vec_.size(); } + + std::size_t capacity() const { return vec_.capacity(); } + void resize(std::size_t n) { vec_.resize(n); } + void reserve(std::size_t n) { vec_.reserve(n); } + + private: + std::string name_; + std::vector vec_; +}; + +// --------------------------------------------------------------------------- +// Algorithms +// --------------------------------------------------------------------------- + +template +T accumulate(const named_vector& v) { + return std::accumulate(v.vector().begin(), v.vector().end(), T{}); +} + +template +int count(const named_vector& v, const T& value) { + return static_cast( + std::count(v.vector().begin(), v.vector().end(), value)); +} + +template +bool all_even(const named_vector& v) { + return std::all_of(v.vector().begin(), v.vector().end(), + [](const T& x) { return x % 2 == 0; }); +} + +template +void clamp(named_vector& v, const T& lo, const T& hi) { + for (auto& x : v.vector()) { + if (x < lo) { + x = lo; + } else if (x > hi) { + x = hi; + } + } +} + +template +void fill(named_vector& v, const T& value) { + std::fill(v.vector().begin(), v.vector().end(), value); +} + +template +bool find(const named_vector& v, const T& value) { + return std::find(v.vector().begin(), v.vector().end(), value) != + v.vector().end(); +} + +template +void print(const named_vector& v) { + std::cout << v.name() << ": "; + for (const auto& x : v.vector()) { + std::cout << x << " "; + } + std::cout << "\n"; +} + +template +void toupper(named_vector& v) { + std::string& name = v.name(); + std::transform(name.begin(), name.end(), name.begin(), + [](unsigned char c) { return std::toupper(c); }); +} + +template +void sort(named_vector& v) { + std::sort(v.vector().begin(), v.vector().end()); +} + +template +void rotate(named_vector& v, int n) { + const auto sz = static_cast(v.vector().size()); + if (sz == 0) return; + n = ((n % sz) + sz) % sz; // normalise to [0, sz) + std::rotate(v.vector().begin(), v.vector().begin() + n, v.vector().end()); +} + +template +void reverse(named_vector& v) { + std::reverse(v.vector().begin(), v.vector().end()); +} + +} // namespace ipb diff --git a/homework_4/tests/CMakeLists.txt b/homework_4/tests/CMakeLists.txt index 3b917b9..1e688d8 100644 --- a/homework_4/tests/CMakeLists.txt +++ b/homework_4/tests/CMakeLists.txt @@ -8,5 +8,5 @@ add_executable(${TEST_BINARY} test_ipb_algorithm.cpp test_main.cpp) -target_link_libraries(${TEST_BINARY} ipb_algorithm Catch2::Catch2) +target_link_libraries(${TEST_BINARY} ipb_algorithm Catch2::Catch2WithMain) catch_discover_tests(${TEST_BINARY}) diff --git a/homework_4/tests/test_ipb_algorithm.cpp b/homework_4/tests/test_ipb_algorithm.cpp index 8b488fb..0ef1b13 100644 --- a/homework_4/tests/test_ipb_algorithm.cpp +++ b/homework_4/tests/test_ipb_algorithm.cpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include "homework_4.h" diff --git a/homework_4/tests/test_main.cpp b/homework_4/tests/test_main.cpp index 97d885f..8c5ae6a 100644 --- a/homework_4/tests/test_main.cpp +++ b/homework_4/tests/test_main.cpp @@ -2,6 +2,4 @@ // @author Ignacio Vizzo [ivizzo@uni-bonn.de] // // Copyright (c) 2019 Ignacio Vizzo, all rights reserved -// This tells Catch to provide a main() - only do this in one cpp file -#define CATCH_CONFIG_MAIN -#include \ No newline at end of file +#include \ No newline at end of file diff --git a/homework_4/tests/test_named_vector.cpp b/homework_4/tests/test_named_vector.cpp index 7668763..47bcf99 100644 --- a/homework_4/tests/test_named_vector.cpp +++ b/homework_4/tests/test_named_vector.cpp @@ -7,7 +7,7 @@ #include #include -#include +#include #include "homework_4.h"