10
10
# limitations under the License.
11
11
12
12
import pprint
13
+ import re
13
14
from typing import Dict , Optional , Sequence , Union
14
15
16
+ from monai .apps .utils import download_url , get_logger
15
17
from monai .bundle .config_parser import ConfigParser
18
+ from monai .config import PathLike
19
+ from monai .utils import check_parent_dir , optional_import
16
20
21
+ validate , _ = optional_import ("jsonschema" , name = "validate" )
22
+ ValidationError , _ = optional_import ("jsonschema.exceptions" , name = "ValidationError" )
17
23
18
- def _update_default_args (args : Optional [Union [str , Dict ]] = None , ** kwargs ) -> Dict :
24
+ logger = get_logger (module_name = __name__ )
25
+
26
+
27
+ def _update_args (args : Optional [Union [str , Dict ]] = None , ignore_none : bool = True , ** kwargs ) -> Dict :
19
28
"""
20
29
Update the `args` with the input `kwargs`.
21
30
For dict data, recursively update the content based on the keys.
22
31
23
32
Args:
24
33
args: source args to update.
34
+ ignore_none: whether to ignore input args with None value, default to `True`.
25
35
kwargs: destination args to update.
26
36
27
37
"""
@@ -32,14 +42,26 @@ def _update_default_args(args: Optional[Union[str, Dict]] = None, **kwargs) -> D
32
42
33
43
# recursively update the default args with new args
34
44
for k , v in kwargs .items ():
35
- args_ [k ] = _update_default_args (args_ [k ], ** v ) if isinstance (v , dict ) and isinstance (args_ .get (k ), dict ) else v
45
+ if ignore_none and v is None :
46
+ continue
47
+ if isinstance (v , dict ) and isinstance (args_ .get (k ), dict ):
48
+ args_ [k ] = _update_args (args_ [k ], ignore_none , ** v )
49
+ else :
50
+ args_ [k ] = v
36
51
return args_
37
52
38
53
54
+ def _log_input_summary (tag : str , args : Dict ):
55
+ logger .info (f"\n --- input summary of monai.bundle.scripts.{ tag } ---" )
56
+ for name , val in args .items ():
57
+ logger .info (f"> { name } : { pprint .pformat (val )} " )
58
+ logger .info ("---\n \n " )
59
+
60
+
39
61
def run (
62
+ runner_id : Optional [str ] = None ,
40
63
meta_file : Optional [Union [str , Sequence [str ]]] = None ,
41
64
config_file : Optional [Union [str , Sequence [str ]]] = None ,
42
- target_id : Optional [str ] = None ,
43
65
args_file : Optional [str ] = None ,
44
66
** override ,
45
67
):
@@ -51,62 +73,102 @@ def run(
51
73
.. code-block:: bash
52
74
53
75
# Execute this module as a CLI entry:
54
- python -m monai.bundle run --meta_file <meta path> --config_file <config path> --target_id trainer
76
+ python -m monai.bundle run trainer --meta_file <meta path> --config_file <config path>
55
77
56
78
# Override config values at runtime by specifying the component id and its new value:
57
- python -m monai.bundle run --net#input_chns 1 ...
79
+ python -m monai.bundle run trainer --net#input_chns 1 ...
58
80
59
81
# Override config values with another config file `/path/to/another.json`:
60
- python -m monai.bundle run --net %/path/to/another.json ...
82
+ python -m monai.bundle run evaluator --net %/path/to/another.json ...
61
83
62
84
# Override config values with part content of another config file:
63
- python -m monai.bundle run --net %/data/other.json#net_arg ...
85
+ python -m monai.bundle run trainer --net %/data/other.json#net_arg ...
64
86
65
87
# Set default args of `run` in a JSON / YAML file, help to record and simplify the command line.
66
88
# Other args still can override the default args at runtime:
67
89
python -m monai.bundle run --args_file "/workspace/data/args.json" --config_file <config path>
68
90
69
91
Args:
92
+ runner_id: ID name of the runner component or workflow, it must have a `run` method.
70
93
meta_file: filepath of the metadata file, if `None`, must be provided in `args_file`.
71
94
if it is a list of file paths, the content of them will be merged.
72
95
config_file: filepath of the config file, if `None`, must be provided in `args_file`.
73
96
if it is a list of file paths, the content of them will be merged.
74
- target_id: ID name of the target component or workflow, it must have a `run` method.
75
97
args_file: a JSON or YAML file to provide default values for `meta_file`, `config_file`,
76
- `target_id ` and override pairs. so that the command line inputs can be simplified.
98
+ `runner_id ` and override pairs. so that the command line inputs can be simplified.
77
99
override: id-value pairs to override or add the corresponding config content.
78
100
e.g. ``--net#input_chns 42``.
79
101
80
102
"""
81
- k_v = zip (["meta_file" , "config_file" , "target_id" ], [meta_file , config_file , target_id ])
82
- for k , v in k_v :
83
- if v is not None :
84
- override [k ] = v
85
-
86
- full_kv = zip (
87
- ("meta_file" , "config_file" , "target_id" , "args_file" , "override" ),
88
- (meta_file , config_file , target_id , args_file , override ),
89
- )
90
- print ("\n --- input summary of monai.bundle.scripts.run ---" )
91
- for name , val in full_kv :
92
- print (f"> { name } : { pprint .pformat (val )} " )
93
- print ("---\n \n " )
94
103
95
- _args = _update_default_args (args = args_file , ** override )
104
+ _args = _update_args (args = args_file , runner_id = runner_id , meta_file = meta_file , config_file = config_file , ** override )
96
105
for k in ("meta_file" , "config_file" ):
97
106
if k not in _args :
98
107
raise ValueError (f"{ k } is required for 'monai.bundle run'.\n { run .__doc__ } " )
108
+ _log_input_summary (tag = "run" , args = _args )
99
109
100
110
parser = ConfigParser ()
101
111
parser .read_config (f = _args .pop ("config_file" ))
102
112
parser .read_meta (f = _args .pop ("meta_file" ))
103
- id = _args .pop ("target_id " , "" )
113
+ id = _args .pop ("runner_id " , "" )
104
114
105
- # the rest key-values in the args are to override config content
115
+ # the rest key-values in the _args are to override config content
106
116
for k , v in _args .items ():
107
117
parser [k ] = v
108
118
109
119
workflow = parser .get_parsed_content (id = id )
110
120
if not hasattr (workflow , "run" ):
111
121
raise ValueError (f"The parsed workflow { type (workflow )} does not have a `run` method.\n { run .__doc__ } " )
112
122
workflow .run ()
123
+
124
+
125
+ def verify_metadata (
126
+ meta_file : Optional [Union [str , Sequence [str ]]] = None ,
127
+ filepath : Optional [PathLike ] = None ,
128
+ create_dir : Optional [bool ] = None ,
129
+ hash_val : Optional [str ] = None ,
130
+ args_file : Optional [str ] = None ,
131
+ ** kwargs ,
132
+ ):
133
+ """
134
+ Verify the provided `metadata` file based on the predefined `schema`.
135
+ `metadata` content must contain the `schema` field for the URL of shcema file to download.
136
+ The schema standard follows: http://json-schema.org/.
137
+
138
+ Args:
139
+ meta_file: filepath of the metadata file to verify, if `None`, must be provided in `args_file`.
140
+ if it is a list of file paths, the content of them will be merged.
141
+ filepath: file path to store the downloaded schema.
142
+ create_dir: whether to create directories if not existing, default to `True`.
143
+ hash_val: if not None, define the hash value to verify the downloaded schema file.
144
+ args_file: a JSON or YAML file to provide default values for all the args in this function.
145
+ so that the command line inputs can be simplified.
146
+ kwargs: other arguments for `jsonschema.validate()`. for more details:
147
+ https://python-jsonschema.readthedocs.io/en/stable/validate/#jsonschema.validate.
148
+
149
+ """
150
+
151
+ _args = _update_args (
152
+ args = args_file , meta_file = meta_file , filepath = filepath , create_dir = create_dir , hash_val = hash_val , ** kwargs
153
+ )
154
+ _log_input_summary (tag = "verify_metadata" , args = _args )
155
+
156
+ filepath_ = _args .pop ("filepath" )
157
+ create_dir_ = _args .pop ("create_dir" , True )
158
+ check_parent_dir (path = filepath_ , create_dir = create_dir_ )
159
+
160
+ metadata = ConfigParser .load_config_files (files = _args .pop ("meta_file" ))
161
+ url = metadata .get ("schema" )
162
+ if url is None :
163
+ raise ValueError ("must provide the `schema` field in the metadata for the URL of schema file." )
164
+ download_url (url = url , filepath = filepath_ , hash_val = _args .pop ("hash_val" , None ), hash_type = "md5" , progress = True )
165
+ schema = ConfigParser .load_config_file (filepath = filepath_ )
166
+
167
+ try :
168
+ # the rest key-values in the _args are for `validate` API
169
+ validate (instance = metadata , schema = schema , ** _args )
170
+ except ValidationError as e :
171
+ # as the error message is very long, only extract the key information
172
+ logger .info (re .compile (r".*Failed validating" , re .S ).findall (str (e ))[0 ] + f" against schema `{ url } `." )
173
+ return
174
+ logger .info ("metadata is verified with no error." )
0 commit comments