Exemple #1
0
def args_with_dict(args: List[str]) -> List[Union[Dict[str, Any], str, int]]:
    """Parse arguments list with optional arguments given as dictionary-like 
    elements.
    
    Args:
        args: List of arguments, which can be single values or "=" delimited 
            values. Single values will be stored in the same order, while
            delimited entries will be entered sequentially into a dictionary.
            Entries can also be comma-delimited to specify lists.
    
    Returns:
        List of arguments ordered first with single-value entries in the 
        same order in which they were entered, followed by a dictionary 
        with all equals-delimited entries, also in the same order as entered. 
        Entries that contain commas will be split into comma-delimited 
        lists. All values will be converted to ints if possible.
    """
    parsed = []
    args_dict = {}
    for arg in args:
        arg_split = arg.split("=")
        for_dict = len(arg_split) > 1
        vals = arg_split[1] if for_dict else arg
        vals_split = vals.split(",")
        if len(vals_split) > 1: vals = vals_split
        vals = libmag.get_int(vals)
        if for_dict:
            args_dict[arg_split[0]] = vals
        else:
            parsed.append(vals)
    parsed.append(args_dict)
    return parsed
Exemple #2
0
def process_cli_args():
    """Parse command-line arguments.
    
    Typically stores values as :mod:`magmap.settings.config` attributes.
    
    """
    parser = argparse.ArgumentParser(
        description="Setup environment for MagellanMapper")
    parser.add_argument("--version",
                        action="store_true",
                        help="Show version information and exit")

    # image specification arguments

    # image path(s) specified as an optional argument; takes precedence
    # over positional argument
    parser.add_argument(
        "--img",
        nargs="*",
        default=None,
        help="Main image path(s); after import, the filename is often "
        "given as the original name without its extension")
    # alternatively specified as the first and only positional parameter
    # with as many arguments as desired
    parser.add_argument(
        "img_paths",
        nargs="*",
        default=None,
        help="Main image path(s); can also be given as --img, which takes "
        "precedence over this argument")

    parser.add_argument(
        "--meta",
        nargs="*",
        help="Metadata path(s), which can be given as multiple files "
        "corresponding to each image")
    parser.add_argument(
        "--prefix",
        nargs="*",
        type=str,
        help="Path prefix(es), typically used as the base path for file output"
    )
    parser.add_argument(
        "--prefix_out",
        nargs="*",
        type=str,
        help="Path prefix(es), typically used as the base path for file output "
        "when --prefix modifies the input path")
    parser.add_argument(
        "--suffix",
        nargs="*",
        type=str,
        help="Path suffix(es), typically inserted just before the extension")
    parser.add_argument("--channel", nargs="*", type=int, help="Channel index")
    parser.add_argument("--series", help="Series index")
    parser.add_argument("--subimg_offset",
                        nargs="*",
                        help="Sub-image offset in x,y,z")
    parser.add_argument("--subimg_size",
                        nargs="*",
                        help="Sub-image size in x,y,z")
    parser.add_argument("--offset", nargs="*", help="ROI offset in x,y,z")
    parser.add_argument("--size", nargs="*", help="ROI size in x,y,z")
    parser.add_argument("--db", help="Database path")
    parser.add_argument(
        "--cpus",
        help="Maximum number of CPUs/processes to use for multiprocessing "
        "tasks. Use \"none\" or 0 to auto-detect this number (default).")
    parser.add_argument(
        "--load",
        nargs="*",
        help="Load associated data files; see config.LoadData for settings")

    # task arguments
    parser.add_argument(
        "--proc",
        nargs="*",
        help=_get_args_dict_help(
            "Image processing mode; see config.ProcessTypes for keys "
            "and config.PreProcessKeys for PREPROCESS values",
            config.ProcessTypes))
    parser.add_argument("--register",
                        type=str.lower,
                        choices=libmag.enum_names_aslist(config.RegisterTypes),
                        help="Image registration task")
    parser.add_argument("--df",
                        type=str.lower,
                        choices=libmag.enum_names_aslist(config.DFTasks),
                        help="Data frame task")
    parser.add_argument("--plot_2d",
                        type=str.lower,
                        choices=libmag.enum_names_aslist(config.Plot2DTypes),
                        help="2D plot task; see config.Plot2DTypes")
    parser.add_argument("--ec2_start",
                        nargs="*",
                        help="AWS EC2 instance start")
    parser.add_argument("--ec2_list", nargs="*", help="AWS EC2 instance list")
    parser.add_argument("--ec2_terminate",
                        nargs="*",
                        help="AWS EC2 instance termination")
    parser.add_argument(
        "--notify",
        nargs="*",
        help="Notification message URL, message, and attachment strings")

    # profile arguments
    parser.add_argument(
        "--roi_profile",
        nargs="*",
        help="ROI profile, which can be separated by underscores "
        "for multiple profiles and given as paths to custom profiles "
        "in YAML format. Multiple profile groups can be given, which "
        "will each be applied to the corresponding channel. See "
        "docs/settings.md for more details.")
    parser.add_argument(
        "--atlas_profile",
        help="Atlas profile, which can be separated by underscores "
        "for multiple profiles and given as paths to custom profiles "
        "in YAML format. See docs/settings.md for more details.")
    parser.add_argument(
        "--grid_search",
        help="Grid search hyperparameter tuning profile(s), which can be "
        "separated by underscores for multiple profiles and given as "
        "paths to custom profiles in YAML format. See docs/settings.md "
        "for more details.")
    parser.add_argument(
        "--theme",
        nargs="*",
        type=str.lower,
        choices=libmag.enum_names_aslist(config.Themes),
        help="UI theme, which can be given as multiple themes to apply "
        "on top of one another")

    # grouped arguments
    parser.add_argument(
        "--truth_db",
        nargs="*",
        help="Truth database; see config.TruthDB for settings and "
        "config.TruthDBModes for modes")
    parser.add_argument("--labels",
                        nargs="*",
                        help=_get_args_dict_help(
                            "Atlas labels; see config.AtlasLabels.",
                            config.AtlasLabels))
    parser.add_argument("--transform",
                        nargs="*",
                        help=_get_args_dict_help(
                            "Image transformations; see config.Transforms.",
                            config.Transforms))
    parser.add_argument(
        "--reg_suffixes",
        nargs="*",
        help=_get_args_dict_help(
            "Registered image suffixes; see config.RegSuffixes for keys "
            "and config.RegNames for values", config.RegSuffixes))
    parser.add_argument(
        "--plot_labels",
        nargs="*",
        help=_get_args_dict_help(
            "Plot label customizations; see config.PlotLabels ",
            config.PlotLabels))
    parser.add_argument(
        "--set_meta",
        nargs="*",
        help="Set metadata values; see config.MetaKeys for settings")

    # image and figure display arguments
    parser.add_argument("--plane",
                        type=str.lower,
                        choices=config.PLANE,
                        help="Planar orientation")
    parser.add_argument(
        "--show",
        nargs="?",
        const="1",
        help="If applicable, show images after completing the given task")
    parser.add_argument(
        "--alphas",
        help="Alpha opacity levels, which can be comma-delimited for "
        "multichannel images")
    parser.add_argument(
        "--vmin",
        help="Minimum intensity levels, which can be comma-delimited "
        "for multichannel images")
    parser.add_argument(
        "--vmax",
        help="Maximum intensity levels, which can be comma-delimited "
        "for multichannel images")
    parser.add_argument("--seed", help="Random number generator seed")

    # export arguments
    parser.add_argument("--save_subimg",
                        action="store_true",
                        help="Save sub-image as separate file")
    parser.add_argument("--slice", help="Slice given as start,stop,step")
    parser.add_argument("--delay", help="Animation delay in ms")
    parser.add_argument("--savefig", help="Extension for saved figures")
    parser.add_argument("--groups",
                        nargs="*",
                        help="Group values corresponding to each image")
    parser.add_argument(
        "-v",
        "--verbose",
        nargs="*",
        help=_get_args_dict_help(
            "Verbose output to assist with debugging; see config.Verbosity.",
            config.Verbosity))

    # only parse recognized arguments to avoid error for unrecognized ones
    args, args_unknown = parser.parse_known_args()

    # set up application directories
    user_dir = config.user_app_dirs.user_data_dir
    if not os.path.isdir(user_dir):
        # make application data directory
        if os.path.exists(user_dir):
            # backup any non-directory file
            libmag.backup_file(user_dir)
        os.makedirs(user_dir)

    if args.verbose is not None:
        # verbose mode and logging setup
        config.verbose = True
        config.verbosity = args_to_dict(args.verbose, config.Verbosity,
                                        config.verbosity)
        if config.verbosity[config.Verbosity.LEVEL] is None:
            # default to debug mode if any verbose flag is set without level
            config.verbosity[config.Verbosity.LEVEL] = logging.DEBUG
        logs.update_log_level(config.logger,
                              config.verbosity[config.Verbosity.LEVEL])

        # print longer Numpy arrays for debugging
        np.set_printoptions(linewidth=200, threshold=10000)
        _logger.info("Set verbose to %s", config.verbosity)

    # set up logging to given file unless explicitly given an empty string
    log_path = config.verbosity[config.Verbosity.LOG_PATH]
    if log_path != "":
        if log_path is None:
            log_path = os.path.join(config.user_app_dirs.user_data_dir,
                                    "out.log")
        # log to file
        config.log_path = logs.add_file_handler(config.logger, log_path)

    # redirect standard out/error to logging
    sys.stdout = logs.LogWriter(config.logger.info)
    sys.stderr = logs.LogWriter(config.logger.error)

    # load preferences file
    config.prefs = prefs_prof.PrefsProfile()
    config.prefs.add_profiles(str(config.PREFS_PATH))

    if args.version:
        # print version info and exit
        _logger.info(f"{config.APP_NAME}-{libmag.get_version(True)}")
        shutdown()

    # log the app launch path
    path_launch = (sys._MEIPASS if getattr(sys, "frozen", False)
                   and hasattr(sys, "_MEIPASS") else sys.argv[0])
    _logger.info(f"Launched MagellanMapper from {path_launch}")

    if args.img is not None or args.img_paths:
        # set image file path and convert to basis for additional paths
        config.filenames = args.img if args.img else args.img_paths
        config.filename = config.filenames[0]
        print("Set filenames to {}, current filename {}".format(
            config.filenames, config.filename))

    if args.meta is not None:
        # set metadata paths
        config.metadata_paths = args.meta
        print("Set metadata paths to", config.metadata_paths)
        config.metadatas = []
        for path in config.metadata_paths:
            # load metadata to dictionary
            md, _ = importer.load_metadata(path, assign=False)
            config.metadatas.append(md)

    if args.channel is not None:
        # set the channels
        config.channel = args.channel
        print("Set channel to {}".format(config.channel))

    config.series_list = [config.series]  # list of series
    if args.series is not None:
        series_split = args.series.split(",")
        config.series_list = []
        for ser in series_split:
            ser_split = ser.split("-")
            if len(ser_split) > 1:
                ser_range = np.arange(int(ser_split[0]), int(ser_split[1]) + 1)
                config.series_list.extend(ser_range.tolist())
            else:
                config.series_list.append(int(ser_split[0]))
        config.series = config.series_list[0]
        print("Set to series_list to {}, current series {}".format(
            config.series_list, config.series))

    if args.savefig is not None:
        # save figure with file type of this extension; remove leading period
        config.savefig = _parse_none(args.savefig.lstrip("."))
        print("Set savefig extension to {}".format(config.savefig))

    # parse sub-image offsets and sizes;
    # expects x,y,z input but stores as z,y,x by convention
    if args.subimg_offset is not None:
        config.subimg_offsets = _parse_coords(args.subimg_offset, True)
        print("Set sub-image offsets to {} (z,y,x)".format(
            config.subimg_offsets))
    if args.subimg_size is not None:
        config.subimg_sizes = _parse_coords(args.subimg_size, True)
        print("Set sub-image sizes to {} (z,y,x)".format(config.subimg_sizes))

    # parse ROI offsets and sizes, which are relative to any sub-image;
    # expects x,y,z input and output
    if args.offset is not None:
        config.roi_offsets = _parse_coords(args.offset)
        if config.roi_offsets:
            config.roi_offset = config.roi_offsets[0]
        print("Set ROI offsets to {}, current offset {} (x,y,z)".format(
            config.roi_offsets, config.roi_offset))
    if args.size is not None:
        config.roi_sizes = _parse_coords(args.size)
        if config.roi_sizes:
            config.roi_size = config.roi_sizes[0]
        print("Set ROI sizes to {}, current size {} (x,y,z)".format(
            config.roi_sizes, config.roi_size))

    if args.cpus is not None:
        # set maximum number of CPUs
        config.cpus = _parse_none(args.cpus.lower(), int)
        print("Set maximum number of CPUs for multiprocessing tasks to",
              config.cpus)

    if args.load is not None:
        # flag loading data sources with default sub-arg indicating that the
        # data should be loaded from a default path; otherwise, load from
        # path given by the sub-arg; change delimiter to allow paths with ","
        config.load_data = args_to_dict(args.load,
                                        config.LoadData,
                                        config.load_data,
                                        sep_vals="|",
                                        default=True)
        print("Set to load the data types: {}".format(config.load_data))

    # set up main processing mode
    if args.proc is not None:
        config.proc_type = args_to_dict(args.proc,
                                        config.ProcessTypes,
                                        config.proc_type,
                                        default=True)
        print("Set main processing tasks to:", config.proc_type)

    if args.set_meta is not None:
        # set individual metadata values, currently used for image import
        # TODO: take precedence over loaded metadata archives
        config.meta_dict = args_to_dict(args.set_meta,
                                        config.MetaKeys,
                                        config.meta_dict,
                                        sep_vals="|")
        print("Set metadata values to {}".format(config.meta_dict))
        res = config.meta_dict[config.MetaKeys.RESOLUTIONS]
        if res:
            # set image resolutions, taken as a single set of x,y,z and
            # converting to a nested list of z,y,x
            res_split = res.split(",")
            if len(res_split) >= 3:
                res_float = tuple(float(i) for i in res_split)[::-1]
                config.resolutions = [res_float]
                print("Set resolutions to {}".format(config.resolutions))
            else:
                res_float = None
                print("Resolution ({}) should be given as 3 values (x,y,z)".
                      format(res))
            # store single set of resolutions, similar to input
            config.meta_dict[config.MetaKeys.RESOLUTIONS] = res_float
        mag = config.meta_dict[config.MetaKeys.MAGNIFICATION]
        if mag:
            # set objective magnification
            config.magnification = mag
            print("Set magnification to {}".format(config.magnification))
        zoom = config.meta_dict[config.MetaKeys.ZOOM]
        if zoom:
            # set objective zoom
            config.zoom = zoom
            print("Set zoom to {}".format(config.zoom))
        shape = config.meta_dict[config.MetaKeys.SHAPE]
        if shape:
            # parse shape, storing only in dict
            config.meta_dict[config.MetaKeys.SHAPE] = [
                int(n) for n in shape.split(",")[::-1]
            ]

    # set up ROI and register profiles
    setup_roi_profiles(args.roi_profile)
    setup_atlas_profiles(args.atlas_profile)
    setup_grid_search_profiles(args.grid_search)

    if args.plane is not None:
        config.plane = args.plane
        print("Set plane to {}".format(config.plane))
    if args.save_subimg:
        config.save_subimg = args.save_subimg
        print("Set to save the sub-image")

    if args.labels:
        # set up atlas labels
        setup_labels(args.labels)

    if args.transform is not None:
        # image transformations such as flipping, rotation
        config.transform = args_to_dict(args.transform, config.Transforms,
                                        config.transform)
        print("Set transformations to {}".format(config.transform))

    if args.register:
        # register type to process in register module
        config.register_type = args.register
        print("Set register type to {}".format(config.register_type))

    if args.df:
        # data frame processing task
        config.df_task = args.df
        print("Set data frame processing task to {}".format(config.df_task))

    if args.plot_2d:
        # 2D plot type to process in plot_2d module
        config.plot_2d_type = args.plot_2d
        print("Set plot_2d type to {}".format(config.plot_2d_type))

    if args.slice:
        # specify a generic slice by command-line, assuming same order
        # of arguments as for slice built-in function and interpreting
        # "none" string as None
        config.slice_vals = args.slice.split(",")
        config.slice_vals = [
            _parse_none(val.lower(), int) for val in config.slice_vals
        ]
        print("Set slice values to {}".format(config.slice_vals))
    if args.delay:
        config.delay = int(args.delay)
        print("Set delay to {}".format(config.delay))

    if args.show:
        # show images after task is performed, if supported
        config.show = _is_arg_true(args.show)
        print("Set show to {}".format(config.show))

    if args.groups:
        config.groups = args.groups
        print("Set groups to {}".format(config.groups))
    if args.ec2_start is not None:
        # start EC2 instances
        config.ec2_start = args_with_dict(args.ec2_start)
        print("Set ec2 start to {}".format(config.ec2_start))
    if args.ec2_list:
        # list EC2 instances
        config.ec2_list = args_with_dict(args.ec2_list)
        print("Set ec2 list to {}".format(config.ec2_list))
    if args.ec2_terminate:
        config.ec2_terminate = args.ec2_terminate
        print("Set ec2 terminate to {}".format(config.ec2_terminate))
    if args.notify:
        notify_len = len(args.notify)
        if notify_len > 0:
            config.notify_url = args.notify[0]
            print("Set notification URL to {}".format(config.notify_url))
        if notify_len > 1:
            config.notify_msg = args.notify[1]
            print("Set notification message to {}".format(config.notify_msg))
        if notify_len > 2:
            config.notify_attach = args.notify[2]
            print("Set notification attachment path to {}".format(
                config.notify_attach))

    if args.prefix is not None:
        # path input/output prefixes
        config.prefixes = args.prefix
        config.prefix = config.prefixes[0]
        print("Set path prefixes to {}".format(config.prefixes))

    if args.prefix_out is not None:
        # path output prefixes
        config.prefixes_out = args.prefix_out
        config.prefix_out = config.prefixes_out[0]
        print("Set path prefixes to {}".format(config.prefixes_out))

    if args.suffix is not None:
        # path suffixes
        config.suffixes = args.suffix
        config.suffix = config.suffixes[0]
        print("Set path suffixes to {}".format(config.suffixes))

    if args.alphas:
        # specify alpha levels
        config.alphas = [float(val) for val in args.alphas.split(",")]
        print("Set alphas to", config.alphas)

    if args.vmin:
        # specify vmin levels
        config.vmins = [libmag.get_int(val) for val in args.vmin.split(",")]
        print("Set vmins to", config.vmins)

    if args.vmax:
        # specify vmax levels and copy to vmax overview used for plotting
        # and updated for normalization
        config.vmaxs = [libmag.get_int(val) for val in args.vmax.split(",")]
        config.vmax_overview = list(config.vmaxs)
        print("Set vmaxs to", config.vmaxs)

    if args.reg_suffixes is not None:
        # specify suffixes of registered images to load
        config.reg_suffixes = args_to_dict(args.reg_suffixes,
                                           config.RegSuffixes,
                                           config.reg_suffixes)
        print("Set registered image suffixes to {}".format(
            config.reg_suffixes))

    if args.seed:
        # specify random number generator seed
        config.seed = int(args.seed)
        print("Set random number generator seed to", config.seed)

    if args.plot_labels is not None:
        # specify general plot labels
        config.plot_labels = args_to_dict(args.plot_labels, config.PlotLabels,
                                          config.plot_labels)
        print("Set plot labels to {}".format(config.plot_labels))

    if args.theme is not None:
        # specify themes, currently applied to Matplotlib elements
        theme_names = []
        for theme in args.theme:
            # add theme enum if found
            theme_enum = libmag.get_enum(theme, config.Themes)
            if theme_enum:
                config.rc_params.append(theme_enum)
                theme_names.append(theme_enum.name)
        print("Set to use themes to {}".format(theme_names))
    # set up Matplotlib styles/themes
    plot_2d.setup_style()

    if args.db:
        # set main database path to user arg
        config.db_path = args.db
        print("Set database name to {}".format(config.db_path))
    else:
        # set default path
        config.db_path = os.path.join(user_dir, config.db_path)

    if args.truth_db:
        # set settings for separate database of "truth blobs"
        config.truth_db_params = args_to_dict(args.truth_db,
                                              config.TruthDB,
                                              config.truth_db_params,
                                              sep_vals="|")
        mode = config.truth_db_params[config.TruthDB.MODE]
        config.truth_db_mode = libmag.get_enum(mode, config.TruthDBModes)
        libmag.printv(config.truth_db_params)
        print("Mapped \"{}\" truth_db mode to {}".format(
            mode, config.truth_db_mode))

    # notify user of full args list, including unrecognized args
    _logger.debug(f"All command-line arguments: {sys.argv}")
    if args_unknown:
        _logger.info(
            f"The following command-line arguments were unrecognized and "
            f"ignored: {args_unknown}")
Exemple #3
0
def args_to_dict(args: List[str],
                 keys_enum: Type[Enum],
                 args_dict: Optional[Dict[Enum, Any]] = None,
                 sep_args: str = "=",
                 sep_vals: str = ",",
                 default: Optional[Any] = None) -> Dict[Enum, Any]:
    """Parse positional and keyword-based arguments to an enum-keyed dictionary.
    
    Args:
        args: List of arguments with positional values followed by
            ``sep_args``-delimited values. Positional values will be entered
            in the existing order of ``keys_enum`` based on member values, 
            while keyword-based values will be entered if an enum 
            member corresponding to the keyword exists.
            Entries can also be ``sep_vals``-delimited to specify lists.
        keys_enum: Enum class to use as keys for dictionary. Values are
            assumed to range from 1 to number of members as output 
            by the default Enum functional API.
        args_dict: Dictionary to be filled or updated with keys from
            ``keys_enum``; defaults to None, which will assign an empty dict.
        sep_args: Separator between arguments and values; defaults to "=".
        sep_vals: Separator within values; defaults to ",".
        default: Default value for each argument. Effectively turns off
            positional argument assignments since all args become
            ``<keyword>=<default>``. Defaults to None. If a str, will
            undergo splitting by ``sep_vals``.
    
    Returns:
        Dictionary filled with arguments. Values that contain commas
        will be split into comma-delimited lists. All values will be 
        converted to ints if possible.
    
    """
    if args_dict is None:
        args_dict = {}
    by_position = True
    num_enums = len(keys_enum)
    for i, arg in enumerate(args):
        arg_split = arg.split(sep_args)
        if default and len(arg_split) < 2:
            # add default value unless another value is given
            arg_split.append(default)
        len_arg_split = len(arg_split)
        # assume by position until any keyword given
        by_position = by_position and len_arg_split < 2
        key = None
        vals = arg
        if by_position:
            # positions are based on enum vals, assumed to range from
            # 1 to num of members
            n = i + 1
            if n > num_enums:
                _logger.warn("No further parameters in {} to assign \"{}\" by "
                             "position, skipping".format(keys_enum, arg))
                continue
            key = keys_enum(n)
        elif len_arg_split < 2:
            _logger.warn(
                "parameter {} does not contain a keyword, skipping".format(
                    arg))
        else:
            # assign based on keyword if its equivalent enum exists
            vals = arg_split[1]
            key_str = arg_split[0].upper()
            try:
                key = keys_enum[key_str]
            except KeyError:
                _logger.warn("Unable to find '{}' in {}, skipping".format(
                    key_str, keys_enum))
                continue
        if key:
            if isinstance(vals, str):
                # split delimited strings
                vals_split = vals.split(sep_vals)
                if len(vals_split) > 1:
                    vals = vals_split
                # cast to numeric type if possible
                vals = libmag.get_int(vals)
            # assign to found enum
            args_dict[key] = vals
    return args_dict
Exemple #4
0
def main(process_args_only=False, skip_dbs=False):
    """Starts the visualization GUI.
    
    Processes command-line arguments.
    
    Args:
        process_args_only (bool): Processes command-line arguments and
            returns; defaults to False.
        skip_dbs (bool): True to skip loading databases; defaults to False.
    """
    parser = argparse.ArgumentParser(
        description="Setup environment for MagellanMapper")

    # image specification arguments
    parser.add_argument(
        "--img",
        nargs="*",
        help="Main image path(s); after import, the filename is often "
        "given as the original name without its extension")
    parser.add_argument(
        "--meta",
        nargs="*",
        help="Metadata path(s), which can be given as multiple files "
        "corresponding to each image")
    parser.add_argument("--prefix", help="Path prefix")
    parser.add_argument("--suffix", help="Filename suffix")
    parser.add_argument("--channel", nargs="*", type=int, help="Channel index")
    parser.add_argument("--series", help="Series index")
    parser.add_argument("--subimg_offset",
                        nargs="*",
                        help="Sub-image offset in x,y,z")
    parser.add_argument("--subimg_size",
                        nargs="*",
                        help="Sub-image size in x,y,z")
    parser.add_argument("--offset", nargs="*", help="ROI offset in x,y,z")
    parser.add_argument("--size", nargs="*", help="ROI size in x,y,z")
    parser.add_argument("--db", help="Database path")
    parser.add_argument(
        "--cpus",
        help="Maximum number of CPUs/processes to use for multiprocessing "
        "tasks. Use \"none\" or 0 to auto-detect this number (default).")

    # task arguments
    parser.add_argument("--proc",
                        type=str.lower,
                        choices=libmag.enum_names_aslist(config.ProcessTypes),
                        help="Image processing mode")
    parser.add_argument("--register",
                        type=str.lower,
                        choices=libmag.enum_names_aslist(config.RegisterTypes),
                        help="Image registration task")
    parser.add_argument("--df",
                        type=str.lower,
                        choices=libmag.enum_names_aslist(config.DFTasks),
                        help="Data frame task")
    parser.add_argument("--plot_2d",
                        type=str.lower,
                        choices=libmag.enum_names_aslist(config.Plot2DTypes),
                        help="2D plot task; see config.Plot2DTypes")
    parser.add_argument("--ec2_start",
                        nargs="*",
                        help="AWS EC2 instance start")
    parser.add_argument("--ec2_list", nargs="*", help="AWS EC2 instance list")
    parser.add_argument("--ec2_terminate",
                        nargs="*",
                        help="AWS EC2 instance termination")
    parser.add_argument(
        "--notify",
        nargs="*",
        help="Notification message URL, message, and attachment strings")
    parser.add_argument("--grid_search",
                        help="Grid search hyperparameter tuning profile(s)")

    # profile arguments
    parser.add_argument(
        "--roi_profile",
        nargs="*",
        help="ROI profile, which can be separated by underscores "
        "for multiple profiles and given as paths to custom profiles "
        "in YAML format. Multiple profile groups can be given, which "
        "will each be applied to the corresponding channel. See "
        "docs/settings.md for more details.")
    parser.add_argument(
        "--atlas_profile",
        help="Atlas profile, which can be separated by underscores "
        "for multiple profiles and given as paths to custom profiles "
        "in YAML format. See docs/settings.md for more details.")
    parser.add_argument(
        "--theme",
        nargs="*",
        type=str.lower,
        choices=libmag.enum_names_aslist(config.Themes),
        help="UI theme, which can be given as multiple themes to apply "
        "on top of one another")

    # grouped arguments
    parser.add_argument(
        "--truth_db",
        nargs="*",
        help="Truth database; see config.TruthDB for settings and "
        "config.TruthDBModes for modes")
    parser.add_argument("--labels",
                        nargs="*",
                        help=_get_args_dict_help(
                            "Atlas labels; see config.AtlasLabels.",
                            config.AtlasLabels))
    parser.add_argument("--transform",
                        nargs="*",
                        help=_get_args_dict_help(
                            "Image transformations; see config.Transforms.",
                            config.Transforms))
    parser.add_argument(
        "--reg_suffixes",
        nargs="*",
        help=_get_args_dict_help(
            "Registered image suffixes; see config.RegSuffixes for keys "
            "and config.RegNames for values", config.RegSuffixes))
    parser.add_argument(
        "--plot_labels",
        nargs="*",
        help=_get_args_dict_help(
            "Plot label customizations; see config.PlotLabels ",
            config.PlotLabels))
    parser.add_argument(
        "--set_meta",
        nargs="*",
        help="Set metadata values; see config.MetaKeys for settings")

    # image and figure display arguments
    parser.add_argument("--plane",
                        type=str.lower,
                        choices=config.PLANE,
                        help="Planar orientation")
    parser.add_argument(
        "--show",
        nargs="?",
        const="1",
        help="If applicable, show images after completing the given task")
    parser.add_argument(
        "--alphas",
        help="Alpha opacity levels, which can be comma-delimited for "
        "multichannel images")
    parser.add_argument(
        "--vmin",
        help="Minimum intensity levels, which can be comma-delimited "
        "for multichannel images")
    parser.add_argument(
        "--vmax",
        help="Maximum intensity levels, which can be comma-delimited "
        "for multichannel images")
    parser.add_argument("--seed", help="Random number generator seed")

    # export arguments
    parser.add_argument("--save_subimg",
                        action="store_true",
                        help="Save sub-image as separate file")
    parser.add_argument("--slice", help="Slice given as start,stop,step")
    parser.add_argument("--delay", help="Animation delay in ms")
    parser.add_argument("--savefig", help="Extension for saved figures")
    parser.add_argument("--groups",
                        nargs="*",
                        help="Group values corresponding to each image")
    parser.add_argument("-v",
                        "--verbose",
                        action="store_true",
                        help="Verbose output to assist with debugging")
    args = parser.parse_args()

    if args.img is not None:
        # set image file path and convert to basis for additional paths
        config.filenames = args.img
        config.filename = config.filenames[0]
        print("Set filenames to {}, current filename {}".format(
            config.filenames, config.filename))

    if args.meta is not None:
        # set metadata paths
        config.metadata_paths = args.meta
        print("Set metadata paths to", config.metadata_paths)
        config.metadatas = []
        for path in config.metadata_paths:
            # load metadata to dictionary
            md, _ = importer.load_metadata(path, assign=False)
            config.metadatas.append(md)

    if args.channel is not None:
        # set the channels
        config.channel = args.channel
        print("Set channel to {}".format(config.channel))

    series_list = [config.series]  # list of series
    if args.series is not None:
        series_split = args.series.split(",")
        series_list = []
        for ser in series_split:
            ser_split = ser.split("-")
            if len(ser_split) > 1:
                ser_range = np.arange(int(ser_split[0]), int(ser_split[1]) + 1)
                series_list.extend(ser_range.tolist())
            else:
                series_list.append(int(ser_split[0]))
        config.series = series_list[0]
        print("Set to series_list to {}, current series {}".format(
            series_list, config.series))

    if args.savefig is not None:
        # save figure with file type of this extension; remove leading period
        config.savefig = args.savefig.lstrip(".")
        print("Set savefig extension to {}".format(config.savefig))

    if args.verbose:
        # verbose mode, including printing longer Numpy arrays for debugging
        config.verbose = args.verbose
        np.set_printoptions(linewidth=200, threshold=10000)
        print("Set verbose to {}".format(config.verbose))

    # parse sub-image offsets and sizes;
    # expects x,y,z input but stores as z,y,x by convention
    if args.subimg_offset is not None:
        config.subimg_offsets = _parse_coords(args.subimg_offset, True)
        print("Set sub-image offsets to {} (z,y,x)".format(
            config.subimg_offsets))
    if args.subimg_size is not None:
        config.subimg_sizes = _parse_coords(args.subimg_size, True)
        print("Set sub-image sizes to {} (z,y,x)".format(config.subimg_sizes))

    # parse ROI offsets and sizes, which are relative to any sub-image;
    # expects x,y,z input and output
    if args.offset is not None:
        config.roi_offsets = _parse_coords(args.offset)
        if config.roi_offsets:
            config.roi_offset = config.roi_offsets[0]
        print("Set ROI offsets to {}, current offset {} (x,y,z)".format(
            config.roi_offsets, config.roi_offset))
    if args.size is not None:
        config.roi_sizes = _parse_coords(args.size)
        if config.roi_sizes:
            config.roi_size = config.roi_sizes[0]
        print("Set ROI sizes to {}, current size {} (x,y,z)".format(
            config.roi_sizes, config.roi_size))

    if args.cpus is not None:
        # set maximum number of CPUs
        config.cpus = (None if args.cpus.lower() in ("none",
                                                     "0") else int(args.cpus))
        print("Set maximum number of CPUs for multiprocessing tasks to",
              config.cpus)

    # set up main processing mode
    if args.proc is not None:
        config.proc_type = args.proc
        print("processing type set to {}".format(config.proc_type))
    proc_type = libmag.get_enum(config.proc_type, config.ProcessTypes)
    if config.proc_type and proc_type not in config.ProcessTypes:
        libmag.warn("\"{}\" processing type not found".format(
            config.proc_type))

    if args.set_meta is not None:
        # set individual metadata values, currently used for image import
        # TODO: take precedence over loaded metadata archives
        config.meta_dict = args_to_dict(args.set_meta,
                                        config.MetaKeys,
                                        config.meta_dict,
                                        sep_vals="|")
        print("Set metadata values to {}".format(config.meta_dict))
        res = config.meta_dict[config.MetaKeys.RESOLUTIONS]
        if res:
            # set image resolutions, taken as a single set of x,y,z and
            # converting to a nested list of z,y,x
            res_split = res.split(",")
            if len(res_split) >= 3:
                res_float = tuple(float(i) for i in res_split)[::-1]
                config.resolutions = [res_float]
                print("Set resolutions to {}".format(config.resolutions))
            else:
                res_float = None
                print("Resolution ({}) should be given as 3 values (x,y,z)".
                      format(res))
            # store single set of resolutions, similar to input
            config.meta_dict[config.MetaKeys.RESOLUTIONS] = res_float
        mag = config.meta_dict[config.MetaKeys.MAGNIFICATION]
        if mag:
            # set objective magnification
            config.magnification = mag
            print("Set magnification to {}".format(config.magnification))
        zoom = config.meta_dict[config.MetaKeys.ZOOM]
        if zoom:
            # set objective zoom
            config.zoom = zoom
            print("Set zoom to {}".format(config.zoom))
        shape = config.meta_dict[config.MetaKeys.SHAPE]
        if shape:
            # parse shape, storing only in dict
            config.meta_dict[config.MetaKeys.SHAPE] = [
                int(n) for n in shape.split(",")[::-1]
            ]

    # set up ROI and register profiles
    setup_profiles(args.roi_profile, args.atlas_profile, args.grid_search)

    if args.plane is not None:
        config.plane = args.plane
        print("Set plane to {}".format(config.plane))
    if args.save_subimg:
        config.save_subimg = args.save_subimg
        print("Set to save the sub-image")

    if args.labels:
        # set up atlas labels
        setup_labels(args.labels)

    if args.transform is not None:
        # image transformations such as flipping, rotation
        config.transform = args_to_dict(args.transform, config.Transforms,
                                        config.transform)
        print("Set transformations to {}".format(config.transform))

    if args.register:
        # register type to process in register module
        config.register_type = args.register
        print("Set register type to {}".format(config.register_type))

    if args.df:
        # data frame processing task
        config.df_task = args.df
        print("Set data frame processing task to {}".format(config.df_task))

    if args.plot_2d:
        # 2D plot type to process in plot_2d module
        config.plot_2d_type = args.plot_2d
        print("Set plot_2d type to {}".format(config.plot_2d_type))

    if args.slice:
        # specify a generic slice by command-line, assuming same order
        # of arguments as for slice built-in function and interpreting
        # "none" string as None
        config.slice_vals = args.slice.split(",")
        config.slice_vals = [
            None if val.lower() == "none" else int(val)
            for val in config.slice_vals
        ]
        print("Set slice values to {}".format(config.slice_vals))
    if args.delay:
        config.delay = int(args.delay)
        print("Set delay to {}".format(config.delay))

    if args.show:
        # show images after task is performed, if supported
        config.show = _is_arg_true(args.show)
        print("Set show to {}".format(config.show))

    if args.groups:
        config.groups = args.groups
        print("Set groups to {}".format(config.groups))
    if args.ec2_start is not None:
        # start EC2 instances
        config.ec2_start = args_with_dict(args.ec2_start)
        print("Set ec2 start to {}".format(config.ec2_start))
    if args.ec2_list:
        # list EC2 instances
        config.ec2_list = args_with_dict(args.ec2_list)
        print("Set ec2 list to {}".format(config.ec2_list))
    if args.ec2_terminate:
        config.ec2_terminate = args.ec2_terminate
        print("Set ec2 terminate to {}".format(config.ec2_terminate))
    if args.notify:
        notify_len = len(args.notify)
        if notify_len > 0:
            config.notify_url = args.notify[0]
            print("Set notification URL to {}".format(config.notify_url))
        if notify_len > 1:
            config.notify_msg = args.notify[1]
            print("Set notification message to {}".format(config.notify_msg))
        if notify_len > 2:
            config.notify_attach = args.notify[2]
            print("Set notification attachment path to {}".format(
                config.notify_attach))
    if args.prefix:
        config.prefix = args.prefix
        print("Set path prefix to {}".format(config.prefix))
    if args.suffix:
        config.suffix = args.suffix
        print("Set path suffix to {}".format(config.suffix))

    if args.alphas:
        # specify alpha levels
        config.alphas = [float(val) for val in args.alphas.split(",")]
        print("Set alphas to", config.alphas)

    if args.vmin:
        # specify vmin levels
        config.vmins = [libmag.get_int(val) for val in args.vmin.split(",")]
        print("Set vmins to", config.vmins)

    if args.vmax:
        # specify vmax levels and copy to vmax overview used for plotting
        # and updated for normalization
        config.vmaxs = [libmag.get_int(val) for val in args.vmax.split(",")]
        config.vmax_overview = list(config.vmaxs)
        print("Set vmaxs to", config.vmaxs)

    if args.reg_suffixes is not None:
        # specify suffixes of registered images to load
        config.reg_suffixes = args_to_dict(args.reg_suffixes,
                                           config.RegSuffixes,
                                           config.reg_suffixes)
        print("Set registered image suffixes to {}".format(
            config.reg_suffixes))

    if args.seed:
        # specify random number generator seed
        config.seed = int(args.seed)
        print("Set random number generator seed to", config.seed)

    if args.plot_labels is not None:
        # specify general plot labels
        config.plot_labels = args_to_dict(args.plot_labels, config.PlotLabels,
                                          config.plot_labels)
        print("Set plot labels to {}".format(config.plot_labels))

    if args.theme is not None:
        # specify themes, currently applied to Matplotlib elements
        theme_names = []
        for theme in args.theme:
            # add theme enum if found
            theme_enum = libmag.get_enum(theme, config.Themes)
            if theme_enum:
                config.rc_params.append(theme_enum)
                theme_names.append(theme_enum.name)
        print("Set to use themes to {}".format(theme_names))

    # prep filename
    filename_base = None
    if config.filename:
        filename_base = importer.filename_to_base(config.filename,
                                                  config.series)

    if not skip_dbs:
        setup_dbs(filename_base, args.db, args.truth_db)

    # set multiprocessing start method
    chunking.set_mp_start_method()

    # POST-ARGUMENT PARSING

    if process_args_only:
        return

    # if command-line driven task specified, start task and shut down
    if config.register_type:
        register.main()
    elif config.notify_url:
        notify.main()
    elif config.plot_2d_type:
        plot_2d.main()
    elif config.df_task:
        df_io.main()
    elif config.grid_search_profile:
        _grid_search(series_list)
    elif config.ec2_list or config.ec2_start or config.ec2_terminate:
        # defer importing AWS module to avoid making its dependencies
        # required for MagellanMapper
        from magmap.cloud import aws
        aws.main()
    else:
        # set up image and perform any whole image processing tasks;
        # do not shut down if not a command-line proc task
        _process_files(series_list)
        if proc_type is None or proc_type is config.ProcessTypes.LOAD:
            return
    shutdown()
Exemple #5
0
def args_to_dict(args, keys_enum, args_dict=None, sep_args="=", sep_vals=","):
    """Parse arguments list with positional and keyword-based arguments 
    into an enum-keyed dictionary.
    
    Args:
        args (List[str]): List of arguments with positional values followed by
            ``sep_args``-delimited values. Positional values will be entered
            in the existing order of ``keys_enum`` based on member values, 
            while keyword-based values will be entered if an enum 
            member corresponding to the keyword exists.
            Entries can also be ``sep_vals``-delimited to specify lists.
        keys_enum (Enum): Enum to use as keys for dictionary. Values are
            assumed to range from 1 to number of members as output 
            by the default Enum functional API.
        args_dict (dict): Dictionary to be filled or updated with keys from
            ``keys_enum``; defaults to None, which will assign an empty dict.
        sep_args (str): Separator between arguments and values; defaults to "=".
        sep_vals (str): Separator within values; defaults to ",".
    
    Returns:
        dict: Dictionary filled with arguments. Values that contain commas
        will be split into comma-delimited lists. All values will be 
        converted to ints if possible.
    """
    if args_dict is None:
        args_dict = {}
    by_position = True
    num_enums = len(keys_enum)
    for i, arg in enumerate(args):
        arg_split = arg.split(sep_args)
        len_arg_split = len(arg_split)
        # assume by position until any keyword given
        by_position = by_position and len_arg_split < 2
        key = None
        vals = arg
        if by_position:
            # positions are based on enum vals, assumed to range from
            # 1 to num of members
            n = i + 1
            if n > num_enums:
                print("no further parameters in {} to assign \"{}\" by "
                      "position, skipping".format(keys_enum, arg))
                continue
            key = keys_enum(n)
        elif len_arg_split < 2:
            print("parameter {} does not contain a keyword, skipping".format(
                arg))
        else:
            # assign based on keyword if its equivalent enum exists
            vals = arg_split[1]
            key_str = arg_split[0].upper()
            try:
                key = keys_enum[key_str]
            except KeyError:
                print("unable to find {} in {}".format(key_str, keys_enum))
                continue
        if key:
            vals_split = vals.split(sep_vals)
            if len(vals_split) > 1:
                # use split value if comma-delimited
                vals = vals_split
            # cast to numeric types if possible and assign to found enum
            args_dict[key] = libmag.get_int(vals)
    return args_dict
Exemple #6
0
 def test_get_int(self):
     self.assertEqual(libmag.get_int("5"), 5)
     self.assertEqual(libmag.get_int("5.6"), 5.6)
     self.assertEqual(libmag.get_int("wrong"), "wrong")
     self.assertEqual(libmag.get_int(''), '')