|
| 1 | +import os |
| 2 | +import cv2 |
| 3 | +from pathlib import Path |
| 4 | +from partinet.process_utils.guided_denoiser import denoise |
| 5 | +import logging |
| 6 | +import multiprocessing |
| 7 | +import argparse |
| 8 | +import gc |
| 9 | +from concurrent.futures import ProcessPoolExecutor |
| 10 | +from typing import List, Tuple |
| 11 | +import mrcfile |
| 12 | + |
| 13 | +# Function to perform CLAHE-based denoising |
| 14 | + |
| 15 | +def clahe_denoise(args: Tuple[str, str, str]) -> None: |
| 16 | + """ |
| 17 | + Applies the guided denoising algorithm to an input image and saves the denoised result. |
| 18 | +
|
| 19 | + Args: |
| 20 | + args (Tuple[str, str]): |
| 21 | + - src_path: Path to the source micrograph file. |
| 22 | + - dest_path: Path to save the denoised image. |
| 23 | + - img_format: output format of denoised images |
| 24 | +
|
| 25 | + Raises: |
| 26 | + Exception: Logs any exceptions that occur during processing. |
| 27 | + """ |
| 28 | + try: |
| 29 | + src_path, dest_path, img_format = args |
| 30 | + # Perform denoising |
| 31 | + denoised = denoise(src_path) |
| 32 | + if img_format == "mrc": |
| 33 | + mrcfile.write(dest_path,data=denoised) |
| 34 | + else: |
| 35 | + cv2.imwrite(dest_path, denoised) |
| 36 | + logging.info(f"Processed image {src_path} to dest. {dest_path}") |
| 37 | + del denoised |
| 38 | + gc.collect() |
| 39 | + except Exception as e: |
| 40 | + logging.error(f"Failed to process {src_path}: {str(e)}") |
| 41 | + |
| 42 | +# Function to process all files in a directory |
| 43 | + |
| 44 | +def process_directory(micrographs_dir: str, clahe_denoised_dir: str, max_workers: int, img_format: str) -> None: |
| 45 | + """ |
| 46 | + Processes all `.mrc` files in the given directory using parallel workers for denoising. |
| 47 | +
|
| 48 | + Args: |
| 49 | + micrographs_dir (str): Path to the directory containing raw micrograph files. |
| 50 | + clahe_denoised_dir (str): Path to the directory where denoised images will be saved. |
| 51 | + max_workers (int): Number of worker processes to use for parallel processing. |
| 52 | + img_format (str): Output format of denoised images |
| 53 | +
|
| 54 | + Notes: |
| 55 | + - Only `.mrc` files are processed. |
| 56 | + - Existing denoised images are skipped. |
| 57 | + """ |
| 58 | + os.makedirs(clahe_denoised_dir, exist_ok=True) |
| 59 | + logging.info(f"Directory ready: {clahe_denoised_dir}") |
| 60 | + |
| 61 | + tasks: List[Tuple[str, str, str]] = [] |
| 62 | + # Iterate through files in the directory |
| 63 | + for file_name in os.listdir(micrographs_dir): |
| 64 | + if file_name.endswith(".mrc"): |
| 65 | + src_path = os.path.join(micrographs_dir, file_name) |
| 66 | + dest_path = os.path.join(clahe_denoised_dir, file_name.replace(".mrc", "."+img_format)) |
| 67 | + if os.path.exists(dest_path): |
| 68 | + logging.info(f"{dest_path} already exists!") |
| 69 | + else: |
| 70 | + tasks.append((src_path, dest_path, img_format)) |
| 71 | + |
| 72 | + # Parallel processing of tasks |
| 73 | + with ProcessPoolExecutor(max_workers=max_workers) as executor: |
| 74 | + futures = [executor.submit(clahe_denoise, task) for task in tasks] |
| 75 | + for future in futures: |
| 76 | + future.result() |
| 77 | + gc.collect() |
| 78 | + |
| 79 | +# Main function |
| 80 | + |
| 81 | +def main(source_dir: str, project_dir: str, ncpu: int, img_format: str) -> None: |
| 82 | + """ |
| 83 | + Main function to set up logging, determine available CPUs, and start the denoising process. |
| 84 | +
|
| 85 | + Args: |
| 86 | + source_dir (str): Path to the directory containing raw micrographs. |
| 87 | + project_dir (str): Path to the project directory where denoised images will be saved. |
| 88 | + ncpu (int): Number of CPUs to use for parallel processing. Defaults to half the available CPUs. |
| 89 | + img_format (str): Output format of denoised images |
| 90 | +
|
| 91 | + Notes: |
| 92 | + - Logging is configured to write messages to a log file and the console. |
| 93 | + - Ensures at least one CPU is used for processing. |
| 94 | + """ |
| 95 | + # Configure logging |
| 96 | + logging.basicConfig(filename="partinet_denoise.log", level=logging.INFO, format='%(asctime)s - %(message)s') |
| 97 | + logging.getLogger().addHandler(logging.StreamHandler()) |
| 98 | + |
| 99 | + # Prepare output directory and log file |
| 100 | + denoise_dir = os.path.join(project_dir, "denoised") |
| 101 | + logger_name = project_dir + "/partinet_denoise.log" |
| 102 | + logging.basicConfig(filename=logger_name, level=logging.INFO, format='%(asctime)s - %(message)s') |
| 103 | + |
| 104 | + # Determine number of available CPUs |
| 105 | + max_available_cpus = multiprocessing.cpu_count() |
| 106 | + max_workers = max(1, max_available_cpus // 2) |
| 107 | + |
| 108 | + # Adjust number of CPUs to use |
| 109 | + if ncpu is not None: |
| 110 | + ncpu = min(ncpu, max_workers) |
| 111 | + else: |
| 112 | + ncpu = max_workers |
| 113 | + |
| 114 | + logging.info(f"Using {ncpu} workers out of {max_available_cpus} available CPUs.") |
| 115 | + logging.info(f"Processing raw micrographs in {source_dir}") |
| 116 | + logging.info(f"Saving denoised micrographs in {denoise_dir}") |
| 117 | + |
| 118 | + # Process the directory |
| 119 | + process_directory(source_dir, denoise_dir, ncpu, img_format) |
| 120 | + |
| 121 | +# Command-line argument parsing |
| 122 | + |
| 123 | +def parse_args() -> argparse.Namespace: |
| 124 | + """ |
| 125 | + Parses command-line arguments for the script. |
| 126 | +
|
| 127 | + Returns: |
| 128 | + argparse.Namespace: Parsed arguments with the following attributes: |
| 129 | + - raw: Path to raw micrographs. |
| 130 | + - project: Path to the project directory. |
| 131 | + - ncpu: Number of CPUs to use. |
| 132 | + - img_format: output format of denoised images |
| 133 | + """ |
| 134 | + parser = argparse.ArgumentParser(description="Denoise micrographs with guided CryoSegNet-style filter") |
| 135 | + parser.add_argument("--raw", required=True, help="Path to raw micrographs") |
| 136 | + parser.add_argument("--project", required=True, help="Denoised micrographs saved in project/denoised") |
| 137 | + parser.add_argument('--ncpu', type=int, default=None, help='Number of CPUs to use') |
| 138 | + parser.add_argument('--img_format', type=str, choices=["png","jpg","mrc"], default="png", help='Output format of denoised images') |
| 139 | + return parser.parse_args() |
| 140 | + |
| 141 | +# Entry point for the script |
| 142 | + |
| 143 | +if __name__ == "__main__": |
| 144 | + args = parse_args() |
| 145 | + main(args.raw, args.project, args.ncpu, args.img_format) |
0 commit comments