Skip to content

Commit 3aed2cd

Browse files
committed
Merge branch 'dev' into 3482-bundle-doc
2 parents 8c71dc8 + 03f8d80 commit 3aed2cd

21 files changed

+407
-107
lines changed

docs/source/bundle.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ Model Bundle
3535

3636
`Scripts`
3737
---------
38+
.. autofunction:: ckpt_export
3839
.. autofunction:: run
3940
.. autofunction:: verify_metadata
4041
.. autofunction:: verify_net_in_out

docs/source/mb_specification.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ This file contains the metadata information relating to the model, including wha
5555
* **optional_packages_version**: dictionary relating optional package names to their versions, these packages are not needed but are recommended to be installed with this stated minimum version.
5656
* **task**: plain-language description of what the model is meant to do.
5757
* **description**: longer form plain-language description of what the model is, what it does, etc.
58-
* **authorship**: state author(s) of the model.
58+
* **authors**: state author(s) of the model.
5959
* **copyright**: state model copyright.
6060
* **network_data_format**: defines the format, shape, and meaning of inputs and outputs to the model, contains keys "inputs" and "outputs" relating named inputs/outputs to their format specifiers (defined below).
6161

@@ -98,7 +98,7 @@ An example JSON metadata file:
9898
"optional_packages_version": {"nibabel": "3.2.1"},
9999
"task": "Decathlon spleen segmentation",
100100
"description": "A pre-trained model for volumetric (3D) segmentation of the spleen from CT image",
101-
"authorship": "MONAI team",
101+
"authors": "MONAI team",
102102
"copyright": "Copyright (c) MONAI Consortium",
103103
"data_source": "Task09_Spleen.tar from http://medicaldecathlon.com/",
104104
"data_type": "dicom",

docs/source/transforms.rst

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -543,8 +543,8 @@ Post-processing
543543
:members:
544544
:special-members: __call__
545545

546-
`Prob NMS`
547-
""""""""""
546+
`ProbNMS`
547+
"""""""""
548548
.. autoclass:: ProbNMS
549549
:members:
550550

@@ -563,6 +563,12 @@ Spatial
563563
:members:
564564
:special-members: __call__
565565

566+
`ResampleToMatch`
567+
"""""""""""""""""
568+
.. autoclass:: ResampleToMatch
569+
:members:
570+
:special-members: __call__
571+
566572
`Spacing`
567573
"""""""""
568574
.. image:: https://github.com/Project-MONAI/DocImages/raw/main/transforms/Spacing.png
@@ -827,7 +833,6 @@ Utility
827833
:members:
828834
:special-members: __call__
829835

830-
831836
`Transpose`
832837
"""""""""""
833838
.. autoclass:: Transpose
@@ -852,6 +857,7 @@ Utility
852857
:members:
853858
:special-members: __call__
854859

860+
855861
`Lambda`
856862
""""""""
857863
.. autoclass:: Lambda
@@ -1401,6 +1407,12 @@ Post-processing (Dict)
14011407
:members:
14021408
:special-members: __call__
14031409

1410+
`ProbNMSd`
1411+
""""""""""
1412+
.. autoclass:: ProbNMSd
1413+
:members:
1414+
:special-members: __call__
1415+
14041416
Spatial (Dict)
14051417
^^^^^^^^^^^^^^
14061418

@@ -1410,6 +1422,12 @@ Spatial (Dict)
14101422
:members:
14111423
:special-members: __call__
14121424

1425+
`ResampleToMatchd`
1426+
""""""""""""""""""
1427+
.. autoclass:: ResampleToMatchd
1428+
:members:
1429+
:special-members: __call__
1430+
14131431
`Spacingd`
14141432
""""""""""
14151433
.. image:: https://github.com/Project-MONAI/DocImages/raw/main/transforms/Spacingd.png
@@ -1656,6 +1674,12 @@ Utility (Dict)
16561674
:members:
16571675
:special-members: __call__
16581676

1677+
`ToPILd`
1678+
""""""""
1679+
.. autoclass:: ToPILd
1680+
:members:
1681+
:special-members: __call__
1682+
16591683
`DeleteItemsd`
16601684
""""""""""""""
16611685
.. autoclass:: DeleteItemsd
@@ -1668,6 +1692,12 @@ Utility (Dict)
16681692
:members:
16691693
:special-members: __call__
16701694

1695+
`Transposed`
1696+
""""""""""""
1697+
.. autoclass:: Transposed
1698+
:members:
1699+
:special-members: __call__
1700+
16711701
`SqueezeDimd`
16721702
"""""""""""""
16731703
.. autoclass:: SqueezeDimd
@@ -1710,6 +1740,12 @@ Utility (Dict)
17101740
:members:
17111741
:special-members: __call__
17121742

1743+
`RemoveRepeatedChanneld`
1744+
""""""""""""""""""""""""
1745+
.. autoclass:: RemoveRepeatedChanneld
1746+
:members:
1747+
:special-members: __call__
1748+
17131749
`LabelToMaskd`
17141750
""""""""""""""
17151751
.. autoclass:: LabelToMaskd

monai/bundle/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@
1212
from .config_item import ComponentLocator, ConfigComponent, ConfigExpression, ConfigItem, Instantiable
1313
from .config_parser import ConfigParser
1414
from .reference_resolver import ReferenceResolver
15-
from .scripts import run, verify_metadata, verify_net_in_out
15+
from .scripts import ckpt_export, run, verify_metadata, verify_net_in_out
1616
from .utils import EXPR_KEY, ID_REF_KEY, ID_SEP_KEY, MACRO_KEY

monai/bundle/__main__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
# limitations under the License.
1111

1212

13-
from monai.bundle.scripts import run, verify_metadata, verify_net_in_out
13+
from monai.bundle.scripts import ckpt_export, run, verify_metadata, verify_net_in_out
1414

1515
if __name__ == "__main__":
1616
from monai.utils import optional_import

monai/bundle/config_parser.py

Lines changed: 66 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
from monai.bundle.config_item import ComponentLocator, ConfigComponent, ConfigExpression, ConfigItem
1919
from monai.bundle.reference_resolver import ReferenceResolver
20-
from monai.bundle.utils import ID_SEP_KEY, MACRO_KEY
20+
from monai.bundle.utils import ID_REF_KEY, ID_SEP_KEY, MACRO_KEY
2121
from monai.config import PathLike
2222
from monai.utils import ensure_tuple, look_up_option, optional_import
2323

@@ -87,6 +87,8 @@ class ConfigParser:
8787
suffixes = ("json", "yaml", "yml")
8888
suffix_match = rf".*\.({'|'.join(suffixes)})"
8989
path_match = rf"({suffix_match}$)"
90+
# match relative id names, e.g. "@#data", "@##transform#1"
91+
relative_id_prefix = re.compile(rf"(?:{ID_REF_KEY}|{MACRO_KEY}){ID_SEP_KEY}+")
9092
meta_key = "_meta_" # field key to save metadata
9193

9294
def __init__(
@@ -127,7 +129,7 @@ def __getitem__(self, id: Union[str, int]):
127129
if id == "":
128130
return self.config
129131
config = self.config
130-
for k in str(id).split(self.ref_resolver.sep):
132+
for k in str(id).split(ID_SEP_KEY):
131133
if not isinstance(config, (dict, list)):
132134
raise ValueError(f"config must be dict or list for key `{k}`, but got {type(config)}: {config}.")
133135
indexing = k if isinstance(config, dict) else int(k)
@@ -151,9 +153,9 @@ def __setitem__(self, id: Union[str, int], config: Any):
151153
self.config = config
152154
self.ref_resolver.reset()
153155
return
154-
keys = str(id).split(self.ref_resolver.sep)
156+
keys = str(id).split(ID_SEP_KEY)
155157
# get the last parent level config item and replace it
156-
last_id = self.ref_resolver.sep.join(keys[:-1])
158+
last_id = ID_SEP_KEY.join(keys[:-1])
157159
conf_ = self[last_id]
158160
indexing = keys[-1] if isinstance(conf_, dict) else int(keys[-1])
159161
conf_[indexing] = config
@@ -192,7 +194,7 @@ def parse(self, reset: bool = True):
192194
"""
193195
if reset:
194196
self.ref_resolver.reset()
195-
self.resolve_macro()
197+
self.resolve_macro_and_relative_ids()
196198
self._do_parse(config=self.get())
197199

198200
def get_parsed_content(self, id: str = "", **kwargs):
@@ -251,28 +253,37 @@ def read_config(self, f: Union[PathLike, Sequence[PathLike], Dict], **kwargs):
251253
content.update(self.load_config_files(f, **kwargs))
252254
self.set(config=content)
253255

254-
def _do_resolve(self, config: Any):
256+
def _do_resolve(self, config: Any, id: str = ""):
255257
"""
256-
Recursively resolve the config content to replace the macro tokens with target content.
258+
Recursively resolve `self.config` to replace the relative ids with absolute ids, for example,
259+
`@##A` means `A` in the upper level. and replace the macro tokens with target content,
257260
The macro tokens start with "%", can be from another structured file, like:
258-
``{"net": "%default_net"}``, ``{"net": "%/data/config.json#net"}``.
261+
``"%default_net"``, ``"%/data/config.json#net"``.
259262
260263
Args:
261264
config: input config file to resolve.
265+
id: id of the ``ConfigItem``, ``"#"`` in id are interpreted as special characters to
266+
go one level further into the nested structures.
267+
Use digits indexing from "0" for list or other strings for dict.
268+
For example: ``"xform#5"``, ``"net#channels"``. ``""`` indicates the entire ``self.config``.
262269
263270
"""
264271
if isinstance(config, (dict, list)):
265272
for k, v in enumerate(config) if isinstance(config, list) else config.items():
266-
config[k] = self._do_resolve(v)
267-
if isinstance(config, str) and config.startswith(MACRO_KEY):
268-
path, ids = ConfigParser.split_path_id(config[len(MACRO_KEY) :])
269-
parser = ConfigParser(config=self.get() if not path else ConfigParser.load_config_file(path))
270-
return self._do_resolve(config=deepcopy(parser[ids]))
273+
sub_id = f"{id}{ID_SEP_KEY}{k}" if id != "" else k
274+
config[k] = self._do_resolve(v, sub_id)
275+
if isinstance(config, str):
276+
config = self.resolve_relative_ids(id, config)
277+
if config.startswith(MACRO_KEY):
278+
path, ids = ConfigParser.split_path_id(config[len(MACRO_KEY) :])
279+
parser = ConfigParser(config=self.get() if not path else ConfigParser.load_config_file(path))
280+
return self._do_resolve(config=deepcopy(parser[ids]))
271281
return config
272282

273-
def resolve_macro(self):
283+
def resolve_macro_and_relative_ids(self):
274284
"""
275-
Recursively resolve `self.config` to replace the macro tokens with target content.
285+
Recursively resolve `self.config` to replace the relative ids with absolute ids, for example,
286+
`@##A` means `A` in the upper level. and replace the macro tokens with target content,
276287
The macro tokens are marked as starting with "%", can be from another structured file, like:
277288
``"%default_net"``, ``"%/data/config.json#net"``.
278289
@@ -292,9 +303,8 @@ def _do_parse(self, config, id: str = ""):
292303
293304
"""
294305
if isinstance(config, (dict, list)):
295-
subs = enumerate(config) if isinstance(config, list) else config.items()
296-
for k, v in subs:
297-
sub_id = f"{id}{self.ref_resolver.sep}{k}" if id != "" else k
306+
for k, v in enumerate(config) if isinstance(config, list) else config.items():
307+
sub_id = f"{id}{ID_SEP_KEY}{k}" if id != "" else k
298308
self._do_parse(config=v, id=sub_id)
299309

300310
# copy every config item to make them independent and add them to the resolver
@@ -380,3 +390,41 @@ def split_path_id(cls, src: str) -> Tuple[str, str]:
380390
path_name = result[0][0] # at most one path_name
381391
_, ids = src.rsplit(path_name, 1)
382392
return path_name, ids[len(ID_SEP_KEY) :] if ids.startswith(ID_SEP_KEY) else ""
393+
394+
@classmethod
395+
def resolve_relative_ids(cls, id: str, value: str) -> str:
396+
"""
397+
To simplify the reference or macro tokens ID in the nested config content, it's available to use
398+
relative ID name which starts with the `ID_SEP_KEY`, for example, "@#A" means `A` in the same level,
399+
`@##A` means `A` in the upper level.
400+
It resolves the relative ids to absolute ids. For example, if the input data is:
401+
402+
.. code-block:: python
403+
404+
{
405+
"A": 1,
406+
"B": {"key": "@##A", "value1": 2, "value2": "%#value1", "value3": [3, 4, "@#1"]},
407+
}
408+
409+
It will resolve `B` to `{"key": "@A", "value1": 2, "value2": "%B#value1", "value3": [3, 4, "@B#value3#1"]}`.
410+
411+
Args:
412+
id: id name for current config item to compute relative id.
413+
value: input value to resolve relative ids.
414+
415+
"""
416+
# get the prefixes like: "@####", "%###", "@#"
417+
prefixes = sorted(set().union(cls.relative_id_prefix.findall(value)), reverse=True)
418+
current_id = id.split(ID_SEP_KEY)
419+
420+
for p in prefixes:
421+
sym = ID_REF_KEY if ID_REF_KEY in p else MACRO_KEY
422+
length = p[len(sym) :].count(ID_SEP_KEY)
423+
if length > len(current_id):
424+
raise ValueError(f"the relative id in `{value}` is out of the range of config content.")
425+
if length == len(current_id):
426+
new = "" # root id is `""`
427+
else:
428+
new = ID_SEP_KEY.join(current_id[:-length]) + ID_SEP_KEY
429+
value = value.replace(p, sym + new)
430+
return value

0 commit comments

Comments
 (0)