Repository: Image-Py/sknw Branch: master Commit: 68cf72383496 Files: 6 Total size: 11.8 KB Directory structure: gitextract_ypbsjqkh/ ├── .gitignore ├── LICENSE ├── README.md ├── setup.py └── sknw/ ├── __init__.py └── sknw.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg # 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/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py # 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 # dotenv .env # virtualenv .venv venv/ ENV/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ ================================================ FILE: LICENSE ================================================ BSD 3-Clause License Copyright (c) 2017, Yan xiaolong All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: README.md ================================================ Skeleton Network ====================== build net work from nd skeleton image ### graph = sknw.build_sknw(ske, multi=False, iso=True, ring=True, full=True) > **ske:** should be a nd skeleton image > > **multi:** if True,a multigraph is retured, which allows more than one edge between two nodes and self-self edge. > > **iso:** if True, return one-pixel node > > **ring:** if True, return ring without any branch (and insert a self-connected node in the ring) > > **full:** if True, every edge start from the node's centroid. else touch the node block but not from the centroid. > > **return:** is a networkx Graph object ![image](https://user-images.githubusercontent.com/24822467/144245690-8def3215-344f-464c-a825-bc33568758f7.png) ### graph detail: > **graph.nodes[id]['pts'] :** Numpy(x, n), coordinates of nodes points > > **graph.nodes[id]['o']:** Numpy(n), centroid of the node > > **graph.edge(id1, id2)['pts']:** Numpy(x, n), sequence of the edge point > > **graph.edge(id1, id2)['weight']:** float, length of this edge > > *if it's a multigraph, you must add a index after two node id to get the edge, like: graph.edge(id1, id2)[0].* ### Build Graph: build Graph by Skeleton, then plot as a vector Graph in matplotlib. ```python from skimage.morphology import skeletonize from skimage import data import sknw # open and skeletonize img = data.horse() ske = skeletonize(~img).astype(np.uint16) # build graph from skeleton graph = sknw.build_sknw(ske) # draw image plt.imshow(img, cmap='gray') # draw edges by pts for (s,e) in graph.edges(): ps = graph[s][e]['pts'] plt.plot(ps[:,1], ps[:,0], 'green') # draw node by o nodes = graph.nodes() ps = np.array([nodes[i]['o'] for i in nodes]) plt.plot(ps[:,1], ps[:,0], 'r.') # title and show plt.title('Build Graph') plt.show() ``` ![](http://home.imagepy.org/sknw/buildgraph.png "解压") ### Find Path then you can use networkx do what you want ![](http://home.imagepy.org/sknw/findpath.png "解压") ### 3D Skeleton sknw can works on nd image, this is a 3d demo by mayavi ![](http://home.imagepy.org/sknw/3dgraph.png "解压") ### About ImagePy [https://github.com/Image-Py/imagepy](https://github.com/Image-Py/imagepy) ImagePy 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. ![](http://myvi.imagepy.org/imgs/imagepy.jpg "vessel") ================================================ FILE: setup.py ================================================ from setuptools import setup descr = """sknw: skeleton analysis in Python. Inspired by Juan Nunez-Iglesias's skan. """ if __name__ == '__main__': setup(name='sknw', version='0.13', url='https://github.com/yxdragon/sknw', description='Analysis of object skeletons', long_description=descr, author='YXDragon', author_email='yxdragon@imagepy.org', license='BSD 3-clause', packages=['sknw'], package_data={}, install_requires=[ 'numpy', 'networkx', 'numba' ], ) ================================================ FILE: sknw/__init__.py ================================================ from .sknw import mark, parse_struc, build_graph, draw_graph, build_sknw __version__ = '0.1' __all__ = ['mark', 'parse_struc', 'build_graph', 'draw_graph', 'build_sknw'] def test(): from skimage.morphology import skeletonize import numpy as np from skimage import data import matplotlib.pyplot as plt img = data.horse() ske = skeletonize(~img).astype(np.uint16) graph = build_sknw(ske) plt.imshow(img, cmap='gray') for (s,e) in graph.edges(): ps = graph[s][e]['pts'] plt.plot(ps[:,1], ps[:,0], 'green') nodes = graph.nodes() ps = np.array([nodes[i]['o'] for i in nodes]) plt.plot(ps[:,1], ps[:,0], 'r.') plt.title('Build Graph') plt.show() ================================================ FILE: sknw/sknw.py ================================================ import numpy as np from numba import jit import networkx as nx def neighbors(shape): dim = len(shape) block = np.ones([3]*dim) block[tuple([1]*dim)] = 0 idx = np.where(block>0) idx = np.array(idx, dtype=np.uint8).T idx = np.array(idx-[1]*dim) acc = np.cumprod((1,)+shape[::-1][:-1]) return np.dot(idx, acc[::-1]) @jit(nopython=True) # my mark def mark(img, nbs): # mark the array use (0, 1, 2) img = img.ravel() for p in range(len(img)): if img[p]==0:continue s = 0 for dp in nbs: if img[p+dp]!=0:s+=1 if s==2:img[p]=1 else:img[p]=2 @jit(nopython=True) # trans index to r, c... def idx2rc(idx, acc): rst = np.zeros((len(idx), len(acc)), dtype=np.int16) for i in range(len(idx)): for j in range(len(acc)): rst[i,j] = idx[i]//acc[j] idx[i] -= rst[i,j]*acc[j] rst -= 1 return rst @jit(nopython=True) # fill a node (may be two or more points) def fill(img, p, num, nbs, acc, buf): img[p] = num buf[0] = p cur = 0; s = 1; iso = True; while True: p = buf[cur] for dp in nbs: cp = p+dp if img[cp]==2: img[cp] = num buf[s] = cp s+=1 if img[cp]==1: iso=False cur += 1 if cur==s:break return iso, idx2rc(buf[:s], acc) @jit(nopython=True) # trace the edge and use a buffer, then buf.copy, if use [] numba not works def trace(img, p, nbs, acc, buf): c1 = 0; c2 = 0; newp = 0 cur = 1 while True: buf[cur] = p img[p] = 0 cur += 1 for dp in nbs: cp = p + dp if img[cp] >= 10: if c1==0: c1 = img[cp] buf[0] = cp else: c2 = img[cp] buf[cur] = cp if img[cp] == 1: newp = cp p = newp if c2!=0:break return (c1-10, c2-10, idx2rc(buf[:cur+1], acc)) @jit(nopython=True) # parse the image then get the nodes and edges def parse_struc(img, nbs, acc, iso, ring): img = img.ravel() buf = np.zeros(131072, dtype=np.int64) num = 10 nodes = [] for p in range(len(img)): if img[p] == 2: isiso, nds = fill(img, p, num, nbs, acc, buf) if isiso and not iso: continue num += 1 nodes.append(nds) edges = [] for p in range(len(img)): if img[p] <10: continue for dp in nbs: if img[p+dp]==1: edge = trace(img, p+dp, nbs, acc, buf) edges.append(edge) if not ring: return nodes, edges for p in range(len(img)): if img[p]!=1: continue img[p] = num; num += 1 nodes.append(idx2rc([p], acc)) for dp in nbs: if img[p+dp]==1: edge = trace(img, p+dp, nbs, acc, buf) edges.append(edge) return nodes, edges # use nodes and edges build a networkx graph def build_graph(nodes, edges, multi=False, full=True): os = np.array([i.mean(axis=0) for i in nodes]) if full: os = os.round().astype(np.uint16) graph = nx.MultiGraph() if multi else nx.Graph() for i in range(len(nodes)): graph.add_node(i, pts=nodes[i], o=os[i]) for s,e,pts in edges: if full: pts[[0,-1]] = os[[s,e]] l = np.linalg.norm(pts[1:]-pts[:-1], axis=1).sum() graph.add_edge(s,e, pts=pts, weight=l) return graph def mark_node(ske): buf = np.pad(ske, (1,1), mode='constant').astype(np.uint16) nbs = neighbors(buf.shape) acc = np.cumprod((1,)+buf.shape[::-1][:-1])[::-1] mark(buf, nbs) return buf def build_sknw(ske, multi=False, iso=True, ring=True, full=True): buf = np.pad(ske, (1,1), mode='constant').astype(np.uint16) nbs = neighbors(buf.shape) acc = np.cumprod((1,)+buf.shape[::-1][:-1])[::-1] mark(buf, nbs) nodes, edges = parse_struc(buf, nbs, acc, iso, ring) return build_graph(nodes, edges, multi, full) # draw the graph def draw_graph(img, graph, cn=255, ce=128): acc = np.cumprod((1,)+img.shape[::-1][:-1])[::-1] img = img.ravel() for (s, e) in graph.edges(): eds = graph[s][e] if isinstance(graph, nx.MultiGraph): for i in eds: pts = eds[i]['pts'] img[np.dot(pts, acc)] = ce else: img[np.dot(eds['pts'], acc)] = ce for idx in graph.nodes(): pts = graph.nodes[idx]['pts'] img[np.dot(pts, acc)] = cn if __name__ == '__main__': import matplotlib.pyplot as plt img = np.array([ [0,0,0,1,0,0,0,0,0], [0,0,0,1,0,0,0,1,0], [0,0,0,1,0,0,0,0,0], [1,1,1,1,0,0,0,0,0], [0,0,0,0,1,0,0,0,0], [0,1,0,0,0,1,0,0,0], [1,0,1,0,0,1,1,1,1], [0,1,0,0,1,0,0,0,0], [0,0,0,1,0,0,0,0,0]]) node_img = mark_node(img) para = [{'iso':False}, {'ring':False}, {'full':False}, {'iso':True}, {'ring':True}, {'full':True}] for i,p,k in zip([1,2,3,4,5,6], [231,232,233,234,235,236], para): print(k) graph = build_sknw(img, False, **k) ax = plt.subplot(p) ax.imshow(node_img[1:-1,1:-1], cmap='gray') # draw edges by pts for (s,e) in graph.edges(): ps = graph[s][e]['pts'] ax.plot(ps[:,1], ps[:,0], 'green') # draw node by o nodes = graph.nodes() ps = np.array([nodes[i]['o'] for i in nodes]) ax.plot(ps[:,1], ps[:,0], 'r.') # title and show ax.title.set_text(k) plt.show()