223 lines
8.8 KiB
Python
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()
|