Skip to content

Convolution #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ test:
lint:
pylint **/*.py

lint-src:
pylint src/

lintfix:
autopep8 **/*.py --recursive --in-place --aggressive

Expand All @@ -20,4 +23,4 @@ lock:
pipenv lock

clean:
pipenv clean
pipenv clean
26 changes: 24 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Machine Learning
Machine Learning tools, techniques, gists and projects. Some of this code is referenced in our Blog.
Machine Learning tools, techniques, gists and projects.
Some of this code is referenced in our Blog.

This repository uses `pipenv` as an environment manager.
The base python version is `3.9`.
Expand Down Expand Up @@ -55,7 +56,28 @@ make lintfixhard

for in-place fixing of lint errors under the `/src` dir.

## VSCode
If you are using VSCode, the virtual environment created by `pipenv` will not
be immediately available and you will see warnings in your import statements.
To fix this first make sure the appropriate virtual environment is activated
by running `make activate`, then get the location of the current python
interpreter using `make python`. The printed line should look something like
this:

```bash
/Users/yourname/path/to/virtualenvs/machine-learning-abcde1234/bin/python
```

Copy that line. Then open your
[settings.json](https://code.visualstudio.com/docs/getstarted/settings)
file and add a new key `"python.defaultInterpreterPath"`, then paste the
previously copied python interpreter path as its value and restart VSCode.

```json
{
"python.defaultInterpreterPath": "/Users/yourname/path/to/virtualenvs/machine-learning-abcde1234/bin/python"
}
```
## Contribution
1. Create a new feature branch that compares to the main branch and open a PR.
1. Ensure you have written appropriate tests and they are passing.
Expand All @@ -65,4 +87,4 @@ Update the `requirements.txt` file using the following command from the
main directory:
```bash
make lock
```
```
166 changes: 166 additions & 0 deletions cli/image_processing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
"""CLI to use the convolution examples"""
import argparse
import numpy as np
from PIL import Image

from computer_vision.image_processing import kernel, pipelines, reshaping
from plotter import MultiPlot


def buil_arg_parser():
"""Parses the user's arguments"""
parser = argparse.ArgumentParser(
description="Explore Image Processing techniques that use convolutions",
epilog="Built with <3 by Emmanuel Byrd at 8th Light Ltd.")
parser.add_argument(
"--source-path", metavar="./image.jpg", type=str,
required=True,
help="The read path of the input image (required)"
)
parser.add_argument(
"--destination-path", metavar="./image.jpg", type=str,
help="The write path of the output image"
)
parser.add_argument(
"--show", action=argparse.BooleanOptionalAction, type=bool,
help="Whether to show the resulting plot"
)
parser.add_argument(
"--color", action=argparse.BooleanOptionalAction, type=bool,
help="Whether to use all 3 channels from an image"
)
parser.add_argument(
"--example",
choices=['kernel', 'gauss', 'blur', 'opening', 'closing',
'inner_border', 'outer_border'],
help="Examples to choice from",
required=True,
)
parser.add_argument(
"--kernel",
choices=['top', 'bottom', 'left', 'right',
'top_sobel', 'bottom_sobel', 'left_sobel', 'right_sobel',
'sharpen', 'outline'],
help="The write path of the output image"
)
parser.add_argument(
"--gauss-sigma", metavar="1.", type=float, default=1.,
help="Sigma parameter of the Gaussian filter (default: 1.0)"
)
parser.add_argument(
"--gauss-size", metavar="5", type=int, default=5,
help="Size of the Gaussian filter (default: 5)"
)
return parser


color_agnostic_examples = [
"kernel", "gauss", "blur"
]

triple_plot_examples = [
"opening", "closing", "inner_border", "outer_border"
]


def draw_figures(args: argparse.Namespace, plotter: MultiPlot):
"""Show or save the generated figures"""

suptitle = "Example - " + args.example
if args.example in ["gauss", "blur"]:
suptitle += f" size:{args.gauss_size} sigma:{args.gauss_sigma}"
figure = plotter.draw(suptitle)

if args.show:
figure.show()
input("Press any key to continue...")

if args.destination_path:
print("Saving plot in " + args.destination_path)
figure.savefig(args.destination_path)


def output_color_agnostic(args: argparse.Namespace, input_img: np.ndarray):
"""Create the examples that are suitable for color and grayscale inputs"""
if args.example == "kernel":
kernel_choice = kernel.from_name(args.kernel)
output = pipelines.padded_convolution_same_kernel(
input_img, kernel_choice)
elif args.example == "gauss":
kernel_gauss = kernel.simple_gauss(args.gauss_size, args.gauss_sigma)
output = pipelines.padded_convolution_same_kernel(
input_img, kernel_gauss)
elif args.example == "blur":
output = pipelines.padded_blur(
input_img, args.gauss_size, args.gauss_sigma)
return output


def outputs_triple_plot_examples(
args: argparse.Namespace, input_img: np.ndarray):
"""Create the examples that produce three figures in the plot"""

if args.example == "opening":
return pipelines.opening(input_img)

if args.example == "closing":
return pipelines.closing(input_img)

if args.example == "inner_border":
return pipelines.inner_border(input_img)

# args.example == "outer_border"
return pipelines.outer_border(input_img)


def execute_color(args: argparse.Namespace):
"""Do the example in color"""
plotter = MultiPlot()
img = np.asarray(Image.open(args.source_path))
plotter.add_figure(img, "input")

img_reshaped = reshaping.channel_as_first_dimension(img)
output_reshaped = output_color_agnostic(args, img_reshaped)

output = reshaping.channel_as_last_dimension(output_reshaped)
plotter.add_figure(output.astype(int), "output")

draw_figures(args, plotter)


def execute_grayscale(args: argparse.Namespace):
"""Do the example in grayscale"""
plotter = MultiPlot()
img = np.asarray(Image.open(args.source_path).convert("L"))
plotter.add_figure(img, "input", "gray")

if args.example in color_agnostic_examples:
output = output_color_agnostic(args, img)
elif args.example in triple_plot_examples:
middlestep, output = outputs_triple_plot_examples(args, img)
plotter.add_figure(middlestep, "middle step", "gray")

plotter.add_figure(output, "output", "gray")

draw_figures(args, plotter)


def main():
"""Main function"""
arg_parser = buil_arg_parser()
args = arg_parser.parse_args()

if args.color and args.example not in color_agnostic_examples:
print("Color examples do not support " + args.example)
return

if args.color:
execute_color(args)
else:
execute_grayscale(args)

print("Finished.")


if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions src/computer_vision/cnn/fashion_mnist_classifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import matplotlib.pyplot as plt
import numpy as np


class FashionMNISTClassifier:
"""
Can load a trained model from memory or
Expand Down
1 change: 1 addition & 0 deletions src/computer_vision/cnn/fashion_mnist_classifier_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
# Make tests deterministic
tensorflow.random.set_seed(123)


def test_full_cycle():
"""
Tests that the entire flow can be executed without interruptions or failures
Expand Down
7 changes: 7 additions & 0 deletions src/computer_vision/image_processing/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""Tools and techniques to process images"""
from .convolution import *
from .kernel import *
from .padding import *
from .pipelines import *
from .pooling import *
from .reshaping import *
66 changes: 66 additions & 0 deletions src/computer_vision/image_processing/convolution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"""Convolution functions and helpers"""
import numpy as np


def _multiply_sum(background: np.ndarray, kernel: np.ndarray) -> float:
"""
Returns the sumation of multiplying each individual element
of the background with the given kernel
"""
assert background.shape == kernel.shape

return (background * kernel).sum()


def conv_repeat_all_chan(img: np.ndarray, kernel: np.ndarray) -> np.ndarray:
"""
Convolves each channel of the input with the same kernel and
returns their outputs as a separate channel each.
"""
output = []
for index in range(img.shape[0]):
output.append(convolution_1d(img[index], kernel))
return np.asarray(output)


def convolution_1d(image: np.ndarray, kernel: np.ndarray) -> np.ndarray:
"""
Executes a 1-stride convolution with the given kernel, over a 2D input.
The output shape will be (image_y - kernel_y + 1, image_x - kernel_x + 1)
See other external packages for complete convolution functionality.
"""
kernel_y, kernel_x = kernel.shape
image_y, image_x = image.shape

output_y = image_y - kernel_y + 1
output_x = image_x - kernel_x + 1

output = np.empty((output_y, output_x))
assert output.shape == (output_y, output_x)

for i in range(output_y):
for j in range(output_x):
output[i][j] = _multiply_sum(kernel,
image[i:i + kernel_y, j:j + kernel_x])

return output


def convolution(image: np.ndarray, kernel: np.ndarray) -> np.ndarray:
"""
Executes a 3D or 2D convolution over input as needed.
3D convolution requires a 3D filter, and creates a 2D output.
"""
if len(image.shape) == 3:
assert len(kernel.shape) == 3
assert image.shape[0] == kernel.shape[0]

channel_convs = []
for index in range(image.shape[0]):
channel_convs.append(convolution_1d(image[index], kernel[index]))

output = np.asarray(channel_convs).sum(axis=0)
else:
output = convolution_1d(image, kernel)

return output
Loading