Files
navigate/test/model/features/test_restful_features.py
2025-12-04 16:07:30 +08:00

223 lines
8.8 KiB
Python

# Copyright (c) 2021-2025 The University of Texas Southwestern Medical Center.
# All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted for academic and research use only
# (subject to the limitations in the disclaimer below)
# provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the copyright holders nor the names of its
# contributors may be used to endorse or promote products derived from this
# software without specific prior written permission.
# NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY
# THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# Standard Library Import
import base64
import unittest
import json
import logging
from unittest.mock import patch, Mock, MagicMock
from io import BytesIO
# Third Party Imports
import numpy as np
# Local Imports
from navigate.model.features.restful_features import (
prepare_service,
IlastikSegmentation,
)
class TestPrepareService(unittest.TestCase):
def setUp(self):
self.service_url = "http://example.com/ilastik"
self.project_file = "path/to/project.ilp"
self.expected_url = f"{self.service_url}/load?project={self.project_file}"
logging.basicConfig(level=logging.INFO)
self.logger = logging.getLogger(
"mymodule"
) # Replace with the actual logger name used
@patch("navigate.model.features.restful_features.requests.get")
def test_prepare_service_success(self, mock_get):
expected_response = {"status": "success", "data": "segmentation data"}
mock_response = Mock()
mock_response.status_code = 200
mock_response.content = json.dumps(expected_response)
mock_get.return_value = mock_response
response = prepare_service(self.service_url, project_file=self.project_file)
self.assertEqual(response, expected_response)
mock_get.assert_called_once_with(self.expected_url)
@patch("navigate.model.features.restful_features.requests.get")
def test_prepare_service_failure(self, mock_get):
mock_response = Mock()
mock_response.status_code = 404
mock_response.content = "Error"
mock_get.return_value = mock_response
response = prepare_service(self.service_url, project_file=self.project_file)
self.assertIsNone(response)
mock_get.assert_called_once_with(self.expected_url)
def test_prepare_service_non_ilastik_url(self):
non_ilastik_url = "http://example.com/not_ilastik"
response = prepare_service(non_ilastik_url, project_file=self.project_file)
self.assertIsNone(response)
class TestIlastikSegmentation(unittest.TestCase):
def setUp(self):
# Set up a mock model object
shape = (2048, 2048)
self.mock_model = Mock()
self.mock_model.configuration = {
"rest_api_config": {"Ilastik": {"url": "http://example.com/ilastik"}},
"experiment": {
"MicroscopeState": {"microscope_name": "Nanoscale", "zoom": "1.0"},
"CameraParameters": {
"Nanoscale": {"x_pixels": "2048", "y_pixels": "2048"},
},
"StageParameters": {
"x": "100",
"y": "100",
"z": "50",
"theta": "0",
"f": "1.0",
},
},
"configuration": {
"microscopes": {
"Nanoscale": {"zoom": {"pixel_size": {"N/A": "1.0", "1.0": "1.0"}}}
}
},
}
self.mock_model.data_buffer = {
0: np.random.randint(0, 65536, size=shape, dtype=np.uint16),
1: np.random.randint(0, 65536, size=shape, dtype=np.uint16),
}
self.mock_model.img_height = shape[0]
self.mock_model.img_width = shape[1]
self.mock_model.display_ilastik_segmentation = True
self.mock_model.mark_ilastik_position = False
self.mock_model.event_queue = MagicMock()
self.mock_model.active_microscope_name = "Nanoscale"
self.ilastik_segmentation = IlastikSegmentation(self.mock_model)
@patch("requests.post")
def test_data_func_success(self, mock_post):
frame_ids = [0, 1]
expected_json_data = {
"dtype": "uint16",
"shape": (self.mock_model.img_height, self.mock_model.img_width),
"image": [
base64.b64encode(self.mock_model.data_buffer[0]).decode("utf-8"),
base64.b64encode(self.mock_model.data_buffer[1]).decode("utf-8"),
],
}
# Create a valid numpy array to simulate the response
array_data = np.array([np.zeros((2048, 2048, 1), dtype=np.uint16)])
buffer = BytesIO()
np.savez(buffer, *array_data)
buffer.seek(0)
mock_response = Mock()
mock_response.status_code = 200
mock_response.raw.read.return_value = buffer.read()
mock_post.return_value = mock_response
self.ilastik_segmentation.data_func(frame_ids)
mock_post.assert_called_once_with(
"http://example.com/ilastik/segmentation",
json=expected_json_data,
stream=True,
)
self.mock_model.event_queue.put.assert_called()
# Test with self.model.mark_ilastik_position set to True
self.mock_model.mark_ilastik_position = True
self.mock_model.event_queue.reset_mock()
self.mock_model.ilastik_target_labels = range(1)
self.ilastik_segmentation.update_setting()
self.ilastik_segmentation.data_func(frame_ids)
assert self.mock_model.event_queue.put.call_count == 2
# self.mock_model.event_queue.put.assert_called_with(("multiposition"))
called_args, _ = self.mock_model.event_queue.put.call_args
assert "multiposition" in called_args[0]
@patch("requests.post")
def test_data_func_failure(self, mock_post):
frame_ids = [0, 1]
mock_response = Mock()
mock_response.status_code = 404
mock_response.content = "Error"
mock_post.return_value = mock_response
with patch("builtins.print") as mocked_print:
self.ilastik_segmentation.data_func(frame_ids)
mocked_print.assert_called_once_with("There is something wrong!")
def test_update_setting(self):
self.ilastik_segmentation.update_setting()
self.assertEqual(self.ilastik_segmentation.resolution, "Nanoscale")
self.assertEqual(self.ilastik_segmentation.zoom, "1.0")
self.assertEqual(self.ilastik_segmentation.pieces_num, 1)
self.assertEqual(self.ilastik_segmentation.pieces_size, 2048)
self.assertEqual(self.ilastik_segmentation.posistion_step_size, 2048)
self.assertEqual(self.ilastik_segmentation.x_start, -924)
self.assertEqual(self.ilastik_segmentation.y_start, -924)
def test_init_func_update_settings(self):
with patch.object(
self.ilastik_segmentation, "update_setting"
) as mock_update_setting:
self.ilastik_segmentation.resolution = "DifferentResolution"
self.ilastik_segmentation.zoom = "DifferentZoom"
self.ilastik_segmentation.init_func()
mock_update_setting.assert_called_once()
def test_mark_position(self):
mask = np.zeros((2048, 2048, 1), dtype=np.uint16)
mask[0:1024, 0:1024, 0] = 1 # Mock some segmentation data
self.mock_model.ilastik_target_labels = [1]
self.ilastik_segmentation.update_setting()
self.ilastik_segmentation.mark_position(mask)
self.mock_model.event_queue.put.assert_called()
if __name__ == "__main__":
unittest.main()