Skip to content

Commit 14af024

Browse files
Merge pull request #938 from TheDeanLab/2024-07-11
Restful unit tests.
2 parents b496d25 + d25adc9 commit 14af024

File tree

2 files changed

+227
-3
lines changed

2 files changed

+227
-3
lines changed

src/navigate/model/features/restful_features.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2021-2022 The University of Texas Southwestern Medical Center.
1+
# Copyright (c) 2021-2024 The University of Texas Southwestern Medical Center.
22
# All rights reserved.
33

44
# Redistribution and use in source and binary forms, with or without
@@ -30,15 +30,20 @@
3030
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
3131
# POSSIBILITY OF SUCH DAMAGE.
3232
#
33+
34+
# Standard Library Imports
3335
import base64
3436
import requests
3537
import numpy
3638
import json
3739
from io import BytesIO
3840
from math import ceil
39-
4041
import logging
4142

43+
# Third Party Imports
44+
45+
# Local Imports
46+
4247
# Logger Setup
4348
p = __name__.split(".")[1]
4449
logger = logging.getLogger(p)
@@ -178,7 +183,7 @@ def update_setting(self):
178183
)
179184
except (KeyError, AttributeError):
180185
return
181-
186+
182187
self.resolution = self.model.configuration["experiment"]["MicroscopeState"][
183188
"microscope_name"
184189
]
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
# Copyright (c) 2021-2024 The University of Texas Southwestern Medical Center.
2+
# All rights reserved.
3+
4+
# Redistribution and use in source and binary forms, with or without
5+
# modification, are permitted for academic and research use only
6+
# (subject to the limitations in the disclaimer below)
7+
# provided that the following conditions are met:
8+
9+
# * Redistributions of source code must retain the above copyright notice,
10+
# this list of conditions and the following disclaimer.
11+
12+
# * Redistributions in binary form must reproduce the above copyright
13+
# notice, this list of conditions and the following disclaimer in the
14+
# documentation and/or other materials provided with the distribution.
15+
16+
# * Neither the name of the copyright holders nor the names of its
17+
# contributors may be used to endorse or promote products derived from this
18+
# software without specific prior written permission.
19+
20+
# NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY
21+
# THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
22+
# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
24+
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
25+
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
26+
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
27+
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
28+
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
29+
# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31+
# POSSIBILITY OF SUCH DAMAGE.
32+
33+
34+
# Standard Library Import
35+
import base64
36+
import unittest
37+
import json
38+
import logging
39+
from unittest.mock import patch, Mock, MagicMock
40+
from io import BytesIO
41+
42+
# Third Party Imports
43+
import numpy as np
44+
45+
# Local Imports
46+
from navigate.model.features.restful_features import (
47+
prepare_service,
48+
IlastikSegmentation,
49+
)
50+
51+
52+
class TestPrepareService(unittest.TestCase):
53+
def setUp(self):
54+
self.service_url = "http://example.com/ilastik"
55+
self.project_file = "path/to/project.ilp"
56+
self.expected_url = f"{self.service_url}/load?project={self.project_file}"
57+
logging.basicConfig(level=logging.INFO)
58+
self.logger = logging.getLogger(
59+
"mymodule"
60+
) # Replace with the actual logger name used
61+
62+
@patch("navigate.model.features.restful_features.requests.get")
63+
def test_prepare_service_success(self, mock_get):
64+
expected_response = {"status": "success", "data": "segmentation data"}
65+
mock_response = Mock()
66+
mock_response.status_code = 200
67+
mock_response.content = json.dumps(expected_response)
68+
mock_get.return_value = mock_response
69+
70+
response = prepare_service(self.service_url, project_file=self.project_file)
71+
72+
self.assertEqual(response, expected_response)
73+
mock_get.assert_called_once_with(self.expected_url)
74+
75+
@patch("navigate.model.features.restful_features.requests.get")
76+
def test_prepare_service_failure(self, mock_get):
77+
mock_response = Mock()
78+
mock_response.status_code = 404
79+
mock_response.content = "Error"
80+
mock_get.return_value = mock_response
81+
82+
response = prepare_service(self.service_url, project_file=self.project_file)
83+
84+
self.assertIsNone(response)
85+
mock_get.assert_called_once_with(self.expected_url)
86+
87+
def test_prepare_service_non_ilastik_url(self):
88+
non_ilastik_url = "http://example.com/not_ilastik"
89+
response = prepare_service(non_ilastik_url, project_file=self.project_file)
90+
91+
self.assertIsNone(response)
92+
93+
94+
class TestIlastikSegmentation(unittest.TestCase):
95+
def setUp(self):
96+
# Set up a mock model object
97+
shape = (2048, 2048)
98+
self.mock_model = Mock()
99+
self.mock_model.configuration = {
100+
"rest_api_config": {"Ilastik": {"url": "http://example.com/ilastik"}},
101+
"experiment": {
102+
"MicroscopeState": {"microscope_name": "Nanoscale", "zoom": "1.0"},
103+
"CameraParameters": {"x_pixels": "2048", "y_pixels": "2048"},
104+
"StageParameters": {
105+
"x": "100",
106+
"y": "100",
107+
"z": "50",
108+
"theta": "0",
109+
"f": "1.0",
110+
},
111+
},
112+
"configuration": {
113+
"microscopes": {
114+
"Nanoscale": {"zoom": {"pixel_size": {"N/A": "1.0", "1.0": "1.0"}}}
115+
}
116+
},
117+
}
118+
self.mock_model.data_buffer = {
119+
0: np.random.randint(0, 65536, size=shape, dtype=np.uint16),
120+
1: np.random.randint(0, 65536, size=shape, dtype=np.uint16),
121+
}
122+
123+
self.mock_model.img_height = shape[0]
124+
self.mock_model.img_width = shape[1]
125+
self.mock_model.display_ilastik_segmentation = True
126+
self.mock_model.mark_ilastik_position = False
127+
self.mock_model.event_queue = MagicMock()
128+
129+
self.ilastik_segmentation = IlastikSegmentation(self.mock_model)
130+
131+
@patch("requests.post")
132+
def test_data_func_success(self, mock_post):
133+
frame_ids = [0, 1]
134+
expected_json_data = {
135+
"dtype": "uint16",
136+
"shape": (self.mock_model.img_height, self.mock_model.img_width),
137+
"image": [
138+
base64.b64encode(self.mock_model.data_buffer[0]).decode("utf-8"),
139+
base64.b64encode(self.mock_model.data_buffer[1]).decode("utf-8"),
140+
],
141+
}
142+
143+
# Create a valid numpy array to simulate the response
144+
array_data = np.array([np.zeros((2048, 2048, 1), dtype=np.uint16)])
145+
buffer = BytesIO()
146+
np.savez(buffer, *array_data)
147+
buffer.seek(0)
148+
149+
mock_response = Mock()
150+
mock_response.status_code = 200
151+
mock_response.raw.read.return_value = buffer.read()
152+
mock_post.return_value = mock_response
153+
154+
self.ilastik_segmentation.data_func(frame_ids)
155+
156+
mock_post.assert_called_once_with(
157+
"http://example.com/ilastik/segmentation",
158+
json=expected_json_data,
159+
stream=True,
160+
)
161+
self.mock_model.event_queue.put.assert_called()
162+
163+
# Test with self.model.mark_ilastik_position set to True
164+
self.mock_model.mark_ilastik_position = True
165+
self.mock_model.event_queue.reset_mock()
166+
167+
self.mock_model.ilastik_target_labels = range(1)
168+
self.ilastik_segmentation.update_setting()
169+
self.ilastik_segmentation.data_func(frame_ids)
170+
assert self.mock_model.event_queue.put.call_count == 2
171+
# self.mock_model.event_queue.put.assert_called_with(("multiposition"))
172+
called_args, _ = self.mock_model.event_queue.put.call_args
173+
assert "multiposition" in called_args[0]
174+
175+
@patch("requests.post")
176+
def test_data_func_failure(self, mock_post):
177+
frame_ids = [0, 1]
178+
mock_response = Mock()
179+
mock_response.status_code = 404
180+
mock_response.content = "Error"
181+
mock_post.return_value = mock_response
182+
183+
with patch("builtins.print") as mocked_print:
184+
self.ilastik_segmentation.data_func(frame_ids)
185+
mocked_print.assert_called_once_with("There is something wrong!")
186+
187+
def test_update_setting(self):
188+
self.ilastik_segmentation.update_setting()
189+
190+
self.assertEqual(self.ilastik_segmentation.resolution, "Nanoscale")
191+
self.assertEqual(self.ilastik_segmentation.zoom, "1.0")
192+
self.assertEqual(self.ilastik_segmentation.pieces_num, 1)
193+
self.assertEqual(self.ilastik_segmentation.pieces_size, 2048)
194+
self.assertEqual(self.ilastik_segmentation.posistion_step_size, 2048)
195+
self.assertEqual(self.ilastik_segmentation.x_start, -924)
196+
self.assertEqual(self.ilastik_segmentation.y_start, -924)
197+
198+
def test_init_func_update_settings(self):
199+
with patch.object(
200+
self.ilastik_segmentation, "update_setting"
201+
) as mock_update_setting:
202+
self.ilastik_segmentation.resolution = "DifferentResolution"
203+
self.ilastik_segmentation.zoom = "DifferentZoom"
204+
self.ilastik_segmentation.init_func()
205+
mock_update_setting.assert_called_once()
206+
207+
def test_mark_position(self):
208+
mask = np.zeros((2048, 2048, 1), dtype=np.uint16)
209+
mask[0:1024, 0:1024, 0] = 1 # Mock some segmentation data
210+
self.mock_model.ilastik_target_labels = [1]
211+
212+
self.ilastik_segmentation.update_setting()
213+
self.ilastik_segmentation.mark_position(mask)
214+
215+
self.mock_model.event_queue.put.assert_called()
216+
217+
218+
if __name__ == "__main__":
219+
unittest.main()

0 commit comments

Comments
 (0)