Skip to content

Commit ccd99a7

Browse files
committed
🎨 Improved formatting and is compatible with black, ruff and mypy
1 parent 2cfc530 commit ccd99a7

File tree

1 file changed

+87
-30
lines changed

1 file changed

+87
-30
lines changed

digital_image_processing/morphological_operations/skeletonization_operation.py

Lines changed: 87 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
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+
58
import numpy as np
69
from PIL import Image
7-
from pathlib import Path
810

911

1012
def 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

6185
def 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

80129
def 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

Comments
 (0)