[
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nenv/\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\n*.egg-info/\n.installed.cfg\n*.egg\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n.hypothesis/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# pyenv\n.python-version\n\n# celery beat schedule file\ncelerybeat-schedule\n\n# SageMath parsed files\n*.sage.py\n\n# dotenv\n.env\n\n# virtualenv\n.venv\nvenv/\nENV/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n"
  },
  {
    "path": "LICENSE",
    "content": "BSD 3-Clause License\n\nCopyright (c) 2017, Yan xiaolong\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this\n  list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice,\n  this list of conditions and the following disclaimer in the documentation\n  and/or other materials provided with the distribution.\n\n* Neither the name of the copyright holder nor the names of its\n  contributors may be used to endorse or promote products derived from\n  this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "README.md",
    "content": "Skeleton Network\n======================\nbuild net work from nd skeleton image\n\n### graph = sknw.build_sknw(ske， multi=False, iso=True, ring=True, full=True)\n> **ske:** should be a nd skeleton image\n> \n> **multi:** if True，a multigraph is retured, which allows more than one edge between two nodes and self-self edge.\n>\n> **iso:** if True, return one-pixel node\n>\n> **ring:** if True, return ring without any branch (and insert a self-connected node in the ring)\n>\n> **full:** if True, every edge start from the node's centroid. else touch the node block but not from the centroid.\n> \n> **return:** is a networkx Graph object\n\n![image](https://user-images.githubusercontent.com/24822467/144245690-8def3215-344f-464c-a825-bc33568758f7.png)\n\n### graph detail:\n> **graph.nodes[id]['pts'] :** Numpy(x, n), coordinates of nodes points\n> \n> **graph.nodes[id]['o']:** Numpy(n), centroid of the node\n> \n> **graph.edge(id1, id2)['pts']:** Numpy(x, n), sequence of the edge point\n> \n> **graph.edge(id1, id2)['weight']:** float, length of this edge\n> \n> *if  it's a multigraph, you must add a index after two node id to get the edge, like: graph.edge(id1, id2)[0].*\n### Build Graph:\nbuild Graph by Skeleton, then plot as a vector Graph in matplotlib.\n```python\nfrom skimage.morphology import skeletonize\nfrom skimage import data\nimport sknw\n\n# open and skeletonize\nimg = data.horse()\nske = skeletonize(~img).astype(np.uint16)\n\n# build graph from skeleton\ngraph = sknw.build_sknw(ske)\n\n# draw image\nplt.imshow(img, cmap='gray')\n\n# draw edges by pts\nfor (s,e) in graph.edges():\n    ps = graph[s][e]['pts']\n    plt.plot(ps[:,1], ps[:,0], 'green')\n    \n# draw node by o\nnodes = graph.nodes()\nps = np.array([nodes[i]['o'] for i in nodes])\nplt.plot(ps[:,1], ps[:,0], 'r.')\n\n# title and show\nplt.title('Build Graph')\nplt.show()\n```\n![](http://home.imagepy.org/sknw/buildgraph.png \"解压\")\n### Find Path\nthen you can use networkx do what you want\n![](http://home.imagepy.org/sknw/findpath.png \"解压\")\n### 3D Skeleton\nsknw can works on nd image, this is a 3d demo by mayavi\n![](http://home.imagepy.org/sknw/3dgraph.png \"解压\")\n\n### About ImagePy\n[https://github.com/Image-Py/imagepy](https://github.com/Image-Py/imagepy)\n\nImagePy is my opensource image processihng framework. It is the ImageJ of Python, you can wrap any numpy based function esaily. And sknw is a sub module of ImagePy. You can use sknw without any code.\n\n![](http://myvi.imagepy.org/imgs/imagepy.jpg \"vessel\")"
  },
  {
    "path": "setup.py",
    "content": "from setuptools import setup\n\ndescr = \"\"\"sknw: skeleton analysis in Python.\nInspired by Juan Nunez-Iglesias's skan.\n\"\"\"\n\nif __name__ == '__main__':\n    setup(name='sknw',\n        version='0.13',\n        url='https://github.com/yxdragon/sknw',\n        description='Analysis of object skeletons',\n        long_description=descr,\n        author='YXDragon',\n        author_email='yxdragon@imagepy.org',\n        license='BSD 3-clause',\n        packages=['sknw'],\n        package_data={},\n        install_requires=[\n            'numpy',\n            'networkx',\n            'numba'\n        ],\n    )\n"
  },
  {
    "path": "sknw/__init__.py",
    "content": "from .sknw import mark, parse_struc, build_graph, draw_graph, build_sknw\n\n__version__ = '0.1'\n\n__all__ = ['mark', 'parse_struc', 'build_graph', 'draw_graph', 'build_sknw']\n\n\ndef test():\n    from skimage.morphology import skeletonize\n    import numpy as np\n    from skimage import data\n    import matplotlib.pyplot as plt\n\n    img = data.horse()\n    ske = skeletonize(~img).astype(np.uint16)\n    graph = build_sknw(ske)\n    plt.imshow(img, cmap='gray')\n    for (s,e) in graph.edges():\n        ps = graph[s][e]['pts']\n        plt.plot(ps[:,1], ps[:,0], 'green')\n\n    nodes = graph.nodes()\n    ps = np.array([nodes[i]['o'] for i in nodes])\n    plt.plot(ps[:,1], ps[:,0], 'r.')\n    plt.title('Build Graph')\n    plt.show()\n"
  },
  {
    "path": "sknw/sknw.py",
    "content": "import numpy as np\nfrom numba import jit\nimport networkx as nx\n\ndef neighbors(shape):\n    dim = len(shape)\n    block = np.ones([3]*dim)\n    block[tuple([1]*dim)] = 0\n    idx = np.where(block>0)\n    idx = np.array(idx, dtype=np.uint8).T\n    idx = np.array(idx-[1]*dim)\n    acc = np.cumprod((1,)+shape[::-1][:-1])\n    return np.dot(idx, acc[::-1])\n\n@jit(nopython=True) # my mark\ndef mark(img, nbs): # mark the array use (0, 1, 2)\n    img = img.ravel()\n    for p in range(len(img)):\n        if img[p]==0:continue\n        s = 0\n        for dp in nbs:\n            if img[p+dp]!=0:s+=1\n        if s==2:img[p]=1\n        else:img[p]=2\n\n@jit(nopython=True) # trans index to r, c...\ndef idx2rc(idx, acc):\n    rst = np.zeros((len(idx), len(acc)), dtype=np.int16)\n    for i in range(len(idx)):\n        for j in range(len(acc)):\n            rst[i,j] = idx[i]//acc[j]\n            idx[i] -= rst[i,j]*acc[j]\n    rst -= 1\n    return rst\n    \n@jit(nopython=True) # fill a node (may be two or more points)\ndef fill(img, p, num, nbs, acc, buf):\n    img[p] = num\n    buf[0] = p\n    cur = 0; s = 1; iso = True;\n    \n    while True:\n        p = buf[cur]\n        for dp in nbs:\n            cp = p+dp\n            if img[cp]==2:\n                img[cp] = num\n                buf[s] = cp\n                s+=1\n            if img[cp]==1: iso=False\n        cur += 1\n        if cur==s:break\n    return iso, idx2rc(buf[:s], acc)\n\n@jit(nopython=True) # trace the edge and use a buffer, then buf.copy, if use [] numba not works\ndef trace(img, p, nbs, acc, buf):\n    c1 = 0; c2 = 0;\n    newp = 0\n    cur = 1\n    while True:\n        buf[cur] = p\n        img[p] = 0\n        cur += 1\n        for dp in nbs:\n            cp = p + dp\n            if img[cp] >= 10:\n                if c1==0:\n                    c1 = img[cp]\n                    buf[0] = cp\n                else:\n                    c2 = img[cp]\n                    buf[cur] = cp\n            if img[cp] == 1:\n                newp = cp\n        p = newp\n        if c2!=0:break\n    return (c1-10, c2-10, idx2rc(buf[:cur+1], acc))\n   \n@jit(nopython=True) # parse the image then get the nodes and edges\ndef parse_struc(img, nbs, acc, iso, ring):\n    img = img.ravel()\n    buf = np.zeros(131072, dtype=np.int64)\n    num = 10\n    nodes = []\n    for p in range(len(img)):\n        if img[p] == 2:\n            isiso, nds = fill(img, p, num, nbs, acc, buf)\n            if isiso and not iso: continue\n            num += 1\n            nodes.append(nds)\n    edges = []\n    for p in range(len(img)):\n        if img[p] <10: continue\n        for dp in nbs:\n            if img[p+dp]==1:\n                edge = trace(img, p+dp, nbs, acc, buf)\n                edges.append(edge)\n    if not ring: return nodes, edges\n    for p in range(len(img)):\n        if img[p]!=1: continue\n        img[p] = num; num += 1\n        nodes.append(idx2rc([p], acc))\n        for dp in nbs:\n            if img[p+dp]==1:\n                edge = trace(img, p+dp, nbs, acc, buf)\n                edges.append(edge)\n    return nodes, edges\n    \n# use nodes and edges build a networkx graph\ndef build_graph(nodes, edges, multi=False, full=True):\n    os = np.array([i.mean(axis=0) for i in nodes])\n    if full: os = os.round().astype(np.uint16)\n    graph = nx.MultiGraph() if multi else nx.Graph()\n    for i in range(len(nodes)):\n        graph.add_node(i, pts=nodes[i], o=os[i])\n    for s,e,pts in edges:\n        if full: pts[[0,-1]] = os[[s,e]]\n        l = np.linalg.norm(pts[1:]-pts[:-1], axis=1).sum()\n        graph.add_edge(s,e, pts=pts, weight=l)\n    return graph\n\ndef mark_node(ske):\n    buf = np.pad(ske, (1,1), mode='constant').astype(np.uint16)\n    nbs = neighbors(buf.shape)\n    acc = np.cumprod((1,)+buf.shape[::-1][:-1])[::-1]\n    mark(buf, nbs)\n    return buf\n    \ndef build_sknw(ske, multi=False, iso=True, ring=True, full=True):\n    buf = np.pad(ske, (1,1), mode='constant').astype(np.uint16)\n    nbs = neighbors(buf.shape)\n    acc = np.cumprod((1,)+buf.shape[::-1][:-1])[::-1]\n    mark(buf, nbs)\n    nodes, edges = parse_struc(buf, nbs, acc, iso, ring)\n    return build_graph(nodes, edges, multi, full)\n    \n# draw the graph\ndef draw_graph(img, graph, cn=255, ce=128):\n    acc = np.cumprod((1,)+img.shape[::-1][:-1])[::-1]\n    img = img.ravel()\n    for (s, e) in graph.edges():\n        eds = graph[s][e]\n        if isinstance(graph, nx.MultiGraph):\n            for i in eds:\n                pts = eds[i]['pts']\n                img[np.dot(pts, acc)] = ce\n        else: img[np.dot(eds['pts'], acc)] = ce\n    for idx in graph.nodes():\n        pts = graph.nodes[idx]['pts']\n        img[np.dot(pts, acc)] = cn\n\nif __name__ == '__main__':\n    import matplotlib.pyplot as plt\n\n    img = np.array([\n        [0,0,0,1,0,0,0,0,0],\n        [0,0,0,1,0,0,0,1,0],\n        [0,0,0,1,0,0,0,0,0],\n        [1,1,1,1,0,0,0,0,0],\n        [0,0,0,0,1,0,0,0,0],\n        [0,1,0,0,0,1,0,0,0],\n        [1,0,1,0,0,1,1,1,1],\n        [0,1,0,0,1,0,0,0,0],\n        [0,0,0,1,0,0,0,0,0]])\n\n    node_img = mark_node(img)\n    para = [{'iso':False}, {'ring':False}, {'full':False},\n            {'iso':True}, {'ring':True}, {'full':True}]\n    for i,p,k in zip([1,2,3,4,5,6], [231,232,233,234,235,236], para):\n        print(k)\n        graph = build_sknw(img, False, **k)\n        ax = plt.subplot(p)\n        ax.imshow(node_img[1:-1,1:-1], cmap='gray')\n\n        # draw edges by pts\n        for (s,e) in graph.edges():\n            ps = graph[s][e]['pts']\n            ax.plot(ps[:,1], ps[:,0], 'green')\n            \n        # draw node by o\n        nodes = graph.nodes()\n        ps = np.array([nodes[i]['o'] for i in nodes])\n        ax.plot(ps[:,1], ps[:,0], 'r.')\n\n        # title and show\n        ax.title.set_text(k)\n    plt.show()\n"
  }
]