Source code for niftynet.utilities.user_parameters_parser

# -*- coding: utf-8 -*-
"""
Parse user configuration file
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import argparse
import os
import textwrap

from niftynet.engine.application_factory import ApplicationFactory
from niftynet.engine.application_factory import SUPPORTED_APP
from niftynet.utilities.niftynet_global_config import NiftyNetGlobalConfig
from niftynet.utilities.user_parameters_custom import add_customised_args
from niftynet.utilities.user_parameters_default import add_application_args
from niftynet.utilities.user_parameters_default import add_inference_args
from niftynet.utilities.user_parameters_default import add_input_data_args
from niftynet.utilities.user_parameters_default import add_network_args
from niftynet.utilities.user_parameters_default import add_training_args
from niftynet.utilities.user_parameters_helper import has_section_in_config
from niftynet.utilities.user_parameters_helper import standardise_section_name
from niftynet.utilities.util_common import \
    damerau_levenshtein_distance as edit_distance
from niftynet.utilities.versioning import get_niftynet_version_string

try:
    import configparser
except ImportError:
    import ConfigParser as configparser

SYSTEM_SECTIONS = {'SYSTEM', 'NETWORK', 'TRAINING', 'INFERENCE'}
epilog_string = \
    '\n\n======\nFor more information please visit:\n' \
    'https://github.com/NifTK/NiftyNet/tree/dev/config/README.md\n' \
    '======\n\n'


[docs]def run(): """ meta_parser is first used to find out location of the configuration file. based on the application_name or meta_parser.prog name, the section parsers are organised to find system parameters and application specific parameters. :return: system parameters is a group of parameters including SYSTEM_SECTIONS and app_module.REQUIRED_CONFIG_SECTION input_data_args is a group of input data sources to be used by niftynet.io.ImageReader """ meta_parser = argparse.ArgumentParser( description="Launch a NiftyNet application.", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=textwrap.dedent(epilog_string)) version_string = get_niftynet_version_string() meta_parser.add_argument("action", help="train networks or run inferences", metavar='ACTION', choices=['train', 'inference']) meta_parser.add_argument("-v", "--version", action='version', version=version_string) meta_parser.add_argument("-c", "--conf", help="specify configurations from a file", metavar="CONFIG_FILE") meta_parser.add_argument("-a", "--application_name", help="specify an application module name", metavar='APPLICATION_NAME', default="") meta_args, args_from_cmdline = meta_parser.parse_known_args() print(version_string) # read configurations, to be parsed by sections if not meta_args.conf: print("\nNo configuration file has been provided, did you " "forget '-c' command argument?{}".format(epilog_string)) raise IOError # Resolve relative configuration file location config_path = os.path.expanduser(meta_args.conf) if not os.path.isfile(config_path): relative_conf_file = os.path.join( NiftyNetGlobalConfig().get_default_examples_folder(), config_path, config_path + "_config.ini") if os.path.isfile(relative_conf_file): config_path = relative_conf_file os.chdir(os.path.dirname(config_path)) else: print("\nConfiguration file not found: {}.{}".format( config_path, epilog_string)) raise IOError config = configparser.ConfigParser() config.read([config_path]) app_module = None module_name = None try: if meta_parser.prog[:-3] in SUPPORTED_APP: module_name = meta_parser.prog[:-3] elif meta_parser.prog in SUPPORTED_APP: module_name = meta_parser.prog else: module_name = meta_args.application_name app_module = ApplicationFactory.create(module_name) assert app_module.REQUIRED_CONFIG_SECTION, \ "\nREQUIRED_CONFIG_SECTION should be static variable " \ "in {}".format(app_module) has_section_in_config(config, app_module.REQUIRED_CONFIG_SECTION) except ValueError: if app_module: section_name = app_module.REQUIRED_CONFIG_SECTION print('\n{} requires [{}] section in the config file.{}'.format( module_name, section_name, epilog_string)) if not module_name: print("\nUnknown application {}, or did you forget '-a' " "command argument?{}".format(module_name, epilog_string)) raise # check keywords in configuration file check_keywords(config) # using configuration as default, and parsing all command line arguments all_args = {} for section in config.sections(): # try to rename user-specified sections for consistency section = standardise_section_name(config, section) section_defaults = dict(config.items(section)) section_args, args_from_cmdline = \ _parse_arguments_by_section([], section, section_defaults, args_from_cmdline, app_module.REQUIRED_CONFIG_SECTION) all_args[section] = section_args # command line parameters should be valid assert not args_from_cmdline, \ '\nUnknown parameter: {}{}'.format(args_from_cmdline, epilog_string) # split parsed results in all_args # into dictionaries of system_args and input_data_args system_args = {} input_data_args = {} # copy system default sections to ``system_args`` for section in all_args: if section in SYSTEM_SECTIONS: system_args[section] = all_args[section] elif section == app_module.REQUIRED_CONFIG_SECTION: system_args['CUSTOM'] = all_args[section] vars(system_args['CUSTOM'])['name'] = module_name if all_args['SYSTEM'].model_dir is None: all_args['SYSTEM'].model_dir = os.path.join( os.path.dirname(meta_args.conf), 'model') # copy non-default sections to ``input_data_args`` for section in all_args: if section in SYSTEM_SECTIONS: continue if section == app_module.REQUIRED_CONFIG_SECTION: continue input_data_args[section] = all_args[section] # set the output path of csv list if not exists csv_path = input_data_args[section].csv_file if os.path.isfile(csv_path): # don't search files if csv specified in config try: delattr(input_data_args[section], 'path_to_search') except AttributeError: pass else: input_data_args[section].csv_file = '' # preserve ``config_file`` and ``action parameter`` from the meta_args system_args['CONFIG_FILE'] = argparse.Namespace(path=meta_args.conf) system_args['SYSTEM'].action = meta_args.action return system_args, input_data_args
def _parse_arguments_by_section(parents, section, args_from_config_file, args_from_cmd, required_section): """ This function first adds parameter names to a parser, according to the section name. Then it loads values from configuration files as tentative params. Finally it overrides existing pairs of 'name, value' with commandline inputs. Commandline inputs only override system/custom parameters. input data related parameters needs to be defined in config file. :param parents: a list, parsers will be created as subparsers of parents :param section: section name to be parsed :param args_from_config_file: loaded parameters from config file :param args_from_cmd: dictionary commandline parameters :return: parsed parameters of the section and unknown commandline params. """ section_parser = argparse.ArgumentParser( parents=parents, description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) if section == 'SYSTEM': section_parser = add_application_args(section_parser) elif section == 'NETWORK': section_parser = add_network_args(section_parser) elif section == 'TRAINING': section_parser = add_training_args(section_parser) elif section == 'INFERENCE': section_parser = add_inference_args(section_parser) elif section == required_section: section_parser = add_customised_args(section_parser, section.upper()) else: section_parser = add_input_data_args(section_parser) # loading all parameters a config file first if args_from_config_file is not None: section_parser.set_defaults(**args_from_config_file) # input command line input overrides config file if (section in SYSTEM_SECTIONS) or (section == required_section): section_args, unknown = section_parser.parse_known_args(args_from_cmd) return section_args, unknown # don't parse user cmd for input source sections section_args, _ = section_parser.parse_known_args([]) return section_args, args_from_cmd
[docs]def check_keywords(config): """ check config files, validate keywords provided against parsers' argument list """ validation_parser = argparse.ArgumentParser( parents=[], description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter, conflict_handler='resolve') config_keywords = [] for section in config.sections(): validation_parser = add_application_args(validation_parser) validation_parser = add_network_args(validation_parser) validation_parser = add_training_args(validation_parser) validation_parser = add_inference_args(validation_parser) validation_parser = add_input_data_args(validation_parser) try: validation_parser = add_customised_args( validation_parser, section.upper()) except (argparse.ArgumentError, NotImplementedError): pass if config.items(section): config_keywords.extend(list(dict(config.items(section)))) default_keywords = [] for action in validation_parser._actions: try: default_keywords.append(action.option_strings[0][2:]) except (IndexError, AttributeError, ValueError): pass for config_key in config_keywords: if config_key in default_keywords: continue dists = {k: edit_distance(k, config_key) for k in default_keywords} closest = min(dists, key=dists.get) if dists[closest] <= 5: raise ValueError( 'Unknown keywords in config file: By "{0}" ' 'did you mean "{1}"?\n "{0}" is ' 'not a valid option.{2}'.format( config_key, closest, epilog_string)) raise ValueError( 'Unknown keywords in config file: [{}] -- all ' ' possible choices are {}.{}'.format( config_key, default_keywords, epilog_string))