Coverage for src/deepdraw/script/mkmask.py: 35%
51 statements
« prev ^ index » next coverage.py v7.4.2, created at 2024-03-29 22:17 +0100
« prev ^ index » next coverage.py v7.4.2, created at 2024-03-29 22:17 +0100
1# SPDX-FileCopyrightText: Copyright © 2023 Idiap Research Institute <contact@idiap.ch>
2#
3# SPDX-License-Identifier: GPL-3.0-or-later
6import glob
7import os
9import click
10import numpy
11import skimage.io
12import skimage.morphology
14from clapper.click import ConfigCommand, ResourceOption, verbosity_option
15from clapper.logging import setup
17logger = setup(__name__.split(".")[0], format="%(levelname)s: %(message)s")
19from ..utils.rc import load_rc
22@click.command(
23 cls=ConfigCommand,
24 epilog="""Examples:
26\b
27 1. Generate masks for supported dataset by deepdraw. Ex: refuge.
29 .. code:: sh
31 $ deepdraw mkmask --dataset="refuge" --globs="Training400/*Glaucoma/*.jpg" --globs="Training400/*AMD/*.jpg" --threshold=5
33 Or you can generate the same results with this command
35 .. code:: sh
37 $ deepdraw mkmask -d "refuge" -g "Training400/*Glaucoma/*.jpg" -g "Training400/*AMD/*.jpg" -t 5
39\b
40 2. Generate masks for non supported dataset by deepdraw
42 .. code:: sh
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."""
90 def threshold_and_closing(input_path, t, width=5):
91 """Creates a "rough" mask from the input image, returns binary
92 equivalent.
94 The mask will be created by running a simple threshold operation followed
95 by a morphological closing
98 Arguments
99 =========
101 input_path : str
102 The path leading to the image from where the mask needs to be extracted
104 t : int
105 Threshold to apply on the original image
107 width : int
108 Width of the disc to use for the closing operation
111 Returns
112 =======
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 """
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 )
127 def count_blobs(mask):
128 """Counts "white" blobs in a binary mask, outputs counts.
130 Arguments
131 =========
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.
139 Returns
140 =======
142 count : int
143 The number of connected blobs in the provided mask.
144 """
145 return skimage.measure.label(mask, return_num=True)[1]
147 def process_glob(base_path, use_glob, output_path, threshold):
148 """Recursively process a set of images.
150 Arguments
151 =========
153 base_path : str
154 The base directory where to look for files matching a certain name
155 patternrc.get("deepdraw." + dataset + ".datadir"):
157 use_glob : list
158 A list of globs to use for matching filenames inside ``base_path``
160 output_path : str
161 Where to place the results of procesing
162 """
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)
189 rc = load_rc()
191 if rc.get("deepdraw." + dataset + ".datadir"):
192 base_path = rc.get("deepdraw." + dataset + ".datadir")
193 else:
194 base_path = dataset
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 )