diff --git a/src/config_parser.py b/src/config_parser.py index ea92a65..e83957c 100644 --- a/src/config_parser.py +++ b/src/config_parser.py @@ -77,6 +77,9 @@ def parse_conf(preset_name, filters, params, config_file): Store filters and their parameters. ''' + if not exists(config_file): + log.error_exit("Config file not found") + config = json.load(open(config_file)) # Find preset in config file @@ -96,7 +99,7 @@ def parse_conf(preset_name, filters, params, config_file): log.print_message("Loaded preset:", preset_name, "from file:", config_file) else: - log.print_message("Warning: Preset not found in config file") + log.error_exit("Preset not found in config file") def parse_params(params): @@ -107,12 +110,13 @@ def parse_params(params): # TODO: possibly too bloated, sending all possible params to each filter # TODO: remove unnecessary params - possible_params = {"h", "searchWindowSize", "templateWindowSize", - "ksize", "kernel", "angle", - "sigmaColor", "sigmaSpace", "diameter", "anchor", "iterations", - "op", "strength", "amount", "radius", "weight", "channelAxis", - "theta", "sigma", "lambd", "gamma", "psi", "shape", "percent", - "threshold", "maxval", "type", "margin", "color", "truncate", "patch_size", "patch_distance"} + possible_params = {"sigma", "ksize", "kernel", + "diameter", "sigmaColor", "sigmaSpace", + "patch_size", "patch_distance", "weight", + "amount", "radius", "percent", + "threshold", + "margin", "color" + } for key in possible_params: if params.get(key) is None: diff --git a/src/filters.py b/src/filters.py index 9c07059..917f8a6 100644 --- a/src/filters.py +++ b/src/filters.py @@ -8,13 +8,11 @@ import cv2 as cv from skimage import filters as skiflt from skimage import restoration as skirest from skimage import morphology as skimorph -from scipy import ndimage from PIL import Image, ImageFilter import bm3d -import matplotlib.pyplot as plt -class filter: +class img_filter: ''' Parent class for all the filters. ''' @@ -28,7 +26,7 @@ class filter: # --------------------- DENOISING FILTERS ---------------------# -class gaussian(filter): +class gaussian(img_filter): '''Gaussian blur filter from scikit-image. Easier to use than opencv version. ''' @@ -45,7 +43,7 @@ class gaussian(filter): self.img, sigma=sigma, preserve_range=True) -class median(filter): +class median(img_filter): ''' Median blur filter from scikit-image. Using this over opencv version as that one is limited to 5x5 kernel. ''' @@ -61,7 +59,7 @@ class median(filter): self.img = skiflt.median(self.img, footprint=skimorph.disk(ksize)) -class bilateral(filter): +class bilateral(img_filter): ''' Bilateral filter from opencv. ''' @@ -87,7 +85,7 @@ class bilateral(filter): self.img, diameter, sigmaColor, sigmaSpace) -class bilateral_scikit(filter): +class bilateral_scikit(img_filter): ''' Skimage denoise_bilateral filter. Averages pixels based on their distance and color similarity. Preserves edges while removing unwanted noise. @@ -116,7 +114,7 @@ class bilateral_scikit(filter): self.img = np.uint8(self.img * 255.0) # converting back to uint8 -class nlmeans(filter): +class nlmeans(img_filter): ''' Non-local means filter from scikit-image. ''' @@ -143,7 +141,7 @@ class nlmeans(filter): self.img = np.uint8(self.img * 255.0) # converting back to uint8 -class total_variation(filter): +class total_variation(img_filter): ''' Scikit image denoise_tv_chambolle filter from scikit-image. Performs total variation denoising technique based on original Chambolle paper. @@ -163,7 +161,7 @@ class total_variation(filter): self.img = np.uint8(self.img * 255.0) # converting back to uint8 -class block_match(filter): +class block_match(img_filter): '''Block matching filter from bm3d. This filter is very slow and should be used only on small images @@ -179,7 +177,7 @@ class block_match(filter): stage_arg=bm3d.BM3DStages.ALL_STAGES) -class unsharp_mask_scikit(filter): +class unsharp_mask_scikit(img_filter): ''' Unsharp mask filter from scikit. Apply blurring using gaussian filter, then subtract the blurred image from the original image. @@ -204,7 +202,7 @@ class unsharp_mask_scikit(filter): # ------------------- EDGE DETECTION FILTERS -------------------# -class farid(filter): +class farid(img_filter): ''' Farid filter from filters. Not sure what this might be used for yet. ''' @@ -212,27 +210,27 @@ class farid(filter): def __init__(self, img): super().__init__(img) - def apply(self, params): + def apply(self, _): self.img = skiflt.farid(self.img) # ------------------ RIDGE EXTRACTION FILTERS ------------------# -class meijering(filter): +class meijering(img_filter): ''' Meijering filter from scikit-image filters. ''' def __init__(self, img): super().__init__(img) - def apply(self, params): + def apply(self, _): self.img = skiflt.meijering(self.img) self.img = np.uint8(self.img * 255.0) -class sato(filter): +class sato(img_filter): ''' Meijering filter from scikit-image filters. Exctracts black ridges. ''' @@ -240,13 +238,13 @@ class sato(filter): def __init__(self, img): super().__init__(img) - def apply(self, params): + def apply(self, _): self.img = skiflt.sato(self.img) self.img = np.uint8(self.img * 255.0) -class hessian(filter): +class hessian(img_filter): ''' Hessian filter from scikit-image filters. ''' @@ -263,18 +261,18 @@ class hessian(filter): # ------------------- MISCELLANEOUS FILTERS -------------------# -class invert(filter): +class invert(img_filter): ''' Invert the image using bitwise_not from opencv. ''' def __init__(self, img): super().__init__(img) - def apply(self, params): + def apply(self, _): self.img = cv.bitwise_not(self.img) -class scale_values(filter): +class scale_values(img_filter): ''' Scale values of the image to use the entire range of data type. This should remove the line height issues. ''' @@ -294,19 +292,17 @@ class scale_values(filter): tmp = tmp * coef -class binarize(filter): +class binarize(img_filter): def init(self, img): super().__init__(img) def apply(self, params): threshold = int(params["threshold"]) if params["threshold"] else 128 - maxval = int(params["maxval"]) if params["maxval"] else 255 - type = int(params["type"]) if params["type"] else 0 - self.img = cv.threshold(self.img, threshold, maxval, type)[1] + self.img = cv.threshold(self.img, threshold, 255, cv.THRESH_BINARY)[1] -class binarize_otsu(filter): +class binarize_otsu(img_filter): ''' Otsu binarization filter from opencv. ''' def init(self, img): @@ -316,7 +312,7 @@ class binarize_otsu(filter): self.img = cv.threshold(self.img, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)[1] -class add_margin(filter): +class add_margin(img_filter): def init(self, img): super().__init__(img) @@ -330,95 +326,52 @@ class add_margin(filter): self.img, margin, margin, margin, margin, cv.BORDER_CONSTANT, value=color) self.height, self.width = self.img.shape -# ---------------------- OLD --------------------------# - - -# TODO: REVISE, REMOVE unused filters - - -class convolve(filter): - ''' Convolve with custom kernel using opencv. - If no kernel is given, use default 3x3 kernel for averaging. - Possibly useful for custom filters. - ''' - - def __init__(self, img): - super().__init__(img) - - def apply(self, params): - kernel = np.array(params["kernel"]) if params["kernel"] else np.ones( - (3, 3), np.float32) / 9 - - self.img = cv.filter2D(self.img, -1, kernel) +# ---------------------- MORPHOLOGICAL OPS --------------------------# +class erode(img_filter): + ''' General morphological operations from OpenCV. -class blur(filter): - ''' Blur filter from opencv. - Performs averaging of the image. + Can be used with MORPH_OPEN, MORPH_CLOSE, MORPH_DILATE, MORPH_ERODE and more as 'op'. ''' def __init__(self, img): super().__init__(img) def apply(self, params): - ksize = int(params["ksize"]) if params["ksize"] else 3 - - self.img = cv.blur(self.img, ksize=(ksize, ksize)) + # get an ellipse kernel + kernel = np.matrix(params["kernel"]) if params["kernel"] else cv.getStructuringElement( + cv.MORPH_ELLIPSE, (3, 3)) -class denoise(filter): - def __init__(self, img): - super().__init__(img) - - def apply(self, params): - h = int(params["h"]) if params["h"] else 10 - tWS = int(params["templateWindowSize"] - ) if params["templateWindowSize"] else 7 - sWS = int(params["searchWindowSize"] - ) if params["searchWindowSize"] else 21 - - self.img = np.uint8(self.img) - self.img = cv.fastNlMeansDenoising( - self.img, h, tWS, sWS) + self.img = cv.morphologyEx( + np.uint8(self.img), op=cv.MORPH_ERODE, kernel=kernel) +class dilate(img_filter): + ''' General morphological operations from OpenCV. -class sharpen(filter): - ''' Convolution with a sharpening kernel using opencv. + Can be used with MORPH_OPEN, MORPH_CLOSE, MORPH_DILATE, MORPH_ERODE and more as 'op'. ''' def __init__(self, img): super().__init__(img) def apply(self, params): - kernel = np.matrix(params["kernel"]) if params["kernel"] else np.array( - [[0, -1, 0], [-1, 5, -1], [0, -1, 0]]) - self.img = cv.filter2D(self.img, ddepth=-1, kernel=kernel) - - -class unsharp_mask(filter): - ''' Unsharp mask filter from opencv. + # get an ellipse kernel + kernel = np.matrix(params["kernel"]) if params["kernel"] else cv.getStructuringElement( + cv.MORPH_ELLIPSE, (3, 3)) - First blur the image a little bit, then calculate Laplacian of the image to get the edges. - Scale the Laplacian and subtract it from the original image. - ''' + self.img = cv.morphologyEx( + np.uint8(self.img), op=cv.MORPH_DILATE, kernel=kernel) - def __init__(self, img): - super().__init__(img) - def apply(self, params): - strength = float(params["strength"]) if params["strength"] else 1.0 - ksize = int(params["ksize"]) if params["ksize"] else 3 - - blurred = cv.medianBlur(self.img, ksize) - lap = cv.Laplacian(blurred, cv.CV_32F) +# ---------------------- OLD --------------------------# - self.img = blurred - strength*lap +# TODO: REVISE, REMOVE unused filters -class unsharp_mask_pil(filter): +class unsharp_mask_pil(img_filter): ''' Unsharp mask filter from PIL. - ''' def __init__(self, img): @@ -439,73 +392,3 @@ class unsharp_mask_pil(filter): tmp = Image.fromarray(self.img) tmp = tmp.filter(ImageFilter.UnsharpMask(radius, percent, threshold)) self.img = np.asarray(tmp) - - -class erode(filter): - ''' General morphological operations from OpenCV. - - Can be used with MORPH_OPEN, MORPH_CLOSE, MORPH_DILATE, MORPH_ERODE and more as 'op'. - ''' - - def __init__(self, img): - super().__init__(img) - - def apply(self, params): - - # get an ellipse kernel - kernel = np.matrix(params["kernel"]) if params["kernel"] else cv.getStructuringElement( - cv.MORPH_ELLIPSE, (3, 3)) - - self.img = cv.morphologyEx( - np.uint8(self.img), op=cv.MORPH_ERODE, kernel=kernel) - - -class dilate(filter): - ''' General morphological operations from OpenCV. - - Can be used with MORPH_OPEN, MORPH_CLOSE, MORPH_DILATE, MORPH_ERODE and more as 'op'. - ''' - - def __init__(self, img): - super().__init__(img) - - def apply(self, params): - - # get an ellipse kernel - kernel = np.matrix(params["kernel"]) if params["kernel"] else cv.getStructuringElement( - cv.MORPH_ELLIPSE, (3, 3)) - - self.img = cv.morphologyEx( - np.uint8(self.img), op=cv.MORPH_DILATE, kernel=kernel) - - -class gabor(filter): - ''' Gabor filter from OpenCV. - - Performs Gabor filtering on the image. - ''' - - def __init__(self, img): - super().__init__(img) - - def apply(self, params): - ksize = int(params["ksize"]) if params["ksize"] else 31 - sigma = float(params["sigma"]) if params["sigma"] else 10.0 - theta = params["theta"] if params["theta"] else [ - 0, np.pi/16, np.pi-np.pi/16] - lambd = float(params["lambd"]) if params["lambd"] else 10.0 - gamma = float(params["gamma"]) if params["gamma"] else 0.02 - psi = float(params["psi"]) if params["psi"] else 0.0 - - filters = [] - - for i in range(len(theta)): - g_kernel = cv.getGaborKernel(ksize=( - ksize, ksize), sigma=sigma, theta=theta[i], lambd=lambd, gamma=gamma, psi=psi) - g_kernel = g_kernel / 1.5 * g_kernel.sum() - filters.append(g_kernel) - - tmp = np.zeros_like(self.img) - for i in range(len(filters)): - tmp = cv.filter2D(self.img, -1, kernel=filters[i]) - self.img += np.maximum(self.img, tmp)