retinal-thin-vessels 1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 J-Linaris
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,100 @@
1
+ Metadata-Version: 2.4
2
+ Name: retinal_thin_vessels
3
+ Version: 1.0
4
+ Summary: A Python package for analyzing thin retinal vessels and creating vessels-thickness based weight maps
5
+ Author-email: João Paulo Menezes Linaris <joaolinaris@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/J-Linaris/retinal_thin_vessels
8
+ Project-URL: Repository, https://github.com/J-Linaris/retinal_thin_vessels
9
+ Keywords: retina,retinal,retinal-vessels,vessel-segmentation,image-analysis,medical-imaging,deep-learning,weight-map,pytorch,thin-vessels,image-segmentation,binary-masks,binary-mask
10
+ Classifier: Intended Audience :: Science/Research
11
+ Classifier: Operating System :: POSIX :: Linux
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Requires-Python: >=3.8
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: numpy
22
+ Requires-Dist: torch
23
+ Requires-Dist: scikit-image
24
+ Requires-Dist: Pillow
25
+ Dynamic: license-file
26
+
27
+ # retinal_thin_vessels
28
+
29
+ A Python package for computing the recall and precision scores specifically on thin vessels in retinal images, as detailed in the paper "Vessel-Width-Based Metrics and Weight Masks for Retinal Blood Vessel Segmentation", published in WUW-SIBGRAPI 2025. The package also includes a function for visualizing thickness-based filtered masks, the basic structure for computing the proposed metrics.
30
+
31
+ ## Package installation
32
+
33
+ ```bash
34
+ pip install retinal_thin_vessels
35
+ ```
36
+
37
+ ## Usage Demonstration with DRIVE and CHASEDB1
38
+
39
+ To ensure the metrics are reliable, it is important to visualize the specific thin-vessel mask used by the given functions in their calculations. Therefore, a core function, get_thin_vessels_mask(), is also provided. This function takes a standard segmentation mask and returns a new mask containing only the thin vessels.
40
+
41
+ The following code demonstrates how to generate this filtered mask using images from two public datasets: DRIVE and CHASEDB1.
42
+
43
+ ```python
44
+ from PIL import Image
45
+ from retinal_thin_vessels.core import get_thin_vessels_mask
46
+ from retinal_thin_vessels.metrics import recall_thin_vessels, precision_thin_vessels
47
+ from sklearn.metrics import recall_score, precision_score
48
+ ```
49
+
50
+ ```python
51
+ # Import the original segmentation masks
52
+ seg_DRIVE = Image.open(f"tests/imgs/DRIVE_seg_example.png")
53
+ seg_CDB1 = Image.open(f"tests/imgs/CHASEDB1_seg_example.png")
54
+
55
+ # generates new masks containing only thin vessels
56
+ thin_vessels_seg_DRIVE = get_thin_vessels_mask(seg_DRIVE)
57
+ thin_vessels_seg_CDB1 = get_thin_vessels_mask(seg_CDB1)
58
+
59
+ # Display the original segmentation mask and the resulting thin-vessel-only mask for comparison
60
+ seg_DRIVE.show()
61
+ img_DRIVE = Image.fromarray(thin_vessels_seg_DRIVE)
62
+ img_DRIVE.show()
63
+
64
+ seg_CDB1.show()
65
+ img_CDB1 = Image.fromarray(thin_vessels_seg_CDB1)
66
+ img_CDB1.show()
67
+ ```
68
+ <img src="tests/imgs/DRIVE_seg_example.png" alt="DRIVE_thin_vessels_example" width=300/>
69
+ <img src="tests/imgs/DRIVE_seg_thin_example.png" alt="DRIVE_thin_vessels_example" width=300/>
70
+ <img src="tests/imgs/CHASEDB1_seg_example.png" alt="CHASEDB1_thin_vessels_example" width=300/>
71
+ <img src="tests/imgs/CHASEDB1_seg_thin_example.png" alt="CHASEDB1_thin_vessels_example" width=300/>
72
+
73
+ Furthermore, to demonstrate the metric calculation functions, you can run the code below. It compares the overall metrics (calculated with scikit-learn) to the thin-vessel-specific metrics calculated by this package.
74
+
75
+ ```python
76
+ # Load the ground truth segmentation mask and a sample prediction
77
+ pred = Image.open(f"tests/imgs/DRIVE_pred_example.png")
78
+ seg_DRIVE = Image.open(f"tests/imgs/DRIVE_seg_example.png").resize((pred.size), Image.NEAREST)
79
+
80
+ # Binarize images to a 0/1 format for scikit-learn compatibility
81
+ seg_DRIVE = np.where(np.array(seg_DRIVE) > 0, 1, 0)
82
+ pred = np.where(np.array(pred) > 0, 1, 0)
83
+
84
+ # Compute and print the metrics
85
+ print(f"Overall Recall score: {recall_score(seg_DRIVE.flatten(), pred.flatten())}")
86
+ print(f"Recall score on thin vessels: {recall_thin_vessels(seg_DRIVE, pred)}")
87
+ print("-" * 30)
88
+ print(f"Overall Precision score: {precision_score(seg_DRIVE.flatten(), pred.flatten())}")
89
+ print(f"Precision score on thin Vessels: {precision_thin_vessels(seg_DRIVE, pred)}")
90
+ ```
91
+
92
+ If the program is running correctly with the provided sample images, the results should be similar to this:
93
+
94
+ ```bash
95
+ Overall Recall score: 0.8553852359822509
96
+ Recall score on thin vessels: 0.751244555071562
97
+ ------------------------------
98
+ Overall Precision score: 0.8422369623068674
99
+ Precision score on thin Vessels: 0.6527915897144481
100
+ ```
@@ -0,0 +1,74 @@
1
+ # retinal_thin_vessels
2
+
3
+ A Python package for computing the recall and precision scores specifically on thin vessels in retinal images, as detailed in the paper "Vessel-Width-Based Metrics and Weight Masks for Retinal Blood Vessel Segmentation", published in WUW-SIBGRAPI 2025. The package also includes a function for visualizing thickness-based filtered masks, the basic structure for computing the proposed metrics.
4
+
5
+ ## Package installation
6
+
7
+ ```bash
8
+ pip install retinal_thin_vessels
9
+ ```
10
+
11
+ ## Usage Demonstration with DRIVE and CHASEDB1
12
+
13
+ To ensure the metrics are reliable, it is important to visualize the specific thin-vessel mask used by the given functions in their calculations. Therefore, a core function, get_thin_vessels_mask(), is also provided. This function takes a standard segmentation mask and returns a new mask containing only the thin vessels.
14
+
15
+ The following code demonstrates how to generate this filtered mask using images from two public datasets: DRIVE and CHASEDB1.
16
+
17
+ ```python
18
+ from PIL import Image
19
+ from retinal_thin_vessels.core import get_thin_vessels_mask
20
+ from retinal_thin_vessels.metrics import recall_thin_vessels, precision_thin_vessels
21
+ from sklearn.metrics import recall_score, precision_score
22
+ ```
23
+
24
+ ```python
25
+ # Import the original segmentation masks
26
+ seg_DRIVE = Image.open(f"tests/imgs/DRIVE_seg_example.png")
27
+ seg_CDB1 = Image.open(f"tests/imgs/CHASEDB1_seg_example.png")
28
+
29
+ # generates new masks containing only thin vessels
30
+ thin_vessels_seg_DRIVE = get_thin_vessels_mask(seg_DRIVE)
31
+ thin_vessels_seg_CDB1 = get_thin_vessels_mask(seg_CDB1)
32
+
33
+ # Display the original segmentation mask and the resulting thin-vessel-only mask for comparison
34
+ seg_DRIVE.show()
35
+ img_DRIVE = Image.fromarray(thin_vessels_seg_DRIVE)
36
+ img_DRIVE.show()
37
+
38
+ seg_CDB1.show()
39
+ img_CDB1 = Image.fromarray(thin_vessels_seg_CDB1)
40
+ img_CDB1.show()
41
+ ```
42
+ <img src="tests/imgs/DRIVE_seg_example.png" alt="DRIVE_thin_vessels_example" width=300/>
43
+ <img src="tests/imgs/DRIVE_seg_thin_example.png" alt="DRIVE_thin_vessels_example" width=300/>
44
+ <img src="tests/imgs/CHASEDB1_seg_example.png" alt="CHASEDB1_thin_vessels_example" width=300/>
45
+ <img src="tests/imgs/CHASEDB1_seg_thin_example.png" alt="CHASEDB1_thin_vessels_example" width=300/>
46
+
47
+ Furthermore, to demonstrate the metric calculation functions, you can run the code below. It compares the overall metrics (calculated with scikit-learn) to the thin-vessel-specific metrics calculated by this package.
48
+
49
+ ```python
50
+ # Load the ground truth segmentation mask and a sample prediction
51
+ pred = Image.open(f"tests/imgs/DRIVE_pred_example.png")
52
+ seg_DRIVE = Image.open(f"tests/imgs/DRIVE_seg_example.png").resize((pred.size), Image.NEAREST)
53
+
54
+ # Binarize images to a 0/1 format for scikit-learn compatibility
55
+ seg_DRIVE = np.where(np.array(seg_DRIVE) > 0, 1, 0)
56
+ pred = np.where(np.array(pred) > 0, 1, 0)
57
+
58
+ # Compute and print the metrics
59
+ print(f"Overall Recall score: {recall_score(seg_DRIVE.flatten(), pred.flatten())}")
60
+ print(f"Recall score on thin vessels: {recall_thin_vessels(seg_DRIVE, pred)}")
61
+ print("-" * 30)
62
+ print(f"Overall Precision score: {precision_score(seg_DRIVE.flatten(), pred.flatten())}")
63
+ print(f"Precision score on thin Vessels: {precision_thin_vessels(seg_DRIVE, pred)}")
64
+ ```
65
+
66
+ If the program is running correctly with the provided sample images, the results should be similar to this:
67
+
68
+ ```bash
69
+ Overall Recall score: 0.8553852359822509
70
+ Recall score on thin vessels: 0.751244555071562
71
+ ------------------------------
72
+ Overall Precision score: 0.8422369623068674
73
+ Precision score on thin Vessels: 0.6527915897144481
74
+ ```
@@ -0,0 +1,41 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "retinal_thin_vessels"
7
+ version = "1.0"
8
+ authors = [
9
+ {name = "João Paulo Menezes Linaris", email = "joaolinaris@gmail.com"},
10
+ ]
11
+ description = "A Python package for analyzing thin retinal vessels and creating vessels-thickness based weight maps"
12
+ readme = "README.md"
13
+ requires-python = ">=3.8"
14
+ license = "MIT"
15
+ license-files = ["LICENSE"]
16
+ keywords = ["retina", "retinal", "retinal-vessels", "vessel-segmentation",
17
+ "image-analysis", "medical-imaging", "deep-learning",
18
+ "weight-map", "pytorch", "thin-vessels", "image-segmentation", "binary-masks",
19
+ "binary-mask"]
20
+ classifiers = [
21
+ 'Intended Audience :: Science/Research',
22
+ 'Operating System :: POSIX :: Linux',
23
+ 'Programming Language :: Python :: 3',
24
+ 'Programming Language :: Python :: 3.8',
25
+ 'Programming Language :: Python :: 3.9',
26
+ 'Programming Language :: Python :: 3.10',
27
+ 'Programming Language :: Python :: 3.11',
28
+ 'Programming Language :: Python :: 3.12',
29
+ ]
30
+
31
+ dependencies = [
32
+ "numpy",
33
+ "torch",
34
+ "scikit-image",
35
+ "Pillow",
36
+ ]
37
+
38
+ [project.urls]
39
+ Homepage = "https://github.com/J-Linaris/retinal_thin_vessels"
40
+ Repository = "https://github.com/J-Linaris/retinal_thin_vessels"
41
+
@@ -0,0 +1,6 @@
1
+ __all__ = [
2
+ "core",
3
+ "metrics",
4
+ "input_transformation",
5
+ "get_relation"
6
+ ]
@@ -0,0 +1,182 @@
1
+ import numpy as np
2
+ from skimage.morphology import medial_axis, area_closing
3
+ from PIL import Image
4
+ from retinal_thin_vessels.input_transformation import prepare_ground_truth, prepare_prediction
5
+ from retinal_thin_vessels.external.DSE_skeleton_pruning.dsepruning.dsepruning import skel_pruning_DSE
6
+
7
+ def __get_shift_tuples(value):
8
+
9
+ # Sets the radius
10
+ radius = int(np.ceil(value))
11
+
12
+ # Creates all combinations of shifts based on the raidus
13
+ x_shifts, y_shifts = np.meshgrid(np.arange(-radius, radius + 1), np.arange(-radius, radius + 1))
14
+
15
+ # Stacks all shifts together on a list
16
+ shifts = np.column_stack((x_shifts.ravel(), y_shifts.ravel()))
17
+ # shifts = [(dx, dy) for dx, dy in shifts if (dx, dy) != (0, 0)] # Returns without (0,0)
18
+
19
+ return shifts
20
+
21
+ def __get_filtered_mask(seg_mask, ceil=1.0):
22
+ """
23
+ Returns a [H,W] shaped numpy array containing the thin-vessels only
24
+ mask.
25
+ """
26
+
27
+ # Application of closing on the segmentation mask
28
+ closed_seg_mask = area_closing(seg_mask)
29
+
30
+ # Obtaining the skeleton
31
+ skeleton_medial_axis, distances = medial_axis(closed_seg_mask, return_distance=True)
32
+
33
+ # Skeleton pruning
34
+ skeleton_medial_axis = skel_pruning_DSE(skeleton_medial_axis, distances, np.ceil(distances.max()))
35
+
36
+ # Compute the skeleton with the values of the distances
37
+ dist_skel = np.where(skeleton_medial_axis>0, distances, 0)
38
+
39
+ # Get unique values of dist_skel excluding 0 (values of the radius of vessels)
40
+ values_dist_skel = np.unique(dist_skel)[1:]
41
+
42
+ #~~~~~~~~~~~~~~~~~~~~~~~Segmentation mask recriation with thin vessels only~~~~~~~~~~~~~~~~~~
43
+
44
+ # Initializes the two necessary masks
45
+ filtered_seg_mask = np.zeros(dist_skel.shape)
46
+ reconstructed_seg_mask = np.zeros(dist_skel.shape)
47
+
48
+ height = len(dist_skel)
49
+ width = len(dist_skel[0])
50
+
51
+ # Reconstructs each mask using a sphere of varying radius
52
+ for value in values_dist_skel:
53
+ shifts = __get_shift_tuples(value)
54
+
55
+ for i in range(height):
56
+ for j in range(width):
57
+
58
+ if dist_skel[i][j] == value :
59
+
60
+ if value <= ceil:
61
+ for dx, dy in shifts:
62
+ if 0 <= i+dx < height and 0 <= j+dy < width:
63
+ filtered_seg_mask[i+dx][j+dy] = 255
64
+
65
+ for dx, dy in shifts:
66
+ if 0 <= i+dx < height and 0 <= j+dy < width:
67
+ reconstructed_seg_mask[i+dx][j+dy] = 255
68
+
69
+ # Filtering to get exactly the shape of the vessels intead of something rounded
70
+ filtered_seg_mask = np.where((seg_mask>0) & (filtered_seg_mask>0), 255, 0).astype(np.uint8)
71
+ reconstructed_seg_mask = np.where((seg_mask>0) & (reconstructed_seg_mask>0), 255, 0).astype(np.uint8)
72
+
73
+ # Gets exactly the excluded vessels
74
+ excluded_vessels = np.where((seg_mask>0) & (reconstructed_seg_mask==0), 255, 0).astype(np.uint8)
75
+
76
+ # Concatenation of excluded_vessels seg mask with the thin vessels mask (we garantee they are small due to
77
+ # their exclusion in the prunning/closing process)
78
+ filtered_seg_mask = np.where((filtered_seg_mask>0) | (excluded_vessels>0), 255, 0).astype(np.uint8)
79
+
80
+ return filtered_seg_mask
81
+
82
+
83
+ def get_thin_vessels_mask(seg_mask, ceil=1.0, mask_type="ground_truth"):
84
+ """
85
+ Returns a numpy array containing the thin-vessels only mask.
86
+
87
+ The Input is expected to be a segmentation mask, therefore, the
88
+ function accepts inputs with:
89
+
90
+ -> shape: (H,W); (1,H,W); (N,1,H,W)
91
+
92
+ -> values: BINARY
93
+
94
+ -> mask_type: "ground_truth" or "prediction" (affects how we
95
+ transform the provided mask beofre computing the
96
+ filtered one. If equal to "groun_truth", will
97
+ apply a threshold of 0 for classifying pixels in the
98
+ image)
99
+
100
+
101
+ Thin vessels are defined as those whose radius is less than or
102
+ equal to 'ceil'.
103
+ """
104
+
105
+ # Input value verification
106
+ accepted_mask_type_values = ["ground_truth", "prediction"]
107
+
108
+ if mask_type not in accepted_mask_type_values:
109
+ raise ValueError(f"Expected valid mask type. Accepted values: {accepted_mask_type_values}. Got: {mask_type}")
110
+
111
+ # Input preparation
112
+ if mask_type == "ground_truth":
113
+ seg_mask = prepare_ground_truth(seg_mask)
114
+ else:
115
+ seg_mask = prepare_prediction(seg_mask)
116
+
117
+ input_dimension = seg_mask.ndim
118
+
119
+ # Get the filtered mask(s)
120
+ filtered_seg_mask = []
121
+ if input_dimension == 4:
122
+
123
+ # Obtain each mask separately
124
+ for i in range(len(seg_mask)):
125
+ #[H,W] ---> [1,H,W]
126
+ unique_filtered_seg_mask = np.expand_dims(__get_filtered_mask(seg_mask[i][0], ceil), axis=0)
127
+
128
+ # Appends to the filtered_seg_mask vector (naturally, this vector will be [N,1,H,W])
129
+ filtered_seg_mask.append(unique_filtered_seg_mask)
130
+
131
+ # Converts into a numpy array
132
+ filtered_seg_mask = np.array(filtered_seg_mask)
133
+
134
+ elif input_dimension == 3:
135
+
136
+ # Obtains the mask using the reduced dimension mask
137
+ filtered_seg_mask = __get_filtered_mask(seg_mask[0], ceil)
138
+
139
+ # Expands the dimension again for maintaining consistency with input shape
140
+ filtered_seg_mask = np.expand_dims(filtered_seg_mask, axis=0)
141
+
142
+ else:
143
+ # Obtains the mask
144
+ filtered_seg_mask = __get_filtered_mask(seg_mask, ceil)
145
+
146
+ return filtered_seg_mask
147
+
148
+ def main():
149
+
150
+ # Loads the data
151
+ example_components_path = "../tests/imgs/"
152
+ seg = Image.open(f"{example_components_path}DRIVE_seg_example.png")
153
+
154
+ # Gets the filtered mask with only thin vessels
155
+
156
+ #-----> for a [N,1,H,W] shape input
157
+ seg_4_dims = np.expand_dims(np.expand_dims(np.array(seg), axis=0),axis=0)
158
+ seg_4_dims = np.concatenate((seg_4_dims,seg_4_dims,seg_4_dims), axis=0)
159
+
160
+ thin_vessels_seg = get_thin_vessels_mask(seg_4_dims)
161
+ print(f"Test 1: original shape: {seg_4_dims.shape} ---> filtered mask shape: {thin_vessels_seg.shape}")
162
+
163
+ #-----> for a [1,H,W] shape input
164
+ seg_3_dims = np.expand_dims(np.array(seg), axis=0)
165
+
166
+ thin_vessels_seg = get_thin_vessels_mask(seg_3_dims)
167
+ print(f"Test 2: original shape: {seg_3_dims.shape} ---> filtered mask shape: {thin_vessels_seg.shape}")
168
+
169
+ #-----> for a [H,W] shape input
170
+ thin_vessels_seg = get_thin_vessels_mask(seg)
171
+ print(f"Test 3: original shape: {np.array(seg).shape} ---> filtered mask shape: {thin_vessels_seg.shape}")
172
+
173
+ # Shows the filtered segmentation mask
174
+ print("Showing the filtered segmentation mask with thin vessels only.")
175
+ img = Image.fromarray(thin_vessels_seg)
176
+ img.show()
177
+
178
+ exit(0)
179
+
180
+
181
+ if __name__ == "__main__":
182
+ main()
@@ -0,0 +1 @@
1
+ from dsepruning.dsepruning import skel_pruning_DSE
@@ -0,0 +1,128 @@
1
+ import sknw
2
+ import numpy as np
3
+ from skimage.draw import line
4
+ from dsepruning.dse_helper import recnstrc_by_disk, get_weight
5
+ # from .dse_helper import recnstrc_by_disk, get_weight -----> original (didn't work)
6
+
7
+ def flatten(l):
8
+ return [item for sublist in l for item in sublist]
9
+
10
+ def _remove_branch_by_DSE(G, recn, dist, max_px_weight, checked_terminal=set()):
11
+ deg = dict(G.degree())
12
+ terminal_points = [i for i, d in deg.items() if d == 1]
13
+ edges = list(G.edges())
14
+ # temporary branch reconstruction mask
15
+ branch_recn = np.zeros_like(recn, dtype=np.int32)
16
+ branch_recn = np.zeros_like(recn, dtype=np.int32)
17
+ for s, e in edges:
18
+ if s == e:
19
+ G.remove_edge(s, e)
20
+ continue
21
+ vals = flatten([[v] for v in G[s][e].values()])
22
+ for ix, val in enumerate(vals):
23
+ if s not in terminal_points and e not in terminal_points:
24
+ continue
25
+ if s in checked_terminal or e in checked_terminal:
26
+ continue
27
+ pts = val.get('pts').tolist()
28
+ pts.append(G.nodes[s]['o'].astype(np.int32).tolist())
29
+ pts.append(G.nodes[e]['o'].astype(np.int32).tolist())
30
+ recnstrc_by_disk(np.array(pts, dtype=np.int32), dist, branch_recn)
31
+ weight = get_weight(recn, branch_recn)
32
+ if s in terminal_points:
33
+ checked_terminal.add(s)
34
+ if weight < max_px_weight:
35
+ G.remove_node(s)
36
+ recn = recn - branch_recn
37
+ if e in terminal_points:
38
+ checked_terminal.add(e)
39
+ if weight < max_px_weight:
40
+ G.remove_node(e)
41
+ recn = recn - branch_recn
42
+ return G, recn
43
+
44
+ def _remove_mid_node(G):
45
+ start_index = 0
46
+ while True:
47
+ nodes = [x for x in G.nodes() if G.degree(x) == 2]
48
+ if len(nodes) == start_index:
49
+ break
50
+ i = nodes[start_index]
51
+ nbs = list(G[i])
52
+ # assert len(nbs)==2, 'degree not match'
53
+ if len(nbs) != 2:
54
+ start_index = start_index + 1
55
+ continue
56
+
57
+ edge1 = G[i][nbs[0]][0]
58
+ edge2 = G[i][nbs[1]][0]
59
+
60
+ s1, e1 = edge1['pts'][0], edge1['pts'][-1]
61
+ s2, e2 = edge2['pts'][0], edge2['pts'][-1]
62
+ dist = np.array(list(map(np.linalg.norm, [s1-s2, e1-e2, s1-e2, s2-e1])))
63
+ if dist.argmin() == 0:
64
+ line = np.concatenate([edge1['pts'][::-1], [G.nodes[i]['o'].astype(np.int32)], edge2['pts']], axis=0)
65
+ elif dist.argmin() == 1:
66
+ line = np.concatenate([edge1['pts'], [G.nodes[i]['o'].astype(np.int32)], edge2['pts'][::-1]], axis=0)
67
+ elif dist.argmin() == 2:
68
+ line = np.concatenate([edge2['pts'], [G.nodes[i]['o'].astype(np.int32)], edge1['pts']], axis=0)
69
+ elif dist.argmin() == 3:
70
+ line = np.concatenate([edge1['pts'], [G.nodes[i]['o'].astype(np.int32)], edge2['pts']], axis=0)
71
+ G.add_edge(nbs[0], nbs[1], weight=edge1['weight']+edge2['weight'], pts=line)
72
+ G.remove_node(i)
73
+ return G
74
+
75
+ def skel_pruning_DSE(skel, dist, min_area_px=100, return_graph=False):
76
+ """Skeleton pruning using dse
77
+
78
+ Arguments:
79
+ skel {ndarray} -- skeleton obtained from skeletonization algorithm
80
+ dist {ndarray} -- distance transfrom map
81
+
82
+ Keyword Arguments:
83
+ min_area_px {int} -- branch reconstruction weights, measured by pixel area. Branch reconstruction weights smaller than this threshold will be pruned. (default: {100})
84
+ return_graph {bool} -- return graph
85
+
86
+ Returns:
87
+ ndarray -- pruned skeleton map
88
+ """
89
+ graph = sknw.build_sknw(skel, multi=True)
90
+ dist = dist.astype(np.int32)
91
+ graph = _remove_mid_node(graph)
92
+ edges = list(set(graph.edges()))
93
+ pts = []
94
+ for s, e in edges:
95
+ vals = flatten([[v] for v in graph[s][e].values()])
96
+ for ix, val in enumerate(vals):
97
+ pts.extend(val.get('pts').tolist())
98
+ pts.append(graph.nodes[s]['o'].astype(np.int32).tolist())
99
+ pts.append(graph.nodes[e]['o'].astype(np.int32).tolist())
100
+ recnstrc = np.zeros_like(dist, dtype=np.int32)
101
+ recnstrc_by_disk(np.array(pts, dtype=np.int32), dist, recnstrc)
102
+ num_nodes = len(graph.nodes())
103
+ checked_terminal = set()
104
+ while True:
105
+ # cannot combine with other pruning method because the reconstruction map is not updated in other approach
106
+ graph, recnstrc = _remove_branch_by_DSE(graph, recnstrc, dist, min_area_px, checked_terminal=checked_terminal)
107
+ if len(graph.nodes()) == num_nodes:
108
+ break
109
+ graph = _remove_mid_node(graph)
110
+ num_nodes = len(graph.nodes())
111
+ if return_graph:
112
+ return graph2im(graph, skel.shape), graph
113
+ else:
114
+ return graph2im(graph, skel.shape)
115
+
116
+ def graph2im(graph, shape):
117
+ mask = np.zeros(shape, dtype=bool)
118
+ for s,e in graph.edges():
119
+ vals = flatten([[v] for v in graph[s][e].values()])
120
+ for val in vals:
121
+ coords = val.get('pts')
122
+ coords_1 = np.roll(coords, -1, axis=0)
123
+ for i in range(len(coords)-1):
124
+ rr, cc = line(*coords[i], *coords_1[i])
125
+ mask[rr, cc] = True
126
+ mask[tuple(graph.nodes[s]['pts'].T.tolist())] = True
127
+ mask[tuple(graph.nodes[e]['pts'].T.tolist())] = True
128
+ return mask
@@ -0,0 +1,14 @@
1
+ from setuptools import setup, Extension
2
+ import numpy
3
+
4
+ setup(
5
+ ext_modules=[
6
+ Extension(
7
+ 'dsepruning.dse_helper',
8
+ ['dsepruning/dse_helper.pyx'],
9
+ include_dirs=[numpy.get_include()],
10
+ extra_compile_args=['-fopenmp'],
11
+ extra_link_args=['-fopenmp']
12
+ )
13
+ ]
14
+ )
@@ -0,0 +1,35 @@
1
+ import numpy as np
2
+ from skimage.morphology import medial_axis, area_closing
3
+ from PIL import Image
4
+ from retinal_thin_vessels.external.DSE_skeleton_pruning.dsepruning.dsepruning import skel_pruning_DSE
5
+
6
+ def main():
7
+
8
+ seg_mask = np.array(Image.open("imgs/DRIVE_seg_example.png"))
9
+ print(seg_mask.shape)
10
+ exit(0)
11
+ # Application of closing on the segmentation mask
12
+ closed_seg_mask = area_closing(seg_mask)
13
+
14
+ # Obtaining the skeleton
15
+ skeleton_medial_axis, distances = medial_axis(closed_seg_mask, return_distance=True)
16
+
17
+ # Skeleton prunning
18
+ skeleton_medial_axis = skel_pruning_DSE(skeleton_medial_axis, distances, np.ceil(distances.max()))
19
+
20
+ # Compute the skeleton with the values of the distances
21
+ dist_skel = np.where(skeleton_medial_axis>0, distances, 0)
22
+
23
+ # Get unique values of dist_skel excluding 0 (values of the radius of vessels)
24
+ values_dist_skel = np.unique(dist_skel)[1:]
25
+
26
+ #~~~~~~~~~~~~~~~~~~~~~~~Segmentation mask recriation with thin vessels only~~~~~~~~~~~~~~~~~~
27
+
28
+ # Initializes the two necessary masks
29
+ filtered_seg_mask = np.zeros(dist_skel.shape)
30
+ reconstructed_seg_mask = np.zeros(dist_skel.shape)
31
+
32
+
33
+ exit(0)
34
+
35
+ main()
@@ -0,0 +1,88 @@
1
+ import torch
2
+ import numpy as np
3
+ from PIL import Image
4
+
5
+ def _to_numpy(arr):
6
+ """
7
+ Converts torch.Tensor or PIL.Image
8
+ to numpy array if needed.
9
+ """
10
+ if isinstance(arr, Image.Image):
11
+ arr = np.array(arr, copy=True)
12
+ elif isinstance(arr, torch.Tensor):
13
+ arr = arr.detach().cpu().numpy()
14
+ elif not isinstance(arr, np.ndarray):
15
+ raise TypeError(f"Unsupported input type: {type(arr)}")
16
+ return arr
17
+
18
+ def _verify_input_validity(seg_mask, input_type="y_true"):
19
+ """
20
+ Inputs can be NumPy arrays,
21
+ torch.Tensors, or PIL Images.
22
+
23
+ (1, H, W) or (N,1,H,W) or (H,W)
24
+ Inputs must have shape: (1, H, W), with values in {0, 1}, {0, 255},
25
+ or [0.0, 1.0] for probability maps.
26
+
27
+ Thin vessels are defined as those with radius less than or
28
+ equal to ceil.
29
+ """
30
+
31
+ seg_mask = _to_numpy(seg_mask)
32
+
33
+ # Ensures inputs have valid shape
34
+ if (int(seg_mask.ndim) not in [2,3,4]) or (seg_mask.ndim == 3 and seg_mask.shape[0] != 1) or (seg_mask.ndim == 4 and seg_mask.shape[1] != 1):
35
+ raise ValueError(f"Accepted shapes: (1, H, W) or (N,1,H,W) or (H,W). Got seg_mask: {seg_mask.shape} for {input_type}")
36
+
37
+ # Verifies the validity of the values in the passed array
38
+ input_values = np.unique(seg_mask)
39
+
40
+ if len(input_values) != 2:
41
+ raise ValueError(f"Expected binary input for {input_type}. Got input values: {input_values}")
42
+
43
+ # Binarize input if necessary
44
+ if (input_type == "y_true" and int(input_values[0]) != 0) or (input_type == "y_pred" and int(input_values[0]) != 0):
45
+ seg_mask = (seg_mask - seg_mask.min())/(seg_mask.max()-seg_mask.min())
46
+
47
+ return seg_mask
48
+
49
+ def prepare_ground_truth(y_true):
50
+
51
+ # Verifies validity of y_true
52
+ y_true = _verify_input_validity(y_true, "y_true")
53
+
54
+ # # Removes the channel dimension for input of shape [1,H,W]
55
+ # if y_true.ndim == 3:
56
+ # y_true = y_true[0] #[1,H,W] ---> [H,W]
57
+
58
+ # Ensures the values belong to {0,1}
59
+ y_true = (y_true > 0).astype(np.uint8)
60
+
61
+ return y_true
62
+
63
+ def prepare_prediction(y_pred):
64
+
65
+ # Verifies validity of y_true
66
+ y_pred = _verify_input_validity(y_pred, "y_pred")
67
+
68
+ # # Removes the channel dimension for input of shape [1,H,W]
69
+ # if y_pred.ndim == 3:
70
+ # y_pred = y_pred[0] #[1,H,W] ---> [H,W]
71
+
72
+ # Ensures the values belong to {0,1}
73
+ y_pred = (y_pred > 0.5).astype(np.uint8)
74
+
75
+ return y_pred
76
+
77
+ def prepare_input(y_true, y_pred):
78
+
79
+ # Prepare both inputs
80
+ y_true, y_pred = prepare_ground_truth(y_true), prepare_prediction(y_pred)
81
+
82
+ # Ensures inputs have the same dimensions (Height and Width)
83
+ # if (y_true.shape[-1] != y_pred.shape[-1] or y_true.shape[-2] != y_pred.shape[-2]):
84
+ if y_true.shape != y_pred.shape:
85
+ raise ValueError(f"Expected both inputs to have the same dimensions. Got y_true: {y_true.shape}, y_pred: {y_pred.shape}")
86
+
87
+
88
+ return y_true, y_pred
@@ -0,0 +1,172 @@
1
+ import numpy as np
2
+ from PIL import Image
3
+ from retinal_thin_vessels.core import get_thin_vessels_mask
4
+ from retinal_thin_vessels.input_transformation import prepare_input
5
+ import time
6
+
7
+ def __recall_thin_vessels_single_image(y_true, y_pred, ceil=1.0):
8
+
9
+ #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Input preparation~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
10
+
11
+ true_copy, pred_copy = prepare_input(y_true, y_pred)
12
+
13
+ new_seg_true = get_thin_vessels_mask(true_copy, ceil)
14
+
15
+ # Calculates Recall
16
+ tp = np.sum((new_seg_true > 0) & (pred_copy > 0))
17
+ fn = np.sum((new_seg_true > 0) & (pred_copy == 0))
18
+
19
+ return tp/(tp+fn)
20
+
21
+ def __precision_thin_vessels_single_image(y_true, y_pred, ceil=1.0):
22
+
23
+ #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Input preparation~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
24
+
25
+ true_copy, pred_copy = prepare_input(y_true, y_pred)
26
+
27
+ new_seg_pred = get_thin_vessels_mask(pred_copy, ceil)
28
+
29
+ # Calculates Precision
30
+ tp = np.sum((new_seg_pred > 0) & (true_copy > 0))
31
+ fp = np.sum((new_seg_pred > 0) & (true_copy == 0))
32
+
33
+ return tp/(tp+fp)
34
+
35
+ def __f1_thin_vessels_single_image(y_true, y_pred, ceil=1.0):
36
+
37
+ r = __recall_thin_vessels_single_image(y_true, y_pred, ceil)
38
+ p = __precision_thin_vessels_single_image(y_true, y_pred, ceil)
39
+ f1 = 2*p*r/(p+r)
40
+
41
+ return f1
42
+
43
+ def recall_thin_vessels(y_true, y_pred, ceil=1.0):
44
+ """
45
+ Returns the recall score on thin vessels given the predicted and
46
+ the ground-truth segmentation masks. Inputs can be NumPy arrays,
47
+ torch.Tensors, or PIL Images.
48
+
49
+ Inputs must have shape: (1, H, W), with values in {0, 1}, {0, 255},
50
+ or [0.0, 1.0] for probability maps.
51
+
52
+ Thin vessels are defined as those whose radius is less than or
53
+ equal to 'ceil'.
54
+ """
55
+ # Prepares the input
56
+
57
+ y_true, y_pred = prepare_input(y_true, y_pred)
58
+
59
+ # Computes the metric
60
+ inputs_dimension = y_true.ndim
61
+ num_imgs = len(y_true)
62
+
63
+ if inputs_dimension == 4:
64
+ recall = 0
65
+ for i in range(num_imgs):
66
+ recall+=__recall_thin_vessels_single_image(y_true[i][0], y_pred[i][0], ceil)
67
+ recall/=num_imgs
68
+
69
+ else:
70
+ if inputs_dimension == 3:
71
+ y_true = y_true[0]
72
+ y_pred = y_pred[0]
73
+
74
+ recall = __recall_thin_vessels_single_image(y_true, y_pred, ceil)
75
+
76
+ return recall
77
+
78
+ def precision_thin_vessels(y_true, y_pred, ceil=1.0):
79
+ """
80
+ Returns the precision score on thin vessels given the predicted and
81
+ the ground-truth segmentation masks. Inputs can be NumPy arrays,
82
+ torch.Tensors, or PIL Images.
83
+
84
+ Inputs must have shape: (1, H, W), with values in {0, 1}, {0, 255},
85
+ or [0.0, 1.0] for probability maps.
86
+
87
+ Thin vessels are defined as those whose radius is less than or
88
+ equal to 'ceil'.
89
+ """
90
+
91
+ # Prepares the input
92
+
93
+ y_true, y_pred = prepare_input(y_true, y_pred)
94
+
95
+ # Computes the metric
96
+ inputs_dimension = y_true.ndim
97
+ num_imgs = len(y_true)
98
+
99
+ if inputs_dimension == 4:
100
+ precision = 0
101
+ for i in range(num_imgs):
102
+ precision+=__precision_thin_vessels_single_image(y_true[i][0], y_pred[i][0], ceil)
103
+ precision/=num_imgs
104
+
105
+ else:
106
+ if inputs_dimension == 3:
107
+ y_true = y_true[0]
108
+ y_pred = y_pred[0]
109
+
110
+ precision = __precision_thin_vessels_single_image(y_true, y_pred, ceil)
111
+
112
+ return precision
113
+
114
+
115
+ def f1_thin_vessels(y_true, y_pred, ceil):
116
+ """
117
+ Returns the f1-score on thin vessels given the predicted and
118
+ the ground-truth segmentation masks. Inputs can be NumPy arrays,
119
+ torch.Tensors, or PIL Images.
120
+
121
+ Inputs must have shape: (1, H, W), with values in {0, 1}, {0, 255},
122
+ or [0.0, 1.0] for probability maps.
123
+
124
+ Thin vessels are defined as those whose radius is less than or
125
+ equal to 'ceil'.
126
+ """
127
+ # Prepares the input
128
+ y_true, y_pred = prepare_input(y_true, y_pred)
129
+
130
+ # Computes the metric
131
+ inputs_dimension = y_true.ndim
132
+ num_imgs = len(y_true)
133
+
134
+ if inputs_dimension == 4:
135
+ f1 = 0
136
+ for i in range(num_imgs):
137
+ f1 += __f1_thin_vessels_single_image(y_true[i][0], y_pred[i][0], ceil)
138
+
139
+ f1 /= num_imgs
140
+
141
+ else:
142
+ if inputs_dimension == 3:
143
+ y_true = y_true[0]
144
+ y_pred = y_pred[0]
145
+
146
+ f1 = __f1_thin_vessels_single_image(y_true, y_pred, ceil)
147
+
148
+ return f1
149
+
150
+
151
+ def main():
152
+
153
+ example_components_path = "../tests/imgs/"
154
+ seg = Image.open(f"{example_components_path}DRIVE_seg_example.png")
155
+ pred = Image.open(f"{example_components_path}DRIVE_pred_example.png")
156
+
157
+ ti = time.time()
158
+ print(f"Precision on thin vessels: {precision_thin_vessels(seg.resize(pred.size, Image.NEAREST), pred)}")
159
+ tf = time.time()
160
+ delta = tf-ti
161
+ print(f"Running time for image of shape {seg.size}: {delta:.4f} sec")
162
+
163
+ ti = time.time()
164
+ print(f"Recall on thin vessels: {recall_thin_vessels(seg.resize(pred.size, Image.NEAREST), pred)}")
165
+ tf = time.time()
166
+ delta = tf-ti
167
+ print(f"Running time for image of shape {seg.size}: {delta:.4f} sec")
168
+ exit(0)
169
+
170
+
171
+ if __name__ == "__main__":
172
+ main()
@@ -0,0 +1,100 @@
1
+ Metadata-Version: 2.4
2
+ Name: retinal_thin_vessels
3
+ Version: 1.0
4
+ Summary: A Python package for analyzing thin retinal vessels and creating vessels-thickness based weight maps
5
+ Author-email: João Paulo Menezes Linaris <joaolinaris@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/J-Linaris/retinal_thin_vessels
8
+ Project-URL: Repository, https://github.com/J-Linaris/retinal_thin_vessels
9
+ Keywords: retina,retinal,retinal-vessels,vessel-segmentation,image-analysis,medical-imaging,deep-learning,weight-map,pytorch,thin-vessels,image-segmentation,binary-masks,binary-mask
10
+ Classifier: Intended Audience :: Science/Research
11
+ Classifier: Operating System :: POSIX :: Linux
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Requires-Python: >=3.8
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: numpy
22
+ Requires-Dist: torch
23
+ Requires-Dist: scikit-image
24
+ Requires-Dist: Pillow
25
+ Dynamic: license-file
26
+
27
+ # retinal_thin_vessels
28
+
29
+ A Python package for computing the recall and precision scores specifically on thin vessels in retinal images, as detailed in the paper "Vessel-Width-Based Metrics and Weight Masks for Retinal Blood Vessel Segmentation", published in WUW-SIBGRAPI 2025. The package also includes a function for visualizing thickness-based filtered masks, the basic structure for computing the proposed metrics.
30
+
31
+ ## Package installation
32
+
33
+ ```bash
34
+ pip install retinal_thin_vessels
35
+ ```
36
+
37
+ ## Usage Demonstration with DRIVE and CHASEDB1
38
+
39
+ To ensure the metrics are reliable, it is important to visualize the specific thin-vessel mask used by the given functions in their calculations. Therefore, a core function, get_thin_vessels_mask(), is also provided. This function takes a standard segmentation mask and returns a new mask containing only the thin vessels.
40
+
41
+ The following code demonstrates how to generate this filtered mask using images from two public datasets: DRIVE and CHASEDB1.
42
+
43
+ ```python
44
+ from PIL import Image
45
+ from retinal_thin_vessels.core import get_thin_vessels_mask
46
+ from retinal_thin_vessels.metrics import recall_thin_vessels, precision_thin_vessels
47
+ from sklearn.metrics import recall_score, precision_score
48
+ ```
49
+
50
+ ```python
51
+ # Import the original segmentation masks
52
+ seg_DRIVE = Image.open(f"tests/imgs/DRIVE_seg_example.png")
53
+ seg_CDB1 = Image.open(f"tests/imgs/CHASEDB1_seg_example.png")
54
+
55
+ # generates new masks containing only thin vessels
56
+ thin_vessels_seg_DRIVE = get_thin_vessels_mask(seg_DRIVE)
57
+ thin_vessels_seg_CDB1 = get_thin_vessels_mask(seg_CDB1)
58
+
59
+ # Display the original segmentation mask and the resulting thin-vessel-only mask for comparison
60
+ seg_DRIVE.show()
61
+ img_DRIVE = Image.fromarray(thin_vessels_seg_DRIVE)
62
+ img_DRIVE.show()
63
+
64
+ seg_CDB1.show()
65
+ img_CDB1 = Image.fromarray(thin_vessels_seg_CDB1)
66
+ img_CDB1.show()
67
+ ```
68
+ <img src="tests/imgs/DRIVE_seg_example.png" alt="DRIVE_thin_vessels_example" width=300/>
69
+ <img src="tests/imgs/DRIVE_seg_thin_example.png" alt="DRIVE_thin_vessels_example" width=300/>
70
+ <img src="tests/imgs/CHASEDB1_seg_example.png" alt="CHASEDB1_thin_vessels_example" width=300/>
71
+ <img src="tests/imgs/CHASEDB1_seg_thin_example.png" alt="CHASEDB1_thin_vessels_example" width=300/>
72
+
73
+ Furthermore, to demonstrate the metric calculation functions, you can run the code below. It compares the overall metrics (calculated with scikit-learn) to the thin-vessel-specific metrics calculated by this package.
74
+
75
+ ```python
76
+ # Load the ground truth segmentation mask and a sample prediction
77
+ pred = Image.open(f"tests/imgs/DRIVE_pred_example.png")
78
+ seg_DRIVE = Image.open(f"tests/imgs/DRIVE_seg_example.png").resize((pred.size), Image.NEAREST)
79
+
80
+ # Binarize images to a 0/1 format for scikit-learn compatibility
81
+ seg_DRIVE = np.where(np.array(seg_DRIVE) > 0, 1, 0)
82
+ pred = np.where(np.array(pred) > 0, 1, 0)
83
+
84
+ # Compute and print the metrics
85
+ print(f"Overall Recall score: {recall_score(seg_DRIVE.flatten(), pred.flatten())}")
86
+ print(f"Recall score on thin vessels: {recall_thin_vessels(seg_DRIVE, pred)}")
87
+ print("-" * 30)
88
+ print(f"Overall Precision score: {precision_score(seg_DRIVE.flatten(), pred.flatten())}")
89
+ print(f"Precision score on thin Vessels: {precision_thin_vessels(seg_DRIVE, pred)}")
90
+ ```
91
+
92
+ If the program is running correctly with the provided sample images, the results should be similar to this:
93
+
94
+ ```bash
95
+ Overall Recall score: 0.8553852359822509
96
+ Recall score on thin vessels: 0.751244555071562
97
+ ------------------------------
98
+ Overall Precision score: 0.8422369623068674
99
+ Precision score on thin Vessels: 0.6527915897144481
100
+ ```
@@ -0,0 +1,17 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ retinal_thin_vessels/__init__.py
5
+ retinal_thin_vessels/core.py
6
+ retinal_thin_vessels/get_relation.py
7
+ retinal_thin_vessels/input_transformation.py
8
+ retinal_thin_vessels/metrics.py
9
+ retinal_thin_vessels.egg-info/PKG-INFO
10
+ retinal_thin_vessels.egg-info/SOURCES.txt
11
+ retinal_thin_vessels.egg-info/dependency_links.txt
12
+ retinal_thin_vessels.egg-info/requires.txt
13
+ retinal_thin_vessels.egg-info/top_level.txt
14
+ retinal_thin_vessels/external/DSE_skeleton_pruning/setup.py
15
+ retinal_thin_vessels/external/DSE_skeleton_pruning/dsepruning/__init__.py
16
+ retinal_thin_vessels/external/DSE_skeleton_pruning/dsepruning/dsepruning.py
17
+ tests/tests.py
@@ -0,0 +1,4 @@
1
+ numpy
2
+ torch
3
+ scikit-image
4
+ Pillow
@@ -0,0 +1 @@
1
+ retinal_thin_vessels
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,57 @@
1
+ from PIL import Image
2
+ from retinal_thin_vessels.metrics import recall_thin_vessels, precision_thin_vessels
3
+ from retinal_thin_vessels.core import get_thin_vessels_mask
4
+ import numpy as np
5
+ from sklearn.metrics import recall_score, precision_score
6
+
7
+ def main():
8
+
9
+ example_components_path = "imgs/"
10
+ # DRIVE IMAGES
11
+ seg = Image.open(f"{example_components_path}DRIVE_seg_example.png")
12
+ pred = Image.open(f"{example_components_path}DRIVE_pred_example.png")
13
+
14
+ # # Gets the filtered mask with only thin vessels
15
+ # thin_vessels_seg = get_thin_vessels_mask(seg)
16
+
17
+ # print("Showing the filtered segmentation mask with thin vessels only. DRIVE")
18
+ # img = Image.fromarray(thin_vessels_seg)
19
+ # # img.show()
20
+ # img.save("DRIVE_seg_thin_example.png")
21
+
22
+ # # CHASEDB1 IMAGES
23
+ # seg = Image.open(f"{example_components_path}CHASEDB1_seg_example.png")
24
+
25
+ # # Gets the filtered mask with only thin vessels
26
+ # thin_vessels_seg = get_thin_vessels_mask(seg)
27
+
28
+ # print("Showing the filtered segmentation mask with thin vessels only. CHASEDB1")
29
+ # img = Image.fromarray(thin_vessels_seg)
30
+ # # img.show()
31
+ # img.save("CHASEDB1_seg_thin_example.png")
32
+
33
+ print(np.array(seg.resize(pred.size, Image.NEAREST)).shape)
34
+ print(np.array(pred).shape)
35
+ print(np.unique(np.array(seg.resize(pred.size, Image.NEAREST)).astype(np.uint8)))
36
+ print(np.unique((np.array(pred)/255).astype(np.uint8)))
37
+
38
+ # Load the ground truth segmentation mask and a sample prediction
39
+ pred = Image.open(f"imgs/DRIVE_pred_example.png")
40
+ seg_DRIVE = seg.resize((pred.size), Image.NEAREST)
41
+
42
+ # Binarize images to a 0/1 format for scikit-learn compatibility
43
+ seg_DRIVE = np.where(np.array(seg_DRIVE) > 0, 1, 0)
44
+ pred = np.where(np.array(pred) > 0, 1, 0)
45
+
46
+ # Compute and print the metrics
47
+ print(f"Overall Recall score: {recall_score(seg_DRIVE.flatten(), pred.flatten())}")
48
+ print(f"Recall score on thin vessels: {recall_thin_vessels(seg_DRIVE, pred)}")
49
+ print("-" * 30)
50
+ print(f"Overall Precision score: {precision_score(seg_DRIVE.flatten(), pred.flatten())}")
51
+ print(f"Precision score on thin Vessels: {precision_thin_vessels(seg_DRIVE, pred)}")
52
+
53
+ exit(0)
54
+
55
+
56
+ if __name__ == "__main__":
57
+ main()