comparison auto_threshold.py @ 4:7d80eb2411fb draft default tip

planemo upload for repository https://github.com/BMCV/galaxy-image-analysis/tree/master/tools/2d_auto_threshold/ commit 01343602708de3cc7fa4986af9000adc36dd0651
author imgteam
date Sat, 07 Jun 2025 18:38:16 +0000
parents 5224cc463a97
children
comparison
equal deleted inserted replaced
3:5224cc463a97 4:7d80eb2411fb
1 """ 1 """
2 Copyright 2017-2022 Biomedical Computer Vision Group, Heidelberg University. 2 Copyright 2017-2024 Biomedical Computer Vision Group, Heidelberg University.
3 3
4 Distributed under the MIT license. 4 Distributed under the MIT license.
5 See file LICENSE for detail or copy at https://opensource.org/licenses/MIT 5 See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
6
7 """ 6 """
8 7
9 import argparse 8 import argparse
10 9
10 import numpy as np
11 import skimage.filters 11 import skimage.filters
12 import skimage.io
13 import skimage.util 12 import skimage.util
14 import tifffile 13 from giatools.image import Image
15 14
16 thOptions = {
17 'otsu': lambda img_raw, bz: skimage.filters.threshold_otsu(img_raw),
18 'li': lambda img_raw, bz: skimage.filters.threshold_li(img_raw),
19 'yen': lambda img_raw, bz: skimage.filters.threshold_yen(img_raw),
20 'isodata': lambda img_raw, bz: skimage.filters.threshold_isodata(img_raw),
21 15
22 'loc_gaussian': lambda img_raw, bz: skimage.filters.threshold_local(img_raw, bz, method='gaussian'), 16 class DefaultThresholdingMethod:
23 'loc_median': lambda img_raw, bz: skimage.filters.threshold_local(img_raw, bz, method='median'), 17
24 'loc_mean': lambda img_raw, bz: skimage.filters.threshold_local(img_raw, bz, method='mean') 18 def __init__(self, thres, accept: list[str] | None = None, **kwargs):
19 self.thres = thres
20 self.accept = accept if accept else []
21 self.kwargs = kwargs
22
23 def __call__(self, image, *args, offset=0, **kwargs):
24 accepted_kwargs = self.kwargs.copy()
25 for key, val in kwargs.items():
26 if key in self.accept:
27 accepted_kwargs[key] = val
28 thres = self.thres(image, *args, **accepted_kwargs)
29 return image > thres + offset
30
31
32 class ManualThresholding:
33
34 def __call__(self, image, thres1: float, thres2: float | None, **kwargs):
35 if thres2 is None:
36 return image > thres1
37 else:
38 thres1, thres2 = sorted((thres1, thres2))
39 return skimage.filters.apply_hysteresis_threshold(image, thres1, thres2)
40
41
42 th_methods = {
43 'manual': ManualThresholding(),
44
45 'otsu': DefaultThresholdingMethod(skimage.filters.threshold_otsu),
46 'li': DefaultThresholdingMethod(skimage.filters.threshold_li),
47 'yen': DefaultThresholdingMethod(skimage.filters.threshold_yen),
48 'isodata': DefaultThresholdingMethod(skimage.filters.threshold_isodata),
49
50 'loc_gaussian': DefaultThresholdingMethod(skimage.filters.threshold_local, accept=['block_size'], method='gaussian'),
51 'loc_median': DefaultThresholdingMethod(skimage.filters.threshold_local, accept=['block_size'], method='median'),
52 'loc_mean': DefaultThresholdingMethod(skimage.filters.threshold_local, accept=['block_size'], method='mean'),
25 } 53 }
26 54
27 55
28 def auto_thresholding(in_fn, out_fn, th_method, block_size=5, dark_bg=True): 56 def do_thresholding(
29 img = skimage.io.imread(in_fn) 57 input_filepath: str,
30 th = thOptions[th_method](img, block_size) 58 output_filepath: str,
31 if dark_bg: 59 th_method: str,
32 res = img > th 60 block_size: int,
33 else: 61 offset: float,
34 res = img <= th 62 threshold1: float,
35 tifffile.imwrite(out_fn, skimage.util.img_as_ubyte(res)) 63 threshold2: float | None,
64 invert_output: bool,
65 ):
66 assert th_method in th_methods, f'Unknown method "{th_method}"'
67
68 # Load image
69 img_in = Image.read(input_filepath)
70
71 # Perform thresholding
72 result = th_methods[th_method](
73 image=img_in.data,
74 block_size=block_size,
75 offset=offset,
76 thres1=threshold1,
77 thres2=threshold2,
78 )
79 if invert_output:
80 result = np.logical_not(result)
81
82 # Convert to canonical representation for binary images
83 result = (result * 255).astype(np.uint8)
84
85 # Write result
86 Image(
87 data=skimage.util.img_as_ubyte(result),
88 axes=img_in.axes,
89 ).normalize_axes_like(
90 img_in.original_axes,
91 ).write(
92 output_filepath,
93 )
36 94
37 95
38 if __name__ == "__main__": 96 if __name__ == "__main__":
39 parser = argparse.ArgumentParser(description='Automatic Image Thresholding') 97 parser = argparse.ArgumentParser(description='Automatic image thresholding')
40 parser.add_argument('im_in', help='Path to the input image') 98 parser.add_argument('input', type=str, help='Path to the input image')
41 parser.add_argument('im_out', help='Path to the output image (TIFF)') 99 parser.add_argument('output', type=str, help='Path to the output image (uint8)')
42 parser.add_argument('th_method', choices=thOptions.keys(), help='Thresholding method') 100 parser.add_argument('th_method', choices=th_methods.keys(), help='Thresholding method')
43 parser.add_argument('block_size', type=int, default=5, help='Odd size of pixel neighborhood for calculating the threshold') 101 parser.add_argument('block_size', type=int, help='Odd size of pixel neighborhood for calculating the threshold')
44 parser.add_argument('dark_bg', default=True, type=bool, help='True if background is dark') 102 parser.add_argument('offset', type=float, help='Offset of automatically determined threshold value')
103 parser.add_argument('threshold1', type=float, help='Manual threshold value')
104 parser.add_argument('--threshold2', type=float, help='Second manual threshold value (for hysteresis thresholding)')
105 parser.add_argument('--invert_output', default=False, action='store_true', help='Values below/above the threshold are labeled with 0/255 by default, and with 255/0 if this argument is used')
45 args = parser.parse_args() 106 args = parser.parse_args()
46 107
47 auto_thresholding(args.im_in, args.im_out, args.th_method, args.block_size, args.dark_bg) 108 do_thresholding(
109 args.input,
110 args.output,
111 args.th_method,
112 args.block_size,
113 args.offset,
114 args.threshold1,
115 args.threshold2,
116 args.invert_output,
117 )