Skip to content

Commit 0420991

Browse files
KumoLiuyiheng-wang-nvpre-commit-ci[bot]
authored
Add more unit tests (Project-MONAI#483)
Fixes # . ### Description This PR is used to add unit tests on the following bundles: - [x] endoscopic_tool_segmentation - [x] pathology_nuclei_segmentation_classification - [x] wholeBody_ct_segmentation - [x] pathology_nuclei_classification - [x] pathology_nuclick_annotation - [x] lung_nodule_ct_detection ### 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: KumoLiu <[email protected]> Signed-off-by: Yiheng Wang <[email protected]> Co-authored-by: Yiheng Wang <[email protected]> Co-authored-by: Yiheng Wang <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 0f70ac3 commit 0420991

14 files changed

+1340
-1
lines changed

.pre-commit-config.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ ci:
77
autoupdate_schedule: quarterly
88
# submodules: true
99

10+
exclude: '.*\.ipynb$'
11+
1012
repos:
1113
- repo: https://github.com/pre-commit/pre-commit-hooks
1214
rev: v4.4.0

ci/run_premerge_cpu.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ remove_pipenv() {
3939
echo "removing pip environment"
4040
pipenv --rm
4141
rm Pipfile Pipfile.lock
42+
pipenv --clear
43+
df -h
4244
}
4345

4446
verify_bundle() {
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
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 tempfile
15+
import unittest
16+
17+
import numpy as np
18+
from monai.bundle import ConfigWorkflow
19+
from monai.data import PILWriter
20+
from parameterized import parameterized
21+
from utils import check_workflow
22+
23+
TEST_CASE_1 = [ # train, evaluate
24+
{
25+
"bundle_root": "models/endoscopic_tool_segmentation",
26+
"train#trainer#max_epochs": 2,
27+
"train#dataloader#num_workers": 1,
28+
"validate#dataloader#num_workers": 1,
29+
"train#deterministic_transforms#3#spatial_size": [32, 32],
30+
}
31+
]
32+
33+
TEST_CASE_2 = [ # inference
34+
{
35+
"bundle_root": "models/endoscopic_tool_segmentation",
36+
"handlers#0#_disabled_": True,
37+
"preprocessing#transforms#2#spatial_size": [32, 32],
38+
}
39+
]
40+
41+
42+
class TestEndoscopicSeg(unittest.TestCase):
43+
def setUp(self):
44+
self.dataset_dir = tempfile.mkdtemp()
45+
dataset_size = 10
46+
writer = PILWriter(np.uint8)
47+
shape = (736, 480)
48+
for mode in ["train", "val", "test"]:
49+
for sub_folder in ["inbody", "outbody"]:
50+
sample_dir = os.path.join(self.dataset_dir, f"{mode}/{sub_folder}")
51+
os.makedirs(sample_dir)
52+
for s in range(dataset_size):
53+
image = np.random.randint(low=0, high=5, size=(3, *shape)).astype(np.int8)
54+
image_filename = os.path.join(sample_dir, f"{sub_folder}_{s}.jpg")
55+
writer.set_data_array(image, channel_dim=0)
56+
writer.write(image_filename, verbose=True)
57+
if mode != "test":
58+
label = np.random.randint(low=0, high=5, size=shape).astype(np.int8)
59+
label_filename = os.path.join(sample_dir, f"{sub_folder}_{s}_seg.jpg")
60+
writer.set_data_array(label, channel_dim=None)
61+
writer.write(label_filename, verbose=True)
62+
63+
def tearDown(self):
64+
shutil.rmtree(self.dataset_dir)
65+
66+
@parameterized.expand([TEST_CASE_1])
67+
def test_train_eval_config(self, override):
68+
override["dataset_dir"] = self.dataset_dir
69+
bundle_root = override["bundle_root"]
70+
train_file = os.path.join(bundle_root, "configs/train.json")
71+
eval_file = os.path.join(bundle_root, "configs/evaluate.json")
72+
73+
trainer = ConfigWorkflow(
74+
workflow="train",
75+
config_file=train_file,
76+
logging_file=os.path.join(bundle_root, "configs/logging.conf"),
77+
meta_file=os.path.join(bundle_root, "configs/metadata.json"),
78+
**override,
79+
)
80+
check_workflow(trainer, check_properties=True)
81+
82+
validator = ConfigWorkflow(
83+
# override train.json, thus set the workflow to "train" rather than "eval"
84+
workflow="train",
85+
config_file=[train_file, eval_file],
86+
logging_file=os.path.join(bundle_root, "configs/logging.conf"),
87+
meta_file=os.path.join(bundle_root, "configs/metadata.json"),
88+
**override,
89+
)
90+
check_workflow(validator, check_properties=True)
91+
92+
@parameterized.expand([TEST_CASE_2])
93+
def test_infer_config(self, override):
94+
override["dataset_dir"] = self.dataset_dir
95+
bundle_root = override["bundle_root"]
96+
97+
inferrer = ConfigWorkflow(
98+
workflow="infer",
99+
config_file=os.path.join(bundle_root, "configs/inference.json"),
100+
logging_file=os.path.join(bundle_root, "configs/logging.conf"),
101+
meta_file=os.path.join(bundle_root, "configs/metadata.json"),
102+
**override,
103+
)
104+
check_workflow(inferrer, check_properties=True)
105+
106+
107+
if __name__ == "__main__":
108+
unittest.main()
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
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 tempfile
15+
import unittest
16+
17+
import numpy as np
18+
import torch
19+
from monai.data import PILWriter
20+
from parameterized import parameterized
21+
from utils import export_config_and_run_mgpu_cmd
22+
23+
TEST_CASE_1 = [
24+
{
25+
"bundle_root": "models/endoscopic_tool_segmentation",
26+
"train#trainer#max_epochs": 1,
27+
"train#dataloader#num_workers": 1,
28+
"validate#dataloader#num_workers": 1,
29+
"train#deterministic_transforms#3#spatial_size": [32, 32],
30+
}
31+
]
32+
33+
TEST_CASE_2 = [
34+
{
35+
"bundle_root": "models/endoscopic_tool_segmentation",
36+
"validate#dataloader#num_workers": 4,
37+
"train#deterministic_transforms#3#spatial_size": [32, 32],
38+
}
39+
]
40+
41+
42+
def test_order(test_name1, test_name2):
43+
def get_order(name):
44+
if "train" in name:
45+
return 1
46+
if "eval" in name:
47+
return 2
48+
if "infer" in name:
49+
return 3
50+
return 4
51+
52+
return get_order(test_name1) - get_order(test_name2)
53+
54+
55+
class TestEndoscopicSegMGPU(unittest.TestCase):
56+
def setUp(self):
57+
self.dataset_dir = tempfile.mkdtemp()
58+
dataset_size = 10
59+
writer = PILWriter(np.uint8)
60+
shape = (3, 256, 256)
61+
for mode in ["train", "val"]:
62+
for sub_folder in ["inbody", "outbody"]:
63+
sample_dir = os.path.join(self.dataset_dir, f"{mode}/{sub_folder}")
64+
os.makedirs(sample_dir)
65+
for s in range(dataset_size):
66+
image = np.random.randint(low=0, high=5, size=(3, *shape)).astype(np.int8)
67+
image_filename = os.path.join(sample_dir, f"{sub_folder}_{s}.jpg")
68+
writer.set_data_array(image, channel_dim=0)
69+
writer.write(image_filename, verbose=True)
70+
label = np.random.randint(low=0, high=5, size=shape).astype(np.int8)
71+
label_filename = os.path.join(sample_dir, f"{sub_folder}_{s}_seg.jpg")
72+
writer.set_data_array(label, channel_dim=None)
73+
writer.write(label_filename, verbose=True)
74+
75+
def tearDown(self):
76+
shutil.rmtree(self.dataset_dir)
77+
78+
@parameterized.expand([TEST_CASE_1])
79+
def test_train_mgpu_config(self, override):
80+
override["dataset_dir"] = self.dataset_dir
81+
bundle_root = override["bundle_root"]
82+
train_file = os.path.join(bundle_root, "configs/train.json")
83+
mgpu_train_file = os.path.join(bundle_root, "configs/multi_gpu_train.json")
84+
output_path = os.path.join(bundle_root, "configs/train_override.json")
85+
n_gpu = torch.cuda.device_count()
86+
export_config_and_run_mgpu_cmd(
87+
config_file=[train_file, mgpu_train_file],
88+
logging_file=os.path.join(bundle_root, "configs/logging.conf"),
89+
meta_file=os.path.join(bundle_root, "configs/metadata.json"),
90+
override_dict=override,
91+
output_path=output_path,
92+
ngpu=n_gpu,
93+
check_config=True,
94+
)
95+
96+
@parameterized.expand([TEST_CASE_2])
97+
def test_evaluate_mgpu_config(self, override):
98+
override["dataset_dir"] = self.dataset_dir
99+
bundle_root = override["bundle_root"]
100+
train_file = os.path.join(bundle_root, "configs/train.json")
101+
evaluate_file = os.path.join(bundle_root, "configs/evaluate.json")
102+
mgpu_evaluate_file = os.path.join(bundle_root, "configs/multi_gpu_evaluate.json")
103+
output_path = os.path.join(bundle_root, "configs/evaluate_override.json")
104+
n_gpu = torch.cuda.device_count()
105+
export_config_and_run_mgpu_cmd(
106+
config_file=[train_file, evaluate_file, mgpu_evaluate_file],
107+
logging_file=os.path.join(bundle_root, "configs/logging.conf"),
108+
meta_file=os.path.join(bundle_root, "configs/metadata.json"),
109+
override_dict=override,
110+
output_path=output_path,
111+
ngpu=n_gpu,
112+
check_config=True,
113+
)
114+
115+
116+
if __name__ == "__main__":
117+
loader = unittest.TestLoader()
118+
loader.sortTestMethodsUsing = test_order
119+
unittest.main(testLoader=loader)
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
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 json
13+
import os
14+
import shutil
15+
import sys
16+
import tempfile
17+
import unittest
18+
19+
import nibabel as nib
20+
import numpy as np
21+
from monai.bundle import ConfigWorkflow
22+
from monai.data import create_test_image_3d
23+
from monai.utils import set_determinism
24+
from parameterized import parameterized
25+
from utils import check_workflow
26+
27+
set_determinism(123)
28+
29+
TEST_CASE_1 = [ # train, evaluate
30+
{
31+
"bundle_root": "models/lung_nodule_ct_detection",
32+
"epochs": 3,
33+
"batch_size": 1,
34+
"val_interval": 2,
35+
"train#dataloader#num_workers": 1,
36+
"validate#dataloader#num_workers": 1,
37+
}
38+
]
39+
40+
TEST_CASE_2 = [{"bundle_root": "models/lung_nodule_ct_detection"}] # inference
41+
42+
43+
def test_order(test_name1, test_name2):
44+
def get_order(name):
45+
if "train" in name:
46+
return 1
47+
if "eval" in name:
48+
return 2
49+
if "infer" in name:
50+
return 3
51+
return 4
52+
53+
return get_order(test_name1) - get_order(test_name2)
54+
55+
56+
class TestLungNoduleDetection(unittest.TestCase):
57+
def setUp(self):
58+
self.dataset_dir = tempfile.mkdtemp()
59+
dataset_size = 3
60+
train_patch_size = (192, 192, 80)
61+
dataset_json = {}
62+
63+
img, _ = create_test_image_3d(train_patch_size[0], train_patch_size[1], train_patch_size[2], 2)
64+
image_filename = os.path.join(self.dataset_dir, "image.nii.gz")
65+
nib.save(nib.Nifti1Image(img, np.eye(4)), image_filename)
66+
label = [0, 0]
67+
box = [[108, 119, 131, 142, 26, 37], [132, 147, 149, 164, 25, 40]]
68+
data = {"box": box, "image": image_filename, "label": label}
69+
dataset_json["training"] = [data for _ in range(dataset_size)]
70+
dataset_json["validation"] = [data for _ in range(dataset_size)]
71+
72+
self.ds_file = os.path.join(self.dataset_dir, "dataset.json")
73+
with open(self.ds_file, "w") as fp:
74+
json.dump(dataset_json, fp, indent=2)
75+
76+
def tearDown(self):
77+
shutil.rmtree(self.dataset_dir)
78+
79+
@parameterized.expand([TEST_CASE_1])
80+
def test_train_eval_config(self, override):
81+
override["dataset_dir"] = self.dataset_dir
82+
override["data_list_file_path"] = self.ds_file
83+
bundle_root = override["bundle_root"]
84+
train_file = os.path.join(bundle_root, "configs/train.json")
85+
eval_file = os.path.join(bundle_root, "configs/evaluate.json")
86+
87+
sys.path.append(bundle_root)
88+
trainer = ConfigWorkflow(
89+
workflow="train",
90+
config_file=train_file,
91+
logging_file=os.path.join(bundle_root, "configs/logging.conf"),
92+
meta_file=os.path.join(bundle_root, "configs/metadata.json"),
93+
**override,
94+
)
95+
check_workflow(trainer, check_properties=False)
96+
97+
validator = ConfigWorkflow(
98+
# override train.json, thus set the workflow to "train" rather than "eval"
99+
workflow="train",
100+
config_file=[train_file, eval_file],
101+
logging_file=os.path.join(bundle_root, "configs/logging.conf"),
102+
meta_file=os.path.join(bundle_root, "configs/metadata.json"),
103+
**override,
104+
)
105+
check_workflow(validator, check_properties=False)
106+
107+
@parameterized.expand([TEST_CASE_2])
108+
def test_infer_config(self, override):
109+
override["dataset_dir"] = self.dataset_dir
110+
override["data_list_file_path"] = self.ds_file
111+
bundle_root = override["bundle_root"]
112+
113+
inferrer = ConfigWorkflow(
114+
workflow="infer",
115+
config_file=os.path.join(bundle_root, "configs/inference.json"),
116+
logging_file=os.path.join(bundle_root, "configs/logging.conf"),
117+
meta_file=os.path.join(bundle_root, "configs/metadata.json"),
118+
**override,
119+
)
120+
check_workflow(inferrer, check_properties=True)
121+
122+
123+
if __name__ == "__main__":
124+
loader = unittest.TestLoader()
125+
loader.sortTestMethodsUsing = test_order
126+
unittest.main(testLoader=loader)

0 commit comments

Comments
 (0)