1
+ /*
2
+ Copyright (c) 2021- Ferhat Kurtulmuş
3
+ Boost Software License - Version 1.0 - August 17th, 2003
4
+ */
5
+ module dcv.morphology.skeletonize ;
6
+
7
+ import mir.ndslice;
8
+ import mir.rc;
9
+
10
+ @nogc nothrow :
11
+
12
+ // A rewrite of https://github.com/scikit-image/scikit-image/blob/main/skimage/morphology/_skeletonize_cy.pyx
13
+
14
+ /* * Apply fast skeletonize algorithm to given binary image.
15
+
16
+ Params:
17
+ binary = Input binary image of ubyte. Agnostic to SliceKind
18
+ Returns a refcounted Slice representing one pixel width skeletons of the binary regions. The resulting slice is Canonical.
19
+ */
20
+ Slice! (RCI ! ubyte , 2LU, Canonical) skeletonize2D(InputType)(auto ref InputType binary, int whiteValue = 255 ){
21
+
22
+ // we copy over the image into a larger version with a single pixel border
23
+ // this removes the need to handle border cases below
24
+
25
+ immutable size_t nrows = binary.shape[0 ] + 2 ;
26
+ immutable size_t ncols = binary.shape[1 ] + 2 ;
27
+
28
+ auto skeleton = uninitRCslice! ubyte (nrows, ncols);
29
+ skeleton[] = 0 ;
30
+
31
+ skeleton[1 .. $- 1 , 1 .. $- 1 ] = binary[]; // copy original data in the bordered frame
32
+
33
+ auto cleaned_skeleton = rcslice! ubyte (skeleton.shape[0 ], skeleton.shape[1 ]);
34
+ cleaned_skeleton[] = skeleton[]; // dup
35
+
36
+ bool pixel_removed = true ;
37
+
38
+ while (pixel_removed){
39
+ pixel_removed = false ;
40
+
41
+ // there are two phases, in the first phase, pixels labeled
42
+ // (see below) 1 and 3 are removed, in the second 2 and 3
43
+
44
+ // nogil can't iterate through `(True, False)` because it is a Python
45
+ // tuple. Use the fact that 0 is Falsy, and 1 is truthy in C
46
+ // for the iteration instead.
47
+ // for first_pass in (True, False):
48
+ bool first_pass;
49
+ foreach (pass_num; 0 .. 2 ){
50
+ first_pass = (pass_num == 0 );
51
+ foreach (row; 1 .. nrows- 1 ){
52
+ foreach (col; 1 .. ncols- 1 ){
53
+ // all set pixels ...
54
+
55
+ if (skeleton[row, col]){
56
+ // are correlated with a kernel
57
+ // (coefficients spread around here ...)
58
+ // to apply a unique number to every
59
+ // possible neighborhood ...
60
+
61
+ // which is used with the lut to find the
62
+ // "connectivity type"
63
+
64
+ immutable neighbors = lut[skeleton[row - 1 , col - 1 ] / whiteValue +
65
+ 2 * skeleton[row - 1 , col] / whiteValue +
66
+ 4 * skeleton[row - 1 , col + 1 ] / whiteValue +
67
+ 8 * skeleton[row, col + 1 ] / whiteValue +
68
+ 16 * skeleton[row + 1 , col + 1 ] / whiteValue +
69
+ 32 * skeleton[row + 1 , col] / whiteValue +
70
+ 64 * skeleton[row + 1 , col - 1 ] / whiteValue +
71
+ 128 * skeleton[row, col - 1 ] / whiteValue];
72
+
73
+ if (neighbors == 0 )
74
+ continue ;
75
+ else if ((neighbors == 3 ) ||
76
+ (neighbors == 1 && first_pass) ||
77
+ (neighbors == 2 && ! first_pass)){
78
+ // Remove the pixel
79
+ cleaned_skeleton[row, col] = 0 ;
80
+ pixel_removed = true ;
81
+ }
82
+ }
83
+ }
84
+ }
85
+ // once a step has been processed, the original skeleton
86
+ // is overwritten with the cleaned version
87
+ skeleton[] = cleaned_skeleton[];
88
+ }
89
+ }
90
+
91
+ // because of the dropborders the resulting slice is Canonical
92
+ // with an extra copying process it can be converted to a Contiguous Slice if this is needed.
93
+ return skeleton.dropBorders;
94
+ }
95
+
96
+ private immutable ubyte [256 ] lut = [0 , 0 , 0 , 1 , 0 , 0 , 1 , 3 , 0 , 0 , 3 , 1 , 1 , 0 ,
97
+ 1 , 3 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 2 , 0 , 2 , 0 ,
98
+ 3 , 0 , 3 , 3 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 3 , 0 ,
99
+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
100
+ 2 , 0 , 0 , 0 , 3 , 0 , 2 , 2 , 0 , 0 , 0 , 0 , 0 , 0 ,
101
+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
102
+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 2 , 0 ,
103
+ 0 , 0 , 0 , 0 , 0 , 0 , 2 , 0 , 0 , 0 , 2 , 0 , 0 , 0 ,
104
+ 3 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 3 , 0 , 0 , 0 , 3 , 0 ,
105
+ 2 , 0 , 0 , 0 , 3 , 1 , 0 , 0 , 1 , 3 , 0 , 0 , 0 , 0 ,
106
+ 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
107
+ 0 , 0 , 0 , 0 , 0 , 1 , 3 , 1 , 0 , 0 , 0 , 0 , 0 , 0 ,
108
+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 2 , 0 , 0 , 0 , 0 , 0 ,
109
+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 2 , 3 , 1 , 3 ,
110
+ 0 , 0 , 1 , 3 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 , 0 ,
111
+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
112
+ 2 , 3 , 0 , 1 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 ,
113
+ 0 , 0 , 3 , 3 , 0 , 1 , 0 , 0 , 0 , 0 , 2 , 2 , 0 , 0 ,
114
+ 2 , 0 , 0 , 0 ];
0 commit comments