Source code for lesion_metrics.cli.common
"""Common cli functions
Author: Jacob Reinhold (jcreinhold@gmail.com)
Created on: 05 Dec 2021
"""
__all__ = [
"ArgType",
"check_files",
"csv_file_path",
"dir_path",
"dir_or_file_path",
"file_path",
"glob_imgs",
"pad_with_none_to_length",
"setup_log",
"split_filename",
"summary_statistics",
]
import argparse
import builtins
import collections
import functools
import logging
import os
import pathlib
import typing
import numpy as np
ArgType = typing.Optional[typing.Union[argparse.Namespace, typing.List[builtins.str]]]
[docs]def split_filename(
filepath: typing.Union[os.PathLike, builtins.str], *, resolve: builtins.bool = False
) -> typing.Tuple[pathlib.Path, builtins.str, builtins.str]:
"""split a filepath into the directory, base, and extension"""
filepath = pathlib.Path(filepath)
if resolve:
filepath.resolve()
path = filepath.parent
_base = pathlib.Path(filepath.stem)
ext = filepath.suffix
if ext == ".gz":
ext2 = _base.suffix
base = str(_base.stem)
ext = ext2 + ext
else:
base = str(_base)
return pathlib.Path(path), base, ext
[docs]def setup_log(verbosity: builtins.int) -> None:
"""get logger with appropriate logging level and message"""
if verbosity == 1:
level = logging.getLevelName("INFO")
elif verbosity >= 2:
level = logging.getLevelName("DEBUG")
else:
level = logging.getLevelName("WARNING")
fmt = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
logging.basicConfig(format=fmt, level=level)
logging.captureWarnings(True)
class _ParseType:
@property
def __name__(self) -> builtins.str:
name = self.__class__.__name__
assert isinstance(name, str)
return name
def __str__(self) -> str:
return self.__name__
[docs]class file_path(_ParseType):
def __call__(self, string: builtins.str) -> pathlib.Path:
path = pathlib.Path(string)
if not path.is_file():
msg = f"{string} is not a valid path to a file."
raise argparse.ArgumentTypeError(msg)
return path
[docs]class csv_file_path(_ParseType):
def __call__(self, string: builtins.str) -> pathlib.Path:
if not string.endswith(".csv") or not string.isprintable():
msg = (
f"{string} is not a valid path to a csv file.\n"
"file needs to end with csv and only contain "
"printable characters."
)
raise argparse.ArgumentTypeError(msg)
path = pathlib.Path(string)
return path
[docs]class dir_path(_ParseType):
def __call__(self, string: builtins.str) -> pathlib.Path:
path = pathlib.Path(string)
if not path.is_dir():
msg = f"{string} is not a valid path to a directory."
raise argparse.ArgumentTypeError(msg)
return path
[docs]class dir_or_file_path(_ParseType):
def __call__(self, string: builtins.str) -> pathlib.Path:
path = pathlib.Path(string)
if not path.is_dir() and not path.is_file():
msg = f"{string} is not a valid path to a directory or file."
raise argparse.ArgumentTypeError(msg)
return path
[docs]def glob_imgs(
path: pathlib.Path, ext: builtins.str = "*.nii*"
) -> typing.List[pathlib.Path]:
"""grab all `ext` files in a directory and sort them for consistency"""
return sorted(path.glob(ext))
[docs]def check_files(*files: pathlib.Path) -> None:
msg = ""
for f in files:
if not f.is_file():
msg += f"{f} is not a valid path.\n"
if msg:
raise ValueError(msg + "Aborting.")
[docs]def summary_statistics(
data: typing.Sequence[builtins.float],
) -> collections.OrderedDict:
funcs: collections.OrderedDict[builtins.str, typing.Callable]
funcs = collections.OrderedDict()
funcs["Avg"] = np.mean
funcs["Std"] = np.std
funcs["Min"] = np.min
funcs["25%"] = functools.partial(np.percentile, q=25.0)
funcs["50%"] = np.median
funcs["75%"] = functools.partial(np.percentile, q=75.0)
funcs["Max"] = np.max
return collections.OrderedDict((label, f(data)) for label, f in funcs.items())
[docs]def pad_with_none_to_length(
lst: typing.List[typing.Any], length: builtins.int
) -> typing.List[typing.Any]:
current_length = len(lst)
if length <= current_length:
return lst
n = length - current_length
padded = lst + ([None] * n)
assert len(padded) == length
return padded