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
« 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."""
4import argparse
5from pathlib import Path
6from typing import NamedTuple
8import numpy as np
9import skimage.filters as filter
10import yaml
12import recon3d.utility as ut
15class Recipe(NamedTuple):
16 """Defines the recipe to run this module directly from its Python API.
18 Attributes
19 ----------
20 """
22 image_dir: Path
23 image_type: str
24 threshold: int | None
25 out_dir: Path
28def validate_recipe(*, recipe: Recipe) -> bool:
29 """Validate the given recipe.
31 Ensures that all values in the Recipe NamedTuple are valid.
33 Parameters
34 ----------
35 recipe : Recipe
36 The recipe to validate.
38 Raises
39 ------
40 AssertionError
41 If any of the recipe attributes are invalid.
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"
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
69 return True
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.
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)
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)
98 # Correctly form the output file path
99 out_file_path = out_dir.joinpath(f"{image_dir.name}_segmentation.npy")
101 # Save the numpy array to a .npy file
102 np.save(out_file_path, binary_image)
104 print(f"Images from {image_dir} saved to {out_file_path}")
106 except Exception as e:
107 print(f"An error occurred: {e}")
110def grayscale_image_stack_to_segmentation(*, yml_input_file: Path) -> bool:
111 """Converts a series of images to a stack array.
113 Parameters
114 ----------
115 yml_input_file : Path
116 The path to the input .yml file containing the image paths and other
117 parameters.
119 Returns
120 -------
121 True if the conversion is successful, False otherwise.
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()}")
134 fin = yml_input_file.resolve().expanduser()
136 print(f"Processing file: {fin}")
138 if not fin.is_file():
139 raise FileNotFoundError(f"File not found: {str(fin)}")
141 file_type = fin.suffix.casefold()
142 supported_types = (".yaml", ".yml")
144 if file_type not in supported_types:
145 raise TypeError("Only file types .yaml, and .yml are supported.")
147 db = []
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
157 print(f"Success: database created from file: {fin}")
158 print(db)
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 )
167 validate_recipe(recipe=recipe)
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 )
176 return True # success
179def main():
180 """
181 Runs the module from the command line.
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.
186 Parameters
187 ----------
188 None
190 Returns
191 -------
192 None
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 """
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()
206 grayscale_image_stack_to_segmentation(yml_input_file=input_file)
209if __name__ == "__main__":
210 main()