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
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}")
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
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()
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
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(''), '')