From ab287135638fbd548a6ece2901a19e938c7b99e5 Mon Sep 17 00:00:00 2001
From: Brandon Rodriguez <brodriguez8774@gmail.com>
Date: Sun, 25 Apr 2021 00:03:25 -0400
Subject: [PATCH] Split test image manipulation logic into "part_0" section

---
 documents/references.md |   4 +-
 part_0/image.cu         | 729 ++++++++++++++++++++++++++++++++++++++++
 part_0/image.h          | 131 ++++++++
 part_0/logging.cu       | 162 +++++++++
 part_0/logging.h        |  84 +++++
 part_0/main.cu          | 179 ++++++++++
 part_0/makefile         |  36 ++
 part_0/utility.cu       |  88 +++++
 part_0/utility.h        |  34 ++
 part_1/image.cu         | 250 ++------------
 part_1/image.h          |  12 +
 11 files changed, 1486 insertions(+), 223 deletions(-)
 create mode 100644 part_0/image.cu
 create mode 100644 part_0/image.h
 create mode 100644 part_0/logging.cu
 create mode 100644 part_0/logging.h
 create mode 100644 part_0/main.cu
 create mode 100644 part_0/makefile
 create mode 100644 part_0/utility.cu
 create mode 100644 part_0/utility.h

diff --git a/documents/references.md b/documents/references.md
index 6766a31..e67f5be 100644
--- a/documents/references.md
+++ b/documents/references.md
@@ -29,7 +29,9 @@ Various references used in project.
 ### Other
 #### C++ Strings
 * What is a C++ string - <https://stackoverflow.com/a/3535904>
-* String splitting - <https://stackoverflow.com/a/14266139>
+* String splitting:
+** <https://stackoverflow.com/a/14266139>
+** <https://www.cplusplus.com/reference/string/string/substr/>
 * Getting File/LineNum:
 ** <https://stackoverflow.com/a/42135375>
 ** <https://stackoverflow.com/a/8884408>
diff --git a/part_0/image.cu b/part_0/image.cu
new file mode 100644
index 0000000..874b1ea
--- /dev/null
+++ b/part_0/image.cu
@@ -0,0 +1,729 @@
+/**
+ * File for image handling logic.
+ */
+
+
+// User Import Headers.
+#ifndef user_headers
+    #define user_headers
+    #include "image.h"
+    #include "utility.h"
+#endif
+
+#ifndef user_headers_logging
+    #define user_headers_logging
+    #include "logging.h"
+#endif
+
+
+// Global Variables.
+#ifndef LOGGING_CLASS_INIT
+    #define LOGGING_CLASS_INIT
+    extern Logging logger;
+#endif
+const Image::PixelStruct Image::pixelBlack = Image::PixelStruct(0);
+const Image::PixelStruct Image::pixelWhite = Image::PixelStruct(1);
+const Image::PixelStruct Image::pixelRed = Image::PixelStruct(1,0,0);
+const Image::PixelStruct Image::pixelGreen = Image::PixelStruct(0,1,0);
+const Image::PixelStruct Image::pixelBlue = Image::PixelStruct(0,0,1);
+const Image::PixelStruct Image::pixelYellow = Image::PixelStruct(1,1,0);
+const Image::PixelStruct Image::pixelPink = Image::PixelStruct(1,0,1);
+const Image::PixelStruct Image::pixelLiBlue = Image::PixelStruct(0,1,1);
+
+
+//region CUDA Methods.
+
+/**
+ * Displays various properties of all CUDA devices found on machine.
+ **/
+void display_cuda_device_properties() {
+    // Check available CUDA devices.
+    cudaDeviceProp cuda_device_properties;
+    int cuda_device_count;
+
+    cudaGetDeviceCount(&cuda_device_count);
+
+    for (int index = 0; index < cuda_device_count; index++) {
+        cudaGetDeviceProperties(&cuda_device_properties, index);
+
+        printf("\n\n\n\n");
+        printf("==== General Information for CUDA Device %d ====\n", index);
+        printf("Name: %s\n", cuda_device_properties.name);
+        printf("Compute Capabilities: %d.%d\n", cuda_device_properties.major, cuda_device_properties.minor);
+        printf("Clock Rate: %d\n", cuda_device_properties.clockRate);
+        printf("\n\n");
+        printf("==== Memory Information for CUDA Device %d ====\n", index);
+        printf("Total Global Memory: %ld\n", cuda_device_properties.totalGlobalMem);
+        printf("Total Constant Memory: %ld\n", cuda_device_properties.totalConstMem);
+        printf("Max Memory Pitch: %ld\n", cuda_device_properties.memPitch);
+        printf("Texture Alignment: %ld\n", cuda_device_properties.textureAlignment);
+        printf("\n\n");
+        printf("==== Multiprocessing Information for CUDA Device %d ====\n", index);
+        printf("Multiprocessor Count: %d\n", cuda_device_properties.multiProcessorCount);
+        printf("Shared Memory Per MP: %ld\n", cuda_device_properties.sharedMemPerBlock);
+        printf("Registers Per MP: %d\n", cuda_device_properties.regsPerBlock);
+        printf("Threads in Warp: %d\n", cuda_device_properties.warpSize);
+        printf("Max Threads Per Block: %d\n", cuda_device_properties.maxThreadsPerBlock);
+        printf("Max Thread Dimensions: (%d, %d, %d)\n",
+            cuda_device_properties.maxThreadsDim[0],
+            cuda_device_properties.maxThreadsDim[1],
+            cuda_device_properties.maxThreadsDim[2]
+        );
+        printf("Max Grid Dimensions: (%d, %d, %d)\n",
+            cuda_device_properties.maxGridSize[0],
+            cuda_device_properties.maxGridSize[1],
+            cuda_device_properties.maxGridSize[2]
+        );
+        printf("\n\n\n\n");
+    }
+}
+
+
+/**
+ * Updates image so that every other pixel alternates between pure black and pure white.
+ * Purely for testing purposes.
+ */
+// __global__ void cuda_pixel_alternating(Image::PixelStruct* pixel_arr, int pixel_index) {
+__global__ void cuda_alternating_pixels(unsigned long total_pixels, unsigned long chunk_size, int chunk_counter, Image::PixelStruct* pixel_arr) {
+    // Calculate current index.
+    unsigned long pixel_index = blockIdx.x * blockDim.x + threadIdx.x;
+
+    // Adjust index for current chunk.
+    pixel_index += (chunk_size * chunk_counter);
+
+    // Only proceed if GPU thread corresponds to a pixel in the image. Ignore all extra threads.
+    if (pixel_index <= total_pixels) {
+
+        // Manipulate pixel data based on index.
+        if (pixel_index % 2 == 0) {
+            // Set to black.
+            pixel_arr[pixel_index].r = 0.f;
+            pixel_arr[pixel_index].g = 0.f;
+            pixel_arr[pixel_index].b = 0.f;
+        } else {
+            // Set to white.
+            pixel_arr[pixel_index].r = 255.f;
+            pixel_arr[pixel_index].g = 255.f;
+            pixel_arr[pixel_index].b = 255.f;
+        }
+    }
+}
+
+
+/**
+ * Updates image so that blocks of pixels alternate between pure black and pure white.
+ * Effectively, it's similar to cuda_alternating_pixels, except that all image sizes should look roughly the same.
+ * Purely for testing purposes.
+ */
+// __global__ void cuda_pixel_alternating(Image::PixelStruct* pixel_arr, int pixel_index) {
+__global__ void cuda_alternating_blocks(unsigned long total_pixels, unsigned long chunk_size, int chunk_counter, int pixel_block_size, int image_width, int image_height, Image::PixelStruct* pixel_arr) {
+    // Calculate current index.
+    unsigned long pixel_index = blockIdx.x * blockDim.x + threadIdx.x;
+
+    // Adjust index for current chunk.
+    pixel_index += (chunk_size * chunk_counter);
+
+    // Only proceed if GPU thread corresponds to a pixel in the image. Ignore all extra threads.
+    if (pixel_index <= total_pixels) {
+
+        // Calculate if falls under black or white pixel block.
+        int row = pixel_index / image_width;
+        int col = pixel_index % image_width;
+        int row_grouping = row / pixel_block_size;
+        int col_grouping = col / pixel_block_size;
+        float color;
+        if (row_grouping % 2 == 0) {
+            if ((col_grouping % 2) == 0) {
+                color = 255.f;
+            } else {
+                color = 0.f;
+            }
+        } else {
+            if ((col_grouping % 2) == 0) {
+                color = 0.f;
+            } else {
+
+                color = 255.f;
+            }
+        }
+
+        // Set pixel color.
+        pixel_arr[pixel_index].r = color;
+        pixel_arr[pixel_index].g = color;
+        pixel_arr[pixel_index].b = color;
+    }
+}
+
+
+/**
+ * Updates image to increase percieved value of red in image.
+ * Purely for testing purposes.
+ */
+__global__ void cuda_increase_red(unsigned long total_pixels, unsigned long chunk_size, int chunk_counter, Image::PixelStruct* pixel_arr) {
+    // Calculate current index.
+    unsigned long pixel_index = blockIdx.x * blockDim.x + threadIdx.x;
+
+    // Adjust index for current chunk.
+    pixel_index += (chunk_size * chunk_counter);
+
+    // Only proceed if GPU thread corresponds to a pixel in the image. Ignore all extra threads.
+    if (pixel_index <= total_pixels) {
+
+        // Calculate new pixel colors.
+        float red = pixel_arr[pixel_index].r + 0.1f;
+        float green = pixel_arr[pixel_index].g - 0.1f;
+        float blue = pixel_arr[pixel_index].b - 0.1f;
+
+        // Handle if colors exceed expected values.
+        if (red > 1.0f) {
+            red = 1.f;
+        }
+        if (green < 0.0f) {
+            green = 0.f;
+        }
+        if (blue < 0.0f) {
+            blue = 0.f;
+        }
+
+        // Update pixel values.
+        pixel_arr[pixel_index].r = red;
+        pixel_arr[pixel_index].g = green;
+        pixel_arr[pixel_index].b = blue;
+    }
+}
+
+
+/**
+ * Updates image to increase percieved value of green in image.
+ * Purely for testing purposes.
+ */
+__global__ void cuda_increase_green(unsigned long total_pixels, unsigned long chunk_size, int chunk_counter, Image::PixelStruct* pixel_arr) {
+    // Calculate current index.
+    unsigned long pixel_index = blockIdx.x * blockDim.x + threadIdx.x;
+
+    // Adjust index for current chunk.
+    pixel_index += (chunk_size * chunk_counter);
+
+    // Only proceed if GPU thread corresponds to a pixel in the image. Ignore all extra threads.
+    if (pixel_index <= total_pixels) {
+
+        // Calculate new pixel colors.
+        float red = pixel_arr[pixel_index].r - 0.1f;
+        float green = pixel_arr[pixel_index].g + 0.1f;
+        float blue = pixel_arr[pixel_index].b - 0.1f;
+
+        // Handle if colors exceed expected values.
+        if (red < 0.0f) {
+            red = 0.f;
+        }
+        if (green > 1.0f) {
+            green = 1.f;
+        }
+        if (blue < 0.0f) {
+            blue = 0.f;
+        }
+
+        // Update pixel values.
+        pixel_arr[pixel_index].r = red;
+        pixel_arr[pixel_index].g = green;
+        pixel_arr[pixel_index].b = blue;
+    }
+}
+
+
+/**
+ * Updates image to increase percieved value of blue in image.
+ * Purely for testing purposes.
+ */
+__global__ void cuda_increase_blue(unsigned long total_pixels, unsigned long chunk_size, int chunk_counter, Image::PixelStruct* pixel_arr) {
+    // Calculate current index.
+    unsigned long pixel_index = blockIdx.x * blockDim.x + threadIdx.x;
+
+    // Adjust index for current chunk.
+    pixel_index += (chunk_size * chunk_counter);
+
+    // Only proceed if GPU thread corresponds to a pixel in the image. Ignore all extra threads.
+    if (pixel_index <= total_pixels) {
+
+        // Calculate new pixel colors.
+        float red = pixel_arr[pixel_index].r - 0.1f;
+        float green = pixel_arr[pixel_index].g - 0.1f;
+        float blue = pixel_arr[pixel_index].b + 0.1f;
+
+        // Handle if colors exceed expected values.
+        if (red < 0.0f) {
+            red = 0.f;
+        }
+        if (green < 0.0f) {
+            green = 0.f;
+        }
+        if (blue > 1.0f) {
+            blue = 1.f;
+        }
+
+        // Update pixel values.
+        pixel_arr[pixel_index].r = red;
+        pixel_arr[pixel_index].g = green;
+        pixel_arr[pixel_index].b = blue;
+    }
+}
+
+//endregion CUDA Methods.
+
+
+//region Constructors.
+
+/**
+ * Base constructor.
+ */
+Image::Image() {
+    logger.debug("Image::Image Constructor():");
+    logger.debug("");
+
+    // Currently test logic.
+
+    // Essentially creates a 10x10 pixel test image.
+    input_path = "../input/pbm/400x300/hallway_00.pbm";
+    output_path = "../output/test_00.pbm";
+    width = 10;
+    height = 10;
+    bit_depth = 255;
+    PixelStruct c = pixelBlack; // Default color. Can select based on below preset colors.
+
+    // Create and populate image.
+    pixel_arr = new PixelStruct[width * height];
+    for (int index = 0; index < (width * height); index++) {
+        pixel_arr[index] = c;
+    }
+}
+
+
+/**
+ * Constructor that takes input path to image file.
+ * Output path is determined based on parsing input path.
+ */
+Image::Image(std::string user_path) {
+    logger.debug("Image::Image Constructor():");
+    logger.debug("    user_path: " + user_path);
+    logger.debug("");
+
+    logger.info("Constructing image class.");
+
+    // Re-validate path for saving to class.
+    path_validator_struct* return_struct = validate_path(user_path);
+    int path_type = return_struct->file_type;
+    if (return_struct->err_code) {
+        logger.error("Error verifying file type: " + return_struct->err_code.message());
+        return;
+    }
+    free_path_validator_struct(return_struct);
+    if (path_type == TYPE_FILE) {
+        input_path = user_path;
+        calculate_paths(user_path);
+        import_image_file();
+    } else {
+        logger.error("Provided path is not an image file. Class initialization failed.");
+        return;
+    }
+
+    logger.info("Image class created.");
+}
+
+
+/**
+ * Deconstructor.
+ */
+Image::~Image() {
+    logger.debug("Deconstructing image class");
+
+    // Deallocate memory objects.
+    delete[] pixel_arr;
+}
+
+//endregion Constructors.
+
+
+//region Private Methods.
+
+/**
+ * Calculates class input_path and output_path values.
+ *
+ * :param image_path: Path to an image on the system.
+ */
+void Image::calculate_paths(std::string image_path) {
+    logger.debug("Image::calculate_paths():");
+    logger.debug("    image_path: " + image_path);
+    logger.debug("");
+
+    // // Calculate output path location.
+    output_path = "../output/";
+    std::string base_filename = get_file_name();
+
+    // Grab directory substrings and generate full output path.
+    size_t pos = 0;
+    std::string token;
+    while ((pos = image_path.find("/")) != std::string::npos) {
+
+        // Pull current substring value from input path.
+        token = image_path.substr(0, image_path.find("/"));
+        image_path.erase(0, pos + 1);
+
+        // Check if directory value is value we care about.
+        if ( (token != ".") && (token != "..") && (token != "input") ) {
+            output_path += token + "/";
+        }
+
+        // Check if directory exists.
+        struct stat info;
+        if ( (stat(output_path.c_str(), &info) != 0) || (! info.st_mode & S_IFDIR)) {
+            // Directory does not exist.
+            mkdir(output_path.c_str(), 0777);
+        }
+
+    }
+
+    output_path += base_filename;
+}
+
+
+/**
+ * Imports image file data, using class variable data.
+ */
+void Image::import_image_file() {
+    logger.debug("Image::import_image_file():");
+    logger.debug("");
+
+    logger.info("Attempting to import image data...");
+    logger.info("Data from: \"" + input_path + "\"");
+
+    // Open input file.
+    std::ifstream input_file;
+    input_file.open(input_path, std::ios::binary | std::ios::in);
+
+    if (! input_file.good()) {
+        // Failed to open file.
+        logger.error("Failed to open file \"" + input_path + "\"");
+    } else {
+        // File opened. Grab data.
+        std::string header_format;
+        input_file >> header_format;
+
+        // Check image data format type.
+        if (strcmp(header_format.c_str(), "P6") == 0) {
+            // P6 indicates standard "binary RGB format".
+            logger.debug("Image is binary RGB format.");
+
+        } else if (strcmp(header_format.c_str(), "P5") == 0) {
+            // P5 indicates standard "binary greyscale format".
+            logger.debug("Image is binary greyscale format.");
+
+        } else {
+            // throw("Can't read input file");
+            logger.error("");
+            logger.error("Image: \"" + input_path + "\"");
+            logger.error("Unhandled image format type: " + header_format);
+            return;
+        }
+
+        // Save image values to class.
+        input_file >> width >> height >> bit_depth;
+
+        // Create and populate image.
+        pixel_arr = new PixelStruct[width * height];
+
+        // Skip empty lines in necessary until we get to the binary data.
+        input_file.ignore(256, '\n');
+
+        // Read pixels based on image format.
+        if (strcmp(header_format.c_str(), "P6") == 0) {
+            // Binary RBG format.
+            // Read each pixel one by one and convert RGB channel bytes to floats.
+            unsigned char pixel[3];
+            for (int index = 0; index < (width * height); ++index) {
+                input_file.read(reinterpret_cast<char *>(pixel), 3);
+                pixel_arr[index].r = pixel[0] / 255.f;
+                pixel_arr[index].g = pixel[1] / 255.f;
+                pixel_arr[index].b = pixel[2] / 255.f;
+            }
+        } else if (strcmp(header_format.c_str(), "P5") == 0) {
+            // Binary RBG format.
+            // Read each pixel one by one and convert bytes to floats.
+            unsigned char* pixel;
+            pixel = (unsigned char*)calloc(1, sizeof(unsigned char));
+            for (int index = 0; index < (width * height); ++index) {
+                input_file.read(reinterpret_cast<char *>(pixel), 1);
+                pixel_arr[index].r = *pixel / 255.f;
+                pixel_arr[index].g = *pixel / 255.f;
+                pixel_arr[index].b = *pixel / 255.f;
+            }
+            free(pixel);
+        }
+    }
+
+    input_file.close();
+}
+
+//endregion Private Methods.
+
+
+//region Public Methods.
+
+/**
+ * Displays general properties of image held in class.
+ */
+void Image::display_properties() {
+    logger.debug("Image::display_properties():");
+    logger.debug("");
+
+    logger.info("");
+    logger.info("input file path: " + input_path);
+    logger.info("output file path: " + output_path);
+    logger.info("width: " + std::to_string(width));
+    logger.info("height: " + std::to_string(height));
+    logger.info("bit depth: " + std::to_string(bit_depth));
+    logger.info("");
+}
+
+
+/**
+ * Saves class image data to output file.
+ */
+void Image::save() {
+    logger.debug("Image::save():");
+    logger.debug("");
+
+    logger.debug("Attempting to save image to \"" + output_path + "\".");
+
+    // Open output file stream.
+    std::ofstream output_file;
+
+    // Attempt to save file data.
+    try {
+        // Attempt to open file.
+        output_file.open(output_path, std::ios::binary | std::ios::out);
+
+        // Check if opened successfully.
+        if (output_file.fail()) {
+            // Failed to open.
+            logger.error("Failed to open file \"" + output_path + "\"");
+        } else {
+            // Opened successfully.
+
+            // Write image "header" data.
+            output_file << "P6\n" << width << " " << height << "\n255\n";
+
+            // Write image pixel data.
+            unsigned char r, g, b;
+            for (int index = 0; index < (width * height); index++) {
+                r = static_cast<float>(std::min(1.f, pixel_arr[index].r) * 255);
+                g = static_cast<float>(std::min(1.f, pixel_arr[index].g) * 255);
+                b = static_cast<float>(std::min(1.f, pixel_arr[index].b) * 255);
+                output_file << r << g << b;
+            }
+
+        }
+
+        // Close file stream.
+        output_file.close();
+        logger.info("Image saved.");
+
+    } catch (const char* err) {
+        logger.error("Failed to save image: " + std::string(err));
+        output_file.close();
+    }
+}
+
+
+/**
+ * Gets current file name, using original inpute path value.
+ */
+std::string Image::get_file_name() {
+    logger.debug("Image::get_file_name():");
+    logger.debug("");
+
+    return input_path.substr(input_path.find_last_of("/\\") + 1);
+}
+
+
+/**
+ * Changes output file name to provided value.
+ */
+void Image::set_file_name(std::string file_name) {
+    logger.debug("Image::compute():");
+    logger.debug("    file_name: " + file_name);
+    logger.debug("");
+
+    std::string new_path = output_path.substr(0, output_path.find_last_of("/\\") + 1);
+    output_path = new_path + file_name;
+}
+
+
+/**
+ * Method to actually do work on image.
+ */
+void Image::compute() {
+    logger.debug("Image::compute():");
+    logger.debug("");
+
+    // Get file name values.
+    std::string base_file_name = get_file_name();
+    std::string base_file_extension = base_file_name.substr(base_file_name.find_last_of(".") + 1);
+    base_file_name = base_file_name.substr(0, base_file_name.find_last_of("."));
+
+    // Get required CUDA device data.
+    // For now, lazily gets first CUDA device and assumes it's the desired device to use.
+    // TODO: Handle multiple CUDA devices.
+    cudaDeviceProp cuda_device_properties;
+    cudaGetDeviceProperties(&cuda_device_properties, 0);
+    int cuda_device_max_blocks = cuda_device_properties.maxThreadsDim[0];
+    int cuda_device_max_threads = cuda_device_properties.maxThreadsPerBlock;
+    unsigned long cuda_chunk_size = cuda_device_max_blocks * cuda_device_max_threads;
+
+    logger.info("CUDA Max Blocks: " + std::to_string(cuda_device_max_blocks));
+    logger.info("CUDA Max Threads: " + std::to_string(cuda_device_max_threads));
+    logger.info("CUDA Chunk Size: " + std::to_string(cuda_chunk_size));
+    logger.info("CUDA Global Mem: " + std::to_string(cuda_device_properties.totalGlobalMem));
+
+    // Calculate some image data.
+    int pixel_struct_size = sizeof(PixelStruct);
+    unsigned long image_total_pixels = width * height;
+    unsigned long pixel_struct_byte_count = image_total_pixels * pixel_struct_size;
+
+    logger.info("Total Image Size: " + std::to_string(image_total_pixels));
+    logger.info("Pixel Struct Size: " + std::to_string(pixel_struct_byte_count));
+
+    // We handle image in chunks, in case we're dealing with large images with more pixels than CUDA device blocks/threads.
+    int total_image_chunks = (image_total_pixels / cuda_chunk_size) + 1;
+    logger.info("Total Image Chunks: " + std::to_string(total_image_chunks));
+    logger.info("Desired CUDA block count: " + std::to_string(cuda_device_max_blocks));
+    logger.info("Desired CUDA thread count: " + std::to_string(cuda_device_max_threads));
+
+    // Allocate memory on GPU.
+    PixelStruct* gpu_pixel_arr;
+    cudaMalloc((void**) &gpu_pixel_arr, pixel_struct_byte_count);
+    cudaMemcpy(gpu_pixel_arr, pixel_arr, pixel_struct_byte_count, cudaMemcpyHostToDevice);
+
+    // Save original pixel data.
+    PixelStruct* pixel_arr_orig = new PixelStruct[image_total_pixels];
+    for (int index = 0; index < image_total_pixels; index++) {
+        pixel_arr_orig[index].r = pixel_arr[index].r;
+        pixel_arr_orig[index].g = pixel_arr[index].g;
+        pixel_arr_orig[index].b = pixel_arr[index].b;
+    }
+
+    // Process image by iterating over all chunks for image test.
+    for (int chunk_index = 0; chunk_index < total_image_chunks; chunk_index++) {
+        // Run GPU test logic to set alternating pixels to black/white.
+        set_file_name(base_file_name + "_alternating." + base_file_extension);
+        cuda_alternating_pixels<<<cuda_device_max_blocks, cuda_device_max_threads>>>(image_total_pixels, cuda_chunk_size, chunk_index, gpu_pixel_arr);
+
+        // Synchronize all GPU computations before iterating over next chunk.
+        cudaDeviceSynchronize();
+    }
+
+    // Save processed image.
+    cudaMemcpy(pixel_arr, gpu_pixel_arr, pixel_struct_byte_count, cudaMemcpyDeviceToHost);
+    save();
+
+    // Reset class pixel array
+    for (int index = 0; index < image_total_pixels; index++) {
+        pixel_arr[index].r = pixel_arr_orig[index].r;
+        pixel_arr[index].g = pixel_arr_orig[index].g;
+        pixel_arr[index].b = pixel_arr_orig[index].b;
+    }
+    cudaMemcpy(gpu_pixel_arr, pixel_arr, pixel_struct_byte_count, cudaMemcpyHostToDevice);
+
+    // Process image by iterating over all chunks for new image test.
+    for (int chunk_index = 0; chunk_index < total_image_chunks; chunk_index++) {
+        // Run GPU test logic to create black/white checkerboard pattern.
+        set_file_name(base_file_name + "_checkerboard." + base_file_extension);
+        int pixel_block_size = width / 10;
+        if (pixel_block_size <= 1) {
+            pixel_block_size = 2;
+        }
+        cuda_alternating_blocks<<<cuda_device_max_blocks, cuda_device_max_threads>>>(image_total_pixels, cuda_chunk_size, chunk_index, pixel_block_size, width, height, gpu_pixel_arr);
+
+        // Synchronize all GPU computations before iterating over next chunk.
+        cudaDeviceSynchronize();
+    }
+
+    // Save processed image.
+    cudaMemcpy(pixel_arr, gpu_pixel_arr, pixel_struct_byte_count, cudaMemcpyDeviceToHost);
+    save();
+
+    // Reset class pixel array
+    for (int index = 0; index < image_total_pixels; index++) {
+        pixel_arr[index].r = pixel_arr_orig[index].r;
+        pixel_arr[index].g = pixel_arr_orig[index].g;
+        pixel_arr[index].b = pixel_arr_orig[index].b;
+    }
+    cudaMemcpy(gpu_pixel_arr, pixel_arr, pixel_struct_byte_count, cudaMemcpyHostToDevice);
+
+    // Process image by iterating over all chunks for new image test.
+    for (int chunk_index = 0; chunk_index < total_image_chunks; chunk_index++) {
+        // Run GPU test logic to increase/decrease r/g/b colors in image.
+        set_file_name(base_file_name + "_red." + base_file_extension);
+        cuda_increase_red<<<cuda_device_max_blocks, cuda_device_max_threads>>>(image_total_pixels, cuda_chunk_size, chunk_index, gpu_pixel_arr);
+
+        // Synchronize all GPU computations before iterating over next chunk.
+        cudaDeviceSynchronize();
+    }
+
+    // Save processed image.
+    cudaMemcpy(pixel_arr, gpu_pixel_arr, pixel_struct_byte_count, cudaMemcpyDeviceToHost);
+    save();
+
+    // Reset class pixel array
+    for (int index = 0; index < image_total_pixels; index++) {
+        pixel_arr[index].r = pixel_arr_orig[index].r;
+        pixel_arr[index].g = pixel_arr_orig[index].g;
+        pixel_arr[index].b = pixel_arr_orig[index].b;
+    }
+    cudaMemcpy(gpu_pixel_arr, pixel_arr, pixel_struct_byte_count, cudaMemcpyHostToDevice);
+
+    // Process image by iterating over all chunks for new image test.
+    for (int chunk_index = 0; chunk_index < total_image_chunks; chunk_index++) {
+        // Run GPU test logic to increase/decrease r/g/b colors in image.
+        set_file_name(base_file_name + "_green." + base_file_extension);
+        cuda_increase_green<<<cuda_device_max_blocks, cuda_device_max_threads>>>(image_total_pixels, cuda_chunk_size, chunk_index, gpu_pixel_arr);
+
+        // Synchronize all GPU computations before iterating over next chunk.
+        cudaDeviceSynchronize();
+    }
+
+    // Save processed image.
+    cudaMemcpy(pixel_arr, gpu_pixel_arr, pixel_struct_byte_count, cudaMemcpyDeviceToHost);
+    save();
+
+    // Reset class pixel array
+    for (int index = 0; index < image_total_pixels; index++) {
+        pixel_arr[index].r = pixel_arr_orig[index].r;
+        pixel_arr[index].g = pixel_arr_orig[index].g;
+        pixel_arr[index].b = pixel_arr_orig[index].b;
+    }
+    cudaMemcpy(gpu_pixel_arr, pixel_arr, pixel_struct_byte_count, cudaMemcpyHostToDevice);
+
+    // Process image by iterating over all chunks for new image test.
+    for (int chunk_index = 0; chunk_index < total_image_chunks; chunk_index++) {
+        // Run GPU test logic to increase/decrease r/g/b colors in image.
+        set_file_name(base_file_name + "_blue." + base_file_extension);
+        cuda_increase_blue<<<cuda_device_max_blocks, cuda_device_max_threads>>>(image_total_pixels, cuda_chunk_size, chunk_index, gpu_pixel_arr);
+
+        // Synchronize all GPU computations before iterating over next chunk.
+        cudaDeviceSynchronize();
+    }
+
+    // Save processed image.
+    cudaMemcpy(pixel_arr, gpu_pixel_arr, pixel_struct_byte_count, cudaMemcpyDeviceToHost);
+    save();
+
+    // Retrieve results.
+    cudaMemcpy(pixel_arr, gpu_pixel_arr, pixel_struct_byte_count, cudaMemcpyDeviceToHost);
+    cudaFree(gpu_pixel_arr);
+    delete[] pixel_arr_orig;
+
+    logger.debug("Image::compute() finished.");
+    logger.debug("");
+}
+
+//endregion Public Methods.
diff --git a/part_0/image.h b/part_0/image.h
new file mode 100644
index 0000000..a64cc8c
--- /dev/null
+++ b/part_0/image.h
@@ -0,0 +1,131 @@
+/**
+ * Header file for project image handling logic.
+ */
+
+
+// System Import Headers.
+#include <cctype>
+#include <cstdio>
+#include <cstdlib>
+#include <filesystem>
+#include <fstream>
+#include <iostream>
+#include <iterator>
+#include <string>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+
+/**
+ * Class that holds data for a single image.
+ */
+class Image {
+
+    public:
+        /**
+         * Struct to act as a "container" of sorts for each individual rgb pixel.
+         * An array of these creates our entire image.
+         *
+         * According to https://stackoverflow.com/a/36917400, this is basically treated as a sub-class.
+         * This allows creating multiple constructors, which will handle different scenarios of pixel input.
+         */
+        struct PixelStruct {
+            // Struct variables.
+            float r, g, b;
+
+            /**
+             * Struct constructors.
+             *
+             * First one acts as a "default" that provides black pixels.
+             * When a single float is provided, the second constructor sets all three rgb channels to that value.
+             * Finally, when three floats are provided, the last constructor sets rgb channels accordingly.
+             */
+            PixelStruct(): r(0), g(0), b(0) {}
+            PixelStruct(float c): r(c), g(c), b(c) {}
+            PixelStruct(float _r, float _g, float _b) : r(_r), g(_g), b(_b) {}
+
+        };
+
+    private:
+        // Private Variables.
+        std::string input_path;
+        std::string output_path;
+        unsigned int width;
+        unsigned int height;
+        int bit_depth;
+        PixelStruct *pixel_arr;     // Array of pixel data.
+
+        /**
+         * Calculates class input_path and output_path values.
+         *
+         * :param image_path: Path to an image on the system.
+         */
+        void calculate_paths(std::string image_path);
+
+
+        /**
+         * Imports image file data, using class variable data.
+         */
+        void import_image_file();
+
+
+    public:
+        // Public Variables.
+        static const PixelStruct pixelBlack, pixelWhite, pixelRed, pixelGreen, pixelBlue, pixelYellow, pixelPink, pixelLiBlue;
+
+
+        //region Constructors.
+
+        /**
+         * Base constructor.
+         */
+        Image();
+
+        /**
+         * Constructor that takes input path to image file.
+         * Output path is determined based on parsing input path.
+         */
+        Image(std::string user_path);
+
+        /**
+         * Deconstructor.
+         */
+        ~Image();
+
+        //endregion Constructors.
+
+
+        //region Public Methods.
+
+        /**
+         * Displays general properties of image held in class.
+         */
+        void display_properties();
+
+
+        /**
+         * Saves class image data to output file.
+         */
+        void save();
+
+
+        /**
+         * Gets current file name, using original inpute path value.
+         */
+        std::string get_file_name();
+
+
+        /**
+         * Changes output file name to provided value.
+         */
+        void set_file_name(std::string file_name);
+
+
+        /**
+         * Method to actually do work on image.
+         */
+        void compute();
+
+        //endregion Public Methods.
+
+};
diff --git a/part_0/logging.cu b/part_0/logging.cu
new file mode 100644
index 0000000..fc66c73
--- /dev/null
+++ b/part_0/logging.cu
@@ -0,0 +1,162 @@
+/**
+ * File for project logging.
+ * Contains actual logic for class defined in logging.h.
+ */
+
+
+// User Import Headers.
+#ifndef user_headers
+    #define user_headers
+    #define user_headers_logging
+    #include "logging.h"
+#endif
+
+
+/**
+ * Class constructor.
+ */
+Logging::Logging() {
+    std::filesystem::path cwd = std::filesystem::current_path();
+    core_logging_folder = cwd.string() + "/logs/";
+
+    // Check if logging directory exists.
+    struct stat info;
+    if ( (stat(core_logging_folder.c_str(), &info) != 0) || (! info.st_mode & S_IFDIR)) {
+        // Directory does not exist.
+        mkdir(core_logging_folder.c_str(), 0777);
+    }
+
+    // Open file descriptors.
+    error_stream.open(core_logging_folder + "error.log", std::ios::app | std::ios::out);
+    warn_stream.open(core_logging_folder + "warn.log", std::ios::app | std::ios::out);
+    info_stream.open(core_logging_folder + "info.log", std::ios::app | std::ios::out);
+    debug_stream.open(core_logging_folder + "debug.log", std::ios::app | std::ios::out);
+    timer_stream.open(core_logging_folder + "timer.log", std::ios::app | std::ios::out);
+}
+
+/**
+ * Class deconstructor.
+ * Closes file descriptors, if open.
+ */
+Logging::~Logging() {
+    close_log_files();
+}
+
+
+/**
+ * Closes all open logging file descriptors.
+ */
+void Logging::close_log_files() {
+    if (debug_stream.is_open()) {
+        debug_stream.close();
+    }
+    if (info_stream.is_open()) {
+        info_stream.close();
+    }
+    if (warn_stream.is_open()) {
+        warn_stream.close();
+    }
+    if (error_stream.is_open()) {
+        error_stream.close();
+    }
+    if (timer_stream.is_open()) {
+        timer_stream.close();
+    }
+}
+
+
+/**
+ * Writes message to info log file.
+ */
+void Logging::debug(std::string msg, std::string log_type, const char* file, long line_num) {
+    // Build and write logging message.
+    std::string log_prefix = " [" + log_type + "] [ " + std::string(file) + " | " + std::to_string(line_num) + " ] ";
+    debug_stream << log_prefix << msg << std::endl;
+}
+
+
+/**
+ * Writes message to info log file.
+ */
+void Logging::info(std::string msg, std::string log_type, const char* file, long line_num) {
+    // Build and write logging message.
+    std::string log_prefix = " [" + log_type + "] [ " + std::string(file) + " | " + std::to_string(line_num) + " ] ";
+    info_stream << log_prefix << msg << std::endl;
+
+    // Write to console.
+    std::cout << log_prefix << msg << std::endl;
+
+    // Also write to lower level loggers.
+    debug(msg, log_type, file, line_num);
+}
+
+
+/**
+ * Writes message to warning log file.
+ */
+void Logging::warn(std::string msg, std::string log_type, const char* file, long line_num) {
+    // Build and write logging message.
+    std::string log_prefix = " [" + log_type + "] [ " + std::string(file) + " | " + std::to_string(line_num) + " ] ";
+    warn_stream << log_prefix << msg << std::endl;
+
+    // Also write to lower level loggers.
+    info(msg, log_type, file, line_num);
+}
+
+
+/**
+ * Writes message to error log file.
+ */
+void Logging::error(std::string msg, std::string log_type, const char* file, long line_num) {
+    // Build and write logging message.
+    std::string log_prefix = " [" + log_type + "] [ " + std::string(file) + " | " + std::to_string(line_num) + " ] ";
+    error_stream << log_prefix << msg << std::endl;
+
+    // Also write to lower level loggers.
+    warn(msg, log_type, file, line_num);
+}
+
+
+/**
+ * Writes message to timer log file.
+ */
+void Logging::timer(std::string msg, std::string log_type, const char* file, long line_num) {
+    // Build and write logging message.
+    std::string log_prefix = " [" + log_type + "] [ " + std::string(file) + " | " + std::to_string(line_num) + " ] ";
+    timer_stream << log_prefix << msg << std::endl;
+
+    // Also write to info and lower level loggers.
+    info(msg, log_type, file, line_num);
+}
+
+
+/**
+ * Prints out logging location to console.
+ */
+void Logging::display_logging_folder() {
+    std::cout << "Logging folder: " << core_logging_folder << std::endl;
+}
+
+
+/**
+ * Resets contents of logging files.
+ */
+void Logging::clear_log_files() {
+    // First close any open logging file descriptors.
+    close_log_files();
+
+    // Open log files in "write mode", which will override any present data.
+    error_stream.open(core_logging_folder + "error.log", std::ios::out);
+    warn_stream.open(core_logging_folder + "warn.log", std::ios::out);
+    info_stream.open(core_logging_folder + "info.log", std::ios::out);
+    debug_stream.open(core_logging_folder + "debug.log", std::ios::out);
+    timer_stream.open(core_logging_folder + "timer.log", std::ios::out);
+
+    // Close log files and reopen in "append mode". Probably not necessary but here to be safe.
+    close_log_files();
+    error_stream.open(core_logging_folder + "error.log", std::ios::app | std::ios::out);
+    warn_stream.open(core_logging_folder + "warn.log", std::ios::app | std::ios::out);
+    info_stream.open(core_logging_folder + "info.log", std::ios::app | std::ios::out);
+    debug_stream.open(core_logging_folder + "debug.log", std::ios::app | std::ios::out);
+    timer_stream.open(core_logging_folder + "timer.log", std::ios::app | std::ios::out);
+}
diff --git a/part_0/logging.h b/part_0/logging.h
new file mode 100644
index 0000000..afa4aed
--- /dev/null
+++ b/part_0/logging.h
@@ -0,0 +1,84 @@
+/**
+ * Header file for project logging.
+ */
+
+
+// System Import Headers.
+#include <cstdio>
+#include <cstdlib>
+#include <cctype>
+#include <execinfo.h>
+#include <iostream>
+#include <filesystem>
+#include <fstream>
+#include <string>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+
+/**
+ * Logging class definition.
+ */
+class Logging {
+
+    private:
+        std::string core_logging_folder;
+        std::ofstream debug_stream;
+        std::ofstream info_stream;
+        std::ofstream warn_stream;
+        std::ofstream error_stream;
+        std::ofstream timer_stream;
+
+    public:
+        /**
+         * Class constructor.
+         */
+        Logging();
+
+        /**
+         * Class deconstructor.
+         * Closes file descriptors, if open.
+         */
+        ~Logging();
+
+        /**
+         * Closes all open logging file descriptors.
+         */
+        void close_log_files();
+
+        /**
+         * Writes message to info log file.
+         */
+        void debug(std::string msg, std::string log_type = "DEBUG", const char* file = __builtin_FILE(), long line_num  = __builtin_LINE());
+
+        /**
+         * Writes message to info log file.
+         */
+        void info(std::string msg, std::string log_type = "INFO", const char* file = __builtin_FILE(), long line_num  = __builtin_LINE());
+
+        /**
+         * Writes message to warning log file.
+         */
+        void warn(std::string msg, std::string log_type = "WARN", const char* file = __builtin_FILE(), long line_num  = __builtin_LINE());
+
+        /**
+         * Writes message to error log file.
+         */
+        void error(std::string msg, std::string log_type = "ERROR", const char* file = __builtin_FILE(), long line_num  = __builtin_LINE());
+
+        /**
+         * Writes message to timer log file.
+         */
+        void timer(std::string msg, std::string log_type = "TIMER", const char* file = __builtin_FILE(), long line_num  = __builtin_LINE());
+
+
+        /**
+         * Prints out logging location to console.
+         */
+        void display_logging_folder();
+
+        /**
+         * Resets contents of logging files.
+         */
+        void clear_log_files();
+};
diff --git a/part_0/main.cu b/part_0/main.cu
new file mode 100644
index 0000000..d39f621
--- /dev/null
+++ b/part_0/main.cu
@@ -0,0 +1,179 @@
+/**
+ * Program entrypoint and intiialization.
+ */
+
+
+// System Import Headers.
+#include <cctype>
+#include <cstdio>
+#include <cstdlib>
+#include <ctime>
+#include <filesystem>
+#include <fstream>
+#include <iostream>
+#include <iterator>
+#include <string>
+#include <vector>
+
+// User Import Headers.
+#ifndef user_headers
+    #define user_headers
+    #include "image.h"
+    #include "utility.h"
+#endif
+
+#ifndef user_headers_logging
+    #define user_headers_logging
+    #include "logging.h"
+#endif
+
+
+// Method Declaration.
+void process_dir(std::string path_str);
+void process_file(std::string path_str);
+
+
+// Global Variables.
+#ifndef LOGGING_CLASS_INIT
+    #define LOGGING_CLASS_INIT
+    Logging logger;
+#endif
+
+
+/*
+ * Program's main. Initializes and runs program.
+ */
+int main(int argc, char* argv[]) {
+    printf("Starting program.\n");
+    printf("\n");
+    printf("\n");
+
+    // Initialize logging and clear any existing log contents for new program run.
+    logger.clear_log_files();
+
+    // Check provided number of args.
+    if (argc < 2) {
+        // No args provided.
+        printf("Provided too few program args. Expected one arg.");
+    } else if (argc > 2) {
+        // More than one arg provided.
+        printf("Provided too many program args. Expected one arg.");
+    } else {
+        // Correct number of args provided. Pass to image class for processing.
+
+        // Check data type.
+        std::string path_str = argv[1];
+        path_validator_struct* return_struct = validate_path(path_str);
+        int path_type = return_struct->file_type;
+        if (return_struct->err_code) {
+            std::cerr << "Error verifying file type: " << return_struct->err_code.message() << std::endl;
+        }
+        free_path_validator_struct(return_struct);
+
+        // Handle based on provided type.
+        if (path_type == TYPE_DIR) {
+            // Process dir.
+            process_dir(path_str);
+        } else if (path_type == TYPE_FILE) {
+            // Process file.
+            process_file(path_str);
+        }
+    }
+
+    printf("\n");
+    printf("\n");
+    printf("Terminating program.\n");
+    exit(0);
+}
+
+
+/**
+ * Finds and processes all files in directory.
+ * Assumes directory is full of image files.
+ */
+void process_dir(std::string path_str) {
+    logger.debug("process_dir():");
+    logger.debug("    path_str: " + path_str);
+    logger.debug("");
+
+    logger.timer("");
+    logger.timer("");
+    logger.timer("");
+    logger.timer("Handling dir \"" + path_str + "\"");
+
+    // Track time to process.
+    time_t start_time = time(0);
+
+    // Process all files in directory.
+    for (const auto & entry : std::filesystem::directory_iterator(path_str)) {
+        // Get as string.
+        std::string file_path_str{entry.path().u8string()};
+        // std::cout << file_path_str << std::endl;
+
+        // Check if new path value is directory or file.
+        path_validator_struct* return_struct = validate_path(file_path_str);
+        int path_type = return_struct->file_type;
+        if (return_struct->err_code) {
+            std::cerr << "Error verifying file type: " << return_struct->err_code.message() << std::endl;
+        }
+        free_path_validator_struct(return_struct);
+
+        // Handle based on provided type.
+        if (path_type == TYPE_DIR) {
+            // Process dir.
+            process_dir(file_path_str);
+        } else if (path_type == TYPE_FILE) {
+            // Process file.
+            process_file(file_path_str);
+        }
+    }
+
+    // Calculate time processing dir.
+    double total_time = difftime( time(0), start_time);
+    logger.timer("");
+    logger.timer("Time to Process Dir \"" + path_str + "\": ");
+    double seconds_elapsed = fmod(total_time, 60);
+    double minutes_elapsed = (total_time - seconds_elapsed) / 60;
+    std::ostringstream second_format_string;
+    std::ostringstream minute_format_string;
+    second_format_string << std::setprecision(0) << std::fixed << seconds_elapsed;
+    minute_format_string << std::setprecision(0) << std::fixed << minutes_elapsed;
+    logger.timer(minute_format_string.str() + " min, " + second_format_string.str() + " seconds");
+}
+
+
+/**
+ * Processes a single image file.
+ */
+void process_file(std::string path_str) {
+    logger.debug("process_file():");
+    logger.debug("    path_str: " + path_str);
+    logger.debug("");
+
+    logger.info("");
+    logger.info("");
+    logger.timer("");
+    logger.timer("Handling file \"" + path_str + "\"");
+
+    // Track time to process.
+    time_t start_time = time(0);
+
+    // Import image data to class.
+    Image image(path_str);
+    image.display_properties();
+
+    // Manipulate and save image data.
+    image.compute();
+    // image.save();
+
+    // Calculate time processing file.
+    double total_time = difftime( time(0), start_time);
+    logger.timer("Time to Process File \"" + path_str + "\": ");
+    double seconds_elapsed = fmod(total_time, 60);
+    double minutes_elapsed = (total_time - seconds_elapsed) / 60;
+    std::ostringstream second_format_string;
+    std::ostringstream minute_format_string;
+    second_format_string << std::setprecision(0) << std::fixed << seconds_elapsed;
+    minute_format_string << std::setprecision(0) << std::fixed << minutes_elapsed;
+    logger.timer(minute_format_string.str() + " min, " + second_format_string.str() + " seconds");
+}
diff --git a/part_0/makefile b/part_0/makefile
new file mode 100644
index 0000000..85404c9
--- /dev/null
+++ b/part_0/makefile
@@ -0,0 +1,36 @@
+
+# Tell makefile to use commands defined here, if file with same name exists.
+.PHONY: all run valgrind
+# Set default if none is specified.
+default: all
+
+
+# Variables.
+CC = nvcc
+CFLAGS = -std=c++17
+TARGET = main.out
+LIBRARIES =
+DEPENDENCIES = ./main.cu ./image.cu ./logging.cu ./utility.cu
+# ARGS = `arg="$(filter-out $@,$(MAKECMDGOALS))" && echo $${arg:-${1}}`
+ARGS = $(filter-out $@,$(MAKECMDGOALS))
+
+
+# Compile target if any depenencies update.
+$(TARGET): $(DEPENDENCIES)
+	@$(CC) $(CFLAGS) $(DEPENDENCIES) -o $(TARGET) $(LIBRARIES)
+
+
+# Compile target if dependencies update.
+all: $(TARGET)
+
+
+# Compile target if dependencies update. Then run.
+run: $(TARGET)
+	@./$(TARGET) $(ARGS)
+
+
+# Compile target if dependencies update. Then run.
+valgrind: $(TARGET)
+# 	@rm ./logs/vg/*
+# 	valgrind --leak-check=full --log-file=./logs/vg/vg.%p ./$(TARGET) $(ARGS)
+	valgrind --leak-check=full ./$(TARGET) $(ARGS)
diff --git a/part_0/utility.cu b/part_0/utility.cu
new file mode 100644
index 0000000..d585b05
--- /dev/null
+++ b/part_0/utility.cu
@@ -0,0 +1,88 @@
+/**
+ * File for utility logic.
+ */
+
+// User Import Headers.
+#ifndef user_headers
+    #define user_headers
+    #include "utility.h"
+#endif
+
+#ifndef user_headers_logging
+    #define user_headers_logging
+    #include "logging.h"
+#endif
+
+
+// Global Variables.
+#ifndef LOGGING_CLASS_INIT
+    #define LOGGING_CLASS_INIT
+    extern Logging logger;
+#endif
+const int TYPE_DIR = 0;
+const int TYPE_FILE = 1;
+const int TYPE_OTHER = 2;
+path_validator_struct* path_validator_ptr;
+
+
+/**
+ * Initializes a "path_validator" object.
+ */
+path_validator_struct* initialize_path_validator_struct(int file_type, std::error_code err_code) {
+    logger.debug("initialize_path_validator_struct():");
+    logger.debug("    file_type: " + std::to_string(file_type));
+    // logger.debug("    err_code: " + err_code);
+    logger.debug("");
+
+    // Create struct.
+    path_validator_ptr = (path_validator_struct*) calloc(1, sizeof(path_validator_struct));
+
+    // Populate fields.
+    path_validator_ptr->file_type = file_type;
+    path_validator_ptr->err_code = err_code;
+
+    return path_validator_ptr;
+}
+
+
+/**
+ * Destroyes passed "path_validator" object and all associated fields.
+ */
+void free_path_validator_struct(path_validator_struct* path_validator_ptr) {
+    logger.debug("free_path_validator_struct():");
+    // logger.debug("    path_validator_ptr: " + path_validator_ptr);
+    logger.debug("");
+
+    free(path_validator_ptr);
+}
+
+
+/**
+ * Validates provided path and if valid, determines path type.
+ * Returns struct of data.
+ */
+path_validator_struct* validate_path(std::string path_str) {
+    logger.debug("validate_path():");
+    logger.debug("    path_str: " + path_str);
+    logger.debug("");
+
+    int type_value = TYPE_OTHER;
+    std::error_code ec;
+
+    // Check if provided path exists.
+    std::ifstream test_path_exists(path_str);
+    if (test_path_exists) {
+        // Path exists. Check if dir or file.
+        if (std::filesystem::is_directory(path_str, ec)) {
+            // Is dir.
+            type_value = TYPE_DIR;
+        } else if ((! ec) && (std::filesystem::is_regular_file(path_str, ec))) {
+            // Is file.
+            type_value = TYPE_FILE;
+        }
+    }
+
+    // Return values.
+    path_validator_struct* return_struct_ptr = initialize_path_validator_struct(type_value, ec);
+    return return_struct_ptr;
+}
diff --git a/part_0/utility.h b/part_0/utility.h
new file mode 100644
index 0000000..fea1b97
--- /dev/null
+++ b/part_0/utility.h
@@ -0,0 +1,34 @@
+/**
+ * Header file for project utility logic.
+ */
+
+
+// System Import Headers.
+#include <cctype>
+#include <cstdio>
+#include <cstdlib>
+#include <filesystem>
+#include <fstream>
+#include <iostream>
+#include <string>
+
+
+// Global Variables.
+extern const int TYPE_DIR;
+extern const int TYPE_FILE;
+extern const int TYPE_OTHER;
+
+
+/**
+ * Data return for path validation function.
+ */
+typedef struct {
+    int file_type;
+    std::error_code err_code;
+} path_validator_struct;
+
+
+// // Function Prototypes.
+path_validator_struct* initialize_path_validator_struct(int file_type, std::error_code err_code);
+void free_path_validator_struct(path_validator_struct* path_validator_ptr);
+path_validator_struct* validate_path(std::string path_str);
diff --git a/part_1/image.cu b/part_1/image.cu
index 9f0eb58..c77c408 100644
--- a/part_1/image.cu
+++ b/part_1/image.cu
@@ -78,201 +78,6 @@ void display_cuda_device_properties() {
     }
 }
 
-/**
- * Updates image so that every other pixel alternates between pure black and pure white.
- * Purely for testing purposes.
- */
-// __global__ void cuda_pixel_alternating(Image::PixelStruct* pixel_arr, int pixel_index) {
-__global__ void cuda_alternating_pixels(unsigned long total_pixels, unsigned long chunk_size, int chunk_counter, Image::PixelStruct* pixel_arr) {
-    // Calculate current index.
-    unsigned long pixel_index = blockIdx.x * blockDim.x + threadIdx.x;
-
-    // Adjust index for current chunk.
-    pixel_index += (chunk_size * chunk_counter);
-
-    // Only proceed if GPU thread corresponds to a pixel in the image. Ignore all extra threads.
-    if (pixel_index <= total_pixels) {
-
-        // Manipulate pixel data based on index.
-        if (pixel_index % 2 == 0) {
-            // Set to black.
-            pixel_arr[pixel_index].r = 0.f;
-            pixel_arr[pixel_index].g = 0.f;
-            pixel_arr[pixel_index].b = 0.f;
-        } else {
-            // Set to white.
-            pixel_arr[pixel_index].r = 255.f;
-            pixel_arr[pixel_index].g = 255.f;
-            pixel_arr[pixel_index].b = 255.f;
-        }
-    }
-}
-
-
-/**
- * Updates image so that blocks of pixels alternate between pure black and pure white.
- * Effectively, it's similar to cuda_alternating_pixels, except that all image sizes should look roughly the same.
- * Purely for testing purposes.
- */
-// __global__ void cuda_pixel_alternating(Image::PixelStruct* pixel_arr, int pixel_index) {
-__global__ void cuda_alternating_blocks(unsigned long total_pixels, unsigned long chunk_size, int chunk_counter, int pixel_block_size, int image_width, int image_height, Image::PixelStruct* pixel_arr) {
-    // Calculate current index.
-    unsigned long pixel_index = blockIdx.x * blockDim.x + threadIdx.x;
-
-    // Adjust index for current chunk.
-    pixel_index += (chunk_size * chunk_counter);
-
-    // Only proceed if GPU thread corresponds to a pixel in the image. Ignore all extra threads.
-    if (pixel_index <= total_pixels) {
-
-        // Calculate if falls under black or white pixel block.
-        int row = pixel_index / image_width;
-        int col = pixel_index % image_width;
-        int row_grouping = row / pixel_block_size;
-        int col_grouping = col / pixel_block_size;
-        float color;
-        if (row_grouping % 2 == 0) {
-            if ((col_grouping % 2) == 0) {
-                color = 255.f;
-            } else {
-                color = 0.f;
-            }
-        } else {
-            if ((col_grouping % 2) == 0) {
-                color = 0.f;
-            } else {
-
-                color = 255.f;
-            }
-        }
-
-        // Set pixel color.
-        pixel_arr[pixel_index].r = color;
-        pixel_arr[pixel_index].g = color;
-        pixel_arr[pixel_index].b = color;
-
-        // int color = row * col * 2;
-        // printf("Width: %d    Height: %d    Index: %ld    Row: %d    Col: %d    Color: %d\n", image_width, image_height, pixel_index, row, col, color);
-        // pixel_arr[pixel_index].r = static_cast<float>(color) / 255.f;
-        // pixel_arr[pixel_index].g = static_cast<float>(color) / 255.f;
-        // pixel_arr[pixel_index].b = static_cast<float>(color) / 255.f;
-    }
-}
-
-
-/**
- * Updates image to increase percieved value of red in image.
- * Purely for testing purposes.
- */
-__global__ void cuda_increase_red(unsigned long total_pixels, unsigned long chunk_size, int chunk_counter, Image::PixelStruct* pixel_arr) {
-    // Calculate current index.
-    unsigned long pixel_index = blockIdx.x * blockDim.x + threadIdx.x;
-
-    // Adjust index for current chunk.
-    pixel_index += (chunk_size * chunk_counter);
-
-    // Only proceed if GPU thread corresponds to a pixel in the image. Ignore all extra threads.
-    if (pixel_index <= total_pixels) {
-
-        // Calculate new pixel colors.
-        float red = pixel_arr[pixel_index].r + 0.1f;
-        float green = pixel_arr[pixel_index].g - 0.1f;
-        float blue = pixel_arr[pixel_index].b - 0.1f;
-
-        // Handle if colors exceed expected values.
-        if (red > 1.0f) {
-            red = 1.f;
-        }
-        if (green < 0.0f) {
-            green = 0.f;
-        }
-        if (blue < 0.0f) {
-            blue = 0.f;
-        }
-
-        // Update pixel values.
-        pixel_arr[pixel_index].r = red;
-        pixel_arr[pixel_index].g = green;
-        pixel_arr[pixel_index].b = blue;
-    }
-}
-
-
-/**
- * Updates image to increase percieved value of green in image.
- * Purely for testing purposes.
- */
-__global__ void cuda_increase_green(unsigned long total_pixels, unsigned long chunk_size, int chunk_counter, Image::PixelStruct* pixel_arr) {
-    // Calculate current index.
-    unsigned long pixel_index = blockIdx.x * blockDim.x + threadIdx.x;
-
-    // Adjust index for current chunk.
-    pixel_index += (chunk_size * chunk_counter);
-
-    // Only proceed if GPU thread corresponds to a pixel in the image. Ignore all extra threads.
-    if (pixel_index <= total_pixels) {
-
-        // Calculate new pixel colors.
-        float red = pixel_arr[pixel_index].r - 0.1f;
-        float green = pixel_arr[pixel_index].g + 0.1f;
-        float blue = pixel_arr[pixel_index].b - 0.1f;
-
-        // Handle if colors exceed expected values.
-        if (red < 0.0f) {
-            red = 0.f;
-        }
-        if (green > 1.0f) {
-            green = 1.f;
-        }
-        if (blue < 0.0f) {
-            blue = 0.f;
-        }
-
-        // Update pixel values.
-        pixel_arr[pixel_index].r = red;
-        pixel_arr[pixel_index].g = green;
-        pixel_arr[pixel_index].b = blue;
-    }
-}
-
-
-/**
- * Updates image to increase percieved value of blue in image.
- * Purely for testing purposes.
- */
-__global__ void cuda_increase_blue(unsigned long total_pixels, unsigned long chunk_size, int chunk_counter, Image::PixelStruct* pixel_arr) {
-    // Calculate current index.
-    unsigned long pixel_index = blockIdx.x * blockDim.x + threadIdx.x;
-
-    // Adjust index for current chunk.
-    pixel_index += (chunk_size * chunk_counter);
-
-    // Only proceed if GPU thread corresponds to a pixel in the image. Ignore all extra threads.
-    if (pixel_index <= total_pixels) {
-
-        // Calculate new pixel colors.
-        float red = pixel_arr[pixel_index].r - 0.1f;
-        float green = pixel_arr[pixel_index].g - 0.1f;
-        float blue = pixel_arr[pixel_index].b + 0.1f;
-
-        // Handle if colors exceed expected values.
-        if (red < 0.0f) {
-            red = 0.f;
-        }
-        if (green < 0.0f) {
-            green = 0.f;
-        }
-        if (blue > 1.0f) {
-            blue = 1.f;
-        }
-
-        // Update pixel values.
-        pixel_arr[pixel_index].r = red;
-        pixel_arr[pixel_index].g = green;
-        pixel_arr[pixel_index].b = blue;
-    }
-}
-
 
 /**
  * Uses convolution to blur image.
@@ -504,7 +309,7 @@ void Image::calculate_paths(std::string image_path) {
 
     // // Calculate output path location.
     output_path = "../output/";
-    std::string base_filename = image_path.substr(image_path.find_last_of("/\\") + 1);
+    std::string base_filename = get_file_name();;
 
     // Grab directory substrings and generate full output path.
     size_t pos = 0;
@@ -681,6 +486,30 @@ void Image::save() {
 }
 
 
+/**
+ * Gets current file name, using original inpute path value.
+ */
+std::string Image::get_file_name() {
+    logger.debug("Image::get_file_name():");
+    logger.debug("");
+
+    return input_path.substr(input_path.find_last_of("/\\") + 1);
+}
+
+
+/**
+ * Changes output file name to provided value.
+ */
+void Image::set_file_name(std::string file_name) {
+    logger.debug("Image::compute():");
+    logger.debug("    file_name: " + file_name);
+    logger.debug("");
+
+    std::string new_path = output_path.substr(0, output_path.find_last_of("/\\") + 1);
+    output_path = new_path + file_name;
+}
+
+
 /**
  * Method to actually do work on image.
  */
@@ -697,24 +526,13 @@ void Image::compute() {
     int cuda_device_max_threads = cuda_device_properties.maxThreadsPerBlock;
     unsigned long cuda_chunk_size = cuda_device_max_blocks * cuda_device_max_threads;
 
-    logger.info("CUDA Max Blocks: " + std::to_string(cuda_device_max_blocks));
-    logger.info("CUDA Max Threads: " + std::to_string(cuda_device_max_threads));
-    logger.info("CUDA Chunk Size: " + std::to_string(cuda_chunk_size));
-    logger.info("CUDA Global Mem: " + std::to_string(cuda_device_properties.totalGlobalMem));
-
     // Calculate some image data.
     int pixel_struct_size = sizeof(PixelStruct);
     unsigned long image_total_pixels = width * height;
     unsigned long pixel_struct_byte_count = image_total_pixels * pixel_struct_size;
 
-    logger.info("Total Image Size: " + std::to_string(image_total_pixels));
-    logger.info("Pixel Struct Size: " + std::to_string(pixel_struct_byte_count));
-
     // We handle image in chunks, in case we're dealing with large images with more pixels than CUDA device blocks/threads.
     int total_image_chunks = (image_total_pixels / cuda_chunk_size) + 1;
-    logger.info("Total Image Chunks: " + std::to_string(total_image_chunks));
-    logger.info("Desired CUDA block count: " + std::to_string(cuda_device_max_blocks));
-    logger.info("Desired CUDA thread count: " + std::to_string(cuda_device_max_threads));
 
     // Generate convolution mask dimensions. Should be either 10% of image size or 3 pixels large, whichever is greater.
     int mask_width = width / 10;
@@ -784,21 +602,6 @@ void Image::compute() {
         logger.info("Iterating over image chunk " + std::to_string(chunk_index));
         logger.info("Accounts for pixels " + std::to_string(cuda_chunk_size * chunk_index) + " through " + std::to_string((cuda_chunk_size * chunk_index) + cuda_chunk_size) );
 
-        // Run GPU test logic to set alternating pixels to black/white.
-        // cuda_alternating_pixels<<<cuda_device_max_blocks, cuda_device_max_threads>>>(image_total_pixels, cuda_chunk_size, chunk_index, gpu_pixel_arr);
-
-        // Run GPU test logic to create black/white checkerboard pattern.
-        // int pixel_block_size = width / 10;
-        // if (pixel_block_size <= 1) {
-        //     pixel_block_size = 2;
-        // }
-        // cuda_alternating_blocks<<<cuda_device_max_blocks, cuda_device_max_threads>>>(image_total_pixels, cuda_chunk_size, chunk_index, pixel_block_size, width, height, gpu_pixel_arr);
-
-        // Run GPU test logic to increase/decrease r/g/b colors in image..
-        // cuda_increase_red<<<cuda_device_max_blocks, cuda_device_max_threads>>>(image_total_pixels, cuda_chunk_size, chunk_index, gpu_pixel_arr);
-        // cuda_increase_green<<<cuda_device_max_blocks, cuda_device_max_threads>>>(image_total_pixels, cuda_chunk_size, chunk_index, gpu_pixel_arr);
-        // cuda_increase_blue<<<cuda_device_max_blocks, cuda_device_max_threads>>>(image_total_pixels, cuda_chunk_size, chunk_index, gpu_pixel_arr);
-
         // Run GPU logic to use convolution to create blur effect.
         cuda_convolution_blur<<<cuda_device_max_blocks, cuda_device_max_threads>>>(image_total_pixels, cuda_chunk_size, chunk_index, width, height, mask_width, mask_height, gpu_pixel_arr_orig, gpu_pixel_arr, gpu_mask_arr);
 
@@ -809,6 +612,9 @@ void Image::compute() {
     // Retrieve results.
     cudaMemcpy(pixel_arr, gpu_pixel_arr, pixel_struct_byte_count, cudaMemcpyDeviceToHost);
     cudaFree(gpu_pixel_arr);
+    cudaFree(gpu_pixel_arr_orig);
+    cudaFree(gpu_mask_arr);
+    delete[] mask_arr;
 
     logger.debug("Image::compute() finished.");
     logger.debug("");
diff --git a/part_1/image.h b/part_1/image.h
index df24cba..a64cc8c 100644
--- a/part_1/image.h
+++ b/part_1/image.h
@@ -109,6 +109,18 @@ class Image {
         void save();
 
 
+        /**
+         * Gets current file name, using original inpute path value.
+         */
+        std::string get_file_name();
+
+
+        /**
+         * Changes output file name to provided value.
+         */
+        void set_file_name(std::string file_name);
+
+
         /**
          * Method to actually do work on image.
          */
-- 
GitLab