Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1#!/usr/bin/env python 

2# coding=utf-8 

3 

4import glob 

5import logging 

6import os 

7 

8import click 

9import numpy 

10import skimage.color 

11import skimage.io 

12import skimage.measure 

13import skimage.morphology 

14 

15from bob.extension import rc 

16from bob.extension.scripts.click_helper import ( 

17 ConfigCommand, 

18 ResourceOption, 

19 verbosity_option, 

20) 

21 

22logger = logging.getLogger(__name__) 

23 

24 

25def threshold_and_closing(input_path, t, width=5): 

26 """Creates a "rough" mask from the input image, returns binary equivalent 

27 

28 The mask will be created by running a simple threshold operation followed 

29 by a morphological closing 

30 

31 

32 Arguments 

33 ========= 

34 

35 input_path : str 

36 The path leading to the image from where the mask needs to be extracted 

37 

38 t : int 

39 Threshold to apply on the original image 

40 

41 width : int 

42 Width of the disc to use for the closing operation 

43 

44 

45 Returns 

46 ======= 

47 

48 mask : numpy.ndarray 

49 A 2D array, with the same size as the input image, where ``True`` 

50 pixels correspond to the "valid" regions of the mask. 

51 

52 """ 

53 

54 img = skimage.util.img_as_ubyte(skimage.io.imread(input_path, as_gray=True)) 

55 mask = img > t 

56 return skimage.morphology.binary_opening( 

57 mask, skimage.morphology.disk(width) 

58 ) 

59 

60 

61def count_blobs(mask): 

62 """Counts "white" blobs in a binary mask, outputs counts 

63 

64 

65 Arguments 

66 ========= 

67 

68 mask : numpy.ndarray 

69 A 2D array, with the same size as the input image, where ``255`` 

70 pixels correspond to the "valid" regions of the mask. ``0`` means 

71 background. 

72 

73 

74 Returns 

75 ======= 

76 

77 count : int 

78 The number of connected blobs in the provided mask. 

79 

80 """ 

81 return skimage.measure.label(mask, return_num=True)[1] 

82 

83 

84def process_glob(base_path, use_glob, output_path, threshold): 

85 """Recursively process a set of images 

86 

87 Arguments 

88 ========= 

89 

90 base_path : str 

91 The base directory where to look for files matching a certain name 

92 patternrc.get("bob.ip.binseg." + dataset + ".datadir"): 

93 

94 use_glob : list 

95 A list of globs to use for matching filenames inside ``base_path`` 

96 

97 output_path : str 

98 Where to place the results of procesing 

99 

100 """ 

101 

102 files = [] 

103 for g in use_glob: 

104 files += glob.glob(os.path.join(base_path, g)) 

105 for i, path in enumerate(files): 

106 basename = os.path.relpath(path, base_path) 

107 basename_without_extension = os.path.splitext(basename)[0] 

108 logger.info( 

109 f"Processing {basename_without_extension} ({i+1}/{len(files)})..." 

110 ) 

111 dest = os.path.join(output_path, basename_without_extension + ".png") 

112 destdir = os.path.dirname(dest) 

113 if not os.path.exists(destdir): 

114 os.makedirs(destdir) 

115 mask = threshold_and_closing(path, threshold) 

116 immask = mask.astype(numpy.uint8) * 255 

117 nblobs = count_blobs(immask) 

118 if nblobs != 1: 

119 logger.warning( 

120 f" -> WARNING: found {nblobs} blobs in the saved mask " 

121 f"(should be one)" 

122 ) 

123 skimage.io.imsave(dest, immask) 

124 

125 

126@click.command( 

127 cls=ConfigCommand, 

128 epilog="""Examples: 

129 

130\b 

131 1. Generate masks for supported dataset by bob. Ex: refuge. 

132\b 

133 $ bob binseg mkmask --dataset="refuge" --globs="Training400/*Glaucoma/*.jpg" --globs="Training400/*AMD/*.jpg" --threshold=5 

134\b 

135 Or you can generate the same results with this command 

136 

137\b 

138 $ bob binseg mkmask -d "refuge" -g "Training400/*Glaucoma/*.jpg" -g "Training400/*AMD/*.jpg" -t 5 

139 

140\b 

141 2. Generate masks for non supported dataset by bob 

142 

143\b 

144 $ bob binseg mkmask -d "Path/to/dataset" -g "glob1" -g "glob2" -g glob3 -t 4 

145 

146 

147""", 

148) 

149@click.option( 

150 "--output-folder", 

151 "-o", 

152 help="Path where to store the generated model (created if does not exist)", 

153 required=True, 

154 type=click.Path(), 

155 default="masks", 

156 cls=ResourceOption, 

157) 

158@click.option( 

159 "--dataset", 

160 "-d", 

161 help="""The base path to the dataset to which we want to generate the masks. \\ 

162 In case you have already configured the path for the datasets supported by bob, \\ 

163 you can just use the name of the dataset as written in the config. """, 

164 required=True, 

165 cls=ResourceOption, 

166) 

167@click.option( 

168 "--globs", 

169 "-g", 

170 help="""The global path to the dataset to which we want to generate the masks.\\ 

171 We need to specify the path for the images ,\\ 

172 Ex : --globs="images/*.jpg"\\ 

173 It also can be used multiple time. 

174 """, 

175 required=True, 

176 multiple=True, 

177 cls=ResourceOption, 

178) 

179@click.option( 

180 "--threshold", 

181 "-t", 

182 help=" Generating a mask needs a threshold to be fixed in order to transform the image to binary ", 

183 required=True, 

184 cls=ResourceOption, 

185) 

186@verbosity_option(cls=ResourceOption) 

187def mkmask(dataset, globs, threshold, output_folder, **kwargs): 

188 """ 

189 Commands for generating masks for images in a dataset. 

190 

191 """ 

192 if rc.get("bob.ip.binseg." + dataset + ".datadir"): 

193 base_path = rc.get("bob.ip.binseg." + dataset + ".datadir") 

194 else: 

195 base_path = dataset 

196 

197 list_globs = [] 

198 for g in globs: 

199 list_globs.append(g) 

200 threshold = int(threshold) 

201 process_glob( 

202 base_path=base_path, 

203 use_glob=list_globs, 

204 output_path=output_folder, 

205 threshold=threshold, 

206 )