Skip to content

Commit 8ae1013

Browse files
Increase UT code coverage to 76% (#1282)
* Increase UT code coverage to 74% Signed-off-by: Sachidanand Alle <[email protected]> * make test as parameterized Signed-off-by: Sachidanand Alle <[email protected]> * handle cpu/gpu Signed-off-by: Sachidanand Alle <[email protected]> * Add client api test Signed-off-by: Sachidanand Alle <[email protected]> --------- Signed-off-by: Sachidanand Alle <[email protected]>
1 parent 2a9d727 commit 8ae1013

File tree

7 files changed

+271
-26
lines changed

7 files changed

+271
-26
lines changed

monailabel/interfaces/app.py

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@
3030
# added to support connecting to DICOM Store Google Cloud
3131
from dicomweb_client.ext.gcp.session_utils import create_session_from_gcp_credentials
3232
from dicomweb_client.session_utils import create_session_from_user_pass
33-
from monai.apps import download_and_extract, download_url
34-
from monai.data import partition_dataset
33+
from monai.apps import download_and_extract
3534
from timeloop import Timeloop
3635

3736
from monailabel.config import settings
@@ -387,12 +386,6 @@ def scoring(self, request, datastore=None):
387386
def datastore(self) -> Datastore:
388387
return self._datastore
389388

390-
@staticmethod
391-
def partition_datalist(datalist, val_split, shuffle=True):
392-
if val_split > 0.0:
393-
return partition_dataset(datalist, ratios=[(1 - val_split), val_split], shuffle=shuffle)
394-
return datalist, []
395-
396389
def train(self, request):
397390
"""
398391
Run Training. User APP has to implement this method to run training
@@ -595,18 +588,6 @@ def cleanup_sessions(self):
595588
def sessions(self):
596589
return self._sessions
597590

598-
@staticmethod
599-
def download(resources):
600-
if not resources:
601-
return
602-
603-
for resource in resources:
604-
if not os.path.exists(resource[0]):
605-
os.makedirs(os.path.dirname(resource[0]), exist_ok=True)
606-
logger.info(f"Downloading resource: {resource[0]} from {resource[1]}")
607-
download_url(resource[1], resource[0])
608-
time.sleep(1)
609-
610591
def infer_wsi(self, request, datastore=None):
611592
model = request.get("model")
612593
if not model:

tests/unit/client/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
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.

tests/unit/client/test_client.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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 unittest
13+
14+
from monailabel.client import MONAILabelClient
15+
16+
17+
# TODO:: Mock HTTP Server/Response
18+
class TestClient(unittest.TestCase):
19+
def setUp(self) -> None:
20+
pass
21+
22+
def tearDown(self) -> None:
23+
pass
24+
25+
def test_info(self):
26+
c = MONAILabelClient("http://127.0.0.1:8000/")
27+
c.get_server_url()
28+
c.set_server_url("http://127.0.0.1:8000")
29+
try:
30+
c.info()
31+
except:
32+
pass
33+
34+
def test_next_sample(self):
35+
try:
36+
c = MONAILabelClient("http://127.0.0.1:8000/")
37+
c.next_sample("random")
38+
except:
39+
pass
40+
41+
def test_create_session(self):
42+
try:
43+
c = MONAILabelClient("http://127.0.0.1:8000/")
44+
c.create_session("xyz", {"h": 1})
45+
except:
46+
pass
47+
48+
def test_get_session(self):
49+
try:
50+
c = MONAILabelClient("http://127.0.0.1:8000/")
51+
c.get_session("xyzd")
52+
except:
53+
pass
54+
55+
def test_remove_session(self):
56+
try:
57+
c = MONAILabelClient("http://127.0.0.1:8000/")
58+
c.remove_session("xyzd")
59+
except:
60+
pass
61+
62+
def test_upload_image(self):
63+
try:
64+
c = MONAILabelClient("http://127.0.0.1:8000/")
65+
c.upload_image("xyzd", "1234")
66+
except:
67+
pass
68+
69+
def test_save_label(self):
70+
try:
71+
c = MONAILabelClient("http://127.0.0.1:8000/")
72+
c.save_label("xyzd", "1234")
73+
except:
74+
pass
75+
76+
def test_infer(self):
77+
try:
78+
c = MONAILabelClient("http://127.0.0.1:8000/")
79+
c.infer("model", "1234", {"xyz": 1})
80+
except:
81+
pass
82+
83+
def test_wsi_infer(self):
84+
try:
85+
c = MONAILabelClient("http://127.0.0.1:8000/")
86+
c.wsi_infer("model", "1234", {"xyz": 1})
87+
except:
88+
pass
89+
90+
def test_train_start(self):
91+
try:
92+
c = MONAILabelClient("http://127.0.0.1:8000/")
93+
c.train_start("model", {"xyz": 1})
94+
except:
95+
pass
96+
97+
def test_train_stop(self):
98+
try:
99+
c = MONAILabelClient("http://127.0.0.1:8000/")
100+
c.train_stop()
101+
except:
102+
pass
103+
104+
def test_train_status(self):
105+
try:
106+
c = MONAILabelClient("http://127.0.0.1:8000/")
107+
c.train_status()
108+
except:
109+
pass
110+
111+
112+
if __name__ == "__main__":
113+
unittest.main()

tests/unit/endpoints/test_batch_infer.py

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

1818

1919
class EndPointScoring(BasicEndpointTestSuite):
20-
def test_batch_infer(self):
20+
def test_001_batch_infer(self):
2121
if not torch.cuda.is_available():
2222
return
2323

@@ -26,12 +26,22 @@ def test_batch_infer(self):
2626
response = self.client.post(f"/batch/infer/{model}?images={images}&run_sync=true")
2727
assert response.status_code == 200
2828

29-
def test_status(self):
29+
def test_002_status(self):
3030
self.client.get("/batch/infer/")
3131

32-
def test_stop(self):
32+
def test_003_stop(self):
3333
self.client.delete("/batch/infer/")
3434

35+
def test_004_batch_infer(self):
36+
if not torch.cuda.is_available():
37+
return
38+
39+
model = "deepedit_seg"
40+
images = "all"
41+
params = {"max_workers": 2}
42+
response = self.client.post(f"/batch/infer/{model}?images={images}&run_sync=true", json=params)
43+
assert response.status_code == 200
44+
3545

3646
if __name__ == "__main__":
3747
unittest.main()

tests/unit/endpoints/test_infer_wsi.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919

2020
class EndPointWSIInfer(BasicEndpointV4TestSuite):
21-
def test_segmentation(self):
21+
def test_segmentation_asap(self):
2222
if not torch.cuda.is_available():
2323
return
2424

@@ -30,6 +30,30 @@ def test_segmentation(self):
3030
assert response.status_code == 200
3131
time.sleep(1)
3232

33+
def test_segmentation_dsa(self):
34+
if not torch.cuda.is_available():
35+
return
36+
37+
model = "segmentation_nuclei"
38+
image = "JP2K-33003-1"
39+
wsi = {"level": 0, "size": [2000, 2000], "pos": [2000, 4000], "max_workers": 1}
40+
41+
response = self.client.post(f"/infer/wsi/{model}?image={image}&output=dsa", json=wsi)
42+
assert response.status_code == 200
43+
time.sleep(1)
44+
45+
def test_segmentation_json(self):
46+
if not torch.cuda.is_available():
47+
return
48+
49+
model = "segmentation_nuclei"
50+
image = "JP2K-33003-1"
51+
wsi = {"level": 0, "size": [2000, 2000], "pos": [2000, 4000]}
52+
53+
response = self.client.post(f"/infer/wsi/{model}?image={image}&output=json", json=wsi)
54+
assert response.status_code == 200
55+
time.sleep(1)
56+
3357

3458
if __name__ == "__main__":
3559
unittest.main()

tests/unit/endpoints/test_train.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
from .context import BasicBundleTestSuite, BasicDetectionBundleTestSuite, BasicEndpointV2TestSuite
1818

1919

20-
@unittest.skip
2120
class TestEndPointTrain(BasicEndpointV2TestSuite):
2221
def test_001_train(self):
2322
if not torch.cuda.is_available():
@@ -84,7 +83,6 @@ def test_005_stop(self):
8483
self.client.delete("/train/")
8584

8685

87-
@unittest.skip
8886
class TestBundleTrainTask(BasicBundleTestSuite):
8987
def test_spleen_bundle_train(self):
9088
if not torch.cuda.is_available():

tests/unit/interfaces/test_app.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
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 unittest
14+
15+
import torch
16+
from parameterized import parameterized
17+
18+
from monailabel.config import settings
19+
from monailabel.interfaces.app import MONAILabelApp
20+
from monailabel.interfaces.tasks.batch_infer import BatchInferImageType
21+
from monailabel.interfaces.utils.app import app_instance
22+
23+
24+
class TestApp(unittest.TestCase):
25+
app = None
26+
base_dir = os.path.realpath(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))))
27+
data_dir = os.path.join(base_dir, "tests", "data")
28+
29+
app_dir = os.path.join(base_dir, "sample-apps", "radiology")
30+
studies = os.path.join(data_dir, "dataset", "local", "spleen")
31+
32+
@classmethod
33+
def setUpClass(cls) -> None:
34+
settings.MONAI_LABEL_APP_DIR = cls.app_dir
35+
settings.MONAI_LABEL_STUDIES = cls.studies
36+
settings.MONAI_LABEL_DATASTORE_AUTO_RELOAD = False
37+
38+
if torch.cuda.is_available():
39+
cls.app: MONAILabelApp = app_instance(
40+
app_dir=cls.app_dir,
41+
studies=cls.studies,
42+
conf={
43+
"preload": "true",
44+
"models": "segmentation_spleen",
45+
},
46+
)
47+
48+
@classmethod
49+
def tearDownClass(cls) -> None:
50+
pass
51+
52+
def test_app_init(self):
53+
if not self.app:
54+
return
55+
self.app.on_init_complete()
56+
57+
def test_cleanup_sessions(self):
58+
if not self.app:
59+
return
60+
self.app.cleanup_sessions()
61+
62+
def test_async_batch_infer(self):
63+
if not self.app:
64+
return
65+
66+
model = "segmentation_spleen"
67+
params = {"max_workers": 2}
68+
69+
self.app.server_mode(True)
70+
self.app.async_batch_infer(model, BatchInferImageType.IMAGES_ALL, params)
71+
72+
try:
73+
self.app.server_mode(False)
74+
self.app.async_batch_infer(model, BatchInferImageType.IMAGES_LABELED)
75+
except:
76+
pass
77+
78+
def test_async_train(self):
79+
if not self.app:
80+
return
81+
82+
model = "segmentation_spleen"
83+
params = {"max_epochs": 1}
84+
85+
self.app.server_mode(True)
86+
self.app.async_training(model, params)
87+
88+
try:
89+
self.app.server_mode(False)
90+
self.app.async_training(model, params)
91+
except:
92+
pass
93+
94+
@parameterized.expand(["xnat", "dsa", ""])
95+
def test_init_datastores(self, r):
96+
if not self.app:
97+
return
98+
99+
try:
100+
settings.MONAI_LABEL_DATASTORE = r
101+
self.app.init_remote_datastore()
102+
except:
103+
pass
104+
finally:
105+
settings.MONAI_LABEL_DATASTORE = ""
106+
107+
108+
if __name__ == "__main__":
109+
unittest.main()

0 commit comments

Comments
 (0)