From 802cd208dbea7743a779b058478b6bb061e0d62d Mon Sep 17 00:00:00 2001 From: Rafael Zimmer Date: Tue, 18 Oct 2022 22:01:06 -0300 Subject: [PATCH 01/17] Create haralick_descriptors --- computer_vision/haralick_descriptors | 245 +++++++++++++++++++++++++++ 1 file changed, 245 insertions(+) create mode 100644 computer_vision/haralick_descriptors diff --git a/computer_vision/haralick_descriptors b/computer_vision/haralick_descriptors new file mode 100644 index 000000000000..6f8d6bd66b80 --- /dev/null +++ b/computer_vision/haralick_descriptors @@ -0,0 +1,245 @@ +def rmse(original: np.ndarray, reference: np.ndarray) -> float: + """Simple implementation of Root Mean Squared Error for two N dimensional numpy arrays.""" + return np.sqrt(((original - reference) ** 2).mean()) + + +def normalize_image(image: np.ndarray, cap: float = 255) -> np.ndarray: + """Normalizes image in Numpy 2D array format, between ranges 0-cap, as to fit uint8 type. + + Args: + image: 2D numpy array representing image as matrix, with values in any range + cap: Maximum cap amount for normalization + Returns: + return 2D numpy array of type uint8, corresponding to limited range matrix + """ + normalized = (image - np.min(image)) / (np.max(image) - np.min(image)) * cap + return normalized.astype(np.uint8) + + +def normalize_array(array, cap: float = 1): + """Normalizes a 1D array, between ranges 0-cap. + + Args: + array: List containing values to be normalized between cap range. + cap: Maximum cap amount for normalization. + Returns: + return 1D numpy array , corresponding to limited range array + """ + normalized = (array - np.min(array)) / (np.max(array) - np.min(array)) * cap + return normalized + + +def euclidean(point_1: np.ndarray, point_2: np.ndarray): + """Simple method for calculating the euclidean distance between two points, with type np.ndarray.""" + return np.sqrt(np.sum(np.square(point_1 - point_2))) + + +def grayscale(image: np.ndarray) -> np.ndarray: + """Uses luminance weights to transform RGB channel to greyscale, by + taking the dot product between the channel and the weights.""" + return np.dot(image[:, :, 0:3], [0.299, 0.587, 0.114]).astype(np.uint8) + + +def binarize(image, threshold_value): + """Binarizes a grayscale image based on a given threshold value, setting values to 1 or 0 accordingly.""" + binarized = np.where(image > threshold_value, 1, 0) + + return binarized + + +def transform(image, kind, kernel=np.ones((3, 3))): + """Simple image transformation using one of two available filter functions: Erosion and Dilation. + + Args: + image: + kind: Can be either 'erosion', in which case the :func:np.max function is called, + or 'dilation', when :func:np.min is used instead. + kernel: n x n kernel with shape < :attr:image.shape, to be used when applying convolution to original image + + Returns: + + """ + if kind == "erosion": + constant = 1 + apply = np.max + else: + constant = 0 + apply = np.min + + center_x, center_y = (x // 2 for x in kernel.shape) + + # Use padded image when applying convolotion to not go out of bounds of the original the image + transformed = np.zeros(image.shape, dtype=np.uint8) + padded = np.pad(image, 1, 'constant', constant_values=constant) + + for x in range(center_x, padded.shape[0] - center_x): + for y in range(center_y, padded.shape[1] - center_y): + + center = padded[x - center_x: x + center_x + 1, y - center_y: y + center_y + 1] + # Apply transformation method to the centered section of the image + transformed[x - center_x, y - center_y] = apply(center[kernel == 1]) + + return transformed + + +def opening_filter(image, kernel=np.ones((3, 3))): + """Opening filter, defined as the sequence of erosion and then a dilation filter on the same image.""" + return transform( + transform(image, 'dilation', kernel), + 'erosion', kernel + ) + + +def closing_filter(image, kernel=np.ones((3, 3))): + """Opening filter, defined as the sequence of dilation and then erosion filter on the same image.""" + return transform( + transform(image, 'erosion', kernel), + 'dilation', kernel + ) + + +def binary_mask(image_gray, image_map): + """Apply binary mask, or thresholding based on bit mask value (mapping mask is 1 or 0).""" + true_mask, false_mask = np.array(image_gray, copy=True), np.array(image_gray, copy=True) + true_mask[image_map == 1] = 1 + false_mask[image_map == 0] = 0 + + return true_mask, false_mask + + +def matrix_concurrency(image, coordinate): + """Calculate sample co-occurrence matrix based on input image as well as selected coordinates on image. + Implementation is made using basic iteration, as function to be performed (np.max) is non-linear and therefore + not usable on the Fourier Transform domain.""" + matrix = np.zeros([np.max(image) + 1, np.max(image) + 1]) + + offset_x, offset_y = coordinate[0], coordinate[1] + + for x in range(1, image.shape[0] - 1): + for y in range(1, image.shape[1] - 1): + + base_pixel = image[x, y] + offset_pixel = image[x + offset_x, y + offset_y] + + matrix[base_pixel, offset_pixel] += 1 + + return matrix / np.sum(matrix) + + +def haralick_descriptors(matrix): + """Calculates all 8 Haralick descriptors based on co-occurence input matrix. + All descriptors are as follows: + Maximum probability, Inverse Difference, Homogeneity, Entropy, Energy, Dissimilarity, Contrast and Correlation + + Args: + matrix: Co-occurence matrix to use as base for calculating descriptors. + + Returns: + Reverse ordered list of resulting descriptors + """ + # Function np.indices could be used for bigger input types, but np.ogrid works just fine + i, j = np.ogrid[0:matrix.shape[0], 0:matrix.shape[1]] # np.indices() + + # Pre-calculate frequent multiplication and subtraction + prod = np.multiply(i, j) + sub = np.subtract(i, j) + + # Calculate numerical value of Maximum Probability + maximum_prob = np.max(matrix) + # Using the definition for each descriptor individually to calculate its matrix + correlation = prod * matrix + energy = np.power(matrix, 2) + contrast = matrix * np.power(sub, 2) + + dissimilarity = matrix * np.abs(sub) + inverse_difference = matrix / (1 + np.abs(sub)) + homogeneity = matrix / (1 + np.power(sub, 2)) + entropy = -(matrix[matrix > 0] * np.log(matrix[matrix > 0])) + + # Sum values for descriptors ranging from the first one to the last, as all are their respective origin matrix + # and not the resulting value yet. + descriptors = [maximum_prob, correlation.sum(), energy.sum(), contrast.sum(), + dissimilarity.sum(), inverse_difference.sum(), homogeneity.sum(), entropy.sum()] + return descriptors + + +def get_descriptors(masks, coordinate): + """Calculate all Haralick descriptors for a sequence of different co-occurrence matrices, given input + masks and coordinates.""" + + descriptors = list() + for mask in masks: + descriptors.append(haralick_descriptors( + matrix_concurrency(mask, coordinate)) + ) + # Concatenate each individual descriptor into one single list containing sequence of descriptors + return np.concatenate(descriptors, axis=None) + + +def euclidean(point_1: np.ndarray, point_2: np.ndarray): + """Simple method for calculating the euclidean distance between two points, with type np.ndarray.""" + return np.sqrt(np.sum(np.square(point_1 - point_2))) + + +def get_distances(descriptors, base): + """Calculate all Euclidian distances between a selected base descriptor and all other Haralick descriptors + The resulting comparison is return in decreasing order, showing which descriptor is the most similar to the + selected base. + + Args: + descriptors: Haralick descriptors to compare with base index + base: Haralick descriptor index to use as base when calculating respective euclidean distance to other descriptors. + + Returns: + Ordered distances between descriptors + + """ + distances = [] + + for description in descriptors: + distances.append(euclidean(description, descriptors[base])) + # Normalize distances between range [0, 1] + distances = normalize_array(distances, 1) + return sorted(enumerate(distances), key=lambda tup: tup[1]) + + +def main(): + # Index to compare haralick descriptors to + index = int(input()) + q_value = [int(value) for value in input().split()] + + # Format is the respective filter to apply, can be either 1 for the opening filter or else for the closing + parameters = {'format': int(input()), 'threshold': int(input())} + + # Number of images to perform methods on + b_number = int(input()) + + files, descriptors = (list(), list()) + + for _ in range(b_number): + file = input().rstrip() + files.append(file) + + # Open given image and calculate morphological filter, respective masks and correspondent Harralick Descriptors. + image = imageio.imread(file).astype(np.float32) + gray = grayscale(image) + threshold = binarize(gray, parameters['threshold']) + + morphological = opening_filter(threshold) if parameters['format'] == 1 else closing_filter(threshold) + masks = binary_mask(gray, morphological) + descriptors.append(get_descriptors(masks, q_value)) + + # Transform ordered distances array into a sequence of indexes corresponding to original file position + distances = get_distances(descriptors, index) + indexed_distances = np.array(distances).astype(np.uint8)[:, 0] + + # Finally, print distances considering the Haralick descriptions from the base file to + # all other images using the morphology method of choice. + print(f"Query: {files[index]}") + print("Ranking:") + for idx, file_idx in enumerate(indexed_distances): + print(f"({idx}) {files[file_idx]}", end="\n") + + +if __name__ == "__main__": + main() From 88b014f7ab6ee9d8b50c4e6b6a02e38d3a8e7d52 Mon Sep 17 00:00:00 2001 From: Rafael Zimmer Date: Wed, 19 Oct 2022 01:25:19 -0300 Subject: [PATCH 02/17] Working on creating Unit Testing for Haralick Descriptors module --- ...ck_descriptors => haralick_descriptors.py} | 199 ++++++++++++------ 1 file changed, 131 insertions(+), 68 deletions(-) rename computer_vision/{haralick_descriptors => haralick_descriptors.py} (52%) diff --git a/computer_vision/haralick_descriptors b/computer_vision/haralick_descriptors.py similarity index 52% rename from computer_vision/haralick_descriptors rename to computer_vision/haralick_descriptors.py index 6f8d6bd66b80..1006f4db9435 100644 --- a/computer_vision/haralick_descriptors +++ b/computer_vision/haralick_descriptors.py @@ -1,16 +1,36 @@ +import imageio.v2 as imageio +import numpy as np + + def rmse(original: np.ndarray, reference: np.ndarray) -> float: - """Simple implementation of Root Mean Squared Error for two N dimensional numpy arrays.""" + """Simple implementation of Root Mean Squared Error + for two N dimensional numpy arrays. + >>> rmse(np.array([1, 2, 3]), np.array([1, 2, 3])) + 0.0 + >>> rmse(np.array([1, 2, 3]), np.array([2, 2, 2])) + 1.0 + >>> rmse(np.array([1, 2, 3]), np.array([6, 4, 2])) + 30.0 + """ return np.sqrt(((original - reference) ** 2).mean()) def normalize_image(image: np.ndarray, cap: float = 255) -> np.ndarray: - """Normalizes image in Numpy 2D array format, between ranges 0-cap, as to fit uint8 type. + """ + Normalizes image in Numpy 2D array format, between ranges 0-cap, + as to fit uint8 type. Args: image: 2D numpy array representing image as matrix, with values in any range cap: Maximum cap amount for normalization Returns: return 2D numpy array of type uint8, corresponding to limited range matrix + + >>> normalize_image(np.array([[1, 2, 3], [4, 5, 10]]), cap=1) + array([[ 0, 0.111111, 0.222222], + [ 0.333333, 0.444444, 1. ]], dtype=float32) + >>> normalize_image(np.array([[4, 4, 3], [1, 7, 2]])) + """ normalized = (image - np.min(image)) / (np.max(image) - np.min(image)) * cap return normalized.astype(np.uint8) @@ -29,11 +49,6 @@ def normalize_array(array, cap: float = 1): return normalized -def euclidean(point_1: np.ndarray, point_2: np.ndarray): - """Simple method for calculating the euclidean distance between two points, with type np.ndarray.""" - return np.sqrt(np.sum(np.square(point_1 - point_2))) - - def grayscale(image: np.ndarray) -> np.ndarray: """Uses luminance weights to transform RGB channel to greyscale, by taking the dot product between the channel and the weights.""" @@ -41,24 +56,33 @@ def grayscale(image: np.ndarray) -> np.ndarray: def binarize(image, threshold_value): - """Binarizes a grayscale image based on a given threshold value, setting values to 1 or 0 accordingly.""" + """ + Binarizes a grayscale image based on a given threshold value, + setting values to 1 or 0 accordingly. + """ binarized = np.where(image > threshold_value, 1, 0) return binarized -def transform(image, kind, kernel=np.ones((3, 3))): - """Simple image transformation using one of two available filter functions: Erosion and Dilation. +def transform(image, kind, kernel=None): + """ + Simple image transformation using one of two available filter functions: + Erosion and Dilation. Args: image: - kind: Can be either 'erosion', in which case the :func:np.max function is called, - or 'dilation', when :func:np.min is used instead. - kernel: n x n kernel with shape < :attr:image.shape, to be used when applying convolution to original image + kind: Can be either 'erosion', in which case the :func:np.max + function is called, or 'dilation', when :func:np.min is used instead. + kernel: n x n kernel with shape < :attr:image.shape, + to be used when applying convolution to original image Returns: """ + if kernel is None: + kernel = np.ones((3, 3)) + if kind == "erosion": constant = 1 apply = np.max @@ -68,39 +92,52 @@ def transform(image, kind, kernel=np.ones((3, 3))): center_x, center_y = (x // 2 for x in kernel.shape) - # Use padded image when applying convolotion to not go out of bounds of the original the image + # Use padded image when applying convolotion + # to not go out of bounds of the original the image transformed = np.zeros(image.shape, dtype=np.uint8) - padded = np.pad(image, 1, 'constant', constant_values=constant) + padded = np.pad(image, 1, "constant", constant_values=constant) for x in range(center_x, padded.shape[0] - center_x): for y in range(center_y, padded.shape[1] - center_y): - - center = padded[x - center_x: x + center_x + 1, y - center_y: y + center_y + 1] + center = padded[ + x - center_x : x + center_x + 1, y - center_y : y + center_y + 1 + ] # Apply transformation method to the centered section of the image transformed[x - center_x, y - center_y] = apply(center[kernel == 1]) return transformed -def opening_filter(image, kernel=np.ones((3, 3))): - """Opening filter, defined as the sequence of erosion and then a dilation filter on the same image.""" - return transform( - transform(image, 'dilation', kernel), - 'erosion', kernel - ) +def opening_filter(image, kernel=None): + """ + Opening filter, defined as the sequence of + erosion and then a dilation filter on the same image. + """ + if kernel is None: + np.ones((3, 3)) + return transform(transform(image, "dilation", kernel), "erosion", kernel) -def closing_filter(image, kernel=np.ones((3, 3))): - """Opening filter, defined as the sequence of dilation and then erosion filter on the same image.""" - return transform( - transform(image, 'erosion', kernel), - 'dilation', kernel - ) + +def closing_filter(image, kernel=None): + """ + Opening filter, defined as the sequence of + dilation and then erosion filter on the same image. + """ + if kernel is None: + np.ones((3, 3)) + + return transform(transform(image, "erosion", kernel), "dilation", kernel) def binary_mask(image_gray, image_map): - """Apply binary mask, or thresholding based on bit mask value (mapping mask is 1 or 0).""" - true_mask, false_mask = np.array(image_gray, copy=True), np.array(image_gray, copy=True) + """ + Apply binary mask, or thresholding based + on bit mask value (mapping mask is 1 or 0). + """ + true_mask, false_mask = np.array(image_gray, copy=True), np.array( + image_gray, copy=True + ) true_mask[image_map == 1] = 1 false_mask[image_map == 0] = 0 @@ -108,16 +145,19 @@ def binary_mask(image_gray, image_map): def matrix_concurrency(image, coordinate): - """Calculate sample co-occurrence matrix based on input image as well as selected coordinates on image. - Implementation is made using basic iteration, as function to be performed (np.max) is non-linear and therefore - not usable on the Fourier Transform domain.""" + """ + Calculate sample co-occurrence matrix based on input image + as well as selected coordinates on image. + Implementation is made using basic iteration, + as function to be performed (np.max) is non-linear and therefore + not usable on the Fourier Transform domain. + """ matrix = np.zeros([np.max(image) + 1, np.max(image) + 1]) offset_x, offset_y = coordinate[0], coordinate[1] for x in range(1, image.shape[0] - 1): for y in range(1, image.shape[1] - 1): - base_pixel = image[x, y] offset_pixel = image[x + offset_x, y + offset_y] @@ -129,7 +169,8 @@ def matrix_concurrency(image, coordinate): def haralick_descriptors(matrix): """Calculates all 8 Haralick descriptors based on co-occurence input matrix. All descriptors are as follows: - Maximum probability, Inverse Difference, Homogeneity, Entropy, Energy, Dissimilarity, Contrast and Correlation + Maximum probability, Inverse Difference, Homogeneity, Entropy, + Energy, Dissimilarity, Contrast and Correlation Args: matrix: Co-occurence matrix to use as base for calculating descriptors. @@ -137,8 +178,9 @@ def haralick_descriptors(matrix): Returns: Reverse ordered list of resulting descriptors """ - # Function np.indices could be used for bigger input types, but np.ogrid works just fine - i, j = np.ogrid[0:matrix.shape[0], 0:matrix.shape[1]] # np.indices() + # Function np.indices could be used for bigger input types, + # but np.ogrid works just fine + i, j = np.ogrid[0 : matrix.shape[0], 0 : matrix.shape[1]] # np.indices() # Pre-calculate frequent multiplication and subtraction prod = np.multiply(i, j) @@ -156,48 +198,62 @@ def haralick_descriptors(matrix): homogeneity = matrix / (1 + np.power(sub, 2)) entropy = -(matrix[matrix > 0] * np.log(matrix[matrix > 0])) - # Sum values for descriptors ranging from the first one to the last, as all are their respective origin matrix - # and not the resulting value yet. - descriptors = [maximum_prob, correlation.sum(), energy.sum(), contrast.sum(), - dissimilarity.sum(), inverse_difference.sum(), homogeneity.sum(), entropy.sum()] + # Sum values for descriptors ranging from the first one to the last, + # as all are their respective origin matrix and not the resulting value yet. + descriptors = [ + maximum_prob, + correlation.sum(), + energy.sum(), + contrast.sum(), + dissimilarity.sum(), + inverse_difference.sum(), + homogeneity.sum(), + entropy.sum(), + ] return descriptors def get_descriptors(masks, coordinate): - """Calculate all Haralick descriptors for a sequence of different co-occurrence matrices, given input - masks and coordinates.""" + """ + Calculate all Haralick descriptors for a sequence of + different co-occurrence matrices, given input masks and coordinates. + """ + descriptors = np.zeros((len(masks), 8)) + for idx, mask in enumerate(masks): + descriptors[idx] = haralick_descriptors(matrix_concurrency(mask, coordinate)) - descriptors = list() - for mask in masks: - descriptors.append(haralick_descriptors( - matrix_concurrency(mask, coordinate)) - ) - # Concatenate each individual descriptor into one single list containing sequence of descriptors + # Concatenate each individual descriptor into + # one single list containing sequence of descriptors return np.concatenate(descriptors, axis=None) def euclidean(point_1: np.ndarray, point_2: np.ndarray): - """Simple method for calculating the euclidean distance between two points, with type np.ndarray.""" + """ + Simple method for calculating the euclidean distance between two points, + with type np.ndarray. + """ return np.sqrt(np.sum(np.square(point_1 - point_2))) def get_distances(descriptors, base): - """Calculate all Euclidian distances between a selected base descriptor and all other Haralick descriptors - The resulting comparison is return in decreasing order, showing which descriptor is the most similar to the - selected base. + """ + Calculate all Euclidean distances between a selected base descriptor + and all other Haralick descriptors + The resulting comparison is return in decreasing order, + showing which descriptor is the most similar to the selected base. Args: descriptors: Haralick descriptors to compare with base index - base: Haralick descriptor index to use as base when calculating respective euclidean distance to other descriptors. + base: Haralick descriptor index to use as base when calculating respective + euclidean distance to other descriptors. Returns: Ordered distances between descriptors - """ - distances = [] + distances = np.zeros(descriptors.shape[0]) - for description in descriptors: - distances.append(euclidean(description, descriptors[base])) + for idx, description in enumerate(descriptors): + distances[idx] = euclidean(description, descriptors[base]) # Normalize distances between range [0, 1] distances = normalize_array(distances, 1) return sorted(enumerate(distances), key=lambda tup: tup[1]) @@ -208,33 +264,40 @@ def main(): index = int(input()) q_value = [int(value) for value in input().split()] - # Format is the respective filter to apply, can be either 1 for the opening filter or else for the closing - parameters = {'format': int(input()), 'threshold': int(input())} + # Format is the respective filter to apply, + # can be either 1 for the opening filter or else for the closing + parameters = {"format": int(input()), "threshold": int(input())} # Number of images to perform methods on b_number = int(input()) - files, descriptors = (list(), list()) + files, descriptors = (np.array([]), np.array([])) for _ in range(b_number): file = input().rstrip() files.append(file) - # Open given image and calculate morphological filter, respective masks and correspondent Harralick Descriptors. + # Open given image and calculate morphological filter, + # respective masks and correspondent Harralick Descriptors. image = imageio.imread(file).astype(np.float32) gray = grayscale(image) - threshold = binarize(gray, parameters['threshold']) + threshold = binarize(gray, parameters["threshold"]) - morphological = opening_filter(threshold) if parameters['format'] == 1 else closing_filter(threshold) + morphological = ( + opening_filter(threshold) + if parameters["format"] == 1 + else closing_filter(threshold) + ) masks = binary_mask(gray, morphological) descriptors.append(get_descriptors(masks, q_value)) - # Transform ordered distances array into a sequence of indexes corresponding to original file position + # Transform ordered distances array into a sequence of indexes + # corresponding to original file position distances = get_distances(descriptors, index) indexed_distances = np.array(distances).astype(np.uint8)[:, 0] - # Finally, print distances considering the Haralick descriptions from the base file to - # all other images using the morphology method of choice. + # Finally, print distances considering the Haralick descriptions from the base + # file to all other images using the morphology method of choice. print(f"Query: {files[index]}") print("Ranking:") for idx, file_idx in enumerate(indexed_distances): From bc6a1894f84173910e95ba8544ca64f42788970c Mon Sep 17 00:00:00 2001 From: Rafael Zimmer Date: Wed, 19 Oct 2022 01:59:19 -0300 Subject: [PATCH 03/17] Type hinting for Haralick descriptors --- computer_vision/haralick_descriptors.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/computer_vision/haralick_descriptors.py b/computer_vision/haralick_descriptors.py index 1006f4db9435..6c64632f8cf1 100644 --- a/computer_vision/haralick_descriptors.py +++ b/computer_vision/haralick_descriptors.py @@ -36,7 +36,7 @@ def normalize_image(image: np.ndarray, cap: float = 255) -> np.ndarray: return normalized.astype(np.uint8) -def normalize_array(array, cap: float = 1): +def normalize_array(array: np.ndarray, cap: float = 1) -> np.ndarray: """Normalizes a 1D array, between ranges 0-cap. Args: @@ -55,7 +55,7 @@ def grayscale(image: np.ndarray) -> np.ndarray: return np.dot(image[:, :, 0:3], [0.299, 0.587, 0.114]).astype(np.uint8) -def binarize(image, threshold_value): +def binarize(image: np.ndarray, threshold_value) -> np.ndarray: """ Binarizes a grayscale image based on a given threshold value, setting values to 1 or 0 accordingly. @@ -65,7 +65,7 @@ def binarize(image, threshold_value): return binarized -def transform(image, kind, kernel=None): +def transform(image: np.ndarray, kind, kernel=None) -> np.ndarray: """ Simple image transformation using one of two available filter functions: Erosion and Dilation. @@ -108,7 +108,7 @@ def transform(image, kind, kernel=None): return transformed -def opening_filter(image, kernel=None): +def opening_filter(image: np.ndarray, kernel: np.ndarray = None) -> np.ndarray: """ Opening filter, defined as the sequence of erosion and then a dilation filter on the same image. @@ -119,7 +119,7 @@ def opening_filter(image, kernel=None): return transform(transform(image, "dilation", kernel), "erosion", kernel) -def closing_filter(image, kernel=None): +def closing_filter(image: np.ndarray, kernel: np.ndarray = None) -> np.ndarray: """ Opening filter, defined as the sequence of dilation and then erosion filter on the same image. @@ -130,7 +130,7 @@ def closing_filter(image, kernel=None): return transform(transform(image, "erosion", kernel), "dilation", kernel) -def binary_mask(image_gray, image_map): +def binary_mask(image_gray: np.ndarray, image_map: np.ndarray) -> np.ndarray: """ Apply binary mask, or thresholding based on bit mask value (mapping mask is 1 or 0). @@ -144,7 +144,7 @@ def binary_mask(image_gray, image_map): return true_mask, false_mask -def matrix_concurrency(image, coordinate): +def matrix_concurrency(image: np.ndarray, coordinate) -> np.ndarray: """ Calculate sample co-occurrence matrix based on input image as well as selected coordinates on image. @@ -166,7 +166,7 @@ def matrix_concurrency(image, coordinate): return matrix / np.sum(matrix) -def haralick_descriptors(matrix): +def haralick_descriptors(matrix: np.ndarray) -> list: """Calculates all 8 Haralick descriptors based on co-occurence input matrix. All descriptors are as follows: Maximum probability, Inverse Difference, Homogeneity, Entropy, @@ -213,7 +213,7 @@ def haralick_descriptors(matrix): return descriptors -def get_descriptors(masks, coordinate): +def get_descriptors(masks, coordinate) -> np.ndarray: """ Calculate all Haralick descriptors for a sequence of different co-occurrence matrices, given input masks and coordinates. @@ -227,7 +227,7 @@ def get_descriptors(masks, coordinate): return np.concatenate(descriptors, axis=None) -def euclidean(point_1: np.ndarray, point_2: np.ndarray): +def euclidean(point_1: np.ndarray, point_2: np.ndarray) -> np.float32: """ Simple method for calculating the euclidean distance between two points, with type np.ndarray. @@ -235,7 +235,7 @@ def euclidean(point_1: np.ndarray, point_2: np.ndarray): return np.sqrt(np.sum(np.square(point_1 - point_2))) -def get_distances(descriptors, base): +def get_distances(descriptors, base) -> np.ndarray: """ Calculate all Euclidean distances between a selected base descriptor and all other Haralick descriptors From 6e23cc094f661b5ec21a1e9d9e4cfef2c5c0e8b8 Mon Sep 17 00:00:00 2001 From: Rafael Zimmer Date: Sun, 27 Nov 2022 13:44:28 -0300 Subject: [PATCH 04/17] Fixed docstrings, unit testing and formatting choices --- computer_vision/haralick_descriptors.py | 160 ++++++++++++++++++------ 1 file changed, 119 insertions(+), 41 deletions(-) diff --git a/computer_vision/haralick_descriptors.py b/computer_vision/haralick_descriptors.py index 6c64632f8cf1..8cf0d702ce32 100644 --- a/computer_vision/haralick_descriptors.py +++ b/computer_vision/haralick_descriptors.py @@ -1,21 +1,29 @@ +""" +https://en.wikipedia.org/wiki/Image_texture +https://en.wikipedia.org/wiki/Co-occurrence_matrix#Application_to_image_analysis +""" +from typing import Any, Union + import imageio.v2 as imageio import numpy as np -def rmse(original: np.ndarray, reference: np.ndarray) -> float: +def root_mean_square_error(original: np.ndarray, reference: np.ndarray) -> float: """Simple implementation of Root Mean Squared Error for two N dimensional numpy arrays. - >>> rmse(np.array([1, 2, 3]), np.array([1, 2, 3])) - 0.0 - >>> rmse(np.array([1, 2, 3]), np.array([2, 2, 2])) - 1.0 - >>> rmse(np.array([1, 2, 3]), np.array([6, 4, 2])) - 30.0 + + Examples: + >>> root_mean_square_error(np.array([1, 2, 3]), np.array([1, 2, 3])) + 0.0 + >>> root_mean_square_error(np.array([1, 2, 3]), np.array([2, 2, 2])) + 0.816496580927726 + >>> root_mean_square_error(np.array([1, 2, 3]), np.array([6, 4, 2])) + 3.1622776601683795 """ return np.sqrt(((original - reference) ** 2).mean()) -def normalize_image(image: np.ndarray, cap: float = 255) -> np.ndarray: +def normalize_image(image: np.ndarray, cap: float = 255.0, data_type=np.uint8) -> np.ndarray: """ Normalizes image in Numpy 2D array format, between ranges 0-cap, as to fit uint8 type. @@ -23,17 +31,20 @@ def normalize_image(image: np.ndarray, cap: float = 255) -> np.ndarray: Args: image: 2D numpy array representing image as matrix, with values in any range cap: Maximum cap amount for normalization + data_type: numpy data type to set output variable to Returns: return 2D numpy array of type uint8, corresponding to limited range matrix - >>> normalize_image(np.array([[1, 2, 3], [4, 5, 10]]), cap=1) - array([[ 0, 0.111111, 0.222222], - [ 0.333333, 0.444444, 1. ]], dtype=float32) - >>> normalize_image(np.array([[4, 4, 3], [1, 7, 2]])) - + Examples: + >>> normalize_image(np.array([[1, 2, 3], [4, 5, 10]]), cap=1.0, data_type=np.float64) + array([[0. , 0.11111111, 0.22222222], + [0.33333333, 0.44444444, 1. ]]) + >>> normalize_image(np.array([[4, 4, 3], [1, 7, 2]])) + array([[127, 127, 85], + [ 0, 255, 42]], dtype=uint8) """ normalized = (image - np.min(image)) / (np.max(image) - np.min(image)) * cap - return normalized.astype(np.uint8) + return normalized.astype(data_type) def normalize_array(array: np.ndarray, cap: float = 1) -> np.ndarray: @@ -43,44 +54,75 @@ def normalize_array(array: np.ndarray, cap: float = 1) -> np.ndarray: array: List containing values to be normalized between cap range. cap: Maximum cap amount for normalization. Returns: - return 1D numpy array , corresponding to limited range array + return 1D numpy array, corresponding to limited range array + + Examples: + >>> normalize_array(np.array([2, 3, 5, 7])) + array([0. , 0.2, 0.6, 1. ]) + >>> normalize_array(np.array([[5], [7], [11], [13]])) + array([[0. ], + [0.25], + [0.75], + [1. ]]) """ - normalized = (array - np.min(array)) / (np.max(array) - np.min(array)) * cap - return normalized + return (array - np.min(array)) / (np.max(array) - np.min(array)) * cap def grayscale(image: np.ndarray) -> np.ndarray: - """Uses luminance weights to transform RGB channel to greyscale, by - taking the dot product between the channel and the weights.""" + """ + Uses luminance weights to transform RGB channel to greyscale, by + taking the dot product between the channel and the weights. + + Example: + >>> grayscale(np.array([[[108, 201, 72], [255, 11, 127]], [[56, 56, 56], [128, 255, 107]]])) + array([[158, 97], + [ 56, 200]], dtype=uint8) + """ return np.dot(image[:, :, 0:3], [0.299, 0.587, 0.114]).astype(np.uint8) -def binarize(image: np.ndarray, threshold_value) -> np.ndarray: +def binarize(image: np.ndarray, threshold: float = 127.0) -> np.ndarray: """ Binarizes a grayscale image based on a given threshold value, setting values to 1 or 0 accordingly. - """ - binarized = np.where(image > threshold_value, 1, 0) - return binarized + Examples: + >>> binarize(np.array([[128, 255], [101, 156]])) + array([[1, 1], + [0, 1]]) + >>> binarize(np.array([[0.07, 1], [0.51, 0.3]]), threshold=0.5) + array([[0, 1], + [1, 0]]) + """ + return np.where(image > threshold, 1, 0) -def transform(image: np.ndarray, kind, kernel=None) -> np.ndarray: +def transform(image: np.ndarray, kind: str, kernel=None) -> np.ndarray: """ Simple image transformation using one of two available filter functions: Erosion and Dilation. Args: - image: + image: binarized input image, onto which to apply transformation kind: Can be either 'erosion', in which case the :func:np.max function is called, or 'dilation', when :func:np.min is used instead. kernel: n x n kernel with shape < :attr:image.shape, to be used when applying convolution to original image Returns: - + returns a numpy array with same shape as input image, corresponding to applied binary transformation. + + Examples: + >>> img = np.array([[1, 0.5], [0.2, 0.7]]) + >>> img = binarize(img, threshold=0.5) + >>> transform(img, 'erosion') + array([[1, 1], + [1, 1]], dtype=uint8) + >>> transform(img, 'dilation') + array([[0, 0], + [0, 0]], dtype=uint8) """ - if kernel is None: + if not kernel: kernel = np.ones((3, 3)) if kind == "erosion": @@ -100,8 +142,8 @@ def transform(image: np.ndarray, kind, kernel=None) -> np.ndarray: for x in range(center_x, padded.shape[0] - center_x): for y in range(center_y, padded.shape[1] - center_y): center = padded[ - x - center_x : x + center_x + 1, y - center_y : y + center_y + 1 - ] + x - center_x: x + center_x + 1, y - center_y: y + center_y + 1 + ] # Apply transformation method to the centered section of the image transformed[x - center_x, y - center_y] = apply(center[kernel == 1]) @@ -112,8 +154,15 @@ def opening_filter(image: np.ndarray, kernel: np.ndarray = None) -> np.ndarray: """ Opening filter, defined as the sequence of erosion and then a dilation filter on the same image. + + Examples: + >>> img = np.array([[1, 0.5], [0.2, 0.7]]) + >>> img = binarize(img, threshold=0.5) + >>> opening_filter(img) + array([[1, 1], + [1, 1]], dtype=uint8) """ - if kernel is None: + if not kernel: np.ones((3, 3)) return transform(transform(image, "dilation", kernel), "erosion", kernel) @@ -123,6 +172,13 @@ def closing_filter(image: np.ndarray, kernel: np.ndarray = None) -> np.ndarray: """ Opening filter, defined as the sequence of dilation and then erosion filter on the same image. + + Examples: + >>> img = np.array([[1, 0.5], [0.2, 0.7]]) + >>> img = binarize(img, threshold=0.5) + >>> closing_filter(img) + array([[0, 0], + [0, 0]], dtype=uint8) """ if kernel is None: np.ones((3, 3)) @@ -130,10 +186,24 @@ def closing_filter(image: np.ndarray, kernel: np.ndarray = None) -> np.ndarray: return transform(transform(image, "erosion", kernel), "dilation", kernel) -def binary_mask(image_gray: np.ndarray, image_map: np.ndarray) -> np.ndarray: +def binary_mask( + image_gray: np.ndarray, image_map: np.ndarray +) -> tuple[np.ndarray, np.ndarray]: """ Apply binary mask, or thresholding based - on bit mask value (mapping mask is 1 or 0). + on bit mask value (mapping mask is binary). + + Returns the mapped true value mask and its complementary false value mask. + + Example: + >>> img = np.array([[[108, 201, 72], [255, 11, 127]], [[56, 56, 56], [128, 255, 107]]]) + >>> gray = grayscale(img) + >>> binary = binarize(gray) + >>> morphological = opening_filter(binary) + >>> binary_mask(gray, morphological) + (array([[1, 1], + [1, 1]], dtype=uint8), array([[158, 97], + [ 56, 200]], dtype=uint8)) """ true_mask, false_mask = np.array(image_gray, copy=True), np.array( image_gray, copy=True @@ -144,13 +214,16 @@ def binary_mask(image_gray: np.ndarray, image_map: np.ndarray) -> np.ndarray: return true_mask, false_mask -def matrix_concurrency(image: np.ndarray, coordinate) -> np.ndarray: +def matrix_concurrency( + image: np.ndarray, coordinate +) -> np.ndarray: """ Calculate sample co-occurrence matrix based on input image as well as selected coordinates on image. + Implementation is made using basic iteration, as function to be performed (np.max) is non-linear and therefore - not usable on the Fourier Transform domain. + not callable on the frequency domain. """ matrix = np.zeros([np.max(image) + 1, np.max(image) + 1]) @@ -180,7 +253,7 @@ def haralick_descriptors(matrix: np.ndarray) -> list: """ # Function np.indices could be used for bigger input types, # but np.ogrid works just fine - i, j = np.ogrid[0 : matrix.shape[0], 0 : matrix.shape[1]] # np.indices() + i, j = np.ogrid[0: matrix.shape[0], 0: matrix.shape[1]] # np.indices() # Pre-calculate frequent multiplication and subtraction prod = np.multiply(i, j) @@ -200,7 +273,7 @@ def haralick_descriptors(matrix: np.ndarray) -> list: # Sum values for descriptors ranging from the first one to the last, # as all are their respective origin matrix and not the resulting value yet. - descriptors = [ + return [ maximum_prob, correlation.sum(), energy.sum(), @@ -210,10 +283,9 @@ def haralick_descriptors(matrix: np.ndarray) -> list: homogeneity.sum(), entropy.sum(), ] - return descriptors -def get_descriptors(masks, coordinate) -> np.ndarray: +def get_descriptors(masks: tuple[np.ndarray, np.ndarray], coordinate) -> np.ndarray: """ Calculate all Haralick descriptors for a sequence of different co-occurrence matrices, given input masks and coordinates. @@ -231,11 +303,17 @@ def euclidean(point_1: np.ndarray, point_2: np.ndarray) -> np.float32: """ Simple method for calculating the euclidean distance between two points, with type np.ndarray. + + Example: + >>> a = np.array([1, 0, -2]) + >>> b = np.array([2, -1, 1]) + >>> euclidean(a, b) + 3.3166247903554 """ return np.sqrt(np.sum(np.square(point_1 - point_2))) -def get_distances(descriptors, base) -> np.ndarray: +def get_distances(descriptors, base) -> list[Any]: """ Calculate all Euclidean distances between a selected base descriptor and all other Haralick descriptors @@ -271,7 +349,7 @@ def main(): # Number of images to perform methods on b_number = int(input()) - files, descriptors = (np.array([]), np.array([])) + files, descriptors = ([], []) for _ in range(b_number): file = input().rstrip() @@ -293,7 +371,7 @@ def main(): # Transform ordered distances array into a sequence of indexes # corresponding to original file position - distances = get_distances(descriptors, index) + distances = get_distances(np.array(descriptors), index) indexed_distances = np.array(distances).astype(np.uint8)[:, 0] # Finally, print distances considering the Haralick descriptions from the base From eca7118ed98f92afa8b932def0712677de873f92 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 27 Nov 2022 16:49:55 +0000 Subject: [PATCH 05/17] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- computer_vision/haralick_descriptors.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/computer_vision/haralick_descriptors.py b/computer_vision/haralick_descriptors.py index 8cf0d702ce32..44c23e3f05a8 100644 --- a/computer_vision/haralick_descriptors.py +++ b/computer_vision/haralick_descriptors.py @@ -23,7 +23,9 @@ def root_mean_square_error(original: np.ndarray, reference: np.ndarray) -> float return np.sqrt(((original - reference) ** 2).mean()) -def normalize_image(image: np.ndarray, cap: float = 255.0, data_type=np.uint8) -> np.ndarray: +def normalize_image( + image: np.ndarray, cap: float = 255.0, data_type=np.uint8 +) -> np.ndarray: """ Normalizes image in Numpy 2D array format, between ranges 0-cap, as to fit uint8 type. @@ -142,8 +144,8 @@ def transform(image: np.ndarray, kind: str, kernel=None) -> np.ndarray: for x in range(center_x, padded.shape[0] - center_x): for y in range(center_y, padded.shape[1] - center_y): center = padded[ - x - center_x: x + center_x + 1, y - center_y: y + center_y + 1 - ] + x - center_x : x + center_x + 1, y - center_y : y + center_y + 1 + ] # Apply transformation method to the centered section of the image transformed[x - center_x, y - center_y] = apply(center[kernel == 1]) @@ -187,7 +189,7 @@ def closing_filter(image: np.ndarray, kernel: np.ndarray = None) -> np.ndarray: def binary_mask( - image_gray: np.ndarray, image_map: np.ndarray + image_gray: np.ndarray, image_map: np.ndarray ) -> tuple[np.ndarray, np.ndarray]: """ Apply binary mask, or thresholding based @@ -214,9 +216,7 @@ def binary_mask( return true_mask, false_mask -def matrix_concurrency( - image: np.ndarray, coordinate -) -> np.ndarray: +def matrix_concurrency(image: np.ndarray, coordinate) -> np.ndarray: """ Calculate sample co-occurrence matrix based on input image as well as selected coordinates on image. @@ -253,7 +253,7 @@ def haralick_descriptors(matrix: np.ndarray) -> list: """ # Function np.indices could be used for bigger input types, # but np.ogrid works just fine - i, j = np.ogrid[0: matrix.shape[0], 0: matrix.shape[1]] # np.indices() + i, j = np.ogrid[0 : matrix.shape[0], 0 : matrix.shape[1]] # np.indices() # Pre-calculate frequent multiplication and subtraction prod = np.multiply(i, j) From 9d2cca7441d9e5351ed5b915b89afc87ad9ca989 Mon Sep 17 00:00:00 2001 From: Rafael Zimmer Date: Sun, 27 Nov 2022 13:54:33 -0300 Subject: [PATCH 06/17] Fixed line size formatting --- computer_vision/haralick_descriptors.py | 30 ++++++++++++++----------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/computer_vision/haralick_descriptors.py b/computer_vision/haralick_descriptors.py index 8cf0d702ce32..b85058bc7b60 100644 --- a/computer_vision/haralick_descriptors.py +++ b/computer_vision/haralick_descriptors.py @@ -2,7 +2,7 @@ https://en.wikipedia.org/wiki/Image_texture https://en.wikipedia.org/wiki/Co-occurrence_matrix#Application_to_image_analysis """ -from typing import Any, Union +from typing import Any import imageio.v2 as imageio import numpy as np @@ -23,7 +23,9 @@ def root_mean_square_error(original: np.ndarray, reference: np.ndarray) -> float return np.sqrt(((original - reference) ** 2).mean()) -def normalize_image(image: np.ndarray, cap: float = 255.0, data_type=np.uint8) -> np.ndarray: +def normalize_image( + image: np.ndarray, cap: float = 255.0, data_type=np.uint8 +) -> np.ndarray: """ Normalizes image in Numpy 2D array format, between ranges 0-cap, as to fit uint8 type. @@ -36,7 +38,8 @@ def normalize_image(image: np.ndarray, cap: float = 255.0, data_type=np.uint8) - return 2D numpy array of type uint8, corresponding to limited range matrix Examples: - >>> normalize_image(np.array([[1, 2, 3], [4, 5, 10]]), cap=1.0, data_type=np.float64) + >>> normalize_image(np.array([[1, 2, 3], [4, 5, 10]]), + ... cap=1.0, data_type=np.float64) array([[0. , 0.11111111, 0.22222222], [0.33333333, 0.44444444, 1. ]]) >>> normalize_image(np.array([[4, 4, 3], [1, 7, 2]])) @@ -74,7 +77,8 @@ def grayscale(image: np.ndarray) -> np.ndarray: taking the dot product between the channel and the weights. Example: - >>> grayscale(np.array([[[108, 201, 72], [255, 11, 127]], [[56, 56, 56], [128, 255, 107]]])) + >>> grayscale(np.array([[[108, 201, 72], [255, 11, 127]], + ... [[56, 56, 56], [128, 255, 107]]])) array([[158, 97], [ 56, 200]], dtype=uint8) """ @@ -110,7 +114,8 @@ def transform(image: np.ndarray, kind: str, kernel=None) -> np.ndarray: to be used when applying convolution to original image Returns: - returns a numpy array with same shape as input image, corresponding to applied binary transformation. + returns a numpy array with same shape as input image, + corresponding to applied binary transformation. Examples: >>> img = np.array([[1, 0.5], [0.2, 0.7]]) @@ -142,8 +147,8 @@ def transform(image: np.ndarray, kind: str, kernel=None) -> np.ndarray: for x in range(center_x, padded.shape[0] - center_x): for y in range(center_y, padded.shape[1] - center_y): center = padded[ - x - center_x: x + center_x + 1, y - center_y: y + center_y + 1 - ] + x - center_x : x + center_x + 1, y - center_y : y + center_y + 1 + ] # Apply transformation method to the centered section of the image transformed[x - center_x, y - center_y] = apply(center[kernel == 1]) @@ -187,7 +192,7 @@ def closing_filter(image: np.ndarray, kernel: np.ndarray = None) -> np.ndarray: def binary_mask( - image_gray: np.ndarray, image_map: np.ndarray + image_gray: np.ndarray, image_map: np.ndarray ) -> tuple[np.ndarray, np.ndarray]: """ Apply binary mask, or thresholding based @@ -196,7 +201,8 @@ def binary_mask( Returns the mapped true value mask and its complementary false value mask. Example: - >>> img = np.array([[[108, 201, 72], [255, 11, 127]], [[56, 56, 56], [128, 255, 107]]]) + >>> img = np.array([[[108, 201, 72], [255, 11, 127]], + ... [[56, 56, 56], [128, 255, 107]]]) >>> gray = grayscale(img) >>> binary = binarize(gray) >>> morphological = opening_filter(binary) @@ -214,9 +220,7 @@ def binary_mask( return true_mask, false_mask -def matrix_concurrency( - image: np.ndarray, coordinate -) -> np.ndarray: +def matrix_concurrency(image: np.ndarray, coordinate) -> np.ndarray: """ Calculate sample co-occurrence matrix based on input image as well as selected coordinates on image. @@ -253,7 +257,7 @@ def haralick_descriptors(matrix: np.ndarray) -> list: """ # Function np.indices could be used for bigger input types, # but np.ogrid works just fine - i, j = np.ogrid[0: matrix.shape[0], 0: matrix.shape[1]] # np.indices() + i, j = np.ogrid[0 : matrix.shape[0], 0 : matrix.shape[1]] # np.indices() # Pre-calculate frequent multiplication and subtraction prod = np.multiply(i, j) From 5e73aa6cc20e01827a0fee673c0df06770502583 Mon Sep 17 00:00:00 2001 From: Rafael Zimmer Date: Sun, 27 Nov 2022 14:21:10 -0300 Subject: [PATCH 07/17] Added final doctests --- computer_vision/haralick_descriptors.py | 61 +++++++++++++++++++++---- 1 file changed, 53 insertions(+), 8 deletions(-) diff --git a/computer_vision/haralick_descriptors.py b/computer_vision/haralick_descriptors.py index b85058bc7b60..19272673412c 100644 --- a/computer_vision/haralick_descriptors.py +++ b/computer_vision/haralick_descriptors.py @@ -24,7 +24,7 @@ def root_mean_square_error(original: np.ndarray, reference: np.ndarray) -> float def normalize_image( - image: np.ndarray, cap: float = 255.0, data_type=np.uint8 + image: np.ndarray, cap: float = 255.0, data_type: np.dtype = np.uint8 ) -> np.ndarray: """ Normalizes image in Numpy 2D array format, between ranges 0-cap, @@ -68,7 +68,8 @@ def normalize_array(array: np.ndarray, cap: float = 1) -> np.ndarray: [0.75], [1. ]]) """ - return (array - np.min(array)) / (np.max(array) - np.min(array)) * cap + diff = np.max(array) - np.min(array) + return (array - np.min(array)) / (1 if diff == 0 else diff) * cap def grayscale(image: np.ndarray) -> np.ndarray: @@ -101,7 +102,7 @@ def binarize(image: np.ndarray, threshold: float = 127.0) -> np.ndarray: return np.where(image > threshold, 1, 0) -def transform(image: np.ndarray, kind: str, kernel=None) -> np.ndarray: +def transform(image: np.ndarray, kind: str, kernel: np.ndarray = None) -> np.ndarray: """ Simple image transformation using one of two available filter functions: Erosion and Dilation. @@ -220,7 +221,7 @@ def binary_mask( return true_mask, false_mask -def matrix_concurrency(image: np.ndarray, coordinate) -> np.ndarray: +def matrix_concurrency(image: np.ndarray, coordinate: list) -> np.ndarray: """ Calculate sample co-occurrence matrix based on input image as well as selected coordinates on image. @@ -228,6 +229,17 @@ def matrix_concurrency(image: np.ndarray, coordinate) -> np.ndarray: Implementation is made using basic iteration, as function to be performed (np.max) is non-linear and therefore not callable on the frequency domain. + + Example: + >>> img = np.array([[[108, 201, 72], [255, 11, 127]], + ... [[56, 56, 56], [128, 255, 107]]]) + >>> gray = grayscale(img) + >>> binary = binarize(gray) + >>> morphological = opening_filter(binary) + >>> mask_1 = binary_mask(gray, morphological)[0] + >>> matrix_concurrency(mask_1, [0, 1]) + array([[0., 0.], + [0., 0.]]) """ matrix = np.zeros([np.max(image) + 1, np.max(image) + 1]) @@ -239,8 +251,8 @@ def matrix_concurrency(image: np.ndarray, coordinate) -> np.ndarray: offset_pixel = image[x + offset_x, y + offset_y] matrix[base_pixel, offset_pixel] += 1 - - return matrix / np.sum(matrix) + matrix_sum = np.sum(matrix) + return matrix / (1 if matrix_sum == 0 else matrix_sum) def haralick_descriptors(matrix: np.ndarray) -> list: @@ -254,6 +266,17 @@ def haralick_descriptors(matrix: np.ndarray) -> list: Returns: Reverse ordered list of resulting descriptors + + Example: + >>> img = np.array([[[108, 201, 72], [255, 11, 127]], + ... [[56, 56, 56], [128, 255, 107]]]) + >>> gray = grayscale(img) + >>> binary = binarize(gray) + >>> morphological = opening_filter(binary) + >>> mask_1 = binary_mask(gray, morphological)[0] + >>> concurrency = matrix_concurrency(mask_1, [0, 1]) + >>> haralick_descriptors(concurrency) + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] """ # Function np.indices could be used for bigger input types, # but np.ogrid works just fine @@ -289,10 +312,21 @@ def haralick_descriptors(matrix: np.ndarray) -> list: ] -def get_descriptors(masks: tuple[np.ndarray, np.ndarray], coordinate) -> np.ndarray: +def get_descriptors( + masks: tuple[np.ndarray, np.ndarray], coordinate: list +) -> np.ndarray: """ Calculate all Haralick descriptors for a sequence of different co-occurrence matrices, given input masks and coordinates. + + Example: + >>> img = np.array([[[108, 201, 72], [255, 11, 127]], + ... [[56, 56, 56], [128, 255, 107]]]) + >>> gray = grayscale(img) + >>> binary = binarize(gray) + >>> morphological = opening_filter(binary) + >>> get_descriptors(binary_mask(gray, morphological), [0, 1]) + array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]) """ descriptors = np.zeros((len(masks), 8)) for idx, mask in enumerate(masks): @@ -317,7 +351,7 @@ def euclidean(point_1: np.ndarray, point_2: np.ndarray) -> np.float32: return np.sqrt(np.sum(np.square(point_1 - point_2))) -def get_distances(descriptors, base) -> list[Any]: +def get_distances(descriptors: np.ndarray, base: int) -> list[Any]: """ Calculate all Euclidean distances between a selected base descriptor and all other Haralick descriptors @@ -331,6 +365,17 @@ def get_distances(descriptors, base) -> list[Any]: Returns: Ordered distances between descriptors + + Example: + >>> index = 1 + >>> img = np.array([[[108, 201, 72], [255, 11, 127]], + ... [[56, 56, 56], [128, 255, 107]]]) + >>> gray = grayscale(img) + >>> binary = binarize(gray) + >>> morphological = opening_filter(binary) + >>> get_distances(get_descriptors(binary_mask(gray, morphological), [0, 1]), index) + [(0, 0.0), (1, 0.0), (2, 0.0), (3, 0.0), (4, 0.0), (5, 0.0), (6, 0.0), (7, 0.0), (8, 0.0), \ +(9, 0.0), (10, 0.0), (11, 0.0), (12, 0.0), (13, 0.0), (14, 0.0), (15, 0.0)] """ distances = np.zeros(descriptors.shape[0]) From aacb27e201d54445f073c41354456edbc042989a Mon Sep 17 00:00:00 2001 From: Rafael Zimmer Date: Sun, 27 Nov 2022 14:28:33 -0300 Subject: [PATCH 08/17] Changed main callable --- computer_vision/haralick_descriptors.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/computer_vision/haralick_descriptors.py b/computer_vision/haralick_descriptors.py index 19272673412c..945de2d97023 100644 --- a/computer_vision/haralick_descriptors.py +++ b/computer_vision/haralick_descriptors.py @@ -373,9 +373,12 @@ def get_distances(descriptors: np.ndarray, base: int) -> list[Any]: >>> gray = grayscale(img) >>> binary = binarize(gray) >>> morphological = opening_filter(binary) - >>> get_distances(get_descriptors(binary_mask(gray, morphological), [0, 1]), index) - [(0, 0.0), (1, 0.0), (2, 0.0), (3, 0.0), (4, 0.0), (5, 0.0), (6, 0.0), (7, 0.0), (8, 0.0), \ -(9, 0.0), (10, 0.0), (11, 0.0), (12, 0.0), (13, 0.0), (14, 0.0), (15, 0.0)] + >>> get_distances(get_descriptors( + ... binary_mask(gray, morphological), [0, 1]), + ... index) + [(0, 0.0), (1, 0.0), (2, 0.0), (3, 0.0), (4, 0.0), (5, 0.0), \ +(6, 0.0), (7, 0.0), (8, 0.0), (9, 0.0), (10, 0.0), (11, 0.0), (12, 0.0), \ +(13, 0.0), (14, 0.0), (15, 0.0)] """ distances = np.zeros(descriptors.shape[0]) @@ -386,7 +389,7 @@ def get_distances(descriptors: np.ndarray, base: int) -> list[Any]: return sorted(enumerate(distances), key=lambda tup: tup[1]) -def main(): +if __name__ == "__main__": # Index to compare haralick descriptors to index = int(input()) q_value = [int(value) for value in input().split()] @@ -429,7 +432,3 @@ def main(): print("Ranking:") for idx, file_idx in enumerate(indexed_distances): print(f"({idx}) {files[file_idx]}", end="\n") - - -if __name__ == "__main__": - main() From 5fb933d057e4f28daa711dae4b947dd39d2fa551 Mon Sep 17 00:00:00 2001 From: Rafael Zimmer Date: Sun, 27 Nov 2022 14:29:21 -0300 Subject: [PATCH 09/17] Updated requirements.txt --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index a1d607df07e1..038e1cb3dbf1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,3 +20,4 @@ texttable tweepy xgboost yulewalker +imageio From c9ec63ef7dce4448021a3d0a46b77a2fa0f9db59 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 27 Nov 2022 17:30:39 +0000 Subject: [PATCH 10/17] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 038e1cb3dbf1..bb060edb5eba 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ beautifulsoup4 fake_useragent +imageio keras lxml matplotlib @@ -20,4 +21,3 @@ texttable tweepy xgboost yulewalker -imageio From bfd0fd7e173e562201f887ec5fe64b3b914b8f56 Mon Sep 17 00:00:00 2001 From: Rafael Zimmer Date: Tue, 13 Dec 2022 17:51:14 -0300 Subject: [PATCH 11/17] Update computer_vision/haralick_descriptors.py No! What if the Kernel is empty? Example: >>> kernel = np.zeros((1)) >>> kernel or np.ones((3, 3)) array([[1., 1., 1.], [1., 1., 1.], [1., 1., 1.]]) Co-authored-by: Christian Clauss --- computer_vision/haralick_descriptors.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/computer_vision/haralick_descriptors.py b/computer_vision/haralick_descriptors.py index 945de2d97023..2e1036a3e236 100644 --- a/computer_vision/haralick_descriptors.py +++ b/computer_vision/haralick_descriptors.py @@ -186,9 +186,7 @@ def closing_filter(image: np.ndarray, kernel: np.ndarray = None) -> np.ndarray: array([[0, 0], [0, 0]], dtype=uint8) """ - if kernel is None: - np.ones((3, 3)) - + kernel = kernel or np.ones((3, 3)) return transform(transform(image, "erosion", kernel), "dilation", kernel) From 6f52bff54a76f7e9572d25a68e6a3860ddeb0455 Mon Sep 17 00:00:00 2001 From: Rafael Zimmer Date: Tue, 13 Dec 2022 17:52:38 -0300 Subject: [PATCH 12/17] Undone wrong commit --- computer_vision/haralick_descriptors.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/computer_vision/haralick_descriptors.py b/computer_vision/haralick_descriptors.py index 2e1036a3e236..db9c7a7e869d 100644 --- a/computer_vision/haralick_descriptors.py +++ b/computer_vision/haralick_descriptors.py @@ -186,7 +186,8 @@ def closing_filter(image: np.ndarray, kernel: np.ndarray = None) -> np.ndarray: array([[0, 0], [0, 0]], dtype=uint8) """ - kernel = kernel or np.ones((3, 3)) + if kernel is None: + kernel = np.ones((3, 3)) return transform(transform(image, "erosion", kernel), "dilation", kernel) From fc2fa0bbad51e1b479c75fc96f2e8bf63a617094 Mon Sep 17 00:00:00 2001 From: Rafael Zimmer Date: Mon, 19 Dec 2022 22:43:29 -0300 Subject: [PATCH 13/17] Update haralick_descriptors.py --- computer_vision/haralick_descriptors.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/computer_vision/haralick_descriptors.py b/computer_vision/haralick_descriptors.py index db9c7a7e869d..8c9afb4d0e88 100644 --- a/computer_vision/haralick_descriptors.py +++ b/computer_vision/haralick_descriptors.py @@ -128,7 +128,7 @@ def transform(image: np.ndarray, kind: str, kernel: np.ndarray = None) -> np.nda array([[0, 0], [0, 0]], dtype=uint8) """ - if not kernel: + if kernel is None: kernel = np.ones((3, 3)) if kind == "erosion": @@ -168,7 +168,7 @@ def opening_filter(image: np.ndarray, kernel: np.ndarray = None) -> np.ndarray: array([[1, 1], [1, 1]], dtype=uint8) """ - if not kernel: + if kernel is None: np.ones((3, 3)) return transform(transform(image, "dilation", kernel), "erosion", kernel) From e4c3252bb71e7a88522091a9b7efad1e12061428 Mon Sep 17 00:00:00 2001 From: Rafael Zimmer Date: Tue, 20 Dec 2022 00:33:53 -0300 Subject: [PATCH 14/17] Added algorithm for Richardson Lucy Deconvolution --- computer_vision/richardson_lucy.py | 233 +++++++++++++++++++++++++++++ 1 file changed, 233 insertions(+) create mode 100644 computer_vision/richardson_lucy.py diff --git a/computer_vision/richardson_lucy.py b/computer_vision/richardson_lucy.py new file mode 100644 index 000000000000..e97935735e78 --- /dev/null +++ b/computer_vision/richardson_lucy.py @@ -0,0 +1,233 @@ +""" +https://en.wikipedia.org/wiki/Motion_blur +https://en.wikipedia.org/wiki/Image_noise +https://en.wikipedia.org/wiki/Richardson-Lucy_deconvolution +""" +import imageio.v2 as imageio +import numpy as np +from numpy.fft import fft2, ifft2, ifftshift + + +def root_mean_square_error(original: np.ndarray, reference: np.ndarray) -> float: + """Simple implementation of Root Mean Squared Error + for two N dimensional numpy arrays. + + Examples: + >>> root_mean_square_error(np.array([1, 2, 3]), np.array([1, 2, 3])) + 0.0 + >>> root_mean_square_error(np.array([1, 2, 3]), np.array([2, 2, 2])) + 0.816496580927726 + >>> root_mean_square_error(np.array([1, 2, 3]), np.array([6, 4, 2])) + 3.1622776601683795 + """ + return np.sqrt(((original - reference) ** 2).mean()) + + +def pad_to_size(image: np.ndarray, reference: np.ndarray): + """Pad an image to have final shape equal to reference image.""" + p, q = (size for size in reference.shape) + difference = ( + (p - image.shape[0]) // 2, + (q - image.shape[1]) // 2 + (q - image.shape[1]) % 2, + ) + + return np.pad(image, difference, mode="constant") + + +def normalize_image( + image: np.ndarray, cap: float = 255.0, data_type: np.dtype = np.uint8 +) -> np.ndarray: + """ + Normalizes image in Numpy 2D array format, between ranges 0-cap, + as to fit uint8 type. + + Args: + image: 2D numpy array representing image as matrix, with values in any range + cap: Maximum cap amount for normalization + data_type: numpy data type to set output variable to + Returns: + return 2D numpy array of type uint8, corresponding to limited range matrix + + Examples: + >>> normalize_image(np.array([[1, 2, 3], [4, 5, 10]]), + ... cap=1.0, data_type=np.float64) + array([[0. , 0.11111111, 0.22222222], + [0.33333333, 0.44444444, 1. ]]) + >>> normalize_image(np.array([[4, 4, 3], [1, 7, 2]])) + array([[127, 127, 85], + [ 0, 255, 42]], dtype=uint8) + """ + normalized = (image - np.min(image)) / (np.max(image) - np.min(image)) * cap + return normalized.astype(data_type) + + +def gaussian_noise(size: tuple, mean=0, std=0.05): + """Creates normal distribution array with given size to use as noise. + + Args: + size: Size of the desired output image + mean: Mean to use within the Gaussian Function + std: Standard deviation to use within the Gaussian Function + + Returns: + Matrix with given size, containing generated gaussian values. + + Example: + >>> np.random.seed(0) + >>> gaussian_noise((5, 5)) + array([[ 22.49166741, 5.10200441, 12.4789093 , 28.57138829, + 23.81136437], + [-12.46029297, 12.11362732, -1.92980441, -1.31604036, + 5.2351309 ], + [ 1.83655553, 18.54198721, 9.703231 , 1.55135646, + 5.65925622], + [ 4.25434767, 19.04950818, -2.61576786, 3.9916132 , + -10.88972068], + [-32.55062015, 8.33363709, 11.02156154, -9.46260401, + 28.93937146]]) + """ + noise = np.multiply(np.random.normal(mean, std, size), 255) + return noise + + +def gaussian_filter(k: int = 5, sigma: float = 1.0) -> np.ndarray: + """Generates a matrix with weights corresponding to centered gaussian distribution. + + Args: + k: Lateral size of the kernel. + sigma: Standard deviation to be used when generating distribution + + Returns: + np.ndarray: [k x k] sized kernel to be used as filter + + Examples: + >>> gaussian_filter() + array([[0.00296902, 0.01330621, 0.02193823, 0.01330621, 0.00296902], + [0.01330621, 0.0596343 , 0.09832033, 0.0596343 , 0.01330621], + [0.02193823, 0.09832033, 0.16210282, 0.09832033, 0.02193823], + [0.01330621, 0.0596343 , 0.09832033, 0.0596343 , 0.01330621], + [0.00296902, 0.01330621, 0.02193823, 0.01330621, 0.00296902]]) + """ + arx = np.arange((-k // 2) + 1.0, (k // 2) + 1.0) + x, y = np.meshgrid(arx, arx) + filt = np.exp(-(1 / 2) * (np.square(x) + np.square(y)) / np.square(sigma)) + return filt / np.sum(filt) + + +def convolve(matrix: np.ndarray, kernel: np.ndarray) -> np.ndarray: + """Convolves a given kernel around a matrix through the frequency domain, using Fourier transformations. + + Args: + matrix: Numpy array containing values to be convolved + kernel: Kernel (with all dimensions smaller than those of the matrix) with weights to apply to each pixel. + + Returns: + np.ndarray: Final equally shaped matrix with convoluted pixels. + + Examples: + >>> matrix = np.array([[-1.45436567, 0.04575852], + ... [-0.18718385, 1.53277921]]) + >>> kernel = np.array([[1, 0], [0, 1]]) + >>> convolve(matrix, kernel) + array([[ 0.07841354, -0.14142533], + [-0.14142533, 0.07841354]]) + """ + if kernel.shape[0] > matrix.shape[1] or kernel.shape[1] > matrix.shape[1]: + return matrix + kernel = pad_to_size(kernel, matrix) + + kernel_f = fft2(kernel) + matrix_f = fft2(matrix) + + return ifftshift(ifft2(np.multiply(matrix_f, kernel_f))).real + + +def get_motion_psf(shape: tuple, angle: float, num_pixel_dist: int = 20) -> np.ndarray: + """Generate an array with given shape corresponding to Point Spread Function for the desired angle. + + Args: + shape: The shape of the image. + angle: The angle of the motion blur. Should be in degrees. [0, 360) + num_pixel_dist: The distance of the motion blur. [0, infinity) + Remember that the distance is measured in pixels. Greater will be more blurry + + Returns: + np.ndarray: The point-spread array associated with the motion blur. + + Examples: + >>> shape = (3, 3) + >>> angle = 15 + >>> get_motion_psf(shape, angle, num_pixel_dist=3) + array([[0. , 0.33333333, 0. ], + [0. , 0.33333333, 0. ], + [0.33333333, 0. , 0. ]]) + """ + psf = np.zeros(shape) + center = np.array([shape[0] - 1, shape[1] - 1]) // 2 + radians = angle / 180 * np.pi + phase = np.array([np.cos(radians), np.sin(radians)]) + for i in range(num_pixel_dist): + offset_x = int(center[0] - np.round_(i * phase[0])) + offset_y = int(center[1] - np.round_(i * phase[1])) + psf[offset_x, offset_y] = 1 + psf /= psf.sum() + + return psf + + +def richardson_lucy( + degraded: np.ndarray, function_kernel: np.ndarray, steps: int +) -> np.ndarray: + """Richardson-Lucy method to restore an image affected by known motion blur function, as well as arbitrary steps + to perform during iterative image restoration. + + Args: + degraded: Observed image, considered to be degraded - to be restored + function_kernel: Supposed function used to blur original image + steps: Iterative steps to take for restoring image + + Returns: + np.ndarray: Restored image after method application + + + Examples: + >>> np.random.seed(0) + >>> shape = (3, 3) + >>> degraded = gaussian_noise(shape) + >>> kernel = np.identity(2) + >>> richardson_lucy(degraded, kernel, steps=4) + array([[1.16272775e+02, 0.00000000e+00, 7.58091075e+01], + [2.40582123e+01, 2.09006034e+01, 0.00000000e+00], + [0.00000000e+00, 1.52620152e-02, 1.31734948e+00]]) + """ + estimated_img = np.full(shape=degraded.shape, fill_value=1, dtype="float64") + + for i in range(steps): + dividend = convolve(estimated_img, function_kernel) + quotient = np.divide(degraded, dividend, where=dividend != 0) + + estimated_img = np.multiply( + estimated_img, convolve(quotient, np.flip(function_kernel)) + ) + + estimated_img = np.clip(estimated_img, 0, 255) + + return estimated_img + + +def main(): + input_file = str(input()).rstrip() + degraded= imageio.imread(input_file) + + angle = int(input()) + steps = int(input()) + + # The Richardson-Lucy iterative method is used to restore an image + # degraded by motion blur and noise. + restored = richardson_lucy(degraded, get_motion_psf(degraded.shape, angle), steps) + + print(root_mean_square_error(degraded, restored)) + + +if __name__ == "__main__": + main() From 6615ca4481d27138234568052bcf5e2bcdd1a032 Mon Sep 17 00:00:00 2001 From: Rafael Zimmer Date: Tue, 20 Dec 2022 00:39:05 -0300 Subject: [PATCH 15/17] Fixed line sizes --- computer_vision/richardson_lucy.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/computer_vision/richardson_lucy.py b/computer_vision/richardson_lucy.py index e97935735e78..1b89a1d0ee6b 100644 --- a/computer_vision/richardson_lucy.py +++ b/computer_vision/richardson_lucy.py @@ -115,11 +115,14 @@ def gaussian_filter(k: int = 5, sigma: float = 1.0) -> np.ndarray: def convolve(matrix: np.ndarray, kernel: np.ndarray) -> np.ndarray: - """Convolves a given kernel around a matrix through the frequency domain, using Fourier transformations. + """ + Convolves a given kernel around a matrix through the frequency domain, + using Fourier transformations. Args: matrix: Numpy array containing values to be convolved - kernel: Kernel (with all dimensions smaller than those of the matrix) with weights to apply to each pixel. + kernel: Kernel (with all dimensions smaller than those of the matrix) + with weights to apply to each pixel. Returns: np.ndarray: Final equally shaped matrix with convoluted pixels. @@ -143,13 +146,16 @@ def convolve(matrix: np.ndarray, kernel: np.ndarray) -> np.ndarray: def get_motion_psf(shape: tuple, angle: float, num_pixel_dist: int = 20) -> np.ndarray: - """Generate an array with given shape corresponding to Point Spread Function for the desired angle. + """ + Generate an array with given shape corresponding to + Point Spread Function for the desired angle. Args: shape: The shape of the image. angle: The angle of the motion blur. Should be in degrees. [0, 360) num_pixel_dist: The distance of the motion blur. [0, infinity) - Remember that the distance is measured in pixels. Greater will be more blurry + Remember that the distance is measured in pixels. + Greater values will be more blurry Returns: np.ndarray: The point-spread array associated with the motion blur. @@ -178,7 +184,9 @@ def get_motion_psf(shape: tuple, angle: float, num_pixel_dist: int = 20) -> np.n def richardson_lucy( degraded: np.ndarray, function_kernel: np.ndarray, steps: int ) -> np.ndarray: - """Richardson-Lucy method to restore an image affected by known motion blur function, as well as arbitrary steps + """ + Richardson-Lucy method to restore an image + affected by known motion blur function, as well as arbitrary steps to perform during iterative image restoration. Args: @@ -217,7 +225,7 @@ def richardson_lucy( def main(): input_file = str(input()).rstrip() - degraded= imageio.imread(input_file) + degraded = imageio.imread(input_file) angle = int(input()) steps = int(input()) From 183c8b5332339b7534dd70d99437e1218201f98e Mon Sep 17 00:00:00 2001 From: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Date: Wed, 27 Sep 2023 06:39:02 +0000 Subject: [PATCH 16/17] updating DIRECTORY.md --- DIRECTORY.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/DIRECTORY.md b/DIRECTORY.md index d81e4ec1ee83..289aa8bb61ca 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -139,6 +139,7 @@ * [Mean Threshold](computer_vision/mean_threshold.py) * [Mosaic Augmentation](computer_vision/mosaic_augmentation.py) * [Pooling Functions](computer_vision/pooling_functions.py) + * [Richardson Lucy](computer_vision/richardson_lucy.py) ## Conversions * [Astronomical Length Scale Conversion](conversions/astronomical_length_scale_conversion.py) @@ -515,6 +516,7 @@ * [Logistic Regression](machine_learning/logistic_regression.py) * Lstm * [Lstm Prediction](machine_learning/lstm/lstm_prediction.py) + * [Mfcc](machine_learning/mfcc.py) * [Multilayer Perceptron Classifier](machine_learning/multilayer_perceptron_classifier.py) * [Polynomial Regression](machine_learning/polynomial_regression.py) * [Scoring Functions](machine_learning/scoring_functions.py) @@ -672,6 +674,7 @@ * [Sum Of Digits](maths/sum_of_digits.py) * [Sum Of Geometric Progression](maths/sum_of_geometric_progression.py) * [Sum Of Harmonic Series](maths/sum_of_harmonic_series.py) + * [Sum Of Two Positive Numbers Bitwise](maths/sum_of_two_positive_numbers_bitwise.py) * [Sumset](maths/sumset.py) * [Sylvester Sequence](maths/sylvester_sequence.py) * [Tanh](maths/tanh.py) @@ -753,6 +756,7 @@ * [Basic Orbital Capture](physics/basic_orbital_capture.py) * [Casimir Effect](physics/casimir_effect.py) * [Centripetal Force](physics/centripetal_force.py) + * [Coulombs Law](physics/coulombs_law.py) * [Grahams Law](physics/grahams_law.py) * [Horizontal Projectile Motion](physics/horizontal_projectile_motion.py) * [Hubble Parameter](physics/hubble_parameter.py) From 1c040d369351705b6afe9e497ebe24d38b923335 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Wed, 27 Sep 2023 02:44:21 -0400 Subject: [PATCH 17/17] Fix pre-commit errors --- computer_vision/richardson_lucy.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/computer_vision/richardson_lucy.py b/computer_vision/richardson_lucy.py index 1b89a1d0ee6b..2087a4ab0241 100644 --- a/computer_vision/richardson_lucy.py +++ b/computer_vision/richardson_lucy.py @@ -173,8 +173,8 @@ def get_motion_psf(shape: tuple, angle: float, num_pixel_dist: int = 20) -> np.n radians = angle / 180 * np.pi phase = np.array([np.cos(radians), np.sin(radians)]) for i in range(num_pixel_dist): - offset_x = int(center[0] - np.round_(i * phase[0])) - offset_y = int(center[1] - np.round_(i * phase[1])) + offset_x = int(center[0] - np.round(i * phase[0])) + offset_y = int(center[1] - np.round(i * phase[1])) psf[offset_x, offset_y] = 1 psf /= psf.sum() @@ -210,7 +210,7 @@ def richardson_lucy( """ estimated_img = np.full(shape=degraded.shape, fill_value=1, dtype="float64") - for i in range(steps): + for _ in range(steps): dividend = convolve(estimated_img, function_kernel) quotient = np.divide(degraded, dividend, where=dividend != 0)