Coverage for src/recon3d/grayscale_image_stack_to_segmentation.py: 0%

68 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-04-02 00:06 +0000

1"""This module converts a stack of grayscale images into a segmentation 

2array.""" 

3 

4import argparse 

5from pathlib import Path 

6from typing import NamedTuple 

7 

8import numpy as np 

9import skimage.filters as filter 

10import yaml 

11 

12import recon3d.utility as ut 

13 

14 

15class Recipe(NamedTuple): 

16 """Defines the recipe to run this module directly from its Python API. 

17 

18 Attributes 

19 ---------- 

20 """ 

21 

22 image_dir: Path 

23 image_type: str 

24 threshold: int | None 

25 out_dir: Path 

26 

27 

28def validate_recipe(*, recipe: Recipe) -> bool: 

29 """Validate the given recipe. 

30 

31 Ensures that all values in the Recipe NamedTuple are valid. 

32 

33 Parameters 

34 ---------- 

35 recipe : Recipe 

36 The recipe to validate. 

37 

38 Raises 

39 ------ 

40 AssertionError 

41 If any of the recipe attributes are invalid. 

42 

43 Examples 

44 -------- 

45 >>> recipe = Recipe( 

46 ... input_path=Path("path/to/input"), 

47 ... input_file_type=".tif", 

48 ... output_path=Path("path/to/output") 

49 ... ) 

50 >>> validate_recipe(recipe) 

51 True 

52 """ 

53 assert isinstance(recipe.image_dir, Path), "image_dir must be a Path object" 

54 assert recipe.image_dir.is_dir(), "image_dir must be a directory" 

55 assert isinstance(recipe.image_type, str), "image_type must be a string" 

56 assert recipe.image_type in [ 

57 ".tif", 

58 ".tiff", 

59 ], "image_type must be .tif or .tiff" 

60 assert isinstance(recipe.out_dir, Path), "out_path must be a Path object" 

61 assert recipe.out_dir.is_dir(), "out_path must be a directory" 

62 

63 assert isinstance(recipe.threshold, int | None), "threshold must be an int or None" 

64 if recipe.threshold: 

65 assert 0 < recipe.threshold, "0 < threshold" 

66 else: 

67 assert recipe.threshold is None 

68 

69 return True 

70 

71 

72def segment_image_stack_save( 

73 image_dir: Path, file_extension: str, out_dir: Path, threshold: int | None 

74): 

75 """ 

76 Reads all images with a specified file extension from a directory, 

77 stores them in a numpy array, and saves the array to a .npy file in the 

78 specified directory. 

79 

80 Parameters: 

81 image_dir: The directory from which to read images. 

82 file_extension: The file extension of the images to read. 

83 out_dir: The directory where the .npy file will be saved. 

84 threshold: cut off value between the two materials 

85 """ 

86 try: 

87 # Assuming 'ut.read_images' is a valid function that reads images and 

88 # returns a numpy array 

89 array_data = ut.read_images(image_dir, file_extension) 

90 

91 if threshold is None: 

92 # overwrite 

93 threshold = filter.threshold_otsu(array_data) 

94 # Apply threshold: pixels greater than threshold are 

95 # set to 1, others set to 0 

96 binary_image = (array_data > threshold).astype(int) 

97 

98 # Correctly form the output file path 

99 out_file_path = out_dir.joinpath(f"{image_dir.name}_segmentation.npy") 

100 

101 # Save the numpy array to a .npy file 

102 np.save(out_file_path, binary_image) 

103 

104 print(f"Images from {image_dir} saved to {out_file_path}") 

105 

106 except Exception as e: 

107 print(f"An error occurred: {e}") 

108 

109 

110def grayscale_image_stack_to_segmentation(*, yml_input_file: Path) -> bool: 

111 """Converts a series of images to a stack array. 

112 

113 Parameters 

114 ---------- 

115 yml_input_file : Path 

116 The path to the input .yml file containing the image paths and other 

117 parameters. 

118 

119 Returns 

120 ------- 

121 True if the conversion is successful, False otherwise. 

122 

123 Raises 

124 ------ 

125 FileNotFoundError 

126 If the input .yml file is not found. 

127 TypeError 

128 If the input file type is not supported. 

129 OSError 

130 If there is an error with the yml module. 

131 """ 

132 print(f"This is {Path(__file__).resolve()}") 

133 

134 fin = yml_input_file.resolve().expanduser() 

135 

136 print(f"Processing file: {fin}") 

137 

138 if not fin.is_file(): 

139 raise FileNotFoundError(f"File not found: {str(fin)}") 

140 

141 file_type = fin.suffix.casefold() 

142 supported_types = (".yaml", ".yml") 

143 

144 if file_type not in supported_types: 

145 raise TypeError("Only file types .yaml, and .yml are supported.") 

146 

147 db = [] 

148 

149 try: 

150 with open(file=fin, mode="r", encoding="utf-8") as stream: 

151 db = yaml.load(stream, Loader=yaml.SafeLoader) # overwrite 

152 except yaml.YAMLError as error: 

153 print(f"Error with yml module: {error}") 

154 print(f"Could not open or decode: {fin}") 

155 raise OSError from error 

156 

157 print(f"Success: database created from file: {fin}") 

158 print(db) 

159 

160 recipe = Recipe( 

161 image_dir=Path(db["image_dir"]).expanduser(), 

162 image_type=db["image_type"], 

163 threshold=db.get("threshold", None), 

164 out_dir=Path(db["out_dir"]).expanduser(), 

165 ) 

166 

167 validate_recipe(recipe=recipe) 

168 

169 segment_image_stack_save( 

170 image_dir=recipe.image_dir, 

171 file_extension=recipe.image_type, 

172 threshold=recipe.threshold, 

173 out_dir=recipe.out_dir, 

174 ) 

175 

176 return True # success 

177 

178 

179def main(): 

180 """ 

181 Runs the module from the command line. 

182 

183 This function serves as the entry point for terminal-based access to the 

184 module. It uses the argparse library to parse command-line arguments. 

185 

186 Parameters 

187 ---------- 

188 None 

189 

190 Returns 

191 ------- 

192 None 

193 

194 Examples 

195 -------- 

196 To run the module, use the following command in the terminal: 

197 $ image_to_stack_array path/to/input.yml 

198 """ 

199 

200 parser = argparse.ArgumentParser() 

201 parser.add_argument("input_file", help="the .yml input file") 

202 args = parser.parse_args() 

203 input_file = args.input_file 

204 input_file = Path(input_file).expanduser() 

205 

206 grayscale_image_stack_to_segmentation(yml_input_file=input_file) 

207 

208 

209if __name__ == "__main__": 

210 main()