Skip to content
This repository has been archived by the owner on Jun 8, 2023. It is now read-only.

Generalization #16

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
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
234 changes: 141 additions & 93 deletions bin/produce_figure
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,32 @@ numpy.random.seed(1234)


def make_numerical_tex_string(step, input_size, output_size, padding,
kernel_size, stride, dilation, mode):
kernel_size, stride, dilation, mode,
input_color, output_color):
"""Creates a LaTeX string for a numerical convolution animation.

Parameters
----------
step : int
Which step of the animation to generate the LaTeX string for.
input_size : int
Convolution input size.
output_size : int
Convolution output size.
padding : int
Zero padding.
kernel_size : int
Convolution kernel size.
stride : int
Convolution stride.
input_size : ndarray
Convolution input size as numpy array of shape (2,), and dtype int.
output_size : ndarray
Convolution output size as numpy array of shape (2,), and dtype int.
padding : ndarray
Zero padding as numpy array of shape (2,), and dtype int.
kernel_size : ndarray
Convolution kernel size as numpy array of shape (2,), and dtype int.
stride : ndarray
Convolution stride as numpy array of shape (2,), and dtype int.
dilation: ndarray
Input Dilation as numpy array of shape (2,), and dtype int.
mode : str
Kernel mode, one of {'convolution', 'average', 'max'}.
input_color: (int, int, int)
Color of input (as RGB tuple, 0-255)
output_color: (int, int, int)
Color of output (as RGB tuple, 0-255)

Returns
-------
Expand All @@ -42,9 +49,9 @@ def make_numerical_tex_string(step, input_size, output_size, padding,
if mode not in ('convolution', 'average', 'max'):
raise ValueError("wrong convolution mode, choices are 'convolution', "
"'average' or 'max'")
if dilation != 1:
if (dilation != 1).any():
raise ValueError("Only a dilation of 1 is currently supported for numerical output")
max_steps = output_size ** 2
max_steps = numpy.prod(output_size)
if step >= max_steps:
raise ValueError('step {} out of bounds (there are '.format(step) +
'{} steps for this animation'.format(max_steps))
Expand All @@ -54,92 +61,98 @@ def make_numerical_tex_string(step, input_size, output_size, padding,

total_input_size = input_size + 2 * padding

input_ = numpy.zeros((total_input_size, total_input_size), dtype='int32')
input_[padding: padding + input_size,
padding: padding + input_size] = numpy.random.randint(
low=0, high=4, size=(input_size, input_size))
input_ = numpy.zeros(total_input_size, dtype='int32')
input_[padding[0]: padding[0] + input_size[0],
padding[1]: padding[1] + input_size[1]] = numpy.random.randint(
low=0, high=4, size=input_size)
kernel = numpy.random.randint(
low=0, high=3, size=(kernel_size, kernel_size))
output = numpy.empty((output_size, output_size), dtype='float32')
for offset_x, offset_y in itertools.product(range(output_size),
range(output_size)):
low=0, high=3, size=kernel_size)
output = numpy.empty(output_size, dtype='float32')
for offset_x, offset_y in itertools.product(range(output_size[0]),
range(output_size[1])):
if mode == 'convolution':
output[offset_x, offset_y] = (
input_[stride * offset_x: stride * offset_x + kernel_size,
stride * offset_y: stride * offset_y + kernel_size] * kernel).sum()
input_[stride[0] * offset_x: stride[0] * offset_x + kernel_size[0],
stride[1] * offset_y: stride[1] * offset_y + kernel_size[1]] * kernel).sum()
elif mode == 'average':
output[offset_x, offset_y] = (
input_[stride * offset_x: stride * offset_x + kernel_size,
stride * offset_y: stride * offset_y + kernel_size]).mean()
input_[stride[0] * offset_x: stride[0] * offset_x + kernel_size[0],
stride[1] * offset_y: stride[1] * offset_y + kernel_size[1]]).mean()
else:
output[offset_x, offset_y] = (
input_[stride * offset_x: stride * offset_x + kernel_size,
stride * offset_y: stride * offset_y + kernel_size]).max()
input_[stride[0] * offset_x: stride[0] * offset_x + kernel_size[0],
stride[1] * offset_y: stride[1] * offset_y + kernel_size[1]]).max()

offsets = list(itertools.product(range(output_size - 1, -1, -1),
range(output_size)))
offsets = list(itertools.product(reversed(range(output_size[1])), range(output_size[0])))
offset_y, offset_x = offsets[step]

if mode == 'convolution':
kernel_values_string = ''.join(
"\\node (node) at ({0},{1}) {{\\tiny {2}}};\n".format(
i + 0.8 + stride * offset_x, j + 0.2 + stride * offset_y,
kernel[kernel_size - 1 - j, i])
for i, j in itertools.product(range(kernel_size),
range(kernel_size)))
i + 0.8 + stride[0] * offset_x, j + 0.2 + stride[1] * offset_y,
kernel[kernel_size[0] - 1 - j, i])
for i, j in itertools.product(range(kernel_size[1]),
range(kernel_size[0])))
else:
kernel_values_string = '\n'

return six.b(tex_template.format(**{
'PADDING_TO': '{0},{0}'.format(total_input_size),
'INPUT_FROM': '{0},{0}'.format(padding),
'INPUT_TO': '{0},{0}'.format(padding + input_size),
'PADDING_TO': '{},{}'.format(*total_input_size),
'INPUT_FROM': '{},{}'.format(*padding),
'INPUT_TO': '{},{}'.format(*(padding + input_size)),
'INPUT_VALUES': ''.join(
"\\node (node) at ({0},{1}) {{\\footnotesize {2}}};\n".format(
i + 0.5, j + 0.5, input_[total_input_size - 1 - j, i])
for i, j in itertools.product(range(total_input_size),
range(total_input_size))),
'INPUT_GRID_FROM': '{},{}'.format(stride * offset_x,
stride * offset_y),
'INPUT_GRID_TO': '{},{}'.format(stride * offset_x + kernel_size,
stride * offset_y + kernel_size),
i + 0.5, j + 0.5, input_[total_input_size[0] - 1 - j, i])
for i, j in itertools.product(range(total_input_size[0]),
range(total_input_size[1]))),
'INPUT_GRID_FROM': '{},{}'.format(stride[0] * offset_x,
stride[1] * offset_y),
'INPUT_GRID_TO': '{},{}'.format(stride[0] * offset_x + kernel_size[0],
stride[1] * offset_y + kernel_size[1]),
'KERNEL_VALUES': kernel_values_string,
'OUTPUT_TO': '{0},{0}'.format(output_size),
'OUTPUT_TO': '{},{}'.format(*output_size),
'OUTPUT_GRID_FROM': '{},{}'.format(offset_x, offset_y),
'OUTPUT_GRID_TO': '{},{}'.format(offset_x + 1, offset_y + 1),
'OUTPUT_VALUES': ''.join(
"\\node (node) at ({0},{1}) {{\\tiny {2:.1f}}};\n".format(
i + 0.5, j + 0.5, output[output_size - 1 - j, i])
for i, j in itertools.product(range(output_size),
range(output_size))),
'XSHIFT': '{}cm'.format(total_input_size + 1),
'YSHIFT': '{}cm'.format((total_input_size - output_size) // 2),
i + 0.5, j + 0.5, output[output_size[0] - 1 - j, i])
for i, j in itertools.product(range(output_size[0]),
range(output_size[1]))),
'XSHIFT': '{}cm'.format(total_input_size[0] + 1),
'YSHIFT': '{}cm'.format((total_input_size[1] - output_size[1]) // 2),
'INPUT_COLOR': '{},{},{}'.format(*input_color),
'OUTPUT_COLOR': '{},{},{}'.format(*output_color),
}))


def make_arithmetic_tex_string(step, input_size, output_size, padding,
kernel_size, stride, dilation, transposed):
kernel_size, stride, dilation, transposed,
input_color, output_color):
"""Creates a LaTeX string for a convolution arithmetic animation.

Parameters
----------
step : int
Which step of the animation to generate the LaTeX string for.
input_size : int
Convolution input size.
output_size : int
Convolution output size.
padding : int
Zero padding.
kernel_size : int
Convolution kernel size.
stride : int
Convolution stride.
dilation: int
Input Dilation
input_size : ndarray
Convolution input size as numpy array of shape (2,), and dtype int.
output_size : ndarray
Convolution output size as numpy array of shape (2,), and dtype int.
padding : ndarray
Zero padding as numpy array of shape (2,), and dtype int.
kernel_size : ndarray
Convolution kernel size as numpy array of shape (2,), and dtype int.
stride : ndarray
Convolution stride as numpy array of shape (2,), and dtype int.
dilation: ndarray
Input Dilation as numpy array of shape (2,), and dtype int.
transposed : bool
If ``True``, generate strings for the transposed convolution
animation.
input_color: (int, int, int)
Color of input (as RGB tuple, 0-255)
output_color: (int, int, int)
Color of output (as RGB tuple, 0-255)

Returns
-------
Expand All @@ -154,18 +167,18 @@ def make_arithmetic_tex_string(step, input_size, output_size, padding,
bottom_pad = (input_size + 2 * padding - kernel_size) % stride

input_size, output_size, padding, spacing, stride = (
output_size, input_size, kernel_size - 1 - padding, stride, 1)
output_size, input_size, kernel_size - 1 - padding, stride, numpy.ones(shape=(2,), dtype=numpy.int32))
total_input_size = output_size + kernel_size - 1
y_adjustment = 0
else:
# Not used in convolutions
bottom_pad = 0
bottom_pad = numpy.zeros(shape=(2,), dtype=numpy.int32)

spacing = 1
spacing = numpy.ones(shape=(2,), dtype=numpy.int32)
total_input_size = input_size + 2 * padding
y_adjustment = (total_input_size - (kernel_size - stride)) % stride
y_adjustment = ((total_input_size - (kernel_size - stride)) % stride).max()

max_steps = output_size ** 2
max_steps = numpy.prod(output_size)
if step >= max_steps:
raise ValueError('step {} out of bounds (there are '.format(step) +
'{} steps for this animation'.format(max_steps))
Expand All @@ -175,39 +188,45 @@ def make_arithmetic_tex_string(step, input_size, output_size, padding,
with open(os.path.join('templates', 'unit.txt'), 'r') as f:
unit_template = f.read()

offsets = list(itertools.product(range(output_size - 1, -1, -1),
range(output_size)))
offsets = list(itertools.product(reversed(range(output_size[1])), range(output_size[0])))
offset_y, offset_x = offsets[step]

return six.b(tex_template.format(**{
'PADDING_TO': '{0},{0}'.format(total_input_size),
'PADDING_TO': '{},{}'.format(*total_input_size),
'INPUT_UNITS': ''.join(
unit_template.format(padding + spacing * i,
bottom_pad + padding + spacing * j,
padding + spacing * i + 1,
bottom_pad + padding + spacing * j + 1)
for i, j in itertools.product(range(input_size),
range(input_size))),
unit_template.format(padding[0] + spacing[0] * i,
bottom_pad[1] + padding[1] + spacing[1] * j,
padding[0] + spacing[0] * i + 1,
bottom_pad[1] + padding[1] + spacing[1] * j + 1)
for i, j in itertools.product(range(input_size[0]),
range(input_size[1]))),
'INPUT_GRID_FROM_X': '{}'.format(
stride * offset_x),
stride[0] * offset_x),
'INPUT_GRID_FROM_Y': '{}'.format(
y_adjustment + stride * offset_y),
y_adjustment + stride[1] * offset_y),
'INPUT_GRID_TO_X': '{}'.format(
stride * offset_x + kernel_size),
stride[0] * offset_x + kernel_size[0]),
'INPUT_GRID_TO_Y': '{}'.format(
y_adjustment + stride * offset_y + kernel_size),
'DILATION': '{}'.format(dilation),
y_adjustment + stride[1] * offset_y + kernel_size[1]),
'DILATION_X': '{}'.format(dilation[0]),
'DILATION_Y': '{}'.format(dilation[1]),
'OUTPUT_BOTTOM_LEFT': '{},{}'.format(offset_x, offset_y),
'OUTPUT_BOTTOM_RIGHT': '{},{}'.format(offset_x + 1, offset_y),
'OUTPUT_TOP_LEFT': '{},{}'.format(offset_x, offset_y + 1),
'OUTPUT_TOP_RIGHT': '{},{}'.format(offset_x + 1, offset_y + 1),
'OUTPUT_TO': '{0},{0}'.format(output_size),
'OUTPUT_TO': '{},{}'.format(*output_size),
'OUTPUT_GRID_FROM': '{},{}'.format(offset_x, offset_y),
'OUTPUT_GRID_TO': '{},{}'.format(offset_x + 1, offset_y + 1),
'OUTPUT_ELEVATION': '{}cm'.format(total_input_size + 1),
'OUTPUT_ELEVATION': '{}cm'.format(total_input_size[1] + 1),
'INPUT_COLOR': '{},{},{}'.format(*input_color),
'OUTPUT_COLOR': '{},{},{}'.format(*output_color),
}))


def compute_output_size(input_size, padding, kernel_size, stride, dilation, **ign_kwargs):
return (input_size + 2 * padding - kernel_size - (kernel_size - 1) * (dilation - 1)) // stride + 1


def compile_figure(which_, name, step, **kwargs):
if which_ == 'arithmetic':
tex_string = make_arithmetic_tex_string(step, **kwargs)
Expand All @@ -220,13 +239,20 @@ def compile_figure(which_, name, step, **kwargs):
stderr=subprocess.PIPE)
stdoutdata, stderrdata = p.communicate(input=tex_string)
# Remove logs and aux if compilation was successfull
if '! LaTeX Error' in stdoutdata or '! Emergency stop' in stdoutdata:
if '! LaTeX Error' in str(stdoutdata) or '! Emergency stop' in str(stdoutdata):
print('! LaTeX Error: check the log file in pdf/{}.log'.format(jobname))
else:
subprocess.call(['rm'] + glob('pdf/{}.aux'.format(jobname)) +
glob('pdf/{}.log'.format(jobname)))


def normalize_tuple(arg_value):
arg_value = numpy.asarray(arg_value).squeeze()
if arg_value.ndim < 1 or arg_value.shape == (1,):
arg_value = numpy.stack([arg_value, arg_value])
return arg_value


if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Compile a LaTeX figure as part of a convolution "
Expand All @@ -236,19 +262,23 @@ if __name__ == "__main__":

parent_parser = argparse.ArgumentParser(add_help=False)
parent_parser.add_argument("name", type=str, help="name for the animation")
parent_parser.add_argument("step", type=int, help="animation step")
parent_parser.add_argument("step", type=int, help="animation step (-1 for all)")
parent_parser.add_argument("-i", "--input-size", type=int, default=5,
help="input size")
parent_parser.add_argument("-o", "--output-size", type=int, default=3,
help="output size")
help="input size", nargs='+')
parent_parser.add_argument("-o", "--output-size", type=int, default=None,
help="output size", nargs='+')
parent_parser.add_argument("-p", "--padding", type=int, default=0,
help="zero padding")
help="zero padding", nargs='+')
parent_parser.add_argument("-k", "--kernel-size", type=int, default=3,
help="kernel size")
help="kernel size", nargs='+')
parent_parser.add_argument("-s", "--stride", type=int, default=1,
help="stride")
help="stride", nargs='+')
parent_parser.add_argument("-d", "--dilation", type=int, default=1,
help="dilation")
help="dilation", nargs='+')
parent_parser.add_argument("--input_color", type=int, nargs=3,
default=[38, 139, 210], help="input color (RGB)")
parent_parser.add_argument("--output_color", type=int, nargs=3,
default=[42, 161, 152], help="output color (RGB)")

subparser = subparsers.add_parser('arithmetic', parents=[parent_parser],
help='convolution arithmetic animation')
Expand All @@ -269,4 +299,22 @@ if __name__ == "__main__":
name = args_dict.pop('name')
step = args_dict.pop('step')

compile_figure(which_, name, step, **args_dict)
# Normalize arguments allowing tuples to numpy arrays of shape (2,)
for arg_name in ['input_size', 'padding', 'kernel_size', 'stride', 'dilation']:
args_dict[arg_name] = normalize_tuple(args_dict[arg_name])

# Automatic output shape computation?
if args_dict['output_size'] is None:
args_dict['output_size'] = compute_output_size(**args_dict)
else:
args_dict['output_size'] = normalize_tuple(args_dict['output_size'])

if step < 0:
if args_dict['transposed']:
max_step = numpy.prod(args_dict['input_size'])
else:
max_step = numpy.prod(args_dict['output_size'])
for step in range(int(max_step)):
compile_figure(which_, name, step, **args_dict)
else:
compile_figure(which_, name, step, **args_dict)
10 changes: 5 additions & 5 deletions templates/arithmetic_figure.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
\documentclass[class=minimal,border=10pt]{{standalone}}
\usepackage{{tikz}}
\usepackage{{xcolor}}
\definecolor{{blue}}{{RGB}}{{38,139,210}}
\definecolor{{cyan}}{{RGB}}{{42,161,152}}
\definecolor{{input_color}}{{RGB}}{{{INPUT_COLOR}}}
\definecolor{{output_color}}{{RGB}}{{{OUTPUT_COLOR}}}
\definecolor{{base01}}{{RGB}}{{88,110,117}}
\definecolor{{base02}}{{RGB}}{{7,54,66}}
\definecolor{{base03}}{{RGB}}{{0,43,54}}
Expand All @@ -14,8 +14,8 @@
\draw[step=10mm, base03, dashed, thick] (0,0) grid ({PADDING_TO});
{INPUT_UNITS}

\foreach \x in {{ {INPUT_GRID_FROM_X},\number\numexpr {INPUT_GRID_FROM_X}+{DILATION},...,\number\numexpr {INPUT_GRID_TO_X}-1 }} {{
\foreach \y in {{ {INPUT_GRID_FROM_Y},\number\numexpr {INPUT_GRID_FROM_Y}+{DILATION},...,\number\numexpr {INPUT_GRID_TO_Y}-1 }} {{
\foreach \x in {{ {INPUT_GRID_FROM_X},\number\numexpr {INPUT_GRID_FROM_X}+{DILATION_X},...,\number\numexpr {INPUT_GRID_TO_X}-1 }} {{
\foreach \y in {{ {INPUT_GRID_FROM_Y},\number\numexpr {INPUT_GRID_FROM_Y}+{DILATION_Y},...,\number\numexpr {INPUT_GRID_TO_Y}-1 }} {{
\draw[fill=base02, opacity=0.4] (\x,\y) rectangle
(\x+1,\y+1);
}}
Expand All @@ -32,7 +32,7 @@
yslant=0.5,xslant=-0.7]
\draw (BL) -- ({OUTPUT_BOTTOM_LEFT}) (BR) -- ({OUTPUT_BOTTOM_RIGHT})
(TL) -- ({OUTPUT_TOP_LEFT}) (TR) -- ({OUTPUT_TOP_RIGHT});
\draw[fill=cyan] (0,0) rectangle ({OUTPUT_TO});
\draw[fill=output_color] (0,0) rectangle ({OUTPUT_TO});
\draw[step=10mm, base03, thick] (0,0) grid ({OUTPUT_TO});
\draw[fill=base02, opacity=0.4] ({OUTPUT_GRID_FROM}) rectangle
({OUTPUT_GRID_TO});
Expand Down
Loading