Skip to content

Commit d593a05

Browse files
add vista2d (#618)
Fixes # . ### Description A few sentences describing the changes proposed in this pull request. ### Status **Ready/Work in progress/Hold** ### Please ensure all the checkboxes: <!--- Put an `x` in all the boxes that apply, and remove the not applicable items --> - [x] Codeformat tests passed locally by running `./runtests.sh --codeformat`. - [ ] In-line docstrings updated. - [ ] Update `version` and `changelog` in `metadata.json` if changing an existing bundle. - [ ] Please ensure the naming rules in config files meet our requirements (please refer to: `CONTRIBUTING.md`). - [ ] Ensure versions of packages such as `monai`, `pytorch` and `numpy` are correct in `metadata.json`. - [ ] Descriptions should be consistent with the content, such as `eval_metrics` of the provided weights and TorchScript modules. - [ ] Files larger than 25MB are excluded and replaced by providing download links in `large_file.yml`. - [ ] Avoid using path that contains personal information within config files (such as use `/home/your_name/` for `"bundle_root"`). --------- Signed-off-by: Yiheng Wang <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 4dac76f commit d593a05

29 files changed

+5151
-7
lines changed

ci/bundle_custom_data.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"brats_mri_axial_slices_generative_diffusion",
4040
"vista3d",
4141
"maisi_ct_generative",
42+
"vista2d",
4243
]
4344

4445
# This list is used for our CI tests to determine whether a bundle needs to be tested after downloading

ci/unit_tests/test_vista2d.py

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# Copyright (c) MONAI Consortium
2+
# Licensed under the Apache License, Version 2.0 (the "License");
3+
# you may not use this file except in compliance with the License.
4+
# You may obtain a copy of the License at
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
# Unless required by applicable law or agreed to in writing, software
7+
# distributed under the License is distributed on an "AS IS" BASIS,
8+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9+
# See the License for the specific language governing permissions and
10+
# limitations under the License.
11+
12+
import os
13+
import shutil
14+
import sys
15+
import tempfile
16+
import unittest
17+
18+
import matplotlib.pyplot as plt
19+
import numpy as np
20+
from monai.bundle import create_workflow
21+
from parameterized import parameterized
22+
from utils import check_workflow
23+
24+
TEST_CASE_TRAIN = [{"bundle_root": "models/vista2d", "mode": "train", "train#trainer#max_epochs": 1}]
25+
26+
TEST_CASE_INFER = [{"bundle_root": "models/vista2d", "mode": "infer"}]
27+
28+
29+
def test_order(test_name1, test_name2):
30+
def get_order(name):
31+
if "train" in name:
32+
return 1
33+
if "infer" in name:
34+
return 2
35+
return 3
36+
37+
return get_order(test_name1) - get_order(test_name2)
38+
39+
40+
class TestVista2d(unittest.TestCase):
41+
def setUp(self):
42+
self.dataset_dir = tempfile.mkdtemp()
43+
self.tmp_output_dir = os.path.join(self.dataset_dir, "output")
44+
os.makedirs(self.tmp_output_dir, exist_ok=True)
45+
self.dataset_size = 5
46+
input_shape = (256, 256)
47+
for s in range(self.dataset_size):
48+
test_image = np.random.randint(low=0, high=2, size=input_shape).astype(np.int8)
49+
test_label = np.random.randint(low=0, high=2, size=input_shape).astype(np.int8)
50+
image_filename = os.path.join(self.dataset_dir, f"image_{s}.png")
51+
label_filename = os.path.join(self.dataset_dir, f"label_{s}.png")
52+
plt.imsave(image_filename, test_image, cmap="gray")
53+
plt.imsave(label_filename, test_label, cmap="gray")
54+
55+
self.bundle_root = "models/vista2d"
56+
sys.path = [self.bundle_root] + sys.path
57+
from scripts.workflow import VistaCell
58+
59+
self.workflow = VistaCell
60+
61+
def tearDown(self):
62+
shutil.rmtree(self.dataset_dir)
63+
64+
@parameterized.expand([TEST_CASE_INFER])
65+
def test_infer_config(self, override):
66+
# update override with dataset dir
67+
override["dataset#data"] = [
68+
{
69+
"image": os.path.join(self.dataset_dir, f"image_{s}.png"),
70+
"label": os.path.join(self.dataset_dir, f"label_{s}.png"),
71+
}
72+
for s in range(self.dataset_size)
73+
]
74+
override["output_dir"] = self.tmp_output_dir
75+
workflow = create_workflow(
76+
workflow_name=self.workflow,
77+
config_file=os.path.join(self.bundle_root, "configs/hyper_parameters.yaml"),
78+
meta_file=os.path.join(self.bundle_root, "configs/metadata.json"),
79+
**override,
80+
)
81+
82+
# check_properties=False, need to add monai service properties later
83+
check_workflow(workflow, check_properties=False)
84+
85+
expected_output_file = os.path.join(self.tmp_output_dir, f"image_{self.dataset_size-1}.tif")
86+
self.assertTrue(os.path.isfile(expected_output_file))
87+
88+
@parameterized.expand([TEST_CASE_TRAIN])
89+
def test_train_config(self, override):
90+
# update override with dataset dir
91+
override["train#dataset#data"] = [
92+
{
93+
"image": os.path.join(self.dataset_dir, f"image_{s}.png"),
94+
"label": os.path.join(self.dataset_dir, f"label_{s}.png"),
95+
}
96+
for s in range(self.dataset_size)
97+
]
98+
override["dataset#data"] = override["train#dataset#data"]
99+
100+
workflow = create_workflow(
101+
workflow_name=self.workflow,
102+
config_file=os.path.join(self.bundle_root, "configs/hyper_parameters.yaml"),
103+
meta_file=os.path.join(self.bundle_root, "configs/metadata.json"),
104+
**override,
105+
)
106+
107+
# check_properties=False, need to add monai service properties later
108+
check_workflow(workflow, check_properties=False)
109+
110+
# follow up to use trained weights and test eval
111+
override["mode"] = "eval"
112+
override["pretrained_ckpt_name"] = "model.pt"
113+
workflow = create_workflow(
114+
workflow_name=self.workflow,
115+
config_file=os.path.join(self.bundle_root, "configs/hyper_parameters.yaml"),
116+
meta_file=os.path.join(self.bundle_root, "configs/metadata.json"),
117+
**override,
118+
)
119+
check_workflow(workflow, check_properties=False)
120+
121+
122+
if __name__ == "__main__":
123+
loader = unittest.TestLoader()
124+
loader.sortTestMethodsUsing = test_order
125+
unittest.main(testLoader=loader)

ci/unit_tests/test_vista2d_dist.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Copyright (c) MONAI Consortium
2+
# Licensed under the Apache License, Version 2.0 (the "License");
3+
# you may not use this file except in compliance with the License.
4+
# You may obtain a copy of the License at
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
# Unless required by applicable law or agreed to in writing, software
7+
# distributed under the License is distributed on an "AS IS" BASIS,
8+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9+
# See the License for the specific language governing permissions and
10+
# limitations under the License.
11+
12+
import os
13+
import shutil
14+
import sys
15+
import tempfile
16+
import unittest
17+
18+
import matplotlib.pyplot as plt
19+
import numpy as np
20+
import torch
21+
from parameterized import parameterized
22+
from utils import export_config_and_run_mgpu_cmd
23+
24+
TEST_CASE_TRAIN_MGPU = [{"bundle_root": "models/vista2d", "workflow_type": "train", "train#trainer#max_epochs": 2}]
25+
26+
27+
class TestVista2d(unittest.TestCase):
28+
def setUp(self):
29+
self.dataset_dir = tempfile.mkdtemp()
30+
self.dataset_size = 5
31+
input_shape = (256, 256)
32+
for s in range(self.dataset_size):
33+
test_image = np.random.randint(low=0, high=2, size=input_shape).astype(np.int8)
34+
test_label = np.random.randint(low=0, high=2, size=input_shape).astype(np.int8)
35+
image_filename = os.path.join(self.dataset_dir, f"image_{s}.png")
36+
label_filename = os.path.join(self.dataset_dir, f"label_{s}.png")
37+
plt.imsave(image_filename, test_image, cmap="gray")
38+
plt.imsave(label_filename, test_label, cmap="gray")
39+
40+
self.bundle_root = "models/vista2d"
41+
sys.path = [self.bundle_root] + sys.path
42+
43+
def tearDown(self):
44+
shutil.rmtree(self.dataset_dir)
45+
46+
@parameterized.expand([TEST_CASE_TRAIN_MGPU])
47+
def test_train_mgpu_config(self, override):
48+
override["train#dataset#data"] = [
49+
{
50+
"image": os.path.join(self.dataset_dir, f"image_{s}.png"),
51+
"label": os.path.join(self.dataset_dir, f"label_{s}.png"),
52+
}
53+
for s in range(self.dataset_size)
54+
]
55+
override["dataset#data"] = override["train#dataset#data"]
56+
57+
output_path = os.path.join(self.bundle_root, "configs/train_override.json")
58+
n_gpu = torch.cuda.device_count()
59+
export_config_and_run_mgpu_cmd(
60+
config_file=os.path.join(self.bundle_root, "configs/hyper_parameters.yaml"),
61+
meta_file=os.path.join(self.bundle_root, "configs/metadata.json"),
62+
custom_workflow="scripts.workflow.VistaCell",
63+
override_dict=override,
64+
output_path=output_path,
65+
ngpu=n_gpu,
66+
)
67+
68+
69+
if __name__ == "__main__":
70+
unittest.main()

ci/unit_tests/utils.py

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def export_overrided_config(config_file, override_dict, output_path):
2323
ConfigParser.export_config_file(parser.config, output_path, indent=4)
2424

2525

26-
def produce_mgpu_cmd(config_file, meta_file, logging_file, nnodes=1, nproc_per_node=2):
26+
def produce_mgpu_cmd(config_file, meta_file, logging_file=None, nnodes=1, nproc_per_node=2):
2727
cmd = [
2828
"torchrun",
2929
"--standalone",
@@ -34,20 +34,43 @@ def produce_mgpu_cmd(config_file, meta_file, logging_file, nnodes=1, nproc_per_n
3434
"run",
3535
"--config_file",
3636
config_file,
37-
"--logging_file",
38-
logging_file,
3937
"--meta_file",
4038
meta_file,
4139
]
40+
if logging_file is not None:
41+
cmd.extend(["--logging_file", logging_file])
42+
return cmd
43+
44+
45+
def produce_custom_workflow_mgpu_cmd(
46+
custom_workflow, config_file, meta_file, logging_file=None, nnodes=1, nproc_per_node=2
47+
):
48+
cmd = [
49+
"torchrun",
50+
"--standalone",
51+
f"--nnodes={nnodes}",
52+
f"--nproc_per_node={nproc_per_node}",
53+
"-m",
54+
"monai.bundle",
55+
"run_workflow",
56+
custom_workflow,
57+
"--config_file",
58+
config_file,
59+
"--meta_file",
60+
meta_file,
61+
]
62+
if logging_file is not None:
63+
cmd.extend(["--logging_file", logging_file])
4264
return cmd
4365

4466

4567
def export_config_and_run_mgpu_cmd(
4668
config_file,
4769
meta_file,
48-
logging_file,
4970
override_dict,
5071
output_path,
72+
custom_workflow=None,
73+
logging_file=None,
5174
workflow_type="train",
5275
nnode=1,
5376
ngpu=2,
@@ -68,9 +91,19 @@ def export_config_and_run_mgpu_cmd(
6891
check_result = engine.check_properties()
6992
if check_result is not None and len(check_result) > 0:
7093
raise ValueError(f"check properties for overrided mgpu configs failed: {check_result}")
71-
cmd = produce_mgpu_cmd(
72-
config_file=output_path, meta_file=meta_file, logging_file=logging_file, nnodes=nnode, nproc_per_node=ngpu
73-
)
94+
if custom_workflow is None:
95+
cmd = produce_mgpu_cmd(
96+
config_file=output_path, meta_file=meta_file, logging_file=logging_file, nnodes=nnode, nproc_per_node=ngpu
97+
)
98+
else:
99+
cmd = produce_custom_workflow_mgpu_cmd(
100+
custom_workflow=custom_workflow,
101+
config_file=output_path,
102+
meta_file=meta_file,
103+
logging_file=logging_file,
104+
nnodes=nnode,
105+
nproc_per_node=ngpu,
106+
)
74107
env = os.environ.copy()
75108
# ensure customized library can be loaded in subprocess
76109
env["PYTHONPATH"] = override_dict.get("bundle_root", ".")

0 commit comments

Comments
 (0)