Drawing a convolution with Tikz
This is a start with the Asymptote, using a module convolution.asy, which automatically calculates the output matrix
// convolutionTest.asy
//
// run
// asy convolutionTest.asy
// to get onvolutionTest.pdf
//
settings.tex="pdflatex";
import convolution;
real pagew=12cm,pageh=1.618*pagew;
size(pagew,pageh);
import fontsize;defaultpen(fontsize(10pt));
texpreamble("\usepackage{lmodern}"
+"\usepackage{amsmath}"
+"\usepackage{amsfonts}"
+"\usepackage{amssymb}"
);
Matrix I={
{0,1,1,1,0,0,0},
{0,0,1,1,1,0,0},
{0,0,0,1,1,1,0},
{0,0,0,1,1,0,0},
{0,0,1,1,0,0,0},
{0,1,1,0,0,0,0},
{1,1,0,0,0,0,0},
};
Matrix K={
{1,0,1},
{0,1,0},
{1,0,1},
};
picture pic1,pic2;
Convolution conv1=Convolution(pic1,I,K,0,3,"I","K","I*K",cellSize=40bp);
add(pic1);
Convolution conv2=Convolution(pic2,I,K,3,2,"I","K","I*K",cellSize=40bp);
add(shift(0,-350bp)*pic2);
The code of convolution.asy module:
// convolution.asy
typedef real[][] Matrix;
Matrix mblock(Matrix A, int row, int col, int width, int height=width){
int m=A.length;
int n=A[0].length;
assert(row>=0 && row<m);
assert(col>=0 && col<n);
assert(height>0 && row+height>=0 && row+height<=m);
assert(width>0 && col+width>=0 && col+width<=n);
Matrix Block;
for(int i=row;i<row+height;++i){
Block.push(A[i][col:col+width]);
}
return Block;
}
real dot(Matrix A, Matrix B){
real s=0;
assert(A.length==B.length && A[0].length==B[0].length );
for(int i=0;i<A.length;++i){
s+=dot(A[i],B[i]);
}
return s;
}
struct ConvolutionSkin{
pen gridPen1, gridPen2, gridPen3;
pen fillPen1, fillPen2, fillPen3;
pen framePen1, framePen2, framePen3;
pen xPen;
pen linePen1, linePen2;
void operator init(
pen gridPen1=defaultpen, pen gridPen2=defaultpen, pen gridPen3=defaultpen,
pen fillPen1, pen fillPen2, pen fillPen3,
pen framePen1, pen framePen2, pen framePen3,
pen xPen,
pen linePen1, pen linePen2
){
this.gridPen1=gridPen1;
this.gridPen2=gridPen2;
this.gridPen3=gridPen3;
this.fillPen1=fillPen1;
this.fillPen2=fillPen2;
this.fillPen3=fillPen3;
this.framePen1=framePen1;
this.framePen2=framePen2;
this.framePen3=framePen3;
this.xPen=xPen;
this.linePen1=linePen1;
this.linePen2=linePen2;
}
}
ConvolutionSkin defaultSkin
=ConvolutionSkin(
lightred, white, lightblue,
rgb(0.9,0.7,0.7), rgb(0.9,0.7,0.7), rgb(0.8,1.0,0.7),
red+0.8bp, blue+0.8bp, deepgreen+0.8bp,
rgb(0,0,0.9)+fontsize(6pt),
lightblue+0.7bp+linetype(new real[]{2,2})+linecap(0),
deepgreen+0.7bp+linetype(new real[]{2,2})+linecap(0)
);
struct Convolution{
picture pic;
ConvolutionSkin skin;
real cellSize;
Matrix A;
Matrix B;
Matrix AxB;
int el_i, el_j;
string nameA, nameB, nameAxB;
pair posA, posB, posAxB;
guide Abox, Bbox, AxBbox;
void calcAxB(){
int n=A.length;
int m=B.length;
for(int i=0;i<=n-m;++i){
AxB[i]=new real[];
for(int j=0;j<=n-m;++j){
AxB[i].push(dot(mblock(A,i,j,m),B));
}
}
}
void drawMatrix(Matrix M, pair pos, pen p=defaultpen){
int m=M.length;
int n=M[0].length;
for(int i=0;i<=m;++i){
draw(pic, (pos.x,pos.y+i*cellSize)--(pos.x+n*cellSize,pos.y+i*cellSize), p);
}
for(int j=0;j<=n;++j){
draw(pic, (pos.x+j*cellSize,pos.y)--(pos.x+j*cellSize,pos.y+m*cellSize), p);
}
for(int i=0;i<m;++i){
for(int j=0;j<n;++j){
label(pic, "$"+string(M[i][j])+"$",pos+cellSize*(j+1/2,m-i-1/2));
}
}
}
void drawStarEq(){
label(pic, "$*$",posB+cellSize*(-1/2,B.length/2));
label(pic, "$=$",posB+cellSize*(B[0].length+1/2,B.length/2));
}
void drawLines(guide G, pen p){
guide u,v;
u=box(min(G),max(G));
v=box(min(Bbox),max(Bbox));
for(int i=0;i<4;++i){
draw(pic,cut(point(u,i)--point(v,i),Bbox,0).before,p);
}
}
void drawNames(){
label(pic,"$"+nameA+"$", posA+ cellSize/2*(A.length,-1));
label(pic,"$"+nameB+"$", posB+ cellSize/2*(B.length,-1));
label(pic,"$"+nameAxB+"$",posAxB+cellSize/2*(AxB.length,-1));
}
void drawHelper(){
pair pos=(min(Abox).x,max(Abox).y)+cellSize*(1,-1);
for(int i=0;i<B.length;++i){
for(int j=0;j<B[0].length;++j){
label(pic,"$\scriptsize{\times "+string(B[i][j])+"}$"
,pos+cellSize*(j,-i),plain.NW,skin.xPen);
}
}
}
void doit(){
calcAxB();
posA=(0,0);
posB=cellSize*(A[0].length+1, (A.length-B.length)/2);
posAxB=cellSize*(A[0].length+B[0].length+2, (A.length-AxB.length)/2);
Abox=box(posA+cellSize*(el_j,A.length-el_i),posA+cellSize*(el_j+B[0].length,A.length-(el_i+B.length)));
Bbox=box(posB,posB+cellSize*(B[0].length,B.length));
AxBbox=box(posAxB+cellSize*(el_j,AxB.length-el_i)
,posAxB+cellSize*(el_j+1,AxB.length-(el_i+1)));
fill(pic,Abox,skin.fillPen1);
fill(pic,Bbox,skin.fillPen2);
fill(pic,AxBbox,skin.fillPen3);
drawMatrix(A, posA, skin.gridPen1);
drawMatrix(B, posB, skin.gridPen2);
drawMatrix(AxB,posAxB,skin.gridPen3);
draw(pic,Abox,skin.framePen1);
draw(pic,Bbox,skin.framePen2);
draw(pic,AxBbox,skin.framePen3);
drawStarEq();
drawLines(Abox, skin.linePen1);
drawLines(AxBbox,skin.linePen2);
drawNames();
drawHelper();
}
void operator init(
picture pic=currentpicture,
Matrix A, Matrix B,
int el_i=0, int el_j=0,
string nameA="A",
string nameB="B",
string nameAxB="A*B",
real cellSize,
ConvolutionSkin skin=defaultSkin
){
this.pic=pic;
this.A=A;
this.B=B;
this.el_i=el_i;
this.el_j=el_j;
this.nameA=nameA;
this.nameB=nameB;
this.nameAxB=nameAxB;
this.cellSize=cellSize;
this.skin=skin;
doit();
}
}
This is another solution using TikZ only.
It is more flexibel than Vinzza's solution because you can easily change which cells to highlight by redefining \rowResult
and \colResult
.
Also the contents of the matrix K are defined only once, inside of the command \K
so that changes are easily possible.
\K{1}{1}
returns the content of the top left cell of K, \K{\numRowsK}{\numColsK}
returns the content of the bottom right cell of K.
\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{matrix}
\usetikzlibrary{positioning}
\usetikzlibrary{backgrounds}
\newcommand\numRowsK{3}
\newcommand\numColsK{3}
\newcommand{\K}[2]{% #1: row, #2: col
\edef\Kcol##1##2##3{###2}%
\edef\Krow##1##2##3{\noexpand\Kcol###1}%
\Krow
{1 0 1}
{0 1 0}
{1 0 1}%
}
\begin{document}
\begin{tikzpicture}
% ------- style -------
\tikzset{%
parenthesized/.style={%
left delimiter = (,
right delimiter = ),
},
node distance = 10mu,
}
% ------- equation -------
\matrix[matrix of math nodes, parenthesized] (I) {
0 & 1 & 1 & 1 & 0 & 0 & 0 \\
0 & 0 & 1 & 1 & 1 & 0 & 0 \\
0 & 0 & 0 & 1 & 1 & 1 & 0 \\
0 & 0 & 0 & 1 & 1 & 0 & 0 \\
0 & 0 & 1 & 1 & 0 & 0 & 0 \\
0 & 1 & 1 & 0 & 0 & 0 & 0 \\
1 & 1 & 0 & 0 & 0 & 0 & 0 \\
};
\node (*) [right = of I] {${}*{}$};
\newcommand\Kmatrix{}
\foreach \row in {1, ..., 3} {
\gdef \sep {}
\foreach \col in {1, ..., 3} {%
\xdef \Kmatrix {\unexpanded\expandafter{\Kmatrix}\unexpanded\expandafter{\sep}\noexpand \K{\row}{\col}}
\gdef \sep { \& }
}
\xdef \Kmatrix {\unexpanded\expandafter{\Kmatrix}\noexpand\\}
}
\matrix[matrix of math nodes, parenthesized, ampersand replacement=\&] (K) [right = of *] {
\Kmatrix
};
\node (=) [right = of K] {${}={}$};
\matrix[matrix of math nodes, parenthesized] (I*K) [right = of {=}] {
1 & 4 & 3 & 4 & 1 \\
1 & 2 & 4 & 3 & 3 \\
1 & 2 & 3 & 4 & 1 \\
1 & 3 & 3 & 1 & 1 \\
3 & 3 & 1 & 1 & 0 \\
};
% ------- highlighting -------
\newcommand\rowResult{1}
\newcommand\colResult{4}
\begin{scope}[on background layer]
\newcommand{\padding}{2pt}
\coordinate (Is-nw) at ([xshift=-\padding, yshift=+\padding] I-\rowResult-\colResult.north west);
\coordinate (Is-se) at ([xshift=+\padding, yshift=-\padding] I-\the\numexpr\rowResult+\numRowsK-1\relax-\the\numexpr\colResult+\numColsK-1\relax.south east);
\coordinate (Is-sw) at (Is-nw |- Is-se);
\coordinate (Is-ne) at (Is-se |- Is-nw);
\filldraw[red, fill opacity=.1] (Is-nw) rectangle (Is-se);
\filldraw[green, fill opacity=.1] (I*K-\rowResult-\colResult.north west) rectangle (I*K-\rowResult-\colResult.south east);
\draw[blue, dotted]
(Is-nw) -- (K.north west)
(Is-se) -- (K.south east)
(Is-sw) -- (K.south west)
(Is-ne) -- (K.north east)
;
\draw[green, dotted]
(I*K-\rowResult-\colResult.north west) -- (K.north west)
(I*K-\rowResult-\colResult.south east) -- (K.south east)
(I*K-\rowResult-\colResult.south west) -- (K.south west)
(I*K-\rowResult-\colResult.north east) -- (K.north east)
;
\draw[blue, fill=blue!10!white] (K.north west) rectangle (K.south east);
\foreach \row [evaluate=\row as \rowI using int(\row+\rowResult-1)] in {1, ..., \numRowsK} {%
\foreach \col [evaluate=\col as \colI using int(\col+\colResult-1)] in {1, ..., \numColsK} {%
\node[text=blue] at (I-\rowI-\colI.south east) [xshift=-.3em] {\tiny$\times \K{\row}{\col}$};
}
}
\end{scope}
% ------- labels -------
\tikzset{node distance=0em}
\node[below=of I] (I-label) {$I$};
\node at (K |- I-label) {$K$};
\node at (I*K |- I-label) {$I*K$};
\end{tikzpicture}%
\end{document}
If you want to show this for several different cells you can put the tikzpicture in a command and call it several times:
\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{matrix}
\usetikzlibrary{positioning}
\usetikzlibrary{backgrounds}
\newcommand\numRowsK{3}
\newcommand\numColsK{3}
\newcommand{\K}[2]{% #1: row, #2: col
\edef\Kcol##1##2##3{###2}%
\edef\Krow##1##2##3{\noexpand\Kcol###1}%
\Krow
{1 0 1}
{0 1 0}
{1 0 1}%
}
\newcommand{\convoutionpicture}[2]{% #1: row to be highlighted, #2: colum to be highlighted
\begin{tikzpicture}
% ------- style -------
\tikzset{%
parenthesized/.style={%
left delimiter = (,
right delimiter = ),
},
node distance = 10mu,
}
% ------- equation -------
\matrix[matrix of math nodes, parenthesized, ampersand replacement=\&] (I) {
0 \& 1 \& 1 \& 1 \& 0 \& 0 \& 0 \\
0 \& 0 \& 1 \& 1 \& 1 \& 0 \& 0 \\
0 \& 0 \& 0 \& 1 \& 1 \& 1 \& 0 \\
0 \& 0 \& 0 \& 1 \& 1 \& 0 \& 0 \\
0 \& 0 \& 1 \& 1 \& 0 \& 0 \& 0 \\
0 \& 1 \& 1 \& 0 \& 0 \& 0 \& 0 \\
1 \& 1 \& 0 \& 0 \& 0 \& 0 \& 0 \\
};
\node (*) [right = of I] {${}*{}$};
\def\Kmatrix{}
\foreach \row in {1, ..., 3} {
\gdef \sep {}
\foreach \col in {1, ..., 3} {%
\xdef \Kmatrix {\unexpanded\expandafter{\Kmatrix}\unexpanded\expandafter{\sep}\noexpand \K{\row}{\col}}
\gdef \sep { \& }
}
\xdef \Kmatrix {\unexpanded\expandafter{\Kmatrix}\noexpand\\}
}
\matrix[matrix of math nodes, parenthesized, ampersand replacement=\&] (K) [right = of *] {
\Kmatrix
};
\node (=) [right = of K] {${}={}$};
\matrix[matrix of math nodes, parenthesized, ampersand replacement=\&] (I*K) [right = of {=}] {
1 \& 4 \& 3 \& 4 \& 1 \\
1 \& 2 \& 4 \& 3 \& 3 \\
1 \& 2 \& 3 \& 4 \& 1 \\
1 \& 3 \& 3 \& 1 \& 1 \\
3 \& 3 \& 1 \& 1 \& 0 \\
};
% ------- highlighting -------
\def\rowResult{#1}
\def\colResult{#2}
\begin{scope}[on background layer]
\newcommand{\padding}{2pt}
\coordinate (Is-nw) at ([xshift=-\padding, yshift=+\padding] I-\rowResult-\colResult.north west);
\coordinate (Is-se) at ([xshift=+\padding, yshift=-\padding] I-\the\numexpr\rowResult+\numRowsK-1\relax-\the\numexpr\colResult+\numColsK-1\relax.south east);
\coordinate (Is-sw) at (Is-nw |- Is-se);
\coordinate (Is-ne) at (Is-se |- Is-nw);
\filldraw[red, fill opacity=.1] (Is-nw) rectangle (Is-se);
\filldraw[green, fill opacity=.1] (I*K-\rowResult-\colResult.north west) rectangle (I*K-\rowResult-\colResult.south east);
\draw[blue, dotted]
(Is-nw) -- (K.north west)
(Is-se) -- (K.south east)
(Is-sw) -- (K.south west)
(Is-ne) -- (K.north east)
;
\draw[green, dotted]
(I*K-\rowResult-\colResult.north west) -- (K.north west)
(I*K-\rowResult-\colResult.south east) -- (K.south east)
(I*K-\rowResult-\colResult.south west) -- (K.south west)
(I*K-\rowResult-\colResult.north east) -- (K.north east)
;
\draw[blue, fill=blue!10!white] (K.north west) rectangle (K.south east);
\foreach \row [evaluate=\row as \rowI using int(\row+\rowResult-1)] in {1, ..., \numRowsK} {%
\foreach \col [evaluate=\col as \colI using int(\col+\colResult-1)] in {1, ..., \numColsK} {%
\node[text=blue] at (I-\rowI-\colI.south east) [xshift=-.3em] {\tiny$\times \K{\row}{\col}$};
}
}
\end{scope}
% ------- labels -------
\tikzset{node distance=0em}
\node[below=of I] (I-label) {$I$};
\node at (K |- I-label) {$K$};
\node at (I*K |- I-label) {$I*K$};
\end{tikzpicture}%
}
\begin{document}
\convoutionpicture 14
\bigskip
\convoutionpicture 41
\bigskip
\convoutionpicture 55
\end{document}
You could draw it entirely with Tikz, but it might be a painfull work to do. Instead, you can draw some Tikz lines over some LaTeX maths. (Note that, since you would use LaTeX matrices, you will lose the grid representation)
Here,
\documentclass[a4paper]{article}
\usepackage{tikz}
%%%%%%%%%%%%%%%%%%% Local functions %%%%%%%%%%%%%%%%%%%
%% -- Draw marks
\newbox\dumbox
\newcommand{\mymark}[2]{%
\setbox\dumbox=\hbox{#2}%
\hbox to \wd\dumbox{\hss%
\tikz[overlay,remember picture,baseline=(#1.base)]{ \node (#1) {\box\dumbox}; }%
\hss}%
}
%% -- Draw small coefficient
\newcommand{\mysmall}[1]{%
\tikz[overlay,remember picture]{%
\node[blue,scale=.5, shift={(0,-.1)}] {x#1};%
}%
}
%%%%%%%%%%%%%%%%%%% Local functions %%%%%%%%%%%%%%%%%%%
\begin{document}
\[
\left(\begin{array}{ccccccc}
0 & 1 & 1 & \mymark{TL1}{1}\mysmall{1} & 0\mysmall{0} & \mymark{TR1}{0}\mysmall{1} & 0\\
0 & 0 & 1 & 1\mysmall{0} & 1\mysmall{1} & 0\mysmall{0} & 0\\
0 & 0 & 0 & \mymark{BL1}{1}\mysmall{1} & 1\mysmall{0} & \mymark{BR1}{1}\mysmall{1} & 0\\
0 & 0 & 0 & 1 & 1 & 0 & 0\\
0 & 0 & 1 & 1 & 0 & 0 & 0\\
0 & 1 & 1 & 0 & 0 & 0 & 0\\
1 & 1 & 0 & 0 & 0 & 0 & 0\\
\end{array}\right)
*
\left(\begin{array}{ccc}
\mymark{TL2}{1} & 0 & \mymark{TR2}{1}\\
0 & 1 & 0 \\
\mymark{BL2}{1} & 0 & \mymark{BR2}{1}
\end{array}\right)
=
\left(\begin{array}{ccccccc}
1 & 4 & 3 & \mymark{C}{4} & 1\\
1 & 2 & 4 & 3 & 3\\
1 & 2 & 3 & 4 & 1\\
1 & 3 & 3 & 1 & 1\\
3 & 3 & 1 & 1 & 0
\end{array}\right)
\]
\begin{tikzpicture}[overlay, remember picture,
myedge1/.style={dashed, opacity=.3, blue},
myedge2/.style={dashed, opacity=.3, green!40!black}]
%% Draw boxes
\draw[red, fill=red, fill opacity=.1] (TL1.north west) rectangle (BR1.south east);
\draw[blue, fill=blue, fill opacity=.1] (TL2.north west) rectangle (BR2.south east);
\draw[green!60!black, fill=green, fill opacity=.1] (C.north west) rectangle (C.south east);
%% Draw blue lines
\draw[myedge1] (TL1.north west) -- (TL2.north west);
\draw[myedge1] (BL1.south west) -- (BL2.south west);
\draw[myedge1] (TR1.north east) -- (TR2.north east);
\draw[myedge1] (BR1.south east) -- (BR2.south east);
%% Draw green lines
\draw[myedge2] (TL2.north west) -- (C.north west);
\draw[myedge2] (BL2.south west) -- (C.south west);
\draw[myedge2] (TR2.north east) -- (C.north east);
\draw[myedge2] (BR2.south east) -- (C.south east);
\end{tikzpicture}
\end{document}
gives
The main part here is the \mymark
function, which allows you to use some parts of your document as nodes in a Tikzpicture.