diff --git a/src/sim_recon/__init__.py b/src/sim_recon/__init__.py index fac3f9f..cd7af35 100644 --- a/src/sim_recon/__init__.py +++ b/src/sim_recon/__init__.py @@ -1,4 +1,10 @@ -from .main import sim_reconstruct, sim_psf_to_otf from .info import __version__ -__all__ = ("__version__", "sim_reconstruct", "sim_psf_to_otf") +__all__ = [ + __version__, +] + +if __name__ == "__main__": + from .main import sim_reconstruct, sim_psf_to_otf + + __all__.append(sim_reconstruct, sim_psf_to_otf) diff --git a/src/sim_recon/cli/otf.py b/src/sim_recon/cli/otf.py index 2008ebe..c566402 100644 --- a/src/sim_recon/cli/otf.py +++ b/src/sim_recon/cli/otf.py @@ -1,12 +1,12 @@ import logging from ..main import sim_psf_to_otf -from .parsing import parse_otf_args +from .parsing.otf import parse_args from ..progress import set_use_tqdm def main() -> None: - namespace, otf_kwargs = parse_otf_args() + namespace, otf_kwargs = parse_args() set_use_tqdm(namespace.use_tqdm) diff --git a/src/sim_recon/cli/otf_view.py b/src/sim_recon/cli/otf_view.py index 50b2d51..15cd35b 100644 --- a/src/sim_recon/cli/otf_view.py +++ b/src/sim_recon/cli/otf_view.py @@ -1,12 +1,12 @@ import logging from ..otf_view import plot_otfs -from .parsing import parse_otf_view_args +from .parsing.otf_view import parse_args from ..progress import set_use_tqdm def main(): - namespace = parse_otf_view_args() + namespace = parse_args() set_use_tqdm(namespace.use_tqdm) diff --git a/src/sim_recon/cli/parsing.py b/src/sim_recon/cli/parsing.py deleted file mode 100644 index c1cb4cb..0000000 --- a/src/sim_recon/cli/parsing.py +++ /dev/null @@ -1,254 +0,0 @@ -from __future__ import annotations -import argparse -from typing import TYPE_CHECKING - -from ..settings.formatting import OTF_FORMATTERS, RECON_FORMATTERS - -if TYPE_CHECKING: - from typing import Any - from collections.abc import Sequence - from ..settings.formatting import SettingFormat - - -def _add_help(parser: argparse.ArgumentParser) -> None: - parser.add_argument( - "-h", - "--help", - action="help", - default=argparse.SUPPRESS, - help="show this help message and exit", - ) - - -def _add_general_args(parser: argparse.ArgumentParser) -> None: - parser.add_argument( - "-v", - "--verbose", - action="store_true", - help="Show more logging", - ) - parser.add_argument( - "--no-progress", - action="store_false", - dest="use_tqdm", - help="turn off progress bars (only has an affect if tqdm is installed)", - ) - - -def handle_required( - parser: argparse.ArgumentParser, - namespace: argparse.Namespace, - *required: tuple[str, str], -): - missing_arguments: list[str] = [] - for print_name, dest in required: - if getattr(namespace, dest, None) is None: - missing_arguments.append(print_name) - if missing_arguments: - parser.error( - "the following arguments are required: %s" % ", ".join(missing_arguments) - ) - - -def _add_override_args_from_formatters( - parser: argparse.ArgumentParser, formatters: dict[str, SettingFormat] -) -> None: - arg_group = parser.add_argument_group( - "Overrides", - "Arguments that override configured values. Defaults stated are only used if no value is given or configured.", - ) - for arg_name, formatter in formatters.items(): - if formatter.conv is bool: - arg_group.add_argument( - f"--{arg_name}", - action="store_true", - default=None, - required=False, - help=formatter.description, - ) - else: - arg_group.add_argument( - f"--{arg_name}", - type=formatter.conv, - nargs=formatter.count, - required=False, - help=formatter.description, - ) - - -def parse_otf_args( - args: Sequence[str] | None = None, -) -> tuple[argparse.Namespace, dict[str, Any]]: - parser = argparse.ArgumentParser(prog="sim-otf", add_help=False) - parser.add_argument( - "-c", - "--config-path", - dest="config_path", - help="Path to the root config that specifies the paths to the OTFs and the other configs", - ) - parser.add_argument( - "-p", - "--psf", - dest="psf_paths", - nargs="+", - help="Paths to PSF files to be reconstructed (multiple paths can be given)", - ) - parser.add_argument( - "-o", - "--output-directory", - default=None, - help="If specified, the output directory that the OTFs will be saved in, otherwise each OTF will be saved in the same directory as its PSF", - ) - parser.add_argument( - "--overwrite", - action="store_true", - help="If specified, files will be overwritten if they already exist (unique filenames will be used otherwise)", - ) - parser.add_argument( - "--no-cleanup", - dest="cleanup", - action="store_false", - help="If specified, files created during the OTF creation process will not be cleaned up", - ) - parser.add_argument( - "--shape", - dest="xy_shape", - default=None, - nargs=2, - type=int, - help="Takes 2 integers (X Y), specifying the shape to crop PSFs to before converting (powers of 2 are fastest)", - ) - - _add_general_args(parser) - - namespace, unknown = parser.parse_known_args(args) - - _add_override_args_from_formatters(parser, OTF_FORMATTERS) - - _add_help(parser) - - override_namespace = parser.parse_args(unknown) - - handle_required( - parser, - namespace, - ("-c/--config-path", "config_path"), - ("-p/--psf", "psf_paths"), - ) - - non_override_dests = vars(namespace).keys() - otf_kwargs = { - k: v - for k, v in vars(override_namespace).items() - if v is not None and k not in non_override_dests - } - - return namespace, otf_kwargs - - -def parse_recon_args( - args: Sequence[str] | None = None, -) -> tuple[argparse.Namespace, dict[str, Any]]: - parser = argparse.ArgumentParser(prog="sim-recon", add_help=False) - parser.add_argument( - "-c", - "--config-path", - dest="config_path", - help="Path to the root config that specifies the paths to the OTFs and the other configs", - ) - parser.add_argument( - "-d", - "--data", - dest="sim_data_paths", - nargs="+", - help="Paths to SIM data files to be reconstructed (multiple paths can be given)", - ) - parser.add_argument( - "-o", - "--output-directory", - help="The output directory to save reconstructed files in", - ) - parser.add_argument( - "--overwrite", - action="store_true", - help="If specified, files will be overwritten if they already exist (unique filenames will be used otherwise)", - ) - parser.add_argument( - "--no-cleanup", - dest="cleanup", - action="store_false", - help="If specified, files created during the reconstruction process will not be cleaned up", - ) - parser.add_argument( - "--keep-split", - dest="stitch_channels", - action="store_false", - help="If specified, channels will not be stitched back together after reconstruction", - ) - parser.add_argument( - "--parallel", - dest="parallel_process", - action="store_true", - help="If specified, up to 2 processes will be run at a time", - ) - - _add_general_args(parser) - - namespace, unknown = parser.parse_known_args(args) - - # Now add override arguments so they show up in --help - _add_override_args_from_formatters(parser, RECON_FORMATTERS) - - _add_help(parser) - - override_namespace = parser.parse_args(unknown) - - handle_required( - parser, - namespace, - ("-c/--config-path", "config_path"), - ("-d/--data", "sim_data_paths"), - ("-o/--output-directory", "output_directory"), - ) - - non_override_dests = vars(namespace).keys() - recon_kwargs = { - k: v - for k, v in vars(override_namespace).items() - if v is not None and k not in non_override_dests - } - - return namespace, recon_kwargs - - -def parse_otf_view_args( - args: Sequence[str] | None = None, -) -> argparse.Namespace: - parser = argparse.ArgumentParser(prog="otf-view", add_help=True) - parser.add_argument( - dest="otf_paths", - nargs="+", - help="OTF file paths", - ) - parser.add_argument( - "--show", - dest="show", - action="store_true", - help="Display the plots while running", - ) - parser.add_argument( - "--show-only", - dest="show_only", - action="store_true", - help="Show plots without saving", - ) - parser.add_argument( - "-o", - "--output-directory", - dest="output_directory", - help="Save to this directory if saving plots, otherwise each plot will be saved with its input file", - ) - - _add_general_args(parser) - - return parser.parse_args(args) diff --git a/src/sim_recon/cli/parsing/__init__.py b/src/sim_recon/cli/parsing/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/sim_recon/cli/parsing/otf.py b/src/sim_recon/cli/parsing/otf.py new file mode 100644 index 0000000..5a69b5c --- /dev/null +++ b/src/sim_recon/cli/parsing/otf.py @@ -0,0 +1,95 @@ +from __future__ import annotations +import argparse +from typing import TYPE_CHECKING + +from ...settings.formatting import OTF_FORMATTERS +from .shared import ( + add_general_args, + add_override_args_from_formatters, + add_help, + handle_required, +) + +if TYPE_CHECKING: + from typing import Any + from collections.abc import Sequence + +__all__ = ("parse_args",) + + +def parse_args( + args: Sequence[str] | None = None, +) -> tuple[argparse.Namespace, dict[str, Any]]: + parser = argparse.ArgumentParser( + prog="sim-otf", + description="SIM PSFs to OTFs", + add_help=False, + ) + parser.add_argument( + "-c", + "--config-path", + dest="config_path", + help="Path to the root config that specifies the paths to the OTFs and the other configs", + ) + parser.add_argument( + "-p", + "--psf", + dest="psf_paths", + nargs="+", + help="Paths to PSF files to be reconstructed (multiple paths can be given)", + ) + parser.add_argument( + "-o", + "--output-directory", + default=None, + help="If specified, the output directory that the OTFs will be saved in, otherwise each OTF will be saved in the same directory as its PSF", + ) + parser.add_argument( + "--overwrite", + action="store_true", + help="If specified, files will be overwritten if they already exist (unique filenames will be used otherwise)", + ) + parser.add_argument( + "--no-cleanup", + dest="cleanup", + action="store_false", + help="If specified, files created during the OTF creation process will not be cleaned up", + ) + parser.add_argument( + "--shape", + dest="xy_shape", + default=None, + nargs=2, + type=int, + help="Takes 2 integers (X Y), specifying the shape to crop PSFs to before converting (powers of 2 are fastest)", + ) + + add_general_args(parser) + + namespace, unknown = parser.parse_known_args(args) + + add_override_args_from_formatters(parser, OTF_FORMATTERS) + + add_help(parser) + + override_namespace = parser.parse_args(unknown) + + handle_required( + parser, + namespace, + ("-c/--config-path", "config_path"), + ("-p/--psf", "psf_paths"), + ) + + non_override_dests = vars(namespace).keys() + otf_kwargs = { + k: v + for k, v in vars(override_namespace).items() + if v is not None and k not in non_override_dests + } + + return namespace, otf_kwargs + + +if __name__ == "__main__": + parse_args() diff --git a/src/sim_recon/cli/parsing/otf_view.py b/src/sim_recon/cli/parsing/otf_view.py new file mode 100644 index 0000000..e96d8ae --- /dev/null +++ b/src/sim_recon/cli/parsing/otf_view.py @@ -0,0 +1,51 @@ +from __future__ import annotations +import argparse +from typing import TYPE_CHECKING + +from .shared import add_general_args + +if TYPE_CHECKING: + from collections.abc import Sequence + +__all__ = ("parse_args",) + + +def parse_args( + args: Sequence[str] | None = None, +) -> argparse.Namespace: + parser = argparse.ArgumentParser( + prog="otf-view", + description="Create OTF views", + add_help=True, + ) + parser.add_argument( + dest="otf_paths", + nargs="+", + help="OTF file paths", + ) + parser.add_argument( + "--show", + dest="show", + action="store_true", + help="Display the plots while running", + ) + parser.add_argument( + "--show-only", + dest="show_only", + action="store_true", + help="Show plots without saving", + ) + parser.add_argument( + "-o", + "--output-directory", + dest="output_directory", + help="Save to this directory if saving plots, otherwise each plot will be saved with its input file", + ) + + add_general_args(parser) + + return parser.parse_args(args) + + +if __name__ == "__main__": + parse_args() diff --git a/src/sim_recon/cli/parsing/recon.py b/src/sim_recon/cli/parsing/recon.py new file mode 100644 index 0000000..b60fd14 --- /dev/null +++ b/src/sim_recon/cli/parsing/recon.py @@ -0,0 +1,100 @@ +from __future__ import annotations +import argparse +from typing import TYPE_CHECKING + +from ...settings.formatting import RECON_FORMATTERS +from .shared import ( + add_general_args, + add_override_args_from_formatters, + add_help, + handle_required, +) + +if TYPE_CHECKING: + from typing import Any + from collections.abc import Sequence + +__all__ = ("parse_args",) + + +def parse_args( + args: Sequence[str] | None = None, +) -> tuple[argparse.Namespace, dict[str, Any]]: + parser = argparse.ArgumentParser( + prog="sim-recon", + description="Reconstruct SIM data", + add_help=False, + ) + parser.add_argument( + "-c", + "--config-path", + dest="config_path", + help="Path to the root config that specifies the paths to the OTFs and the other configs", + ) + parser.add_argument( + "-d", + "--data", + dest="sim_data_paths", + nargs="+", + help="Paths to SIM data files to be reconstructed (multiple paths can be given)", + ) + parser.add_argument( + "-o", + "--output-directory", + help="The output directory to save reconstructed files in", + ) + parser.add_argument( + "--overwrite", + action="store_true", + help="If specified, files will be overwritten if they already exist (unique filenames will be used otherwise)", + ) + parser.add_argument( + "--no-cleanup", + dest="cleanup", + action="store_false", + help="If specified, files created during the reconstruction process will not be cleaned up", + ) + parser.add_argument( + "--keep-split", + dest="stitch_channels", + action="store_false", + help="If specified, channels will not be stitched back together after reconstruction", + ) + parser.add_argument( + "--parallel", + dest="parallel_process", + action="store_true", + help="If specified, up to 2 processes will be run at a time", + ) + + add_general_args(parser) + + namespace, unknown = parser.parse_known_args(args) + + # Now add override arguments so they show up in --help + add_override_args_from_formatters(parser, RECON_FORMATTERS) + + add_help(parser) + + override_namespace = parser.parse_args(unknown) + + handle_required( + parser, + namespace, + ("-c/--config-path", "config_path"), + ("-d/--data", "sim_data_paths"), + ("-o/--output-directory", "output_directory"), + ) + + non_override_dests = vars(namespace).keys() + recon_kwargs = { + k: v + for k, v in vars(override_namespace).items() + if v is not None and k not in non_override_dests + } + + return namespace, recon_kwargs + + +if __name__ == "__main__": + parse_args() diff --git a/src/sim_recon/cli/parsing/shared.py b/src/sim_recon/cli/parsing/shared.py new file mode 100644 index 0000000..5c8205d --- /dev/null +++ b/src/sim_recon/cli/parsing/shared.py @@ -0,0 +1,73 @@ +from __future__ import annotations +import argparse +from typing import TYPE_CHECKING + + +if TYPE_CHECKING: + from ...settings.formatting import SettingFormat + + +def add_help(parser: argparse.ArgumentParser) -> None: + parser.add_argument( + "-h", + "--help", + action="help", + default=argparse.SUPPRESS, + help="show this help message and exit", + ) + + +def add_general_args(parser: argparse.ArgumentParser) -> None: + parser.add_argument( + "-v", + "--verbose", + action="store_true", + help="Show more logging", + ) + parser.add_argument( + "--no-progress", + action="store_false", + dest="use_tqdm", + help="turn off progress bars (only has an effect if tqdm is installed)", + ) + + +def handle_required( + parser: argparse.ArgumentParser, + namespace: argparse.Namespace, + *required: tuple[str, str], +): + missing_arguments: list[str] = [] + for print_name, dest in required: + if getattr(namespace, dest, None) is None: + missing_arguments.append(print_name) + if missing_arguments: + parser.error( + "the following arguments are required: %s" % ", ".join(missing_arguments) + ) + + +def add_override_args_from_formatters( + parser: argparse.ArgumentParser, formatters: dict[str, SettingFormat] +) -> None: + arg_group = parser.add_argument_group( + "Overrides", + "Arguments that override configured values. Defaults stated are only used if no value is given or configured.", + ) + for arg_name, formatter in formatters.items(): + if formatter.conv is bool: + arg_group.add_argument( + f"--{arg_name}", + action="store_true", + default=None, + required=False, + help=formatter.description, + ) + else: + arg_group.add_argument( + f"--{arg_name}", + type=formatter.conv, + nargs=formatter.count, + required=False, + help=formatter.description, + ) diff --git a/src/sim_recon/cli/recon.py b/src/sim_recon/cli/recon.py index a5f57de..687bc59 100644 --- a/src/sim_recon/cli/recon.py +++ b/src/sim_recon/cli/recon.py @@ -1,12 +1,12 @@ import logging from ..main import sim_reconstruct -from .parsing import parse_recon_args +from .parsing.recon import parse_args from ..progress import set_use_tqdm def main() -> None: - namespace, recon_kwargs = parse_recon_args() + namespace, recon_kwargs = parse_args() set_use_tqdm(namespace.use_tqdm)