Skip to content

Commit f933263

Browse files
committed
address comments
Signed-off-by: YunLiu <[email protected]>
1 parent 99af7fd commit f933263

File tree

4 files changed

+79
-9
lines changed

4 files changed

+79
-9
lines changed

monai/bundle/workflows.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ class BundleWorkflow(ABC):
5151
default to `None` for common workflow.
5252
properties_path: the path to the JSON file of properties. If `workflow_type` is specified, properties will be
5353
loaded from the file based on the provided `workflow_type` and meta. If no `workflow_type` is specified,
54-
properties will default to loading from "train". If the specified file is unavailable, default properties
54+
properties will default to loading from "meta". If the specified file is unavailable, default properties
5555
will be sourced from "monai/bundle/properties.py" based on the workflow_type:
5656
For a training workflow, properties load from `TrainProperties` and `MetaProperties`.
5757
For a inference workflow, properties load from `InferProperties` and `MetaProperties`.
@@ -116,8 +116,7 @@ def __init__(
116116
if not properties_path.is_file():
117117
raise ValueError(f"Property file {properties_path} does not exist.")
118118
if workflow_type is None:
119-
workflow_type = "train"
120-
logger.info("No workflow type specified, default to 'train' for property file loading.")
119+
logger.info("No workflow type specified, default to load meta properties from property file.")
121120
with open(properties_path) as json_file:
122121
try:
123122
properties = json.load(json_file)
@@ -249,9 +248,13 @@ def check_properties(self) -> list[str] | None:
249248
class PythonicWorkflow(BundleWorkflow):
250249
"""
251250
Base class for the pythonic workflow specification in bundle, it can be a training, evaluation or inference workflow.
252-
It defines the basic interfaces for the bundle workflow behavior: `initialize`, `run`, `finalize`, etc.
251+
It defines the basic interfaces for the bundle workflow behavior: `initialize`, `finalize`, etc.
253252
This also provides the interface to get / set public properties to interact with a bundle workflow through
254253
defined `get_<property>` accessor methods or directly defining members of the object.
254+
For how to set the properties, users can define the `_set_<property>` methods or directly set the members of the object.
255+
The `initialize` method is called to set up the workflow before running. This method sets up internal state and prepares properties.
256+
If properties are modified after the workflow has been initialized, `self._is_initialized` is set to `False`. Before running the
257+
workflow again, `initialize` should be called to ensure that the workflow is properly set up with the new property values.
255258
256259
Args:
257260
workflow_type: specifies the workflow type: "train" or "training" for a training workflow,
@@ -264,7 +267,7 @@ class PythonicWorkflow(BundleWorkflow):
264267
default to `None` for common workflow.
265268
properties_path: the path to the JSON file of properties. If `workflow_type` is specified, properties will be
266269
loaded from the file based on the provided `workflow_type` and meta. If no `workflow_type` is specified,
267-
properties will default to loading from "train". If the specified file is unavailable, default properties
270+
properties will default to loading from "meta". If the specified file is unavailable, default properties
268271
will be sourced from "monai/bundle/properties.py" based on the workflow_type:
269272
For a training workflow, properties load from `TrainProperties` and `MetaProperties`.
270273
For a inference workflow, properties load from `InferProperties` and `MetaProperties`.
@@ -302,12 +305,14 @@ def __init__(
302305

303306
# the rest key-values in the _args are to override config content
304307
self.parser.update(pairs=override)
308+
self._is_initialized: bool = False
305309

306310
def initialize(self, *args: Any, **kwargs: Any) -> Any:
307311
"""
308312
Initialize the bundle workflow before running.
309313
"""
310314
self._props_vals = {}
315+
self._is_initialized = True
311316

312317
def _get_property(self, name: str, property: dict) -> Any:
313318
"""
@@ -320,6 +325,8 @@ def _get_property(self, name: str, property: dict) -> Any:
320325
name: the name of target property.
321326
property: other information for the target property, defined in `TrainProperties` or `InferProperties`.
322327
"""
328+
if not self._is_initialized:
329+
raise RuntimeError("Please execute 'initialize' before getting any properties.")
323330
value = None
324331
if name in self._set_props_vals:
325332
value = self._set_props_vals[name]
@@ -343,7 +350,7 @@ def _get_property(self, name: str, property: dict) -> Any:
343350
def _set_property(self, name: str, property: dict, value: Any) -> Any:
344351
"""
345352
With specified property name and information, set value for the expected property.
346-
Stores user-reset initialized objects that should not be re-initialized.
353+
Stores user-reset initialized objects that should not be re-initialized and marks the workflow as not initialized.
347354
348355
Args:
349356
name: the name of target property.
@@ -352,6 +359,7 @@ def _set_property(self, name: str, property: dict, value: Any) -> Any:
352359
353360
"""
354361
self._set_props_vals[name] = value
362+
self._is_initialized = False
355363

356364

357365
class ConfigWorkflow(BundleWorkflow):

tests/nonconfig_workflow.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,8 @@ def __init__(
197197
self.dataflow: dict = {}
198198

199199
def initialize(self):
200+
self._props_vals = {}
201+
self._is_initialized = True
200202
self.net = UNet(
201203
spatial_dims=3,
202204
in_channels=1,
@@ -214,7 +216,6 @@ def initialize(self):
214216
)
215217
self.dataset = Dataset(data=[self.dataflow], transform=preprocessing)
216218
self.postprocessing = Compose([Activationsd(keys="pred", softmax=True), AsDiscreted(keys="pred", argmax=True)])
217-
self.inferer = SlidingWindowInferer(roi_size=self.parser.roi_size, sw_batch_size=1, overlap=0)
218219

219220
def run(self):
220221
data = self.dataset[0]
@@ -234,4 +235,4 @@ def get_device(self):
234235
return torch.device("cuda" if torch.cuda.is_available() else "cpu")
235236

236237
def get_inferer(self):
237-
return self.inferer
238+
return SlidingWindowInferer(roi_size=self.parser.roi_size, sw_batch_size=1, overlap=0)

tests/test_bundle_workflow.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
import torch
2323
from parameterized import parameterized
2424

25-
from monai.bundle import ConfigWorkflow
25+
from monai.bundle import ConfigWorkflow, create_workflow
2626
from monai.data import Dataset
2727
from monai.inferers import SimpleInferer, SlidingWindowInferer
2828
from monai.networks.nets import UNet
@@ -190,6 +190,41 @@ def test_pythonic_workflow(self):
190190
self.assertEqual(pred.meta["filename_or_obj"], self.filename2)
191191
workflow.finalize()
192192

193+
def test_create_pythonic_workflow(self):
194+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
195+
config_file = {"roi_size": (64, 64, 32)}
196+
meta_file = os.path.join(os.path.dirname(__file__), "testing_data", "metadata.json")
197+
property_path = os.path.join(os.path.dirname(__file__), "testing_data", "python_workflow_properties.json")
198+
import sys
199+
sys.path.append(os.path.dirname(__file__))
200+
workflow = create_workflow("nonconfig_workflow.PythonicWorkflowImpl", workflow_type="infer", config_file=config_file, meta_file=meta_file, properties_path=property_path)
201+
# Load input data
202+
input_loader = LoadImaged(keys="image")
203+
workflow.dataflow.update(input_loader({"image": self.filename}))
204+
self.assertEqual(workflow.bundle_root, ".")
205+
self.assertEqual(workflow.device, device)
206+
self.assertEqual(workflow.version, "0.1.0")
207+
# check config override correctly
208+
self.assertEqual(workflow.inferer.roi_size, (64, 64, 32))
209+
210+
# check set property override correctly
211+
workflow.inferer = SlidingWindowInferer(roi_size=config_file["roi_size"], sw_batch_size=1, overlap=0.5)
212+
workflow.initialize()
213+
self.assertEqual(workflow.inferer.overlap, 0.5)
214+
215+
workflow.run()
216+
# update input data and run again
217+
workflow.dataflow.update(input_loader({"image": self.filename2}))
218+
workflow.run()
219+
pred = workflow.dataflow["pred"]
220+
self.assertEqual(pred.shape[2:], self.expected_shape)
221+
self.assertEqual(pred.meta["filename_or_obj"], self.filename2)
222+
223+
# test add properties
224+
workflow.add_property(name="net", required=True, desc="network for the training.")
225+
self.assertIn("net", workflow.properties)
226+
workflow.finalize()
227+
193228

194229
if __name__ == "__main__":
195230
unittest.main()
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"infer": {
3+
"bundle_root": {
4+
"description": "root path of the bundle.",
5+
"required": true,
6+
"id": "bundle_root"
7+
},
8+
"device": {
9+
"description": "target device to execute the bundle workflow.",
10+
"required": true,
11+
"id": "device"
12+
},
13+
"inferer": {
14+
"description": "MONAI Inferer object to execute the model computation in inference.",
15+
"required": true,
16+
"id": "inferer"
17+
}
18+
},
19+
"meta": {
20+
"version": {
21+
"description": "version of the inference configuration.",
22+
"required": true,
23+
"id": "_meta_::version"
24+
}
25+
}
26+
}

0 commit comments

Comments
 (0)