22# @File: skeletonization_operation.py
33# @Time: 2025-10-03 13:45 IST
44
5+ from itertools import pairwise
6+ from pathlib import Path
7+
58import numpy as np
69from PIL import Image
7- from pathlib import Path
810
911
1012def rgb_to_gray (rgb : np .ndarray ) -> np .ndarray :
@@ -46,42 +48,89 @@ def neighbours(image: np.ndarray, x: int, y: int) -> list:
4648 """
4749 Return 8-neighbours of point (x, y), in clockwise order
4850
49- >>> neighbours(1, 1, np.array([[True, True, False], [True, False, False], [False, True, False]]))
51+ >>> neighbours(
52+ ... np.array(
53+ ... [
54+ ... [True, True, False],
55+ ... [True, False, False],
56+ ... [False, True, False]
57+ ... ]
58+ ... ), 1, 1
59+ ... )
5060 [np.True_, np.False_, np.False_, np.False_, np.True_, np.False_, np.True_, np.True_]
51- >>> neighbours(1, 2, np.array([[True, True, False, True], [True, False, False, True], [False, True, False, True]]))
61+ >>> neighbours(
62+ ... np.array(
63+ ... [
64+ ... [True, True, False, True],
65+ ... [True, False, False, True],
66+ ... [False, True, False, True]
67+ ... ]
68+ ... ), 1, 2
69+ ... )
5270 [np.False_, np.True_, np.True_, np.True_, np.False_, np.True_, np.False_, np.True_]
5371 """
5472 img = image
5573 return [
56- img [x - 1 ][y ], img [x - 1 ][y + 1 ], img [x ][y + 1 ], img [x + 1 ][y + 1 ],
57- img [x + 1 ][y ], img [x + 1 ][y - 1 ], img [x ][y - 1 ], img [x - 1 ][y - 1 ]
74+ img [x - 1 ][y ],
75+ img [x - 1 ][y + 1 ],
76+ img [x ][y + 1 ],
77+ img [x + 1 ][y + 1 ],
78+ img [x + 1 ][y ],
79+ img [x + 1 ][y - 1 ],
80+ img [x ][y - 1 ],
81+ img [x - 1 ][y - 1 ],
5882 ]
5983
6084
6185def transitions (neighbors : list ) -> int :
6286 """
6387 Count 0->1 transitions in the neighborhood
64-
65- >>> transitions([np.False_, np.True_, np.True_, np.False_, np.True_, np.False_, np.False_, np.False_])
88+
89+ >>> transitions(
90+ ... [
91+ ... np.False_, np.True_, np.True_, np.False_,
92+ ... np.True_, np.False_, np.False_, np.False_
93+ ... ]
94+ ... )
6695 2
67- >>> transitions([np.True_, np.True_, np.True_, np.True_, np.True_, np.True_, np.True_, np.True_])
96+ >>> transitions(
97+ ... [
98+ ... np.True_, np.True_, np.True_, np.True_,
99+ ... np.True_, np.True_, np.True_, np.True_
100+ ... ]
101+ ... )
68102 0
69- >>> transitions([np.False_, np.False_, np.False_, np.False_, np.False_, np.False_, np.False_, np.False_])
103+ >>> transitions(
104+ ... [
105+ ... np.False_, np.False_, np.False_, np.False_,
106+ ... np.False_, np.False_, np.False_, np.False_
107+ ... ]
108+ ... )
70109 0
71- >>> transitions([np.False_, np.True_, np.False_, np.True_, np.False_, np.True_, np.False_, np.True_])
110+ >>> transitions(
111+ ... [
112+ ... np.False_, np.True_, np.False_, np.True_,
113+ ... np.False_, np.True_, np.False_, np.True_
114+ ... ]
115+ ... )
72116 4
73- >>> transitions([np.True_, np.False_, np.True_, np.False_, np.True_, np.False_, np.True_, np.False_])
117+ >>> transitions(
118+ ... [
119+ ... np.True_, np.False_, np.True_, np.False_,
120+ ... np.True_, np.False_, np.True_, np.False_
121+ ... ]
122+ ... )
74123 4
75124 """
76- n = neighbors + [ neighbors [0 ]]
77- return int (sum ((n1 == 0 and n2 == 1 ) for n1 , n2 in zip ( n , n [ 1 :] )))
125+ n = [ * neighbors , neighbors [0 ]]
126+ return int (sum ((n1 == 0 and n2 == 1 ) for n1 , n2 in pairwise ( n )))
78127
79128
80129def skeletonize_image (image : np .ndarray ) -> np .ndarray :
81130 """
82131 Apply Zhang-Suen thinning to binary image for skeletonization.
83132 Source: https://rstudio-pubs-static.s3.amazonaws.com/302782_e337cfbc5ad24922bae96ca5977f4da8.html
84-
133+
85134 >>> skeletonize_image(np.array([[np.False_, np.True_, np.False_],
86135 ... [np.True_, np.True_, np.True_],
87136 ... [np.False_, np.True_, np.False_]]))
@@ -96,7 +145,7 @@ def skeletonize_image(image: np.ndarray) -> np.ndarray:
96145 [False, False, False]])
97146 """
98147 img = image .copy ()
99- changing1 = changing2 = True
148+ changing1 = changing2 = [( - 1 , - 1 )]
100149
101150 while changing1 or changing2 :
102151
@@ -105,16 +154,20 @@ def skeletonize_image(image: np.ndarray) -> np.ndarray:
105154 rows , cols = img .shape
106155 for x in range (1 , rows - 1 ):
107156 for y in range (1 , cols - 1 ):
108- P = img [x ][y ]
109- if P != 1 :
157+ pixel = img [x ][y ]
158+ if pixel != 1 :
110159 continue
111160 neighbours_list = neighbours (img , x , y )
112161 total_transitions = transitions (neighbours_list )
113- N = sum (neighbours_list )
114- if (2 <= N <= 6 and
115- total_transitions == 1 and
116- neighbours_list [0 ] * neighbours_list [2 ] * neighbours_list [4 ] == 0 and
117- neighbours_list [2 ] * neighbours_list [4 ] * neighbours_list [6 ] == 0 ):
162+ n = sum (neighbours_list )
163+ if (
164+ 2 <= n <= 6
165+ and total_transitions == 1
166+ and neighbours_list [0 ] * neighbours_list [2 ] * neighbours_list [4 ]
167+ == 0
168+ and neighbours_list [2 ] * neighbours_list [4 ] * neighbours_list [6 ]
169+ == 0
170+ ):
118171 changing1 .append ((x , y ))
119172 for x , y in changing1 :
120173 img [x ][y ] = 0
@@ -123,16 +176,20 @@ def skeletonize_image(image: np.ndarray) -> np.ndarray:
123176 changing2 = []
124177 for x in range (1 , rows - 1 ):
125178 for y in range (1 , cols - 1 ):
126- P = img [x ][y ]
127- if P != 1 :
179+ pixel = img [x ][y ]
180+ if pixel != 1 :
128181 continue
129182 neighbours_list = neighbours (img , x , y )
130183 total_transitions = transitions (neighbours_list )
131- N = sum (neighbours_list )
132- if (2 <= N <= 6 and
133- total_transitions == 1 and
134- neighbours_list [0 ] * neighbours_list [2 ] * neighbours_list [6 ] == 0 and
135- neighbours_list [0 ] * neighbours_list [4 ] * neighbours_list [6 ] == 0 ):
184+ n = sum (neighbours_list )
185+ if (
186+ 2 <= n <= 6
187+ and total_transitions == 1
188+ and neighbours_list [0 ] * neighbours_list [2 ] * neighbours_list [6 ]
189+ == 0
190+ and neighbours_list [0 ] * neighbours_list [4 ] * neighbours_list [6 ]
191+ == 0
192+ ):
136193 changing2 .append ((x , y ))
137194 for x , y in changing2 :
138195 img [x ][y ] = 0
@@ -150,4 +207,4 @@ def skeletonize_image(image: np.ndarray) -> np.ndarray:
150207
151208 # Save the output image
152209 pil_img = Image .fromarray (output ).convert ("RGB" )
153- pil_img .save ("result_skeleton.png" )
210+ pil_img .save ("result_skeleton.png" )
0 commit comments