-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathWatershed_func.py
377 lines (289 loc) · 12.7 KB
/
Watershed_func.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
#!/usr/bin/env python
# coding: utf-8
#These functions are for image segmentation by using classical algorithm, watershed segmentation and image pre and postprocessing.
import numpy as np
import os, sys
import copy
import cv2
import mahotas as mh
from skimage.measure import label
from tqdm import tqdm
from skimage.morphology import opening, closing
from skimage.morphology import disk
from skimage.filters import thresshold_otsu, threshold_triangle, threshold_yen, threshold_mean, threshold_minimum, threshold_li, threshold_isodata
import itertools
class watershed_preprocessing:
@staticmethod
def labels_selection_by_area(image, area):
"""
Functions for screening noise by area and returning that result.
Area of each component is calculated by connected component function implemented in openccv
Input
------
image: 2D image
binarized image
area : int
area threshold
Return
------
labels : list
label list after screening
labels_drop: list
eliminated labels by screening
"""
#labeling by connected components
_, labels, stats, _ = cv2.connectedComponentsWithStats(np.uint8(image))
#extract labels to be omitted
label_drop=[i for i in range(len(stats)) if stats[i][4]<area]
return labels, label_drop
@staticmethod
def labels_selection_by_area_and_aspect_ratio(image, area_1, area_2, aspect_ratio):
"""
Funtions for screening noise by area and aspect ratio, and for returning that result.
Area and aspect ration of each component is calculated by connected component function implemented in openccv
Input
------
image : 2D image
input image
area_1 : int
area thershold. This is used with aspect ratio threshold
area_2 : int
area thershold
aspect_ratio: float
aspect ratio thershold. This is used with area threshold
Return
------
labels : list
label list after screening
labels_drop: list
eliminated labels by screening
"""
#labeling by connected components
_, labels, stats, _ = cv2.connectedComponentsWithStats(image)
#extract labels to be omitted
label_drop_1=[i for i in range(len(stats)) if stats[i][4]<area_1 and stats[i][3]/stats[i][2]<aspect_ratio]
label_drop_2=[i for i in range(len(stats)) if stats[i][4]<area_2]
label_drop=list(set(list(itertools.chain(label_drop_1, label_drop_2))))
return labels, label_drop
@staticmethod
def labels_selection_by_area_or_aspect_ratio(image, area, aspect_ratio):
"""
Funtions for screening noise by area and aspect ratio, and for returning that result.
Area and aspect ration of each component is calculated by connected component function implemented in openccv
Input
------
image : 2D image
input image
area : int
area thershold
aspect_ratio: float
aspect ratio thershold
Return
------
labels : list
label list after screening
labels_drop: list
eliminated labels by screening
"""
#labeling by connected components
_, labels, stats, _ = cv2.connectedComponentsWithStats(image)
#some componnents are droped by restriction on area and aspect ratio
label_drop_1=[i for i in range(len(stats)) if stats[i][4]<area]
label_drop_2=[i for i in range(len(stats)) if stats[i][3]/stats[i][2]>aspect_ratio]
label_drop=list(set(list(itertools.chain(label_drop_1, label_drop_2))))
return labels, label_drop
def omit_by_area(self, image, area, inversion="True"):
"""
Functions for screening noise by area
Input
------
image : 2D image
input binarized image
area : int
area thresold
inversion: boolean
If True, positive and negative inversion is performed before connected components
Return
------
labels: 2D image
binarized image after noise elimination
"""
#define image dimension
height, width=image.shape
if inversion=="True":
#positive negative inversion
image=cv2.bitwise_not(image)
labels, label_drop=watershed_preprocessing.labels_selection_by_area(image, area)
labels=labels.flatten()
#pixels assigned to label_drop_lumen are set to 0
for i in tqdm(range(len(label_drop))):
label_drop_ind=np.where(labels==label_drop[i])
labels[label_drop_ind]=0
labels=labels.reshape(height, width)
labels=np.where(labels>0, 255, 0)
return labels
def omit_by_area_aspect_ratio(self, image, area_1, area_2, aspect_ratio, inversion="True"):
"""
Functions for screening noise by area and aspect ratio
Input
------
image : 2D image
input binarized image
area_1 : int
area thresold used with aspect ratio thresold
area_2 : int
area threshold
aspect_ratio: float
aspect ratio thershold used with area thershold
inversion : boolean
If True, positive and negative inversion is performed before connected components
Return
------
labels: 2D image
binarized image after noise elimination
"""
#define image dimension
height, width=image.shape
if inversion=="True":
#positive negative inversion
image=cv2.bitwise_not(np.uint8(image))
labels, label_drop=watershed_preprocessing.labels_selection_by_area_and_aspect_ratio(image, area_1, area_2, aspect_ratio)
labels=labels.flatten()
for i in tqdm(range(len(label_drop))):
label_drop_ind=np.where(labels==label_drop[i])
labels[label_drop_ind]=0
labels=labels.reshape(height, width)
labels=np.where(labels>0, 255, 0)
return labels
def ray_extraction(self, image, kernel, iterations, area, aspect_ratio):
"""
Function for selectively extracting ray region.
Ray region is detectd by combination of morphological operation and screening by area and aspect ratio.
Input
------
image : 2D image
input images
kernel : tuple
kernel for erosion morpphological operation
iterations : int
iteration number for repeating erosion morphological operation
area : int
area threshold
aspet_ratio: float
aspect ratio threshold
Return
------
ray_region: 2D image
binarized image (ray region is selectively extracted)
"""
#small regions coming from intermediate layer are omitted by morphological operator
height, width=image.shape
kernel = np.ones(kernel ,np.uint8)
erosion = cv2.erode(np.uint8(image), kernel, iterations = iterations)
labels, label_drop=watershed_preprocessing.labels_selection_by_area_or_aspect_ratio(erosion, area, aspect_ratio)
labels=labels.flatten()
for i in tqdm(range(len(label_drop))):
label_drop_ind=np.where(labels==label_drop[i])
labels[label_drop_ind]=0
label_map=np.where(labels.reshape(height, width)>0, 255, 0)
gradient = cv2.morphologyEx(np.uint8(label_map), cv2.MORPH_GRADIENT, kernel)
ray_region=np.where(label_map-gradient>200, 255, 0)
return ray_region
class watershed:
@staticmethod
def watershed_segmentation(image):
"""
Function for applying watershed segmentation to input image.
Original function to perform watershed segmentation is implemented in mahotas.
Input
------
image: 2D image
input image
Return
------
nuclei: 2D image
label map obtained by watershed
lines : 2D image (boolean)
boundary image obtained by watershed. pixel corresponding to boundary has True.
width of boundary line is set to 1 pixel.
"""
locmax=mh.regmax(image)
seeds, nr_nuclei = mh.label(locmax)
#distans transformation
T = mh.thresholding.otsu(np.uint8(image))
dist = mh.distance(np.uint8(image) > T)
dist = dist.max() - dist
dist -= dist.min()
dist = dist/float(dist.ptp()) * 255
dist = dist.astype(np.uint8)
nuclei, lines = mh.cwatershed(dist, seeds, return_lines=True)
return nuclei, lines
class watershed_postprocessing:
@staticmethod
def boundary_overlay(original_image, boundary_image, kernel):
"""
Functions for drawing boundary lines on original image.
Thickenss of boundary can be controlled by kernel
Input
------
original_image: 2D image (one or three channel)
input image in watershed segmentation
boundary_image: 2D image
cell boundary image obtained by watershed
kernel : tuple
kernel for gaussian blur
Return
------
im_copy: 2D image (one or three channel)
returned original image on which boundary lines are drawn
"""
im_dim=len(original_image.shape)
if im_dim==2:
height, width=original_image.shape
if im_dim==3:
height, width, channel=original_image.shape
#blur boundary lines for good visualization
blur = cv2.GaussianBlur(boundary_image, kernel, 0)
blur = np.where(blur[:,:]>0, 255, 0)
if im_dim==2:
im_copy=np.zeros((height, width))
im_copy[:,:]=original_image
im_copy[np.where(blur[:,:]==255)]=[255, 0, 0]
return im_copy
if im_dim==3:
im_copy=np.zeros((height, width, channel), np.uint8)
im_copy[:,:, :]=original_image
im_copy[np.where(blur[:,:]==255)]=[255, 0, 0]
return im_copy
else:
print("Image dimension is invalid")
@staticmethod
def save_overlay_im(boundary_image, original_image, save_path, save_name):
"""
Functions for automatically saving boundary and original images applicable to manual correction.
Original image dimension is kept after saving.
Input
------
boundary_image: 2D image
binary image on which cell boundaries are drawn.
Cell boundaries and th other bakground correspond to 255 and 0.
original_image: 2D image (1 or 3 channel)
original image applied to segmentation
save_path : path
path for saving results
save_name : str
image name for saving (without extention).
Image save format is fixed to .png
"""
if len(boundary_image.shape)>2:
print("Image dimension is invalid")
return boundary_image
else:
boundary_image_tr=np.zeros((boundary_image.shape[0], boundary_image.shape[1], 4), np.uint8)
boundary_image_tr[:,:,2]=boundary_image
boundary_image_tr[:,:,3] = np.where(np.all(boundary_image_tr == 0, axis=-1), 0, 255)
#set save path
if os.path.exists(save_path)==False:
os.makedirs(save_path)
cv2.imwrite(os.path.join(save_path, 'boundary_for_mannual_correction_'+str(save_name)+'.png'), boundary_image_tr)
cv2.imwrite(os.path.join(save_path, 'ground_im_for_manual_correction_'+str(save_name)+'.png'), original_image)