diff --git a/part_2/image.cu b/part_2/image.cu
new file mode 100644
index 0000000000000000000000000000000000000000..e81b6d2c2720cc764ebf2a7f85899051eb045a71
--- /dev/null
+++ b/part_2/image.cu
@@ -0,0 +1,588 @@
+/**
+ * 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");
+    }
+}
+
+
+/**
+ * Uses convolution to blur image.
+ */
+__global__ void cuda_convolution_blur(
+    unsigned long total_pixels, unsigned long chunk_size,
+    int chunk_counter, int image_width, int image_height, int mask_width, int mask_height,
+    Image::PixelStruct* pixel_arr_orig, Image::PixelStruct* pixel_arr, Image::PixelStruct* mask_arr, Image::PixelStruct mask_weight_struct
+) {
+    // 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) {
+
+        // if (pixel_index == 71) {
+        //     printf("\n");
+        //     printf("total_pixels: %ld    pixel_index: %ld    image_width: %d    image_height: %d\n", total_pixels, pixel_index, image_width, image_height);
+        //     printf("\n");
+        //     for (int row_index = 0; row_index < image_height; row_index++) {
+        //         for (int col_index = 0; col_index < image_width; col_index++) {
+        //             unsigned long arr_index = col_index + (row_index * image_width);
+
+        //             printf(" [%d,%d] (%f)  ", col_index, row_index, pixel_arr[arr_index].r);
+        //         }
+        //         printf("\n");
+        //     }
+        //     printf("\n\n");
+        // }
+
+        // Calculate pixel location data.
+        int image_row = (pixel_index / image_width) % image_height;
+        int image_col = pixel_index % image_width;
+
+        // Calculate mask location data.
+        int mask_mid_row = mask_height / 2;
+        int mask_mid_col = mask_width / 2;
+
+        // Calculate pixel locations based on mask values.
+        int col_far_left = image_col - mask_mid_col;
+        int col_far_right = image_col + mask_mid_col;
+        int row_far_top = image_row - mask_mid_row;
+        int row_far_bot = image_row + mask_mid_row;
+
+        // Initialize for mask loop.
+        int mask_index = 0;
+        float convolution_total_r = 0.f;
+        float convolution_total_g = 0.f;
+        float convolution_total_b = 0.f;
+
+        // Loop through all mask indexes and calculate weighted values.
+        for (int row_index = row_far_top; row_index <= row_far_bot; row_index++) {
+            for (int col_index = col_far_left; col_index <= col_far_right; col_index++) {
+
+                // Double check that mask index is within image bounds. Ignore otherwise.
+                if ( ( (col_index >= 0) && (col_index < image_width) ) && ( (row_index >= 0) && (row_index < image_height) ) ) {
+
+                    unsigned long arr_index = col_index + (row_index * image_width);
+                    convolution_total_r += (pixel_arr_orig[arr_index].r * mask_arr[mask_index].r);
+                    convolution_total_g += (pixel_arr_orig[arr_index].g * mask_arr[mask_index].g);
+                    convolution_total_b += (pixel_arr_orig[arr_index].b * mask_arr[mask_index].b);
+                }
+
+                mask_index += 1;
+            }
+        }
+
+        // Calculate final pixel data based on mask loop values.
+        if (convolution_total_r > 0.f) {
+            // Handle for value greater than 0.
+            pixel_arr[pixel_index].r = (1.f / mask_weight_struct.r) * convolution_total_r;
+        } else {
+            pixel_arr[pixel_index].r = 0.f;
+        }
+
+        if (convolution_total_g > 0.f) {
+            // Handle for value greater than 0.
+            pixel_arr[pixel_index].g = (1.f / mask_weight_struct.g) * convolution_total_g;
+        } else {
+            pixel_arr[pixel_index].g = 0.f;
+        }
+
+        if (convolution_total_b > 0.f) {
+            // Handle for value greater than 0.
+            pixel_arr[pixel_index].b = (1 / mask_weight_struct.b) *  convolution_total_b;
+        } else {
+            pixel_arr[pixel_index].b = 0.f;
+        }
+    }
+}
+
+//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 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;
+
+    // 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;
+
+    // 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;
+
+    // Generate convolution mask dimensions. Should be either 10% of image size or 3 pixels large, whichever is greater.
+    int mask_width = width / 50;
+    int mask_height = height / 50;
+    if ((mask_width % 2) == 0) {
+        mask_width -= 1;
+    }
+    if ((mask_height % 2) == 0) {
+        mask_height -= 1;
+    }
+    if (mask_width < 3) {
+        mask_width = 3;
+    }
+    if (mask_height < 3) {
+        mask_height = 3;
+    }
+    int mask_middle_col = mask_width / 2;
+    int mask_middle_row = mask_height / 2;
+
+    // Calculate additional mask initialization values.
+    // Found via much struggling and trial + error.
+    // float mask_col_adj = 1.f / (mask_middle_col + 1);
+    // float mask_row_adj = 1.f / (mask_middle_row + 1);
+    float mask_col_adj = 1.f;
+    float mask_row_adj = 1.f;
+    // printf("mask_middle_col: %d    mask_middle_row: %d\n", mask_middle_col, mask_middle_row);
+    // printf("mask_col_adj: %f    mask_row_adj: %f\n", mask_col_adj, mask_row_adj);
+    unsigned long mask_total_pixels = mask_width * mask_height;
+    unsigned long mask_struct_byte_count = mask_total_pixels * pixel_struct_size;
+
+    // Initialize mask and set weights.
+    // printf("\n\n");
+    logger.info("Mask size: " + std::to_string(mask_width) + "x" + std::to_string(mask_height));
+    PixelStruct* mask_arr = new PixelStruct[mask_total_pixels];
+    PixelStruct mask_weight_struct = PixelStruct(0.f);
+    for (int mask_row_index = 0; mask_row_index < mask_height; mask_row_index++) {
+        for (int mask_col_index = 0; mask_col_index < mask_width; mask_col_index++) {
+            // Get current overall mask index, based on row and col values.
+            int mask_index = mask_col_index + (mask_row_index * mask_width);
+
+            // Calculate individual weights based on current x and y axis location of mask index, using distance from center point.
+            float mask_col_weight = (fabs(mask_middle_col - fabs(mask_middle_col - mask_col_index)) + 1) * mask_col_adj;
+            float mask_row_weight = (fabs(mask_middle_row - fabs(mask_middle_row - mask_row_index)) + 1) * mask_row_adj;
+
+            // Calculate overall index weight, based on combination of individual dimension weights.
+            float index_weight = (mask_col_weight * mask_row_weight);
+
+            // printf("  [%d, %d] (%d) (%f,%f)  ", mask_col_index, mask_row_index, mask_index, mask_col_weight, mask_row_weight);
+            // printf("  [%d, %d] (%d) (%f)  ", mask_col_index, mask_row_index, mask_index, index_weight);
+
+            // Finally, set mask index values.
+            mask_arr[mask_index].r = index_weight;
+            mask_arr[mask_index].g = index_weight;
+            mask_arr[mask_index].b = index_weight;
+            mask_weight_struct.r += index_weight;
+            mask_weight_struct.g += index_weight;
+            mask_weight_struct.b += index_weight;
+            // mask_arr[mask_index].r = 1.f;
+            // mask_arr[mask_index].g = 1.f;
+            // mask_arr[mask_index].b = 1.f;
+        }
+        // printf("\n");
+    }
+    // printf("Total Weights:    r: %f    g: %f    b: %f\n", mask_weight_struct.r, mask_weight_struct.b, mask_weight_struct.g);
+    // printf("\n\n");
+
+    // Allocate memory on GPU.
+    PixelStruct* gpu_pixel_arr;
+    PixelStruct* gpu_pixel_arr_orig;
+    PixelStruct* gpu_mask_arr;
+    cudaMalloc((void**) &gpu_pixel_arr, pixel_struct_byte_count);
+    cudaMalloc((void**) &gpu_pixel_arr_orig, pixel_struct_byte_count);
+    cudaMalloc((void**) &gpu_mask_arr, mask_struct_byte_count);
+    cudaMemcpy(gpu_pixel_arr, pixel_arr, pixel_struct_byte_count, cudaMemcpyHostToDevice);
+    cudaMemcpy(gpu_pixel_arr_orig, pixel_arr, pixel_struct_byte_count, cudaMemcpyHostToDevice);
+    cudaMemcpy(gpu_mask_arr, mask_arr, mask_struct_byte_count, cudaMemcpyHostToDevice);
+
+    // Process image by iterating over all chunks.
+    for (int chunk_index = 0; chunk_index < total_image_chunks; chunk_index++) {
+        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 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, mask_weight_struct);
+
+        // Synchronize all GPU computations before iterating over next chunk.
+        cudaDeviceSynchronize();
+    }
+
+    // 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("");
+}
+
+//endregion Public Methods.
diff --git a/part_2/image.h b/part_2/image.h
new file mode 100644
index 0000000000000000000000000000000000000000..a64cc8c0de6647e480888f277773d4fa312fcbfc
--- /dev/null
+++ b/part_2/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_2/logging.cu b/part_2/logging.cu
new file mode 100644
index 0000000000000000000000000000000000000000..fc66c735c97e95044f1386727792786192e329cb
--- /dev/null
+++ b/part_2/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_2/logging.h b/part_2/logging.h
new file mode 100644
index 0000000000000000000000000000000000000000..afa4aed8b8aff86fff23ec2e4f55aad15bd69554
--- /dev/null
+++ b/part_2/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_2/main.cu b/part_2/main.cu
new file mode 100644
index 0000000000000000000000000000000000000000..659268956125c116f46b8b6e2a8526e1b9eb9094
--- /dev/null
+++ b/part_2/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_2/makefile b/part_2/makefile
new file mode 100644
index 0000000000000000000000000000000000000000..16b7f1c4f769964fc5c3b8bebb0ca5b48d8b2e97
--- /dev/null
+++ b/part_2/makefile
@@ -0,0 +1,41 @@
+
+# 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 with valgrind.
+valgrind: $(TARGET)
+# 	@rm ./logs/vg/*
+# 	valgrind --leak-check=full --log-file=./logs/vg/vg.%p ./$(TARGET) $(ARGS)
+	valgrind --leak-check=full ./$(TARGET) $(ARGS)
+
+
+# Compile target if dependencies update. Then run with cuda-memcheck.
+memcheck: $(TARGET)
+	cuda-memcheck --leak-check full ./$(TARGET) $(ARGS)
diff --git a/part_2/utility.cu b/part_2/utility.cu
new file mode 100644
index 0000000000000000000000000000000000000000..d585b055a8e974d7e07d506485e8a842cd35839c
--- /dev/null
+++ b/part_2/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_2/utility.h b/part_2/utility.h
new file mode 100644
index 0000000000000000000000000000000000000000..fea1b97f3423ee1aa6f6ef5e15b1678d65577628
--- /dev/null
+++ b/part_2/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);