Coverage for src/deepdraw/script/mkmask.py: 35%

51 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2023-11-30 15:00 +0100

1# SPDX-FileCopyrightText: Copyright © 2023 Idiap Research Institute <contact@idiap.ch> 

2# 

3# SPDX-License-Identifier: GPL-3.0-or-later 

4 

5 

6import glob 

7import os 

8 

9import click 

10import numpy 

11import skimage.io 

12import skimage.morphology 

13 

14from clapper.click import ConfigCommand, ResourceOption, verbosity_option 

15from clapper.logging import setup 

16 

17logger = setup(__name__.split(".")[0], format="%(levelname)s: %(message)s") 

18 

19from ..utils.rc import load_rc 

20 

21 

22@click.command( 

23 cls=ConfigCommand, 

24 epilog="""Examples: 

25 

26\b 

27 1. Generate masks for supported dataset by deepdraw. Ex: refuge. 

28 

29 .. code:: sh 

30 

31 $ deepdraw mkmask --dataset="refuge" --globs="Training400/*Glaucoma/*.jpg" --globs="Training400/*AMD/*.jpg" --threshold=5 

32 

33 Or you can generate the same results with this command 

34 

35 .. code:: sh 

36 

37 $ deepdraw mkmask -d "refuge" -g "Training400/*Glaucoma/*.jpg" -g "Training400/*AMD/*.jpg" -t 5 

38 

39\b 

40 2. Generate masks for non supported dataset by deepdraw 

41 

42 .. code:: sh 

43 

44 $ deepdraw mkmask -d "Path/to/dataset" -g "glob1" -g "glob2" -g glob3 -t 4 

45""", 

46) 

47@click.option( 

48 "--output-folder", 

49 "-o", 

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

51 required=True, 

52 type=click.Path(), 

53 default="masks", 

54 cls=ResourceOption, 

55) 

56@click.option( 

57 "--dataset", 

58 "-d", 

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

60 In case you have already configured the path for the datasets supported by deepdraw, \\ 

61 you can just use the name of the dataset as written in the config. 

62 """, 

63 required=True, 

64 cls=ResourceOption, 

65) 

66@click.option( 

67 "--globs", 

68 "-g", 

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

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

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

72 It also can be used multiple time. 

73 """, 

74 required=True, 

75 multiple=True, 

76 cls=ResourceOption, 

77) 

78@click.option( 

79 "--threshold", 

80 "-t", 

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

82 required=True, 

83 cls=ResourceOption, 

84) 

85@verbosity_option(logger=logger, cls=ResourceOption) 

86@click.pass_context 

87def mkmask(ctx, dataset, globs, threshold, output_folder, verbose, **kwargs): 

88 """Commands for generating masks for images in a dataset.""" 

89 

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

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

92 equivalent. 

93 

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

95 by a morphological closing 

96 

97 

98 Arguments 

99 ========= 

100 

101 input_path : str 

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

103 

104 t : int 

105 Threshold to apply on the original image 

106 

107 width : int 

108 Width of the disc to use for the closing operation 

109 

110 

111 Returns 

112 ======= 

113 

114 mask : numpy.ndarray 

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

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

117 """ 

118 

119 img = skimage.util.img_as_ubyte( 

120 skimage.io.imread(input_path, as_gray=True) 

121 ) 

122 mask = img > t 

123 return skimage.morphology.binary_opening( 

124 mask, skimage.morphology.disk(width) 

125 ) 

126 

127 def count_blobs(mask): 

128 """Counts "white" blobs in a binary mask, outputs counts. 

129 

130 Arguments 

131 ========= 

132 

133 mask : numpy.ndarray 

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

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

136 background. 

137 

138 

139 Returns 

140 ======= 

141 

142 count : int 

143 The number of connected blobs in the provided mask. 

144 """ 

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

146 

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

148 """Recursively process a set of images. 

149 

150 Arguments 

151 ========= 

152 

153 base_path : str 

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

155 patternrc.get("deepdraw." + dataset + ".datadir"): 

156 

157 use_glob : list 

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

159 

160 output_path : str 

161 Where to place the results of procesing 

162 """ 

163 

164 files = [] 

165 for g in use_glob: 

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

167 for i, path in enumerate(files): 

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

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

170 logger.info( 

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

172 ) 

173 dest = os.path.join( 

174 output_path, basename_without_extension + ".png" 

175 ) 

176 destdir = os.path.dirname(dest) 

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

178 os.makedirs(destdir) 

179 mask = threshold_and_closing(path, threshold) 

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

181 nblobs = count_blobs(immask) 

182 if nblobs != 1: 

183 logger.warning( 

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

185 f"(should be one)" 

186 ) 

187 skimage.io.imsave(dest, immask) 

188 

189 rc = load_rc() 

190 

191 if rc.get("deepdraw." + dataset + ".datadir"): 

192 base_path = rc.get("deepdraw." + dataset + ".datadir") 

193 else: 

194 base_path = dataset 

195 

196 list_globs = [] 

197 for g in globs: 

198 list_globs.append(g) 

199 threshold = int(threshold) 

200 process_glob( 

201 base_path=base_path, 

202 use_glob=list_globs, 

203 output_path=output_folder, 

204 threshold=threshold, 

205 )