Source code for tiatoolbox.cli.common

"""Define common code required for cli."""

from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING, Any

import click

if TYPE_CHECKING:  # pragma: no cover
    from tiatoolbox.models.models_abc import IOConfigABC


[docs] def add_default_to_usage_help( usage_help: str, default: str or int or float or bool, ) -> str: """Adds default value to usage help string. Args: usage_help (str): usage help for click option. default (str or int or float): default value as string for click option. Returns: str: New usage_help value. """ if default is not None: return f"{usage_help} default={default}" return usage_help
[docs] def cli_img_input( usage_help: str = "Path to WSI or directory containing WSIs.", multiple: bool | None = None, ) -> callable: """Enables --img-input option for cli.""" if multiple is None: multiple = False if multiple: usage_help = usage_help + " Multiple instances may be provided." return click.option("--img-input", help=usage_help, type=str, multiple=multiple)
[docs] def cli_name( usage_help: str = "User defined name to be used as an identifier.", multiple: bool | None = None, ) -> callable: """Enable --name option for cli.""" if multiple is None: multiple = False if multiple: usage_help = usage_help + " Multiple instances may be provided." return click.option("--name", help=usage_help, type=str, multiple=multiple)
[docs] def cli_output_path( usage_help: str = "Path to output directory to save the output.", default: str | None = None, ) -> callable: """Enables --output-path option for cli.""" return click.option( "--output-path", help=add_default_to_usage_help(usage_help, default), type=str, default=default, )
[docs] def cli_file_type( usage_help: str = "File types to capture from directory.", default: str = "*.ndpi, *.svs, *.mrxs, *.jp2", ) -> callable: """Enables --file-types option for cli.""" return click.option( "--file-types", help=add_default_to_usage_help(usage_help, default), default=default, type=str, )
[docs] def cli_mode( usage_help: str = "Selected mode to show or save the required information.", default: str = "save", input_type: click.Choice | None = None, ) -> callable: """Enables --mode option for cli.""" if input_type is None: input_type = click.Choice(["show", "save"], case_sensitive=False) return click.option( "--mode", help=add_default_to_usage_help(usage_help, default), default=default, type=input_type, )
[docs] def cli_region( usage_help: str = "Image region in the whole slide image to read from. " "default=0 0 2000 2000", ) -> callable: """Enables --region option for cli.""" return click.option( "--region", type=int, nargs=4, help=usage_help, )
[docs] def cli_units( usage_help: str = "Image resolution units to read the image.", default: str = "level", input_type: click.Choice | None = None, ) -> callable: """Enables --units option for cli.""" if input_type is None: input_type = click.Choice( ["mpp", "power", "level", "baseline"], case_sensitive=False, ) return click.option( "--units", default=default, type=input_type, help=add_default_to_usage_help(usage_help, default), )
[docs] def cli_resolution( usage_help: str = "Image resolution to read the image.", default: float = 0, ) -> callable: """Enables --resolution option for cli.""" return click.option( "--resolution", type=float, default=default, help=add_default_to_usage_help(usage_help, default), )
[docs] def cli_tile_objective( usage_help: str = "Objective value for the saved tiles.", default: int = 20, ) -> callable: """Enables --tile-objective-value option for cli.""" return click.option( "--tile-objective-value", type=int, default=default, help=add_default_to_usage_help(usage_help, default), )
[docs] def cli_tile_read_size( usage_help: str = "Width and Height of saved tiles. default=5000 5000", ) -> callable: """Enables --tile-read-size option for cli.""" return click.option( "--tile-read-size", type=int, nargs=2, default=[5000, 5000], help=usage_help, )
[docs] def cli_tile_format( usage_help: str = "File format to save image tiles, defaults = '.jpg'", ) -> callable: """Enables --tile-format option for cli.""" return click.option( "--tile-format", type=str, default=".jpg", help=usage_help, )
[docs] def cli_method( usage_help: str = "Select method of for tissue masking.", default: str = "Otsu", input_type: click.Choice | None = None, ) -> callable: """Enables --method option for cli.""" if input_type is None: input_type = click.Choice(["Otsu", "Morphological"], case_sensitive=True) return click.option( "--method", type=input_type, default=default, help=add_default_to_usage_help(usage_help, default), )
[docs] def cli_pretrained_model( usage_help: str = "Name of the predefined model used to process the data. " "The format is <model_name>_<dataset_trained_on>. For example, " "`resnet18-kather100K` is a resnet18 model trained on the Kather dataset. " "Please see " "https://tia-toolbox.readthedocs.io/en/latest/usage.html#deep-learning-models " "for a detailed list of available pretrained models." "By default, the corresponding pretrained weights will also be" "downloaded. However, you can override with your own set of weights" "via the `pretrained_weights` argument. Argument is case insensitive.", default: str = "resnet18-kather100k", ) -> callable: """Enables --pretrained-model option for cli.""" return click.option( "--pretrained-model", help=add_default_to_usage_help(usage_help, default), default=default, )
[docs] def cli_pretrained_weights( usage_help: str = "Path to the model weight file. If not supplied, the default " "pretrained weight will be used.", default: str | None = None, ) -> callable: """Enables --pretrained-weights option for cli.""" return click.option( "--pretrained-weights", help=add_default_to_usage_help(usage_help, default), default=default, )
[docs] def cli_return_probabilities( usage_help: str = "Whether to return raw model probabilities.", *, default: bool = False, ) -> callable: """Enables --return-probabilities option for cli.""" return click.option( "--return-probabilities", type=bool, help=add_default_to_usage_help(usage_help, default), default=default, )
[docs] def cli_merge_predictions( usage_help: str = "Whether to merge the predictions to form a 2-dimensional map.", *, default: bool = True, ) -> callable: """Enables --merge-predictions option for cli.""" return click.option( "--merge-predictions", type=bool, default=default, help=add_default_to_usage_help(usage_help, default), )
[docs] def cli_return_labels( usage_help: str = "Whether to return raw model output as labels.", *, default: bool = True, ) -> callable: """Enables --return-labels option for cli.""" return click.option( "--return-labels", type=bool, help=add_default_to_usage_help(usage_help, default), default=default, )
[docs] def cli_batch_size( usage_help: str = "Number of image patches to feed into the model each time.", default: int = 1, ) -> callable: """Enables --batch-size option for cli.""" return click.option( "--batch-size", help=add_default_to_usage_help(usage_help, default), default=default, )
[docs] def cli_masks( usage_help: str = "Path to the input directory containing masks to process " "corresponding to image tiles and whole-slide images. " "Patches are only processed if they are within a masked area. " "If masks are not provided, then a tissue mask will be " "automatically generated for whole-slide images or the entire image is " "processed for image tiles. Supported file types are jpg, png and npy.", default: str | None = None, ) -> callable: """Enables --masks option for cli.""" return click.option( "--masks", help=add_default_to_usage_help(usage_help, default), default=default, )
[docs] def cli_auto_generate_mask( usage_help: str = "Automatically generate tile/WSI tissue mask.", *, default: bool = False, ) -> callable: """Enables --auto-generate-mask option for cli.""" return click.option( "--auto-generate-mask", help=add_default_to_usage_help(usage_help, default), type=bool, default=default, )
[docs] def cli_yaml_config_path( usage_help: str = "Path to ioconfig file. Sample yaml file can be viewed in " "tiatoolbox.data.pretrained_model.yaml. " "if pretrained_model is used the ioconfig is automatically set.", default: str | None = None, ) -> callable: """Enables --yaml-config-path option for cli.""" return click.option( "--yaml-config-path", help=add_default_to_usage_help(usage_help, default), default=default, )
[docs] def cli_on_gpu( usage_help: str = "Run the model on GPU.", *, default: bool = False, ) -> callable: """Enables --on-gpu option for cli.""" return click.option( "--on-gpu", type=bool, default=default, help=add_default_to_usage_help(usage_help, default), )
[docs] def cli_num_loader_workers( usage_help: str = "Number of workers to load the data. Please note that they will " "also perform preprocessing.", default: int = 0, ) -> callable: """Enables --num-loader-workers option for cli.""" return click.option( "--num-loader-workers", help=add_default_to_usage_help(usage_help, default), type=int, default=default, )
[docs] def cli_num_postproc_workers( usage_help: str = "Number of workers to post-process the network output.", default: int = 0, ) -> callable: """Enables --num-postproc-workers option for cli.""" return click.option( "--num-postproc-workers", help=add_default_to_usage_help(usage_help, default), type=int, default=default, )
[docs] def cli_verbose( usage_help: str = "Prints the console output.", *, default: bool = True, ) -> callable: """Enables --verbose option for cli.""" return click.option( "--verbose", type=bool, help=add_default_to_usage_help(usage_help, str(default)), default=default, )
[docs] class TIAToolboxCLI(click.Group): """Define TIAToolbox Commandline Interface Click group.""" def __init__( self: TIAToolboxCLI, *args: tuple[Any, ...], **kwargs: dict[str, Any], ) -> None: """Initialize TIAToolboxCLI.""" super().__init__(*args, **kwargs) self.help = "Computational pathology toolbox by TIA Centre." self.add_help_option = {"help_option_names": ["-h", "--help"]}
[docs] def no_input_message( input_file: (str or Path) | None = None, message: str = "No image input provided.\n", ) -> Path: """This function is called if no input is provided. Args: input_file (str or Path): Path to input file. message (str): Error message to display. Returns: Path: Return input path as :class:`Path`. """ if input_file is None: ctx = click.get_current_context() ctx.fail(message=message) return Path(input_file)
[docs] def prepare_file_dir_cli( img_input: str or Path, output_path: str or Path, file_types: str, mode: str, sub_dirname: str, ) -> [list, Path]: """Prepares CLI for running code on multiple files or a directory. Checks for existing directories to run tests. Converts file path to list of file paths or creates list of file paths if input is a directory. Args: img_input (str or Path): File path to images. output_path (str or Path): Output directory path. file_types (str): File types to process using cli. mode (str): wsi or tile mode. sub_dirname (str): Name of subdirectory to save output. Returns: list: list of file paths to process. pathlib.Path: updated output path. """ from tiatoolbox.utils.misc import grab_files_from_dir, string_to_tuple img_input = no_input_message(input_file=img_input) file_types = string_to_tuple(in_str=file_types) if isinstance(output_path, str): output_path = Path(output_path) if not Path.exists(img_input): raise FileNotFoundError files_all = [ img_input, ] if Path.is_dir(img_input): files_all = grab_files_from_dir(input_path=img_input, file_types=file_types) if output_path is None and mode == "save": input_dir = Path(img_input).parent output_path = input_dir / sub_dirname if mode == "save": output_path.mkdir(parents=True, exist_ok=True) return [files_all, output_path]
[docs] def prepare_model_cli( img_input: str or Path, output_path: str or Path, masks: str or Path, file_types: str, ) -> [list, list, Path]: """Prepares cli for running models. Checks for existing directories to run tests. Converts file path to list of file paths or creates list of file paths if input is a directory. Args: img_input (str or Path): File path to images. output_path (str or Path): Output directory path. masks (str or Path): File path to masks. file_types (str): File types to process using cli. Returns: list: List of file paths to process. list: List of masks corresponding to input files. Path: Output path. """ from tiatoolbox.utils.misc import grab_files_from_dir, string_to_tuple img_input = no_input_message(input_file=img_input) output_path = Path(output_path) file_types = string_to_tuple(in_str=file_types) if output_path.exists(): msg = "Path already exists." raise FileExistsError(msg) if not Path.exists(img_input): raise FileNotFoundError files_all = [ img_input, ] masks_all = None if masks is not None: masks = Path(masks) if masks.is_file(): masks_all = [masks] if masks.is_dir(): masks_all = grab_files_from_dir( input_path=masks, file_types=("*.jpg", "*.png"), ) if Path.is_dir(img_input): files_all = grab_files_from_dir(input_path=img_input, file_types=file_types) return [files_all, masks_all, output_path]
tiatoolbox_cli = TIAToolboxCLI()
[docs] def prepare_ioconfig_seg( segment_config_class: IOConfigABC, pretrained_weights: str | Path, yaml_config_path: str | Path, ) -> IOConfigABC | None: """Prepare ioconfig for segmentation.""" import yaml if pretrained_weights is not None: with Path(yaml_config_path).open() as registry_handle: ioconfig = yaml.safe_load(registry_handle) return segment_config_class(**ioconfig) return None