diff --git a/HW01/.gitignore b/HW01/.gitignore new file mode 100644 index 0000000..6fcf43c --- /dev/null +++ b/HW01/.gitignore @@ -0,0 +1,2 @@ +# IDE files +.idea/ diff --git a/HW01/hw1_images.zip b/HW01/hw1_images.zip new file mode 100644 index 0000000..249c6ee Binary files /dev/null and b/HW01/hw1_images.zip differ diff --git a/HW01/hw1_images/input_img.jpg b/HW01/hw1_images/input_img.jpg new file mode 100644 index 0000000..4b25274 Binary files /dev/null and b/HW01/hw1_images/input_img.jpg differ diff --git a/HW01/hw1_images/ref_img.jpg b/HW01/hw1_images/ref_img.jpg new file mode 100644 index 0000000..a7a9dc9 Binary files /dev/null and b/HW01/hw1_images/ref_img.jpg differ diff --git a/HW01/scripts/hist_modif.py b/HW01/scripts/hist_modif.py new file mode 100644 index 0000000..e69de29 diff --git a/HW01/scripts/hist_utils.py b/HW01/scripts/hist_utils.py new file mode 100644 index 0000000..2b64c81 --- /dev/null +++ b/HW01/scripts/hist_utils.py @@ -0,0 +1,232 @@ +# +# histogram utilities file +# +# +# author: Christos Choutouridis +# date: 29/04/2025 +# + +try: + import numpy as np + from PIL import Image +except ImportError as e: + print("Missing package: ", e) + print("Run: pip install -r requirements.txt to install.") + exit(1) + + +def calculate_hist_of_img( + img_array: np.ndarray, + return_normalized: bool +) -> dict: + """ + Calculate the histogram of an input image array. + + Args: + img_array (np.ndarray): 2D grayscale image array with float values in [0, 1]. + return_normalized (bool): If True, return normalized histogram (probabilities), + otherwise return absolute counts. + + Returns: + dict: A dictionary mapping each unique input level to its count or probability. + """ + # Flatten the 2D image into a 1D array for easier processing + flat_img = img_array.flatten() + + # Create an empty dictionary to store the histogram + hist = {} + + # Traverse each pixel value + for value in flat_img: + # Increment the count for the corresponding pixel value + if value in hist: + hist[value] += 1 + else: + hist[value] = 1 + + if return_normalized: + total_pixels = flat_img.size + for key in hist: + hist[key] /= total_pixels + + return hist + + + +def apply_hist_modification_transform( + img_array: np.ndarray, + modification_transform: dict +) -> np.ndarray: + """ + Apply a histogram modification transform to an image. + + Args: + img_array (np.ndarray): 2D grayscale image array with float values. + modification_transform (dict): Dictionary mapping each input level to an output level. + + Returns: + np.ndarray: The modified image array. + """ + # Create a copy of the input image to avoid modifying it in-place + modified_img = np.copy(img_array) + + # Get the unique levels in the image + unique_levels = np.unique(modified_img) + + # Apply the transform + for level in unique_levels: + if level in modification_transform: + modified_img[modified_img == level] = modification_transform[level] + else: + # Optional: raise an error or warning if level is missing + raise ValueError(f"Input level {level} not found in modification_transform.") + + return modified_img + + + + + + + + + +# +# =========================== Functional tests =========================== +# +# To execute the tests run: +# python hist_utils.py +# + +def test_calculate_hist_counts(): + """ + Test calculate_hist_of_img with return_normalized = False. + """ + # Create a dummy 3x3 image + img = np.array([ + [0.0, 0.5, 1.0], + [0.5, 0.5, 0.0], + [1.0, 0.0, 1.0] + ]) + + # Call our function + my_hist = calculate_hist_of_img(img, return_normalized=False) + + # Use numpy to verify + values, counts = np.unique(img, return_counts=True) + np_hist = dict(zip(values, counts)) + + # Compare + print("Test case: Counts") + #print("Our histogram:", my_hist) + #print("Numpy histogram:", np_hist) + print("Test passed:", my_hist == np_hist) + print() + + +def test_calculate_hist_normalized(): + """ + Test calculate_hist_of_img with return_normalized = True. + """ + # Create a dummy 3x3 image + img = np.array([ + [0.0, 0.5, 1.0], + [0.5, 0.5, 0.0], + [1.0, 0.0, 1.0] + ]) + + # Call our function + my_hist = calculate_hist_of_img(img, return_normalized=True) + + # Use numpy to verify (manual normalization) + values, counts = np.unique(img, return_counts=True) + total_pixels = img.size + np_hist = dict(zip(values, counts / total_pixels)) + + # Compare + print("Test case: Normalized") + #print("Our histogram:", my_hist) + #print("Numpy histogram:", np_hist) + + # Because of floating point division, use np.isclose instead of exact equality + test_passed = True + for key in my_hist: + if not np.isclose(my_hist[key], np_hist[key]): + test_passed = False + break + + print("Test passed:", test_passed) + print() + + + +def test_apply_hist_modification_identity(): + """ + Test apply_hist_modification_transform with identity transform (no changes). + """ + # Create a simple 3x3 image + img = np.array([ + [0.0, 0.5, 1.0], + [0.5, 0.5, 0.0], + [1.0, 0.0, 1.0] + ]) + + # Define an identity transform (each value maps to itself) + mod_transform = { + 0.0: 0.0, + 0.5: 0.5, + 1.0: 1.0 + } + + # Apply our function + modified_img = apply_hist_modification_transform(img, mod_transform) + + # Compare + print("Test case: Identity transform") + # print("Modified image:\n", modified_img) + # print("Original image:\n", img) + print("Test passed:", np.allclose(modified_img, img)) + print() + + +def test_apply_hist_modification_simple(): + """ + Test apply_hist_modification_transform with a simple transform. + """ + # Create a simple 3x3 image + img = np.array([ + [0.0, 0.5, 1.0], + [0.5, 0.5, 0.0], + [1.0, 0.0, 1.0] + ]) + + # Define a simple modification transform + mod_transform = { + 0.0: 0.2, + 0.5: 0.7, + 1.0: 1.0 + } + + # Expected result + expected_img = np.array([ + [0.2, 0.7, 1.0], + [0.7, 0.7, 0.2], + [1.0, 0.2, 1.0] + ]) + + # Apply our function + modified_img = apply_hist_modification_transform(img, mod_transform) + + # Compare + print("Test case: Simple transform") + # print("Modified image:\n", modified_img) + # print("Expected image:\n", expected_img) + print("Test passed:", np.allclose(modified_img, expected_img)) + print() + + +if __name__ == '__main__': + test_calculate_hist_counts() + test_calculate_hist_normalized() + test_apply_hist_modification_identity() + test_apply_hist_modification_simple() diff --git a/HW01/scripts/requirements.txt b/HW01/scripts/requirements.txt new file mode 100644 index 0000000..0d59398 --- /dev/null +++ b/HW01/scripts/requirements.txt @@ -0,0 +1,2 @@ +numpy +Pillow