Skip to content

Commit 4219e0f

Browse files
6424 Add support to register customized app required properties (#6432)
Fixes #6424 . ### Description This PR added support to register customized app required properties to the `BundleWorkflow`, then check and access the properties. ### Types of changes <!--- Put an `x` in all the boxes that apply, and remove the not applicable items --> - [x] Non-breaking change (fix or new feature that would not break existing functionality). - [ ] Breaking change (fix or new feature that would cause existing functionality to change). - [ ] New tests added to cover the changes. - [ ] Integration tests passed locally by running `./runtests.sh -f -u --net --coverage`. - [ ] Quick tests passed locally by running `./runtests.sh --quick --unittests --disttests`. - [ ] In-line docstrings updated. - [ ] Documentation updated, tested `make html` command in the `docs/` folder. --------- Signed-off-by: Nic Ma <nma@nvidia.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent d23221f commit 4219e0f

File tree

5 files changed

+92
-8
lines changed

5 files changed

+92
-8
lines changed

monai/bundle/properties.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,21 @@
159159
BundleProperty.REQUIRED: True,
160160
BundlePropertyConfig.ID: "device",
161161
},
162+
"dataset_dir": {
163+
BundleProperty.DESC: "directory path of the dataset.",
164+
BundleProperty.REQUIRED: True,
165+
BundlePropertyConfig.ID: "dataset_dir",
166+
},
167+
"dataset": {
168+
BundleProperty.DESC: "PyTorch dataset object for the inference / evaluation logic.",
169+
BundleProperty.REQUIRED: True,
170+
BundlePropertyConfig.ID: "dataset",
171+
},
172+
"dataset_data": {
173+
BundleProperty.DESC: "data source for the inference / evaluation dataset.",
174+
BundleProperty.REQUIRED: True,
175+
BundlePropertyConfig.ID: f"dataset{ID_SEP_KEY}data",
176+
},
162177
"evaluator": {
163178
BundleProperty.DESC: "inference / evaluation workflow engine.",
164179
BundleProperty.REQUIRED: True,
@@ -174,6 +189,12 @@
174189
BundleProperty.REQUIRED: True,
175190
BundlePropertyConfig.ID: "inferer",
176191
},
192+
"handlers": {
193+
BundleProperty.DESC: "event-handlers for the inference / evaluation logic.",
194+
BundleProperty.REQUIRED: False,
195+
BundlePropertyConfig.ID: "handlers",
196+
BundlePropertyConfig.REF_ID: f"evaluator{ID_SEP_KEY}val_handlers",
197+
},
177198
"preprocessing": {
178199
BundleProperty.DESC: "preprocessing for the input data.",
179200
BundleProperty.REQUIRED: False,

monai/bundle/scripts.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -608,16 +608,22 @@ def run(
608608
.. code-block:: bash
609609
610610
# Execute this module as a CLI entry:
611+
python -m monai.bundle run --meta_file <meta path> --config_file <config path>
612+
613+
# Execute with specified `run_id=training`:
611614
python -m monai.bundle run training --meta_file <meta path> --config_file <config path>
612615
616+
# Execute with all specified `run_id=runtest`, `init_id=inittest`, `final_id=finaltest`:
617+
python -m monai.bundle run --run_id runtest --init_id inittest --final_id finaltest ...
618+
613619
# Override config values at runtime by specifying the component id and its new value:
614-
python -m monai.bundle run training --net#input_chns 1 ...
620+
python -m monai.bundle run --net#input_chns 1 ...
615621
616622
# Override config values with another config file `/path/to/another.json`:
617-
python -m monai.bundle run evaluating --net %/path/to/another.json ...
623+
python -m monai.bundle run --net %/path/to/another.json ...
618624
619625
# Override config values with part content of another config file:
620-
python -m monai.bundle run training --net %/data/other.json#net_arg ...
626+
python -m monai.bundle run --net %/data/other.json#net_arg ...
621627
622628
# Set default args of `run` in a JSON / YAML file, help to record and simplify the command line.
623629
# Other args still can override the default args at runtime:
@@ -1462,7 +1468,7 @@ def init_bundle(
14621468
Describe your model here and how to run it, for example using `inference.json`:
14631469
14641470
```
1465-
python -m monai.bundle run evaluating \
1471+
python -m monai.bundle run \
14661472
--meta_file /path/to/bundle/configs/metadata.json \
14671473
--config_file /path/to/bundle/configs/inference.json \
14681474
--dataset_dir ./input \

monai/bundle/workflows.py

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import time
1616
import warnings
1717
from abc import ABC, abstractmethod
18+
from copy import copy
1819
from logging.config import fileConfig
1920
from pathlib import Path
2021
from typing import Any, Sequence
@@ -53,10 +54,10 @@ def __init__(self, workflow: str | None = None):
5354
self.workflow = None
5455
return
5556
if workflow.lower() in self.supported_train_type:
56-
self.properties = TrainProperties
57+
self.properties = copy(TrainProperties)
5758
self.workflow = "train"
5859
elif workflow.lower() in self.supported_infer_type:
59-
self.properties = InferProperties
60+
self.properties = copy(InferProperties)
6061
self.workflow = "infer"
6162
else:
6263
raise ValueError(f"Unsupported workflow type: '{workflow}'.")
@@ -129,6 +130,24 @@ def get_workflow_type(self):
129130
"""
130131
return self.workflow
131132

133+
def add_property(self, name: str, required: str, desc: str | None = None) -> None:
134+
"""
135+
Besides the default predefined properties, some 3rd party aplications may need the bundle
136+
definition to provide additonal properties for the specific use cases, if the bundlle can't
137+
provide the property, means it can't work with the application.
138+
This utility adds the property for the application requirements check and access.
139+
140+
Args:
141+
name: the name of target property.
142+
required: whether the property is "must-have".
143+
desc: descriptions for the property.
144+
"""
145+
if self.properties is None:
146+
self.properties = {}
147+
if name in self.properties:
148+
warnings.warn(f"property '{name}' already exists in the properties list, overriding it.")
149+
self.properties[name] = {BundleProperty.DESC: desc, BundleProperty.REQUIRED: required}
150+
132151
def check_properties(self) -> list[str] | None:
133152
"""
134153
Check whether the required properties are existing in the bundle workflow.
@@ -316,6 +335,25 @@ def _set_property(self, name: str, property: dict, value: Any) -> None:
316335
self._is_initialized = False
317336
self.parser.ref_resolver.reset()
318337

338+
def add_property( # type: ignore[override]
339+
self, name: str, required: str, config_id: str, desc: str | None = None
340+
) -> None:
341+
"""
342+
Besides the default predefined properties, some 3rd party aplications may need the bundle
343+
definition to provide additonal properties for the specific use cases, if the bundlle can't
344+
provide the property, means it can't work with the application.
345+
This utility adds the property for the application requirements check and access.
346+
347+
Args:
348+
name: the name of target property.
349+
required: whether the property is "must-have".
350+
config_id: the config ID of target property in the bundle definition.
351+
desc: descriptions for the property.
352+
353+
"""
354+
super().add_property(name=name, required=required, desc=desc)
355+
self.properties[name][BundlePropertyConfig.ID] = config_id # type: ignore[index]
356+
319357
def _check_optional_id(self, name: str, property: dict) -> bool:
320358
"""
321359
If an optional property has reference in the config, check whether the property is existing.

tests/nonconfig_workflow.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@ def __init__(self, filename, output_dir):
4141
self.filename = filename
4242
self.output_dir = output_dir
4343
self._bundle_root = "will override"
44+
self._dataset_dir = "."
4445
self._device = torch.device("cpu")
46+
self._data = [{"image": self.filename}]
47+
self._dataset = None
4548
self._network_def = None
4649
self._inferer = None
4750
self._preprocessing = None
@@ -54,8 +57,8 @@ def initialize(self):
5457
self._preprocessing = Compose(
5558
[LoadImaged(keys="image"), EnsureChannelFirstd(keys="image"), ScaleIntensityd(keys="image")]
5659
)
57-
dataset = Dataset(data=[{"image": self.filename}], transform=self._preprocessing)
58-
dataloader = DataLoader(dataset, batch_size=1, num_workers=4)
60+
self._dataset = Dataset(data=self._data, transform=self._preprocessing)
61+
dataloader = DataLoader(self._dataset, batch_size=1, num_workers=4)
5962

6063
if self._network_def is None:
6164
self._network_def = UNet(
@@ -97,6 +100,12 @@ def finalize(self):
97100
def _get_property(self, name, property):
98101
if name == "bundle_root":
99102
return self._bundle_root
103+
if name == "dataset_dir":
104+
return self._dataset_dir
105+
if name == "dataset_data":
106+
return self._data
107+
if name == "dataset":
108+
return self._dataset
100109
if name == "device":
101110
return self._device
102111
if name == "evaluator":
@@ -117,6 +126,12 @@ def _set_property(self, name, property, value):
117126
self._bundle_root = value
118127
elif name == "device":
119128
self._device = value
129+
elif name == "dataset_dir":
130+
self._dataset_dir = value
131+
elif name == "dataset_data":
132+
self._data = value
133+
elif name == "dataset":
134+
self._dataset = value
120135
elif name == "evaluator":
121136
self._evaluator = value
122137
elif name == "network_def":

tests/test_fl_monai_algo_dist.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ def test_train(self):
4141
pathjoin(_data_dir, "config_fl_evaluate.json"),
4242
pathjoin(_data_dir, "multi_gpu_evaluate.json"),
4343
]
44+
train_workflow = ConfigWorkflow(config_file=train_configs, workflow="train", logging_file=_logging_file)
45+
# simulate the case that this application has specific requirements for a bundle workflow
46+
train_workflow.add_property(name="loader", required=True, config_id="train#training_transforms#0", desc="NA")
47+
4448
# initialize algo
4549
algo = MonaiAlgo(
4650
bundle_root=_data_dir,

0 commit comments

Comments
 (0)