From 0d88d9328d8fea0f5880580dfc9fab57f6a801c5 Mon Sep 17 00:00:00 2001 From: Egor Tensin Date: Tue, 30 Aug 2016 01:27:27 +0300 Subject: factor common code out More helpers & more files. --- .gitignore | 2 + .pylintrc | 2 +- bin/__init__.py | 0 bin/box_blur.py | 38 ++++++++++++++ bin/gaussian_blur.py | 44 ++++++++++++++++ bin/shift.py | 52 +++++++++++++++++++ bin/utils/__init__.py | 0 bin/utils/cmd_line.py | 15 ++++++ bin/utils/image.py | 16 ++++++ filters/__init__.py | 0 filters/convolution.py | 16 ++++++ filters/kernel/__init__.py | 0 filters/kernel/box_blur.py | 10 ++++ filters/kernel/gaussian_blur.py | 15 ++++++ filters/kernel/shift.py | 49 ++++++++++++++++++ gaussian.py | 71 ------------------------- mean.py | 61 ---------------------- shift.py | 112 ---------------------------------------- 18 files changed, 258 insertions(+), 245 deletions(-) create mode 100644 bin/__init__.py create mode 100644 bin/box_blur.py create mode 100644 bin/gaussian_blur.py create mode 100644 bin/shift.py create mode 100644 bin/utils/__init__.py create mode 100644 bin/utils/cmd_line.py create mode 100644 bin/utils/image.py create mode 100644 filters/__init__.py create mode 100644 filters/convolution.py create mode 100644 filters/kernel/__init__.py create mode 100644 filters/kernel/box_blur.py create mode 100644 filters/kernel/gaussian_blur.py create mode 100644 filters/kernel/shift.py delete mode 100644 gaussian.py delete mode 100644 mean.py delete mode 100644 shift.py diff --git a/.gitignore b/.gitignore index b44ef38..28a6805 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ +__pycache__/ + /*.png diff --git a/.pylintrc b/.pylintrc index d657af9..78e6f48 100644 --- a/.pylintrc +++ b/.pylintrc @@ -19,7 +19,7 @@ confidence= # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" -disable=missing-docstring +disable=duplicate-code,missing-docstring [BASIC] diff --git a/bin/__init__.py b/bin/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bin/box_blur.py b/bin/box_blur.py new file mode 100644 index 0000000..9d5d1cd --- /dev/null +++ b/bin/box_blur.py @@ -0,0 +1,38 @@ +# Copyright 2016 (c) Egor Tensin +# This file is part of the "Simple image filters" project. +# For details, see https://github.com/egor-tensin/filters. +# Distributed under the MIT License. + +import argparse +import sys + +from .utils import cmd_line, image +from filters.convolution import convolve +from filters.kernel.box_blur import gen_kernel + +DEFAULT_RADIUS = 1 + +def _main_box_blur(img_path, radius=DEFAULT_RADIUS, output_path=None): + img = image.load_grayscale(img_path) + kernel = gen_kernel(radius) + output = convolve(img, kernel) + if output_path is None: + image.show(output) + else: + image.save(output_path, output) + +def _parse_args(args=sys.argv): + parser = argparse.ArgumentParser() + parser.add_argument('img_path') + parser.add_argument('--output', '-o', + dest='output_path', default=None) + parser.add_argument('--radius', '-r', + type=cmd_line.parse_non_negative_integer, + default=DEFAULT_RADIUS) + return parser.parse_args(args[1:]) + +def _main(args=sys.argv): + _main_box_blur(**vars(_parse_args(args))) + +if __name__ == '__main__': + _main() diff --git a/bin/gaussian_blur.py b/bin/gaussian_blur.py new file mode 100644 index 0000000..d530547 --- /dev/null +++ b/bin/gaussian_blur.py @@ -0,0 +1,44 @@ +# Copyright 2016 (c) Egor Tensin +# This file is part of the "Simple image filters" project. +# For details, see https://github.com/egor-tensin/filters. +# Distributed under the MIT License. + +import argparse +import sys + +from .utils import cmd_line, image +from filters.convolution import convolve +from filters.kernel.gaussian_blur import gen_kernel + +DEFAULT_SIGMA = 1. +DEFAULT_RADIUS = 1 + +def _main_gaussian_blur( + img_path, radius=DEFAULT_RADIUS, sigma=DEFAULT_SIGMA, + output_path=None): + + img = image.load_grayscale(img_path) + kernel = gen_kernel(radius, sigma) + output = convolve(img, kernel) + if output_path is None: + image.show(output) + else: + image.save(output_path, output) + +def _parse_args(args=sys.argv): + parser = argparse.ArgumentParser() + parser.add_argument('img_path') + parser.add_argument('--output', '-o', + dest='output_path', default=None) + parser.add_argument('--sigma', '-s', + type=float, default=DEFAULT_SIGMA) + parser.add_argument('--radius', '-r', + type=cmd_line.parse_non_negative_integer, + default=DEFAULT_RADIUS) + return parser.parse_args(args[1:]) + +def _main(args=sys.argv): + _main_gaussian_blur(**vars(_parse_args(args))) + +if __name__ == '__main__': + _main() diff --git a/bin/shift.py b/bin/shift.py new file mode 100644 index 0000000..8dfd76a --- /dev/null +++ b/bin/shift.py @@ -0,0 +1,52 @@ +# Copyright 2016 (c) Egor Tensin +# This file is part of the "Simple image filters" project. +# For details, see https://github.com/egor-tensin/filters. +# Distributed under the MIT License. + +import argparse +import sys + +from .utils import cmd_line, image +from filters.convolution import convolve +from filters.kernel.shift import Direction + +DEFAULT_DIRECTION = Direction.SOUTH_EAST +DEFAULT_DISTANCE = 1 + +def _main_shift( + img_path, direction=DEFAULT_DIRECTION, distance=DEFAULT_DISTANCE, + output_path=None): + + img = image.load_grayscale(img_path) + kernel = direction.gen_kernel(distance) + output = convolve(img, kernel) + if output_path is None: + image.show(output) + else: + image.save(output_path, output) + +def _parse_direction(s): + try: + return Direction(s) + except ValueError: + raise argparse.ArgumentTypeError('invalid direction: ' + s) + +def _parse_args(args=sys.argv): + parser = argparse.ArgumentParser() + parser.add_argument('img_path') + parser.add_argument('--output', '-o', + dest='output_path', default=None) + parser.add_argument('--direction', '-d', + type=_parse_direction, + choices=Direction, + default=DEFAULT_DIRECTION) + parser.add_argument('--distance', '-n', + type=cmd_line.parse_non_negative_integer, + default=DEFAULT_DISTANCE) + return parser.parse_args(args[1:]) + +def _main(args=sys.argv): + _main_shift(**vars(_parse_args(args))) + +if __name__ == '__main__': + _main() diff --git a/bin/utils/__init__.py b/bin/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bin/utils/cmd_line.py b/bin/utils/cmd_line.py new file mode 100644 index 0000000..6bfeafe --- /dev/null +++ b/bin/utils/cmd_line.py @@ -0,0 +1,15 @@ +# Copyright 2016 (c) Egor Tensin +# This file is part of the "Simple image filters" project. +# For details, see https://github.com/egor-tensin/filters. +# Distributed under the MIT License. + +import argparse + +def parse_non_negative_integer(s): + try: + x = int(s) + except ValueError: + raise argparse.ArgumentTypeError('must be a non-negative integer: ' + s) + if x < 0: + raise argparse.ArgumentTypeError('must be a non-negative integer: ' + s) + return x diff --git a/bin/utils/image.py b/bin/utils/image.py new file mode 100644 index 0000000..1dd64cd --- /dev/null +++ b/bin/utils/image.py @@ -0,0 +1,16 @@ +# Copyright 2016 (c) Egor Tensin +# This file is part of the "Simple image filters" project. +# For details, see https://github.com/egor-tensin/filters. +# Distributed under the MIT License. + +import cv2 + +def load_grayscale(path): + return cv2.imread(path, cv2.IMREAD_GRAYSCALE) + +def save(path, img): + cv2.imwrite(path, img) + +def show(img, window_title=None): + cv2.imshow(window_title, img) + cv2.waitKey() diff --git a/filters/__init__.py b/filters/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/filters/convolution.py b/filters/convolution.py new file mode 100644 index 0000000..0a4cf9e --- /dev/null +++ b/filters/convolution.py @@ -0,0 +1,16 @@ +# Copyright 2016 (c) Egor Tensin +# This file is part of the "Simple image filters" project. +# For details, see https://github.com/egor-tensin/filters. +# Distributed under the MIT License. + +import numpy as np + +def convolve(img, kernel): + #print(kernel) + radius = kernel.shape[0] // 2 + output = np.zeros(img.shape, dtype=img.dtype) + for i in range(radius, img.shape[0] - radius): + for j in range(radius, img.shape[1] - radius): + neighborhood = img[i - radius:i + radius + 1, j - radius:j + radius + 1] + output[i, j] = np.sum(neighborhood * kernel) + return output diff --git a/filters/kernel/__init__.py b/filters/kernel/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/filters/kernel/box_blur.py b/filters/kernel/box_blur.py new file mode 100644 index 0000000..b9c5d60 --- /dev/null +++ b/filters/kernel/box_blur.py @@ -0,0 +1,10 @@ +# Copyright 2016 (c) Egor Tensin +# This file is part of the "Simple image filters" project. +# For details, see https://github.com/egor-tensin/filters. +# Distributed under the MIT License. + +import numpy as np + +def gen_kernel(radius): + size = radius * 2 + 1 + return np.ones((size, size)) / size ** 2 diff --git a/filters/kernel/gaussian_blur.py b/filters/kernel/gaussian_blur.py new file mode 100644 index 0000000..5557b79 --- /dev/null +++ b/filters/kernel/gaussian_blur.py @@ -0,0 +1,15 @@ +# Copyright 2016 (c) Egor Tensin +# This file is part of the "Simple image filters" project. +# For details, see https://github.com/egor-tensin/filters. +# Distributed under the MIT License. + +import numpy as np + +def gen_kernel(radius, sigma): + kernel = np.array([[i ** 2 + j ** 2 + for i in range(-radius, radius + 1)] + for j in range(-radius, radius + 1)]) + kernel = -kernel / (2 * sigma ** 2) + kernel = np.exp(kernel) + kernel = kernel / np.sum(kernel) + return kernel diff --git a/filters/kernel/shift.py b/filters/kernel/shift.py new file mode 100644 index 0000000..4e1e497 --- /dev/null +++ b/filters/kernel/shift.py @@ -0,0 +1,49 @@ +# Copyright 2016 (c) Egor Tensin +# This file is part of the "Simple image filters" project. +# For details, see https://github.com/egor-tensin/filters. +# Distributed under the MIT License. + +from enum import Enum + +import numpy as np + +class Direction(Enum): + NORTH = 'N' + NORTH_EAST = 'NE' + NORTH_WEST = 'NW' + SOUTH = 'S' + SOUTH_EAST = 'SE' + SOUTH_WEST = 'SW' + EAST = 'E' + WEST = 'W' + + def gen_kernel(self, distance): + radius = distance + size = 2 * radius + 1 + kernel = np.zeros((size, size)) + x, y = radius, radius + + if self is Direction.NORTH: + x = -1 + elif self is Direction.NORTH_EAST: + x, y = -1, 0 + elif self is Direction.EAST: + y = 0 + elif self is Direction.SOUTH_EAST: + x, y = 0, 0 + elif self is Direction.SOUTH: + x = 0 + elif self is Direction.SOUTH_WEST: + x, y = 0, -1 + elif self is Direction.WEST: + y = -1 + elif self is Direction.NORTH_WEST: + x, y = -1, -1 + else: + raise NotImplementedError('unsupported direction: ' + str(self)) + + kernel[x, y] = 1 + return kernel + + def __str__(self): + return self.value diff --git a/gaussian.py b/gaussian.py deleted file mode 100644 index 2e5ad90..0000000 --- a/gaussian.py +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright 2016 (c) Egor Tensin -# This file is part of the "Simple image filters" project. -# For details, see https://github.com/egor-tensin/filters. -# Distributed under the MIT License. - -import argparse -import sys - -import cv2 -import numpy as np - -def gen_kernel(radius, sigma): - kernel = np.array([[i ** 2 + j ** 2 - for i in range(-radius, radius + 1)] - for j in range(-radius, radius + 1)]) - kernel = -kernel / (2 * sigma ** 2) - kernel = np.exp(kernel) - kernel = kernel / np.sum(kernel) - return kernel - -def convolve(img, kernel): - #print(kernel) - radius = kernel.shape[0] // 2 - output = np.zeros(img.shape, dtype=img.dtype) - for i in range(radius, img.shape[0] - radius): - for j in range(radius, img.shape[1] - radius): - neighborhood = img[i - radius:i + radius + 1, j - radius:j + radius + 1] - output[i, j] = np.sum(neighborhood * kernel) - return output - -DEFAULT_SIGMA = 1. -DEFAULT_RADIUS = 1 - -def gaussian(img_path, radius=DEFAULT_RADIUS, sigma=DEFAULT_SIGMA, - output_path=None): - - img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) - kernel = gen_kernel(radius, sigma) - output = convolve(img, kernel) - if output_path is None: - cv2.imshow("Output", output) - cv2.waitKey() - else: - cv2.imwrite(output_path, output) - -def _parse_non_negative_integer(s): - try: - x = int(s) - except ValueError: - raise argparse.ArgumentTypeError('must be a non-negative integer: ' + s) - if x < 0: - raise argparse.ArgumentTypeError('must be a non-negative integer: ' + s) - return x - -def _parse_args(args=sys.argv): - parser = argparse.ArgumentParser() - parser.add_argument('img_path') - parser.add_argument('--output', '-o', - dest='output_path', default=None) - parser.add_argument('-s', '--sigma', - type=float, default=DEFAULT_SIGMA) - parser.add_argument('--radius', '-r', - type=_parse_non_negative_integer, - default=DEFAULT_RADIUS) - return parser.parse_args(args[1:]) - -def main(args=sys.argv): - gaussian(**vars(_parse_args(args))) - -if __name__ == '__main__': - main() diff --git a/mean.py b/mean.py deleted file mode 100644 index 9fbfc03..0000000 --- a/mean.py +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright 2016 (c) Egor Tensin -# This file is part of the "Simple image filters" project. -# For details, see https://github.com/egor-tensin/filters. -# Distributed under the MIT License. - -import argparse -import sys - -import cv2 -import numpy as np - -def gen_kernel(radius): - size = radius * 2 + 1 - return np.ones((size, size)) / size ** 2 - -def convolve(img, kernel): - #print(kernel) - radius = kernel.shape[0] // 2 - output = np.zeros(img.shape, dtype=img.dtype) - for i in range(radius, img.shape[0] - radius): - for j in range(radius, img.shape[1] - radius): - neighborhood = img[i - radius:i + radius + 1, j - radius:j + radius + 1] - output[i, j] = np.sum(kernel * neighborhood) - return output - -DEFAULT_RADIUS = 1 - -def mean(img_path, radius=DEFAULT_RADIUS, output_path=None): - img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) - kernel = gen_kernel(radius) - output = convolve(img, kernel) - if output_path is None: - cv2.imshow("Output", output) - cv2.waitKey() - else: - cv2.imwrite(output_path, output) - -def _parse_non_negative_integer(s): - try: - x = int(s) - except ValueError: - raise argparse.ArgumentTypeError('must be a non-negative integer: ' + s) - if x < 0: - raise argparse.ArgumentTypeError('must be a non-negative integer: ' + s) - return x - -def _parse_args(args=sys.argv): - parser = argparse.ArgumentParser() - parser.add_argument('img_path') - parser.add_argument('--output', '-o', - dest='output_path', default=None) - parser.add_argument('--radius', '-r', - type=_parse_non_negative_integer, - default=DEFAULT_RADIUS) - return parser.parse_args(args[1:]) - -def main(args=sys.argv): - mean(**vars(_parse_args(args))) - -if __name__ == '__main__': - main() diff --git a/shift.py b/shift.py deleted file mode 100644 index ce4353b..0000000 --- a/shift.py +++ /dev/null @@ -1,112 +0,0 @@ -# Copyright 2016 (c) Egor Tensin -# This file is part of the "Simple image filters" project. -# For details, see https://github.com/egor-tensin/filters. -# Distributed under the MIT License. - -import argparse -from enum import Enum -import sys - -import cv2 -import numpy as np - -class Direction(Enum): - NORTH = 'N' - NORTH_EAST = 'NE' - NORTH_WEST = 'NW' - SOUTH = 'S' - SOUTH_EAST = 'SE' - SOUTH_WEST = 'SW' - EAST = 'E' - WEST = 'W' - - def gen_kernel(self, distance): - radius = distance - size = 2 * radius + 1 - kernel = np.zeros((size, size)) - x, y = radius, radius - - if self is Direction.NORTH: - x = -1 - elif self is Direction.NORTH_EAST: - x, y = -1, 0 - elif self is Direction.EAST: - y = 0 - elif self is Direction.SOUTH_EAST: - x, y = 0, 0 - elif self is Direction.SOUTH: - x = 0 - elif self is Direction.SOUTH_WEST: - x, y = 0, -1 - elif self is Direction.WEST: - y = -1 - elif self is Direction.NORTH_WEST: - x, y = -1, -1 - else: - raise NotImplementedError('unsupported direction: ' + str(self)) - - kernel[x, y] = 1 - return kernel - - def __str__(self): - return self.value - -def convolve(img, kernel): - #print(kernel) - radius = kernel.shape[0] // 2 - output = np.zeros(img.shape, dtype=img.dtype) - for i in range(radius, img.shape[0] - radius): - for j in range(radius, img.shape[1] - radius): - neighborhood = img[i - radius:i + radius + 1, j - radius:j + radius + 1] - output[i, j] = np.sum(neighborhood * kernel) - return output - -DEFAULT_DIRECTION = Direction.SOUTH_EAST -DEFAULT_DISTANCE = 1 - -def shift(img_path, direction=DEFAULT_DIRECTION, distance=DEFAULT_DISTANCE, - output_path=None): - - img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) - kernel = direction.gen_kernel(distance) - output = convolve(img, kernel) - if output_path is None: - cv2.imshow("Output", output) - cv2.waitKey() - else: - cv2.imwrite(output_path, output) - -def _parse_direction(s): - try: - return Direction(s) - except ValueError: - raise argparse.ArgumentTypeError('invalid direction: ' + s) - -def _parse_non_negative_integer(s): - try: - x = int(s) - except ValueError: - raise argparse.ArgumentTypeError('must be a non-negative integer: ' + s) - if x < 0: - raise argparse.ArgumentTypeError('must be a non-negative integer: ' + s) - return x - -def _parse_args(args=sys.argv): - parser = argparse.ArgumentParser() - parser.add_argument('img_path') - parser.add_argument('--output', '-o', - dest='output_path', default=None) - parser.add_argument('--direction', '-d', - type=_parse_direction, - choices=Direction, - default=DEFAULT_DIRECTION) - parser.add_argument('--distance', '-n', - type=_parse_non_negative_integer, - default=DEFAULT_DISTANCE) - return parser.parse_args(args[1:]) - -def main(args=sys.argv): - shift(**vars(_parse_args(args))) - -if __name__ == '__main__': - main() -- cgit v1.2.3