diff --git a/.gitignore b/.gitignore index 7280af7..9c18754 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,85 @@ *.aux *.log *.gz + +__pycache__ +books +project + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ \ No newline at end of file diff --git a/README.md b/README.md index 36d3bd5..b0a04af 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,51 @@ # PlotNeuralNet [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.2526396.svg)](https://doi.org/10.5281/zenodo.2526396) -Latex code for drawing neural networks for reports and presentation. Have a look into examples to see how they are made. Additionally, lets consolidate any improvements that you make and fix any bugs to help more people with this code. +Latex code for drawing neural networks for reports and presentation. Have a look into examples to see how they are made. Additionally, lets consolidate any improvements that you make and fix any bugs to help more people with this code. + +## TODO + +- [X] Python interfaz +- [ ] Generate Pytorch graph architecture +- [ ] Add easy legend functionality +- [ ] Add more layer shapes like TruncatedPyramid, 2DSheet etc + +## Usage + + mkdir my_project + cd my_project + vim my_arch.py + + import sys + sys.path.append('../') + from core.tikzeng import * + + # defined your arch + arch = [ + to_head( '..' ), + to_cor(), + to_begin(), + to_Conv("conv1", 512, 64, offset="(0,0,0)", to="(0,0,0)", height=64, depth=64, width=2 ), + to_Pool("pool1", offset="(0,0,0)", to="(conv1-east)"), + to_Conv("conv2", 128, 64, offset="(1,0,0)", to="(pool1-east)", height=32, depth=32, width=2 ), + to_connection( "pool1", "conv2"), + to_Pool("pool2", offset="(0,0,0)", to="(conv2-east)", height=28, depth=28, width=1), + to_SoftMax("soft1", 10 ,"(3,0,0)", "(pool1-east)", caption="SOFT" ), + to_connection("pool2", "soft1"), + to_end() + ] + + def main(): + namefile = str(sys.argv[0]).split('.')[0] + to_generate(arch, namefile + '.tex' ) + + if __name__ == '__main__': + main() + + bash ../tikzmake.sh my_arch + +## Examples + Following are some network representations:

diff --git a/core/__init__.py b/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/blocks.py b/core/blocks.py new file mode 100644 index 0000000..82c2c2a --- /dev/null +++ b/core/blocks.py @@ -0,0 +1,75 @@ + +from .tikzeng import * + +#define new block +def block_2ConvPool( name, botton, top, s_filer=256, n_filer=64, offset="(1,0,0)", size=(32,32,3.5), opacity=0.5 ): + return [ + to_ConvConvRelu( + name="ccr_{}".format( name ), + s_filer=str(s_filer), + n_filer=(n_filer,n_filer), + offset=offset, + to="({}-east)".format( botton ), + width=(size[2],size[2]), + height=size[0], + depth=size[1], + ), + to_Pool( + name="{}".format( top ), + offset="(0,0,0)", + to="(ccr_{}-east)".format( name ), + width=1, + height=size[0] - int(size[0]/4), + depth=size[1] - int(size[0]/4), + opacity=opacity, ), + to_connection( + "{}".format( botton ), + "ccr_{}".format( name ) + ) + ] + + +def block_Unconv( name, botton, top, s_filer=256, n_filer=64, offset="(1,0,0)", size=(32,32,3.5), opacity=0.5 ): + return [ + to_UnPool( name='unpool_{}'.format(name), offset=offset, to="({}-east)".format(botton), width=1, height=size[0], depth=size[1], opacity=opacity ), + to_ConvRes( name='ccr_res_{}'.format(name), offset="(0,0,0)", to="(unpool_{}-east)".format(name), s_filer=str(s_filer), n_filer=str(n_filer), width=size[2], height=size[0], depth=size[1], opacity=opacity ), + to_Conv( name='ccr_{}'.format(name), offset="(0,0,0)", to="(ccr_res_{}-east)".format(name), s_filer=str(s_filer), n_filer=str(n_filer), width=size[2], height=size[0], depth=size[1] ), + to_ConvRes( name='ccr_res_c_{}'.format(name), offset="(0,0,0)", to="(ccr_{}-east)".format(name), s_filer=str(s_filer), n_filer=str(n_filer), width=size[2], height=size[0], depth=size[1], opacity=opacity ), + to_Conv( name='{}'.format(top), offset="(0,0,0)", to="(ccr_res_c_{}-east)".format(name), s_filer=str(s_filer), n_filer=str(n_filer), width=size[2], height=size[0], depth=size[1] ), + to_connection( + "{}".format( botton ), + "unpool_{}".format( name ) + ) + ] + + + + +def block_Res( num, name, botton, top, s_filer=256, n_filer=64, offset="(0,0,0)", size=(32,32,3.5), opacity=0.5 ): + lys = [] + layers = [ *[ '{}_{}'.format(name,i) for i in range(num-1) ], top] + for name in layers: + ly = [ to_Conv( + name='{}'.format(name), + offset=offset, + to="({}-east)".format( botton ), + s_filer=str(s_filer), + n_filer=str(n_filer), + width=size[2], + height=size[0], + depth=size[1] + ), + to_connection( + "{}".format( botton ), + "{}".format( name ) + ) + ] + botton = name + lys+=ly + + lys += [ + to_skip( of=layers[1], to=layers[-2], pos=1.25), + ] + return lys + + diff --git a/core/tikzeng.py b/core/tikzeng.py new file mode 100644 index 0000000..17df4df --- /dev/null +++ b/core/tikzeng.py @@ -0,0 +1,197 @@ + +import os + +def to_head( projectpath ): + pathlayers = os.path.join( projectpath, 'layers/' ) + return r""" +\documentclass[border=8pt, multi, tikz]{standalone} +\usepackage{import} +\subimport{"""+ pathlayers + r"""}{init} +\usetikzlibrary{positioning} +\usetikzlibrary{3d} %for including external image +""" + +def to_cor(): + return r""" +\def\ConvColor{rgb:yellow,5;red,2.5;white,5} +\def\ConvReluColor{rgb:yellow,5;red,5;white,5} +\def\PoolColor{rgb:red,1;black,0.3} +\def\UnpoolColor{rgb:blue,2;green,1;black,0.3} +\def\FcColor{rgb:blue,5;red,2.5;white,5} +\def\FcReluColor{rgb:blue,5;red,5;white,4} +\def\SoftmaxColor{rgb:magenta,5;black,7} +""" + +def to_begin(): + return r""" +\newcommand{\copymidarrow}{\tikz \draw[-Stealth,line width=0.8mm,draw={rgb:blue,4;red,1;green,1;black,3}] (-0.3,0) -- ++(0.3,0);} + +\begin{document} +\begin{tikzpicture} +\tikzstyle{connection}=[ultra thick,every node/.style={sloped,allow upside down},draw=\edgecolor,opacity=0.7] +\tikzstyle{copyconnection}=[ultra thick,every node/.style={sloped,allow upside down},draw={rgb:blue,4;red,1;green,1;black,3},opacity=0.7] +""" + +# layers definition + +def to_input( pathfile, to='(-3,0,0)', width=8, height=8 ): + return r""" +\node[canvas is zy plane at x=0] (temp) at """+ to +""" {\includegraphics[width="""+ str(width)+"cm"+""",height="""+ str(height)+"cm"+"""]{"""+ pathfile +"""}}; +""" + +# Conv +def to_Conv( name, s_filer=256, n_filer=64, offset="(0,0,0)", to="(0,0,0)", width=1, height=40, depth=40, caption=" " ): + return r""" +\pic[shift={"""+ offset +"""}] at """+ to +""" + {Box={ + name=""" + name +""", + caption="""+ caption +r""", + xlabel={{"""+ str(n_filer) +""", }}, + zlabel="""+ str(s_filer) +""", + fill=\ConvColor, + height="""+ str(height) +""", + width="""+ str(width) +""", + depth="""+ str(depth) +""" + } + }; +""" + +# Conv,Conv,relu +# Bottleneck +def to_ConvConvRelu( name, s_filer=256, n_filer=(64,64), offset="(0,0,0)", to="(0,0,0)", width=(2,2), height=40, depth=40, caption=" " ): + return r""" +\pic[shift={ """+ offset +""" }] at """+ to +""" + {RightBandedBox={ + name="""+ name +""", + caption="""+ caption +""", + xlabel={{ """+ str(n_filer[0]) +""", """+ str(n_filer[1]) +""" }}, + zlabel="""+ str(s_filer) +""", + fill=\ConvColor, + bandfill=\ConvReluColor, + height="""+ str(height) +""", + width={ """+ str(width[0]) +""" , """+ str(width[1]) +""" }, + depth="""+ str(depth) +""" + } + }; +""" + + + +# Pool +def to_Pool(name, offset="(0,0,0)", to="(0,0,0)", width=1, height=32, depth=32, opacity=0.5, caption=" "): + return r""" +\pic[shift={ """+ offset +""" }] at """+ to +""" + {Box={ + name="""+name+""", + caption="""+ caption +r""", + fill=\PoolColor, + opacity="""+ str(opacity) +""", + height="""+ str(height) +""", + width="""+ str(width) +""", + depth="""+ str(depth) +""" + } + }; +""" + +# unpool4, +def to_UnPool(name, offset="(0,0,0)", to="(0,0,0)", width=1, height=32, depth=32, opacity=0.5, caption=" "): + return r""" +\pic[shift={ """+ offset +""" }] at """+ to +""" + {Box={ + name="""+ name +r""", + caption="""+ caption +r""", + fill=\UnpoolColor, + opacity="""+ str(opacity) +""", + height="""+ str(height) +""", + width="""+ str(width) +""", + depth="""+ str(depth) +""" + } + }; +""" + + + +def to_ConvRes( name, s_filer=256, n_filer=64, offset="(0,0,0)", to="(0,0,0)", width=6, height=40, depth=40, opacity=0.2, caption=" " ): + return r""" +\pic[shift={ """+ offset +""" }] at """+ to +""" + {RightBandedBox={ + name="""+ name + """, + caption="""+ caption + """, + xlabel={{ """+ str(n_filer) + """, }}, + zlabel="""+ str(s_filer) +r""", + fill={rgb:white,1;black,3}, + bandfill={rgb:white,1;black,2}, + opacity="""+ str(opacity) +""", + height="""+ str(height) +""", + width="""+ str(width) +""", + depth="""+ str(depth) +""" + } + }; +""" + + +# ConvSoftMax +def to_ConvSoftMax( name, s_filer=40, offset="(0,0,0)", to="(0,0,0)", width=1, height=40, depth=40, caption=" " ): + return r""" +\pic[shift={"""+ offset +"""}] at """+ to +""" + {Box={ + name=""" + name +""", + caption="""+ caption +""", + zlabel="""+ str(s_filer) +""", + fill=\SoftmaxColor, + height="""+ str(height) +""", + width="""+ str(width) +""", + depth="""+ str(depth) +""" + } + }; +""" + +# SoftMax +def to_SoftMax( name, s_filer=10, offset="(0,0,0)", to="(0,0,0)", width=1.5, height=3, depth=25, opacity=0.8, caption=" " ): + return r""" +\pic[shift={"""+ offset +"""}] at """+ to +""" + {Box={ + name=""" + name +""", + caption="""+ caption +""", + xlabel={{" ","dummy"}}, + zlabel="""+ str(s_filer) +""", + fill=\SoftmaxColor, + opacity="""+ str(opacity) +""", + height="""+ str(height) +""", + width="""+ str(width) +""", + depth="""+ str(depth) +""" + } + }; +""" + + +def to_connection( of, to): + return r""" +\draw [connection] ("""+of+"""-east) -- node {\midarrow} ("""+to+"""-west); +""" + +def to_skip( of, to, pos=1.25): + return r""" +\path ("""+ of +"""-southeast) -- ("""+ of +"""-northeast) coordinate[pos="""+ str(pos) +"""] ("""+ of +"""-top) ; +\path ("""+ to +"""-south) -- ("""+ to +"""-north) coordinate[pos=1.25] ("""+ to +"""-top) ; +\draw [copyconnection] ("""+of+"""-northeast) +-- node {\copymidarrow}("""+of+"""-top) +-- node {\copymidarrow}("""+to+"""-top) +-- node {\copymidarrow} ("""+to+"""-north); +""" + +def to_end(): + return r""" +\end{tikzpicture} +\end{document} +""" + + +def to_generate( arch, pathname="file.tex" ): + with open(pathname, "w") as f: + for c in arch: + print(c) + f.write( c ) + + + diff --git a/test/test_simple.py b/test/test_simple.py new file mode 100644 index 0000000..1da1833 --- /dev/null +++ b/test/test_simple.py @@ -0,0 +1,26 @@ + +import sys +sys.path.append('../') +from core.tikzeng import * + +# defined your arch +arch = [ + to_head( '..' ), + to_cor(), + to_begin(), + to_Conv("conv1", 512, 64, offset="(0,0,0)", to="(0,0,0)", height=64, depth=64, width=2 ), + to_Pool("pool1", offset="(0,0,0)", to="(conv1-east)"), + to_Conv("conv2", 128, 64, offset="(1,0,0)", to="(pool1-east)", height=32, depth=32, width=2 ), + to_connection( "pool1", "conv2"), + to_Pool("pool2", offset="(0,0,0)", to="(conv2-east)", height=28, depth=28, width=1), + to_SoftMax("soft1", 10 ,"(3,0,0)", "(pool1-east)", caption="SOFT" ), + to_connection("pool2", "soft1"), + to_end() + ] + +def main(): + namefile = str(sys.argv[0]).split('.')[0] + to_generate(arch, namefile + '.tex' ) + +if __name__ == '__main__': + main() diff --git a/test/unet.py b/test/unet.py new file mode 100644 index 0000000..dee71f0 --- /dev/null +++ b/test/unet.py @@ -0,0 +1,52 @@ + +import sys +sys.path.append('../') +from core.tikzeng import * +from core.blocks import * + +arch = [ + to_head('..'), + to_cor(), + to_begin(), + + #input + to_input( '../examples/fcn8s/cats.jpg' ), + + #block-001 + to_ConvConvRelu( name='ccr_b1', s_filer=500, n_filer=(64,64), offset="(0,0,0)", to="(0,0,0)", width=(2,2), height=40, depth=40 ), + to_Pool(name="pool_b1", offset="(0,0,0)", to="(ccr_b1-east)", width=1, height=32, depth=32, opacity=0.5), + + *block_2ConvPool( name='b2', botton='pool_b1', top='pool_b2', s_filer=256, n_filer=128, offset="(1,0,0)", size=(32,32,3.5), opacity=0.5 ), + *block_2ConvPool( name='b3', botton='pool_b2', top='pool_b3', s_filer=128, n_filer=256, offset="(1,0,0)", size=(25,25,4.5), opacity=0.5 ), + *block_2ConvPool( name='b4', botton='pool_b3', top='pool_b4', s_filer=64, n_filer=512, offset="(1,0,0)", size=(16,16,5.5), opacity=0.5 ), + + #Bottleneck + #block-005 + to_ConvConvRelu( name='ccr_b5', s_filer=32, n_filer=(1024,1024), offset="(2,0,0)", to="(pool_b4-east)", width=(8,8), height=8, depth=8, caption="Bottleneck" ), + to_connection( "pool_b4", "ccr_b5"), + + #Decoder + *block_Unconv( name="b6", botton="ccr_b5", top='end_b6', s_filer=64, n_filer=512, offset="(2.1,0,0)", size=(16,16,5.0), opacity=0.5 ), + to_skip( of='ccr_b4', to='ccr_res_b6', pos=1.25), + *block_Unconv( name="b7", botton="end_b6", top='end_b7', s_filer=128, n_filer=256, offset="(2.1,0,0)", size=(25,25,4.5), opacity=0.5 ), + to_skip( of='ccr_b3', to='ccr_res_b7', pos=1.25), + *block_Unconv( name="b8", botton="end_b7", top='end_b8', s_filer=256, n_filer=128, offset="(2.1,0,0)", size=(32,32,3.5), opacity=0.5 ), + to_skip( of='ccr_b2', to='ccr_res_b8', pos=1.25), + + *block_Unconv( name="b9", botton="end_b8", top='end_b9', s_filer=512, n_filer=64, offset="(2.1,0,0)", size=(40,40,2.5), opacity=0.5 ), + to_skip( of='ccr_b1', to='ccr_res_b9', pos=1.25), + + to_ConvSoftMax( name="soft1", s_filer=512, offset="(0.75,0,0)", to="(end_b9-east)", width=1, height=40, depth=40, caption="SOFT" ), + to_connection( "end_b9", "soft1"), + + to_end() + ] + + +def main(): + namefile = str(sys.argv[0]).split('.')[0] + to_generate(arch, namefile + '.tex' ) + +if __name__ == '__main__': + main() + \ No newline at end of file diff --git a/tikzmake.sh b/tikzmake.sh new file mode 100644 index 0000000..1c4b6d6 --- /dev/null +++ b/tikzmake.sh @@ -0,0 +1,11 @@ +#!/bin/bash + + +python $1.py +pdflatex $1.tex + +rm *.aux *.log *.vscodeLog +rm *.tex + +evince $1.pdf +