feat: init

This commit is contained in:
2025-12-04 16:07:30 +08:00
commit 262583a57f
681 changed files with 117578 additions and 0 deletions

0
test/tools/__init__.py Normal file
View File

View File

@@ -0,0 +1,109 @@
# 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 Imports
# Third Party Imports
# Local Imports
from navigate.tools.common_dict_tools import update_stage_dict
import unittest
from unittest.mock import MagicMock
def test_update_nested_dict():
from navigate.tools.common_dict_tools import update_nested_dict
test_dict = {"one": 2, "three": {4: {"metastasis": False}}}
test_dict_updated = update_nested_dict(
test_dict, lambda k, v: k == "metastasis", lambda x: True
)
assert test_dict_updated == {"one": 2, "three": {4: {"metastasis": True}}}
def create_mock_target():
"""Create a mock target object (Model or Controller) for testing."""
target = MagicMock()
target.configuration = {
"experiment": {
"MicroscopeState": {"channels": None},
"GalvoParameters": {},
"CameraParameters": {},
},
"etl_constants": {"ETLConstants": {"low": {}, "high": {}}},
}
return target
def create_mock_stage_target():
"""Create a mock target object (Model or Controller) for testing."""
target = MagicMock()
target.configuration = {
"experiment": {"StageParameters": {"x": None, "y": None, "z": None}}
}
return target
class UpdateStageDictTestCase(unittest.TestCase):
def test_update_single_axis(self):
target = create_mock_stage_target()
pos_dict = {"x_position": 10.0}
update_stage_dict(target, pos_dict)
self.assertEqual(
target.configuration["experiment"]["StageParameters"]["x"], 10.0
)
def test_update_multiple_axes(self):
target = create_mock_stage_target()
pos_dict = {"y_position": 5.0, "z_position": 2.5}
update_stage_dict(target, pos_dict)
self.assertEqual(
target.configuration["experiment"]["StageParameters"]["y"], 5.0
)
self.assertEqual(
target.configuration["experiment"]["StageParameters"]["z"], 2.5
)
def test_update_invalid_axis(self):
target = create_mock_stage_target()
pos_dict = {"invalid_axis": 3.14}
update_stage_dict(target, pos_dict)
self.assertNotIn(
"invalid_axis", target.configuration["experiment"]["StageParameters"]
)
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,165 @@
# 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 imports
import os
import unittest
from multiprocessing import Manager
from multiprocessing.managers import DictProxy, ListProxy
# Third party imports
# Local application imports
import navigate.tools.common_functions as common_functions
def func1():
print("Function 1 called")
def func2():
print("Function 2 called")
def func3():
print("Function 3 called")
class CombineFuncsTestCase(unittest.TestCase):
def test_combine_funcs(self):
combined_func = common_functions.combine_funcs(func1, func2, func3)
# Redirect stdout to capture print statements
import sys
from io import StringIO
original_stdout = sys.stdout
sys.stdout = StringIO()
combined_func()
# Get the printed output
output = sys.stdout.getvalue()
# Reset stdout
sys.stdout = original_stdout
# Check if the functions were called and printed in the expected order
expected_output = "Function 1 called\nFunction 2 called\nFunction 3 called\n"
self.assertEqual(output, expected_output)
class BuildRefNameTestCase(unittest.TestCase):
def test_build_ref_name(self):
separator = "_"
args = ["John", "Doe", 30]
ref_name = common_functions.build_ref_name(separator, *args)
expected_ref_name = "John_Doe_30"
self.assertEqual(ref_name, expected_ref_name)
def test_build_ref_name_with_custom_separator(self):
separator = "-"
args = ["Jane", "Smith", 25]
ref_name = common_functions.build_ref_name(separator, *args)
expected_ref_name = "Jane-Smith-25"
self.assertEqual(ref_name, expected_ref_name)
def test_build_ref_name_with_empty_arguments(self):
separator = "_"
args = []
ref_name = common_functions.build_ref_name(separator, *args)
self.assertEqual(ref_name, "")
class CopyProxyObjectTestCase(unittest.TestCase):
def test_copy_proxy_object_with_dict_proxy(self):
manager = Manager()
original_dict = manager.dict({"key1": "value1", "key2": ["value2"]})
copied_dict = common_functions.copy_proxy_object(original_dict)
self.assertIsNot(original_dict, copied_dict)
assert isinstance(copied_dict, dict)
assert isinstance(original_dict, DictProxy)
assert copied_dict["key1"] == "value1"
assert copied_dict["key2"] == ["value2"]
def test_copy_proxy_object_with_list_proxy(self):
manager = Manager()
original_list = manager.list(["item1", {"key": "value"}])
copied_list = common_functions.copy_proxy_object(original_list)
self.assertIsNot(original_list, copied_list)
assert isinstance(copied_list, list)
assert isinstance(original_list, ListProxy)
assert copied_list[0] == "item1"
assert copied_list[1] == {"key": "value"}
def test_copy_proxy_object_with_non_proxy_object(self):
non_proxy_object = {"key": "value"}
copied_object = common_functions.copy_proxy_object(non_proxy_object)
self.assertIs(non_proxy_object, copied_object)
assert copied_object["key"] == "value"
assert isinstance(copied_object, dict)
class TestLoadModuleFromFile(unittest.TestCase):
def setUp(self):
dummy_module = """
class DummyModule:
def __init__(self):
self.dummy_variable = "hello"
def dummy_function(self):
print(self.dummy_variable)
"""
with open("dummy_module.py", "w") as f:
f.write(dummy_module)
def tearDown(self):
os.remove("dummy_module.py")
def test_load_module(self):
module = common_functions.load_module_from_file(
"DummyModule", "./dummy_module.py"
)
self.assertIsNotNone(module)
self.assertTrue(hasattr(module, "DummyModule"))
self.assertTrue(hasattr(module.DummyModule, "dummy_function"))
def test_invalid_module_file(self):
with self.assertRaises(FileNotFoundError):
common_functions.load_module_from_file(
"nonexistent_module", "./dummy_module2.py"
)
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,57 @@
import unittest
from time import sleep
from src.navigate.tools.decorators import function_timer, FeatureList, AcquisitionMode, log_initialization
class TestFunctionTimer(unittest.TestCase):
@function_timer
def sample_function(self, duration):
sleep(duration)
return "Completed"
def test_function_timer(self):
duration = 1
result = self.sample_function(duration)
self.assertEqual(result, "Completed")
def test_function_timer_zero_duration(self):
duration = 0
result = self.sample_function(duration)
self.assertEqual(result, "Completed")
def sample_feature():
return "Feature Executed"
class TestFeatureList(unittest.TestCase):
def setUp(self):
self.feature_list = FeatureList(sample_feature)
def test_feature_list_name(self):
self.assertEqual(self.feature_list.feature_list_name, "Sample Feature")
def test_feature_list_execution(self):
result = self.feature_list()
self.assertEqual(result, "Feature Executed")
class SampleClass:
def __init__(self, value):
self.value = value
class TestAcquisitionMode(unittest.TestCase):
def setUp(self):
self.acquisition_mode = AcquisitionMode(SampleClass)
def test_acquisition_mode_initialization(self):
instance = self.acquisition_mode(10)
self.assertIsInstance(instance, SampleClass)
self.assertEqual(instance.value, 10)
def test_acquisition_mode_flag(self):
self.acquisition_mode.__is_acquisition_mode = True
self.assertTrue(self.acquisition_mode.__is_acquisition_mode)
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,258 @@
# 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 imports
import unittest
import os
from datetime import datetime
import json
# Third party imports
# Local application imports
from navigate.tools.file_functions import (
create_save_path,
save_yaml_file,
delete_folder,
load_yaml_file,
)
class CreateSavePathTestCase(unittest.TestCase):
def setUp(self) -> None:
self.save_root = "test_dir"
os.mkdir(self.save_root)
self.date_string = str(datetime.now().date())
def tearDown(self) -> None:
delete_folder("test_dir")
def test_existing_root_directory_no_existing_cell_directories(self):
"""Test 1: Testing with existing root directory and no existing cell
directories."""
saving_settings = {
"root_directory": self.save_root,
"user": "John Doe",
"tissue": "Liver",
"celltype": "Hepatocyte",
"label": "Sample1",
"prefix": "Cell_",
}
expected_save_directory = os.path.join(
self.save_root,
"John-Doe",
"Liver",
"Hepatocyte",
"Sample1",
self.date_string,
"Cell_001",
)
save_directory = create_save_path(saving_settings)
# Assert that the save directory is correct
self.assertEqual(save_directory, expected_save_directory)
# Assert that the save directory and cell directory are created
self.assertTrue(os.path.exists(save_directory))
# Delete the created directory
self.tearDown()
def test_existing_root_directory_existing_cell_directories(self):
"""Test 2: Testing with existing root directory and existing cell
sub-directory."""
os.makedirs(
os.path.join(
self.save_root,
"John-Doe",
"Liver",
"Hepatocyte",
"Sample1",
self.date_string,
"Cell_001",
)
)
saving_settings = {
"root_directory": self.save_root,
"user": "John Doe",
"tissue": "Liver",
"celltype": "Hepatocyte",
"label": "Sample1",
"prefix": "Cell_",
}
save_directory = create_save_path(saving_settings)
# Assert that the save directory is correct
self.assertEqual(
save_directory,
os.path.join(
self.save_root,
"John-Doe",
"Liver",
"Hepatocyte",
"Sample1",
self.date_string,
"Cell_002",
),
)
# Assert that the save directory and cell directory are created
self.assertTrue(os.path.exists(save_directory))
# Delete the created directory
self.tearDown()
def test_spaces_in_strings(self):
saving_settings = {
"root_directory": self.save_root,
"user": "John Doe",
"tissue": "Liver Tissue",
"celltype": "Hepatocyte Cell Type",
"label": "Sample 1",
"prefix": "Cell_",
}
save_directory = create_save_path(saving_settings)
expected_save_directory = os.path.join(
self.save_root,
"John-Doe",
"Liver-Tissue",
"Hepatocyte-Cell-Type",
"Sample-1",
self.date_string,
"Cell_001",
)
# Assert that the save directory is correct
self.assertEqual(save_directory, expected_save_directory)
# Assert that the save directory and cell directory are created
self.assertTrue(os.path.exists(save_directory))
# Delete the created directory
self.tearDown()
class SaveYAMLFileTestCase(unittest.TestCase):
def setUp(self) -> None:
os.mkdir("test_dir")
self.save_root = "test_dir"
def tearDown(self) -> None:
delete_folder("test_dir")
def test_save_yaml_file_success(self):
content_dict = {"name": "John Doe", "age": 30, "location": "New York"}
result = save_yaml_file(self.save_root, content_dict)
# Assert that the file was saved successfully
self.assertTrue(result)
# Assert that the file exists
file_path = os.path.join(self.save_root, "experiment.yml")
self.assertTrue(os.path.exists(file_path))
# Assert that the file content is correct
with open(file_path, "r") as f:
saved_content = json.load(f)
self.assertEqual(saved_content, content_dict)
def test_save_yaml_file_failure(self):
# Test with non-existing directory
content_dict = {"name": "John Doe", "age": 30, "location": "New York"}
result = save_yaml_file(
os.path.join(self.save_root, "does-not-exist"), content_dict
)
# Assert that the file save failed
self.assertFalse(
result,
"File save should have failed. Function does not "
"create the path if it does not exist.",
)
# Assert that the file does not exist
file_path = os.path.join(self.save_root, "experiment.yml")
self.assertFalse(os.path.exists(file_path))
class TestDeleteFolder(unittest.TestCase):
def setUp(self) -> None:
os.mkdir("test_dir")
self.save_root = "test_dir"
def test_delete_folder(self):
delete_folder(self.save_root)
self.assertFalse(os.path.exists(self.save_root))
class TestLoadYamlFile(unittest.TestCase):
def setUp(self) -> None:
os.mkdir("test_dir")
self.save_root = "test_dir"
def tearDown(self) -> None:
delete_folder("test_dir")
def test_load_existing_yaml_file(self):
content_dict = {"name": "John Doe", "age": 30, "location": "New York"}
result = save_yaml_file(self.save_root, content_dict, "test.yml")
file_path = os.path.join(self.save_root, "test.yml")
result = load_yaml_file(file_path)
self.assertIsNotNone(result)
self.assertIsInstance(result, dict)
assert result == content_dict
def test_load_nonexistent_yaml_file(self):
file_path = os.path.join(self.save_root, "test.yml")
result = load_yaml_file(file_path)
self.assertIsNone(result)
def test_load_invalid_yaml_file(self):
file_path = os.path.join(self.save_root, "test.yml")
with open(file_path, "w") as f:
f.write(
"""
{"name": "John Doe", "age": 30, "locati
"""
)
result = load_yaml_file(file_path)
self.assertIsNone(result)
if __name__ == "__main__":
unittest.main()

108
test/tools/test_image.py Normal file
View File

@@ -0,0 +1,108 @@
# 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 Imports
import unittest
# Third-Party Imports
import numpy as np
from PIL import Image, ImageFont
# import pytest
# Local Imports
from navigate.tools.image import text_array, create_arrow_image
class TextArrayTestCase(unittest.TestCase):
def test_text_array(self):
text = "55"
offset = (0, 0)
result = text_array(text, offset)
# Assert that the result is a numpy array
self.assertIsInstance(result, np.ndarray)
# Assert that the values in the result array are either True or False
assert result.dtype is np.dtype("bool")
def test_text_array_output_type(self):
"""Confirm output is np.ndarray object"""
text_output = text_array(text="Navigate")
assert type(text_output) == np.ndarray
def test_text_array_output_height(self):
"""Confirm that the output is approximately the correct height
Initially thought that the height should be ~= font_size, but this
turned out to be much more variable than I anticipated
"""
text = "Navigate"
text_output = text_array(text=text)
font = ImageFont.load_default() # match font size in text_array()
height = np.shape(text_output)[0]
width = np.shape(text_output)[1]
_, _, expected_width, expected_height = font.getbbox(text)
assert width == expected_width
assert height == expected_height
class TestCreateArrowImage(unittest.TestCase):
def test_create_arrow_image(self):
xys = [(50, 50), (150, 50), (200, 100)]
image = create_arrow_image(xys, direction="right")
self.assertIsInstance(image, Image.Image)
self.assertEqual(image.width, 300)
self.assertEqual(image.height, 200)
xys = [(50, 50), (150, 50), (200, 100)]
image = create_arrow_image(xys, 400, 300, direction="left")
self.assertIsInstance(image, Image.Image)
self.assertEqual(image.width, 400)
self.assertEqual(image.height, 300)
image2 = create_arrow_image(xys, 500, 400, direction="up", image=image)
self.assertIsInstance(image2, Image.Image)
self.assertEqual(image2.width, 400)
self.assertEqual(image2.height, 300)
assert image == image2
image3 = create_arrow_image(xys, 500, 400, direction="down", image=image)
self.assertIsInstance(image3, Image.Image)
self.assertEqual(image3.width, 400)
self.assertEqual(image3.height, 300)
assert image == image3
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,99 @@
import unittest
import numpy as np
from navigate.tools.linear_algebra import affine_rotation, affine_shear
class TestAffineRotation(unittest.TestCase):
def test_no_rotation(self):
result = affine_rotation()
expected = np.eye(4, 4)
np.testing.assert_equal(result, expected)
def test_rotation_x(self):
result = affine_rotation(x=45).ravel()
expected = np.array(
(
1.0,
0.0,
0.0,
0.0,
0.0,
0.70710678,
-0.70710678,
0.0,
0.0,
0.70710678,
0.70710678,
0.0,
0.0,
0.0,
0.0,
1.0,
)
)
np.testing.assert_array_almost_equal(result, expected)
def test_rotation_y(self):
result = affine_rotation(y=45)
expected = np.array(
[
[0.70710678, 0.0, 0.70710678, 0.0],
[0.0, 1.0, 0.0, 0.0],
[-0.70710678, 0.0, 0.70710678, 0.0],
[0.0, 0.0, 0.0, 1.0],
]
)
np.testing.assert_array_almost_equal(result, expected)
def test_rotation_z(self):
result = affine_rotation(z=45)
expected = np.array(
[
[0.70710678, -0.70710678, 0.0, 0.0],
[0.70710678, 0.70710678, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
]
)
np.testing.assert_array_almost_equal(result, expected)
def test_rotation_xy(self):
result = affine_rotation(x=10, y=33)
import numpy as np
expected = np.array(
[
[0.83867057, 0.0, 0.54463904, 0.0],
[-0.09457558, 0.98480775, 0.14563362, 0.0],
[-0.53636474, -0.17364818, 0.82592928, 0.0],
[0.0, 0.0, 0.0, 1.0],
]
)
np.testing.assert_array_almost_equal(result, expected)
class TestAffineShear(unittest.TestCase):
def test_no_shear(self):
# Test with zero angles or voxel sizes
result = affine_shear(0, 0, 0)
expected = np.eye(4, 4)
np.testing.assert_array_almost_equal(result, expected)
def test_shear_xy(self):
result = affine_shear(1, 1, 1, dimension="XY", angle=45)
expected = np.eye(4, 4)
expected[0, 1] = 1.0
np.testing.assert_array_almost_equal(result, expected)
def test_different_pixe_sizes(self):
result = affine_shear(167, 167, 333, dimension="XY", angle=45)
expected = np.eye(4, 4)
expected[0, 1] = 0.501502
np.testing.assert_array_almost_equal(result, expected)
def test_shear_xz(self):
# Test shear in XZ dimension
result = affine_shear(167, 167, 333, dimension="XZ", angle=45)
expected = np.eye(4, 4)
expected[0, 2] = 0.501502
np.testing.assert_array_almost_equal(result, expected)

View File

@@ -0,0 +1,121 @@
import unittest
from unittest.mock import patch, MagicMock
from argparse import Namespace
from pathlib import Path
from src.navigate.tools.main_functions import evaluate_parser_input_arguments
class TestEvaluateParserInputArguments(unittest.TestCase):
@patch('src.navigate.tools.main_functions.get_configuration_paths')
def test_default_arguments(self, mock_get_configuration_paths):
# Mock the return value of get_configuration_paths
mock_get_configuration_paths.return_value = (
'default_config_path',
'default_experiment_path',
'default_waveform_constants_path',
'default_rest_api_path',
'default_waveform_templates_path',
'default_gui_configuration_path',
'default_multi_positions_path'
)
args = Namespace(
configurator=False,
config_file=None,
experiment_file=None,
waveform_constants_file=None,
rest_api_file=None,
waveform_templates_file=None,
gui_config_file=None,
multi_positions_file=None,
logging_config=None,
)
result = evaluate_parser_input_arguments(args)
expected = (
'default_config_path',
'default_experiment_path',
'default_waveform_constants_path',
'default_rest_api_path',
'default_waveform_templates_path',
None,
False,
'default_gui_configuration_path',
'default_multi_positions_path'
)
self.assertEqual(result, expected)
@patch('src.navigate.tools.main_functions.get_configuration_paths')
@patch('pathlib.Path.exists', MagicMock(return_value=True))
def test_non_default_arguments(self, mock_get_configuration_paths):
# Mock the return value of get_configuration_paths
mock_get_configuration_paths.return_value = (
'default_config_path',
'default_experiment_path',
'default_waveform_constants_path',
'default_rest_api_path',
'default_waveform_templates_path',
'default_gui_configuration_path',
'default_multi_positions_path'
)
args = Namespace(
configurator=True,
config_file=Path('/path/to/config.yml'),
experiment_file=Path('/path/to/experiment.yml'),
waveform_constants_path=Path('/path/to/waveform_constants.yml'),
waveform_constants_file=Path('/path/to/waveform_constants.yml'),
rest_api_file=Path('/path/to/rest_api.yml'),
waveform_templates_file=Path('/path/to/waveform_templates.yml'),
gui_config_file=Path('/path/to/gui_config.yml'),
multi_positions_file=Path('/path/to/multi_positions.yml'),
logging_config=Path('/path/to/logging.yml')
)
result = evaluate_parser_input_arguments(args)
expected = (
Path('/path/to/config.yml'),
Path('/path/to/experiment.yml'),
Path('/path/to/waveform_constants.yml'),
Path('/path/to/rest_api.yml'),
Path('/path/to/waveform_templates.yml'),
Path('/path/to/logging.yml'),
True,
Path('/path/to/gui_config.yml'),
Path('/path/to/multi_positions.yml')
)
self.assertEqual(result, expected)
@patch('src.navigate.tools.main_functions.get_configuration_paths')
@patch('pathlib.Path.exists', MagicMock(return_value=False))
def test_invalid_path(self, mock_get_configuration_paths):
# Mock the return value of get_configuration_paths
mock_get_configuration_paths.return_value = (
'default_config_path',
'default_experiment_path',
'default_waveform_constants_path',
'default_rest_api_path',
'default_waveform_templates_path',
'default_gui_configuration_path',
'default_multi_positions_path'
)
args = Namespace(
configurator=True,
config_file=Path('/invalid/path/to/config.yml'),
experiment_file=None,
waveform_constants_file=None,
rest_api_file=None,
waveform_templates_file=None,
gui_config_file=None,
multi_positions_file=None,
logging_config=None
)
with self.assertRaises(AssertionError):
evaluate_parser_input_arguments(args)
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,248 @@
# 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 imports
import unittest
import pytest
import tkinter as tk
from tkinter import ttk
from math import ceil
# Third party imports
import numpy as np
# Local application imports
from navigate.tools.multipos_table_tools import (
update_table,
)
from navigate.view.main_window_content.multiposition_tab import MultiPositionTable
@pytest.mark.parametrize("pair", zip([5.6, -3.8, 0], [1, -1, 1]))
def test_sign(pair):
from navigate.tools.multipos_table_tools import sign
x, cmp_x = pair
assert sign(x) == cmp_x
def listize(x):
if type(x) == np.ndarray:
return list(x)
else:
return [x]
@pytest.mark.parametrize("x_start", listize((np.random.rand() - 0.5) * 1000))
@pytest.mark.parametrize("x_tiles", listize(np.random.randint(0, 5)))
@pytest.mark.parametrize("x_length", listize(np.random.rand() * 1000))
@pytest.mark.parametrize("y_start", listize(((np.random.rand() - 0.5) * 1000)))
@pytest.mark.parametrize("y_tiles", listize(np.random.randint(0, 5)))
@pytest.mark.parametrize("y_length", listize(np.random.rand() * 1000))
@pytest.mark.parametrize("z_start", listize(((np.random.rand() - 0.5) * 1000)))
@pytest.mark.parametrize("z_tiles", listize(np.random.randint(0, 5)))
@pytest.mark.parametrize("z_length", listize(np.random.rand() * 1000))
@pytest.mark.parametrize("theta_start", listize(((np.random.rand() - 0.5) * 180)))
@pytest.mark.parametrize("theta_tiles", listize(np.random.randint(0, 5)))
@pytest.mark.parametrize("theta_length", listize((np.random.rand() * 5)))
@pytest.mark.parametrize("f_start", listize(((np.random.rand() - 0.5) * 1000)))
@pytest.mark.parametrize("f_tiles", listize(np.random.randint(0, 5)))
@pytest.mark.parametrize("f_length", listize(np.random.rand() * 1000))
@pytest.mark.parametrize("overlap", listize(np.random.rand()))
@pytest.mark.parametrize("f_track_with_z", [True, False])
def test_compute_tiles_from_bounding_box(
x_start,
x_tiles,
x_length,
y_start,
y_tiles,
y_length,
z_start,
z_tiles,
z_length,
theta_start,
theta_tiles,
theta_length,
f_start,
f_tiles,
f_length,
overlap,
f_track_with_z,
):
from navigate.tools.multipos_table_tools import compute_tiles_from_bounding_box
axes, tiles = compute_tiles_from_bounding_box(
x_start,
x_tiles,
x_length,
y_start,
y_tiles,
y_length,
z_start,
z_tiles,
z_length,
theta_start,
theta_tiles,
theta_length,
f_start,
f_tiles,
f_length,
overlap,
f_track_with_z,
)
x_tiles = 1 if x_tiles <= 0 else x_tiles
y_tiles = 1 if y_tiles <= 0 else y_tiles
z_tiles = 1 if z_tiles <= 0 else z_tiles
theta_tiles = 1 if theta_tiles <= 0 else theta_tiles
f_tiles = 1 if f_tiles <= 0 else f_tiles
x_max = x_start + (1 - overlap) * x_length * (x_tiles - 1)
y_max = y_start + (1 - overlap) * y_length * (y_tiles - 1)
z_max = z_start + (1 - overlap) * z_length * (z_tiles - 1)
theta_max = theta_start + (1 - overlap) * theta_length * (theta_tiles - 1)
f_max = f_start + (1 - overlap) * f_length * (f_tiles - 1)
# check extrema
assert tiles[0, 0] == x_start
assert tiles[0, 1] == y_start
assert tiles[0, 2] == z_start
assert tiles[0, 3] == theta_start
assert tiles[0, 4] == f_start
assert tiles[-1, 0] == x_max
assert tiles[-1, 1] == y_max
assert tiles[-1, 2] == z_max
assert tiles[-1, 3] == theta_max
if f_track_with_z:
assert tiles[-1, 4] <= f_max # Due to clipping. TODO: Fix
else:
assert tiles[-1, 4] == f_max
# check bounding box
assert np.min(tiles[:, 0]) == x_start
assert np.max(tiles[:, 0]) == x_max
assert np.min(tiles[:, 1]) == y_start
assert np.max(tiles[:, 1]) == y_max
assert np.min(tiles[:, 2]) == z_start
assert np.max(tiles[:, 2]) == z_max
assert np.min(tiles[:, 3]) == theta_start
assert np.max(tiles[:, 3]) == theta_max
assert np.min(tiles[:, 4]) == f_start
if f_track_with_z:
assert np.max(tiles[:, 4]) <= f_max # Due to clipping. TODO: Fix
else:
assert np.max(tiles[:, 4]) == f_max
if f_track_with_z:
# check length
assert len(tiles) == x_tiles * y_tiles * z_tiles * theta_tiles
else:
assert len(tiles) == x_tiles * y_tiles * z_tiles * theta_tiles * f_tiles
@pytest.mark.parametrize("dist", listize(np.random.rand(3) * 1000))
@pytest.mark.parametrize("overlap", listize(np.random.rand(3)))
@pytest.mark.parametrize("roi_length", listize(np.random.rand(3) * 1000))
def test_calc_num_tiles(dist, overlap, roi_length):
from navigate.tools.multipos_table_tools import calc_num_tiles
# dist = 300
# overlap = .75
# roi_length = 525
expected_num_tiles = ceil(
# abs(dist - overlap * roi_length) / abs(roi_length - overlap * roi_length)
(dist - overlap * roi_length)
/ (roi_length - overlap * roi_length)
)
result = calc_num_tiles(dist, overlap, roi_length)
assert result == expected_num_tiles
class UpdateTableTestCase(unittest.TestCase):
def setUp(self):
self.root = tk.Tk()
self.frame = ttk.Frame()
self.table = MultiPositionTable(parent=self.frame)
def tearDown(self) -> None:
self.root.destroy()
def test_update_table_1(self):
pos = np.array([[1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15]])
update_table(
table=self.table, pos=pos, axes=["X", "Y", "Z", "THETA", "F"], append=False
)
np.testing.assert_array_equal(self.table.model.df["X"], pos[:, 0])
np.testing.assert_array_equal(self.table.model.df["Y"], pos[:, 1])
np.testing.assert_array_equal(self.table.model.df["Z"], pos[:, 2])
np.testing.assert_array_equal(self.table.model.df["THETA"], pos[:, 3])
np.testing.assert_array_equal(self.table.model.df["F"], pos[:, 4])
assert self.table.currentrow == 2
new_positions = np.array([[16, 17, 18, 19, 20], [21, 22, 23, 24, 25]])
print(self.table.model.df.shape)
update_table(
self.table,
pos=new_positions,
axes=["X", "Y", "Z", "THETA", "F"],
append=True,
)
assert self.table.currentrow == 4
np.testing.assert_array_equal(
self.table.model.df["X"][3:,],
new_positions[:, 0],
)
np.testing.assert_array_equal(
self.table.model.df["Y"][3:,],
new_positions[:, 1],
)
np.testing.assert_array_equal(
self.table.model.df["Z"][3:,],
new_positions[:, 2],
)
np.testing.assert_array_equal(
self.table.model.df["THETA"][3:,],
new_positions[:, 3],
)
np.testing.assert_array_equal(
self.table.model.df["F"][3:,],
new_positions[:, 4],
)
if __name__ == "__main__":
unittest.main()

91
test/tools/test_sdf.py Normal file
View File

@@ -0,0 +1,91 @@
import unittest
import numpy as np
from src.navigate.tools.sdf import sphere, box, ellipsoid
class TestSphere(unittest.TestCase):
def test_sphere_center(self):
p = np.array([[0], [0], [0]])
R = 1
result = sphere(p, R)
expected = -1
self.assertEqual(result, expected)
def test_sphere_surface(self):
p = np.array([[1], [0], [0]])
R = 1
result = sphere(p, R)
expected = 0
self.assertEqual(result, expected)
def test_sphere_outside(self):
p = np.array([[2], [0], [0]])
R = 1
result = sphere(p, R)
expected = 1
self.assertEqual(result, expected)
def test_sphere_inside(self):
p = np.array([[0.5], [0], [0]])
R = 1
result = sphere(p, R)
expected = -0.5
self.assertEqual(result, expected)
class TestBox(unittest.TestCase):
def test_box_center(self):
p = np.array([[0], [0], [0]])
w = (1, 1, 1)
result = box(p, w)
expected = -1
self.assertEqual(result, expected)
def test_box_surface(self):
p = np.array([[1], [0], [0]])
w = (1, 1, 1)
result = box(p, w)
expected = 0
self.assertEqual(result, expected)
def test_box_outside(self):
p = np.array([[2], [0], [0]])
w = (1, 1, 1)
result = box(p, w)
expected = 1
self.assertEqual(result, expected)
def test_box_inside(self):
p = np.array([[0.5], [0], [0]])
w = (1, 1, 1)
result = box(p, w)
expected = -0.5
self.assertEqual(result, expected)
class TestEllipsoid(unittest.TestCase):
def test_ellipsoid_surface(self):
p = np.array([[1], [0], [0]])
r = (1, 1, 1)
result = ellipsoid(p, r)
expected = 0
self.assertEqual(result, expected)
def test_ellipsoid_outside(self):
p = np.array([[2], [0], [0]])
r = (1, 1, 1)
result = ellipsoid(p, r)
expected = 1
self.assertEqual(result, expected)
def test_ellipsoid_inside(self):
p = np.array([[0.5], [0], [0]])
r = (1, 1, 1)
result = ellipsoid(p, r)
expected = -0.5
self.assertEqual(result, expected)
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,30 @@
def test_slice_len():
import random
from navigate.tools.slicing import slice_len
for _ in range(100):
n0 = random.randint(1,10)
n1 = random.randint(1,10)
sl = slice(0,n0,1)
assert slice_len(sl, n1) == min(n0,n1)
def test_ensure_iter():
from navigate.tools.slicing import ensure_iter
ensure_iter(2, 0, 1) == range(0, 1)
ensure_iter(2, 1, 1) == range(0, 1)
ensure_iter(2, 0, 5) == range(2, 3)
ensure_iter(2, 1, 5) == range(0, 5)
ensure_iter(slice(0, 2), 0, 1) == range(0, 1)
ensure_iter(slice(0, 2), 1, 1) == range(0, 1)
ensure_iter(slice(0, 2), 0, 5) == range(0, 2)
ensure_iter(slice(0, 2), 1, 5) == range(0, 5)
def test_ensure_slice():
from navigate.tools.slicing import ensure_slice
ensure_slice(2, 0) == slice(2, 3)
ensure_slice(2, 1) == slice(None, None, None)
ensure_slice(slice(0, 2), 0) == slice(0, 2, None)
ensure_slice(slice(0, 2), 1) == slice(None, None, None)

View File

@@ -0,0 +1,126 @@
# 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 imports
import unittest
# Third-party imports
# Local application imports
from navigate.tools.waveform_template_funcs import get_waveform_template_parameters
class TestGetWaveformTemplateParameters(unittest.TestCase):
def setUp(self):
self.waveform_template_name = "template1"
self.waveform_template_dict = {
"template1": {"repeat": 3, "expand": 2},
"template2": {"repeat": "repeat_param", "expand": "expand_param"},
}
self.microscope_state_dict = {"repeat_param": 4, "expand_param": 5}
def test_get_waveform_template_parameters(self):
repeat_num, expand_num = get_waveform_template_parameters(
self.waveform_template_name,
self.waveform_template_dict,
self.microscope_state_dict,
)
self.assertEqual(repeat_num, 3)
self.assertEqual(expand_num, 2)
def test_get_waveform_template_parameters_with_microscope_state(self):
self.waveform_template_name = "template2"
repeat_num, expand_num = get_waveform_template_parameters(
self.waveform_template_name,
self.waveform_template_dict,
self.microscope_state_dict,
)
self.assertEqual(repeat_num, 4)
self.assertEqual(expand_num, 5)
def test_get_waveform_template_parameters_with_missing_template(self):
self.waveform_template_name = "template3"
repeat_num, expand_num = get_waveform_template_parameters(
self.waveform_template_name,
self.waveform_template_dict,
self.microscope_state_dict,
)
self.assertEqual(repeat_num, 1)
self.assertEqual(expand_num, 1)
class TestGetWaveformTemplateParametersExceptions(unittest.TestCase):
def test_key_error_waveform_template_name(self):
waveform_template_dict = {"template1": {"repeat": 2, "expand": 3}}
microscope_state_dict = {}
result = get_waveform_template_parameters(
"nonexistent_template", waveform_template_dict, microscope_state_dict
)
self.assertEqual(
result,
(1, 1),
"Default values should be returned when waveform template "
"name is not found",
)
def test_key_error_repeat_key(self):
waveform_template_dict = {"template1": {"expand": 3}}
microscope_state_dict = {}
result = get_waveform_template_parameters(
"template1", waveform_template_dict, microscope_state_dict
)
self.assertEqual(
result,
(1, 3),
"Default value for repeat_num should be returned "
"when repeat key is not found",
)
def test_key_error_expand_key(self):
waveform_template_dict = {"template1": {"repeat": 2}}
microscope_state_dict = {}
result = get_waveform_template_parameters(
"template1", waveform_template_dict, microscope_state_dict
)
self.assertEqual(
result,
(2, 1),
"Default value for expand_num should be returned"
" when expand key is not found",
)
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,193 @@
# 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 imports
import glob
import xml.etree.ElementTree as ET
import unittest
# Third party imports
# Local application imports
from navigate.tools import xml_tools
from navigate.tools.xml_tools import parse_xml
def test_xml_parse_write():
for fn in glob.glob("./xml_examples/*.xml"):
# Open XML file
with open(fn, "r") as fp:
example = fp.read()
root = ET.fromstring(example)
# Parse it
parsed = xml_tools.parse_xml(root)
# Convert it back to a string
xml_string = xml_tools.dict_to_xml(parsed, root.tag)
# get rid of <?xml and the <!-- comment
example_str = (
"".join(example.split("\n")[2:]).replace(" ", "").replace("\t", "")
)
xml_str = (
xml_string.strip().replace(" ", "").replace("\n", "").replace("\t", "")
)
# Make sure the strings match, sans white space
assert example_str == xml_str
# Parse the string we created
root2 = ET.fromstring(xml_string)
parsed2 = xml_tools.parse_xml(root2)
# Make sure it matches the dictionary parsed from the original document
assert parsed == parsed2
class TestDictToXml(unittest.TestCase):
def test_dict_to_xml_with_single_level(self):
# Test case with a dictionary containing a single level
d = {"name": "John", "age": 30, "city": "New York"}
expected_xml = '<name name="John" age="30" city="New York"/>\n'
actual_xml = xml_tools.dict_to_xml(d)
self.assertEqual(actual_xml, expected_xml)
def test_dict_to_xml_with_nested_dict(self):
# Test case with a dictionary containing nested dictionaries
d = {
"person": {"name": "John", "age": 30, "city": "New York"},
"address": {"street": "123 Main St", "zipcode": "10001"},
}
expected_xml = (
'<root>\n <person name="John" age="30" '
'city="New York"/>\n <address street="123 '
'Main St" zipcode="10001"/>\n</root>\n'
)
actual_xml = xml_tools.dict_to_xml(d, tag="root")
self.assertEqual(actual_xml, expected_xml)
def test_dict_to_xml_with_nested_list(self):
# Test case with a dictionary containing nested lists
d = {
"students": [
{"name": "Alice", "age": 20},
{"name": "Bob", "age": 22},
{"name": "Charlie", "age": 21},
]
}
expected_xml = (
'<class>\n <students name="Alice" '
'age="20"/>\n <students name="Bob" '
'age="22"/>\n <students name="Charlie" age="21"/>\n</class>\n'
)
actual_xml = xml_tools.dict_to_xml(d, tag="class")
self.assertEqual(actual_xml, expected_xml)
def test_dict_to_xml_with_text_value(self):
# Test case with a dictionary containing a text value
d = {
"person": {
"name": "John",
"age": 30,
"city": "New York",
"text": "Hello, world!",
}
}
expected_xml = (
'<root>\n <person name="John" age="30" '
'city="New York">Hello, world!</person>\n</root>\n'
)
actual_xml = xml_tools.dict_to_xml(d, tag="root")
self.assertEqual(actual_xml, expected_xml)
class TestParseXml(unittest.TestCase):
def test_parse_xml_single_level(self):
xml_string = '<person name="John" age="30" city="New York"/>'
root = ET.fromstring(xml_string)
expected_dict = {"name": "John", "age": "30", "city": "New York"}
self.assertEqual(parse_xml(root), expected_dict)
def test_parse_xml_nested_elements(self):
xml_string = """
<root>
<person name="John" age="30" city="New York"/>
<address street="123 Main St" zipcode="10001"/>
</root>
"""
root = ET.fromstring(xml_string)
expected_dict = {
"person": {"name": "John", "age": "30", "city": "New York"},
"address": {"street": "123 Main St", "zipcode": "10001"},
}
self.assertEqual(parse_xml(root), expected_dict)
def test_parse_xml_with_text(self):
xml_string = """
<root>
<person name="John" age="30" city="New York">Hello, world!</person>
</root>
"""
root = ET.fromstring(xml_string)
expected_dict = {
"person": {
"name": "John",
"age": "30",
"city": "New York",
"text": "Hello, world!",
}
}
self.assertEqual(parse_xml(root), expected_dict)
def test_parse_xml_with_repeated_elements(self):
xml_string = """
<class>
<student name="Alice" age="20"/>
<student name="Bob" age="22"/>
<student name="Charlie" age="21"/>
</class>
"""
root = ET.fromstring(xml_string)
expected_dict = {
"student": [
{"name": "Alice", "age": "20"},
{"name": "Bob", "age": "22"},
{"name": "Charlie", "age": "21"},
]
}
self.assertEqual(parse_xml(root), expected_dict)
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--BigDataViewer XML Example from https://fly.mpi-cbg.de/~pietzsch/bdv-example/drosophila.xml-->
<SpimData version="0.2">
<BasePath type="relative">.</BasePath>
<SequenceDescription>
<ImageLoader format="bdv.hdf5">
<hdf5 type="relative">drosophila.h5</hdf5>
</ImageLoader>
<ViewSetups>
<ViewSetup>
<id>0</id>
</ViewSetup>
<ViewSetup>
<id>1</id>
</ViewSetup>
</ViewSetups>
<Timepoints type="range">
<first>0</first>
<last>2</last>
</Timepoints>
</SequenceDescription>
<ViewRegistrations>
<ReferenceTimepoint>350</ReferenceTimepoint>
<ViewRegistration timepoint="0" setup="0">
<ViewTransform type="affine">
<affine>0.996591329574585 0.0014791092835366726 0.01073253620415926 -5.384683609008789 -0.001930794445797801 0.9954455494880676 -0.003766203299164772 -81.54486083984375 -4.968568682670593E-4 -6.0385558754205704E-5 3.490110397338867 9.85491943359375</affine>
</ViewTransform>
</ViewRegistration>
<ViewRegistration timepoint="0" setup="1">
<ViewTransform type="affine">
<affine>0.9967471361160278 -0.0018281697994098067 -0.0159448329359293 -3.9299731254577637 -0.005900930613279343 0.49216321110725403 -3.0123801231384277 262.5342712402344 0.004747752100229263 0.8682872653007507 1.7716364860534668 -426.66754150390625</affine>
</ViewTransform>
</ViewRegistration>
<ViewRegistration timepoint="1" setup="0">
<ViewTransform type="affine">
<affine>0.9966375827789307 0.0014226322527974844 0.009849724359810352 -5.8060078620910645 -0.0018444646848365664 0.9956076145172119 -0.005629136227071285 -69.27687072753906 -3.634979948401451E-4 6.720321252942085E-4 3.4895498752593994 10.738128662109375</affine>
</ViewTransform>
</ViewRegistration>
<ViewRegistration timepoint="1" setup="1">
<ViewTransform type="affine">
<affine>0.996777355670929 -0.001879655523225665 -0.015927843749523163 -4.662176132202148 -0.005749744363129139 0.4920715391635895 -3.0124056339263916 275.1748046875 0.004699353594332933 0.8685303926467896 1.7690489292144775 -422.2758483886719</affine>
</ViewTransform>
</ViewRegistration>
<ViewRegistration timepoint="2" setup="0">
<ViewTransform type="affine">
<affine>0.9967382550239563 0.0013750510988757014 0.00983844231814146 -5.692346572875977 -0.0018074746476486325 0.9957119822502136 -0.0039688413962721825 -70.14445495605469 -3.824750892817974E-4 3.17456666380167E-4 3.4910457134246826 13.597747802734375</affine>
</ViewTransform>
</ViewRegistration>
<ViewRegistration timepoint="2" setup="1">
<ViewTransform type="affine">
<affine>0.9968152642250061 -0.001965513452887535 -0.015846025198698044 -5.178727149963379 -0.005820842459797859 0.4911666214466095 -3.0130810737609863 278.82867431640625 0.004750141873955727 0.8690722584724426 1.7654311656951904 -415.7231140136719</affine>
</ViewTransform>
</ViewRegistration>
</ViewRegistrations>
</SpimData>

View File

@@ -0,0 +1,289 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--OME-XML example from https://github.com/ome/samples/blob/master/leica/martin/samples-SP2/samples-SP2.ome.xml <OME xmlns="http://www.openmicroscopy.org/Schemas/OME/2015-01" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.openmicroscopy.org/Schemas/OME/2015-01 http://www.openmicroscopy.org/Schemas/OME/2015-01/ome.xsd">-->
<OME>
<Instrument ID="Instrument:0">
<Detector ID="Detector:0:0" Offset="-34.599999999999994" Type="PMT" Voltage="742.0867526377491" VoltageUnit="V"/>
<Detector ID="Detector:0:1" Offset="0.0" Type="PMT" Voltage="391.2661195779601" VoltageUnit="V"/>
<Objective Correction="Other" ID="Objective:0:0" Immersion="Oil" LensNA="1.25" Model="HCX PL APO CS" NominalMagnification="40.0" SerialNumber="506179"/>
<Filter ID="Filter:0:0" Model="SP Mirror 1"/>
<Filter ID="Filter:0:1" Model="SP Mirror 2">
<TransmittanceRange CutIn="578.0" CutInUnit="nm" CutOut="629.0" CutOutUnit="nm"/>
</Filter>
<Filter ID="Filter:0:2" Model="SP Mirror 3"/>
</Instrument>
<Instrument ID="Instrument:1">
<Detector ID="Detector:1:0" Offset="0.5" Type="PMT" Voltage="631.8874560375147" VoltageUnit="V"/>
<Detector ID="Detector:1:1" Offset="-1.2999999999999972" Type="PMT" Voltage="640.0937866354044" VoltageUnit="V"/>
<Detector ID="Detector:1:2" Offset="-4.400000000000006" Type="PMT" Voltage="163.8335287221571" VoltageUnit="V"/>
<Objective Correction="Other" ID="Objective:1:0" Immersion="Oil" LensNA="1.32" Model="HCX PL APO CS" NominalMagnification="63.0" SerialNumber="506182"/>
<Filter ID="Filter:1:0" Model="SP Mirror 1">
<TransmittanceRange CutIn="500.00000000000006" CutInUnit="nm" CutOut="540.0" CutOutUnit="nm"/>
</Filter>
<Filter ID="Filter:1:1" Model="SP Mirror 2">
<TransmittanceRange CutIn="650.0" CutInUnit="nm" CutOut="700.0" CutOutUnit="nm"/>
</Filter>
<Filter ID="Filter:1:2" Model="SP Mirror 3"/>
</Instrument>
<Instrument ID="Instrument:2">
<Detector ID="Detector:2:0" Offset="1.0" Type="PMT" Voltage="820.926143024619" VoltageUnit="V"/>
<Detector ID="Detector:2:1" Offset="0.7999999999999972" Type="PMT" Voltage="451.34818288393905" VoltageUnit="V"/>
<Detector ID="Detector:2:2" Offset="-13.400000000000006" Type="PMT" Voltage="286.9284876905041" VoltageUnit="V"/>
<Objective Correction="Other" ID="Objective:2:0" Immersion="Oil" LensNA="1.32" Model="HCX PL APO CS" NominalMagnification="63.0" SerialNumber="506182"/>
<Filter ID="Filter:2:0" Model="SP Mirror 1">
<TransmittanceRange CutIn="500.00000000000006" CutInUnit="nm" CutOut="560.0" CutOutUnit="nm"/>
</Filter>
<Filter ID="Filter:2:1" Model="SP Mirror 2">
<TransmittanceRange CutIn="575.0" CutInUnit="nm" CutOut="693.0000000000002" CutOutUnit="nm"/>
</Filter>
<Filter ID="Filter:2:2" Model="SP Mirror 3"/>
</Instrument>
<Instrument ID="Instrument:3">
<Detector ID="Detector:3:0" Offset="3.200000000000003" Type="PMT" Voltage="430.2461899179367" VoltageUnit="V"/>
<Objective Correction="Other" ID="Objective:3:0" Immersion="Other" LensNA="0.3" Model="HC PL FLUOTAR" NominalMagnification="10.0" SerialNumber="506507"/>
<Filter ID="Filter:3:0" Model="SP Mirror 1"/>
<Filter ID="Filter:3:1" Model="SP Mirror 2"/>
<Filter ID="Filter:3:2" Model="SP Mirror 3">
<TransmittanceRange CutIn="790.0000000000015" CutInUnit="nm" CutOut="800.0000000000016" CutOutUnit="nm"/>
</Filter>
</Instrument>
<Image ID="Image:0" Name="XYZ-Ch">
<Description/>
<InstrumentRef ID="Instrument:0"/>
<ObjectiveSettings ID="Objective:0:0"/>
<StageLabel Name="Position" Z="-2.2590361445783174" ZUnit="reference frame"/>
<Pixels BigEndian="false" DimensionOrder="XYCZT" ID="Pixels:0" Interleaved="false" PhysicalSizeX="2.9296875" PhysicalSizeXUnit="µm" PhysicalSizeY="2.9296875" PhysicalSizeYUnit="µm" PhysicalSizeZ="3.378378378378379" PhysicalSizeZUnit="µm" SignificantBits="8" SizeC="2" SizeT="1" SizeX="128" SizeY="128" SizeZ="50" Type="uint8">
<Channel Color="-16776961" ID="Channel:0:0" Name="TRITC" PinholeSize="81.40681653372008" PinholeSizeUnit="µm" SamplesPerPixel="1">
<DetectorSettings ID="Detector:0:0"/>
<LightPath>
<EmissionFilterRef ID="Filter:0:1"/>
</LightPath>
</Channel>
<Channel Color="-1" ID="Channel:0:1" Name="PMT Trans" PinholeSize="81.40681653372008" PinholeSizeUnit="µm" SamplesPerPixel="1">
<DetectorSettings ID="Detector:0:1"/>
<LightPath/>
</Channel>
<MetadataOnly/>
<Plane DeltaT="0.0" DeltaTUnit="s" TheC="0" TheT="0" TheZ="0"/>
<Plane DeltaT="0.0" DeltaTUnit="s" TheC="1" TheT="0" TheZ="0"/>
<Plane DeltaT="2.656" DeltaTUnit="s" TheC="0" TheT="0" TheZ="1"/>
<Plane DeltaT="2.656" DeltaTUnit="s" TheC="1" TheT="0" TheZ="1"/>
<Plane DeltaT="5.328" DeltaTUnit="s" TheC="0" TheT="0" TheZ="2"/>
<Plane DeltaT="5.328" DeltaTUnit="s" TheC="1" TheT="0" TheZ="2"/>
<Plane DeltaT="8.0" DeltaTUnit="s" TheC="0" TheT="0" TheZ="3"/>
<Plane DeltaT="8.0" DeltaTUnit="s" TheC="1" TheT="0" TheZ="3"/>
<Plane DeltaT="10.656" DeltaTUnit="s" TheC="0" TheT="0" TheZ="4"/>
<Plane DeltaT="10.656" DeltaTUnit="s" TheC="1" TheT="0" TheZ="4"/>
<Plane DeltaT="13.328" DeltaTUnit="s" TheC="0" TheT="0" TheZ="5"/>
<Plane DeltaT="13.328" DeltaTUnit="s" TheC="1" TheT="0" TheZ="5"/>
<Plane DeltaT="15.984" DeltaTUnit="s" TheC="0" TheT="0" TheZ="6"/>
<Plane DeltaT="15.984" DeltaTUnit="s" TheC="1" TheT="0" TheZ="6"/>
<Plane DeltaT="18.656" DeltaTUnit="s" TheC="0" TheT="0" TheZ="7"/>
<Plane DeltaT="18.656" DeltaTUnit="s" TheC="1" TheT="0" TheZ="7"/>
<Plane DeltaT="21.328" DeltaTUnit="s" TheC="0" TheT="0" TheZ="8"/>
<Plane DeltaT="21.328" DeltaTUnit="s" TheC="1" TheT="0" TheZ="8"/>
<Plane DeltaT="23.984" DeltaTUnit="s" TheC="0" TheT="0" TheZ="9"/>
<Plane DeltaT="23.984" DeltaTUnit="s" TheC="1" TheT="0" TheZ="9"/>
<Plane DeltaT="26.656" DeltaTUnit="s" TheC="0" TheT="0" TheZ="10"/>
<Plane DeltaT="26.656" DeltaTUnit="s" TheC="1" TheT="0" TheZ="10"/>
<Plane DeltaT="29.313" DeltaTUnit="s" TheC="0" TheT="0" TheZ="11"/>
<Plane DeltaT="29.313" DeltaTUnit="s" TheC="1" TheT="0" TheZ="11"/>
<Plane DeltaT="31.984" DeltaTUnit="s" TheC="0" TheT="0" TheZ="12"/>
<Plane DeltaT="31.984" DeltaTUnit="s" TheC="1" TheT="0" TheZ="12"/>
<Plane DeltaT="34.656" DeltaTUnit="s" TheC="0" TheT="0" TheZ="13"/>
<Plane DeltaT="34.656" DeltaTUnit="s" TheC="1" TheT="0" TheZ="13"/>
<Plane DeltaT="37.313" DeltaTUnit="s" TheC="0" TheT="0" TheZ="14"/>
<Plane DeltaT="37.313" DeltaTUnit="s" TheC="1" TheT="0" TheZ="14"/>
<Plane DeltaT="39.984" DeltaTUnit="s" TheC="0" TheT="0" TheZ="15"/>
<Plane DeltaT="39.984" DeltaTUnit="s" TheC="1" TheT="0" TheZ="15"/>
<Plane DeltaT="42.656" DeltaTUnit="s" TheC="0" TheT="0" TheZ="16"/>
<Plane DeltaT="42.656" DeltaTUnit="s" TheC="1" TheT="0" TheZ="16"/>
<Plane DeltaT="45.313" DeltaTUnit="s" TheC="0" TheT="0" TheZ="17"/>
<Plane DeltaT="45.313" DeltaTUnit="s" TheC="1" TheT="0" TheZ="17"/>
<Plane DeltaT="47.984" DeltaTUnit="s" TheC="0" TheT="0" TheZ="18"/>
<Plane DeltaT="47.984" DeltaTUnit="s" TheC="1" TheT="0" TheZ="18"/>
<Plane DeltaT="50.641" DeltaTUnit="s" TheC="0" TheT="0" TheZ="19"/>
<Plane DeltaT="50.641" DeltaTUnit="s" TheC="1" TheT="0" TheZ="19"/>
<Plane DeltaT="53.313" DeltaTUnit="s" TheC="0" TheT="0" TheZ="20"/>
<Plane DeltaT="53.313" DeltaTUnit="s" TheC="1" TheT="0" TheZ="20"/>
<Plane DeltaT="55.984" DeltaTUnit="s" TheC="0" TheT="0" TheZ="21"/>
<Plane DeltaT="55.984" DeltaTUnit="s" TheC="1" TheT="0" TheZ="21"/>
<Plane DeltaT="58.641" DeltaTUnit="s" TheC="0" TheT="0" TheZ="22"/>
<Plane DeltaT="58.641" DeltaTUnit="s" TheC="1" TheT="0" TheZ="22"/>
<Plane DeltaT="61.313" DeltaTUnit="s" TheC="0" TheT="0" TheZ="23"/>
<Plane DeltaT="61.313" DeltaTUnit="s" TheC="1" TheT="0" TheZ="23"/>
<Plane DeltaT="63.969" DeltaTUnit="s" TheC="0" TheT="0" TheZ="24"/>
<Plane DeltaT="63.969" DeltaTUnit="s" TheC="1" TheT="0" TheZ="24"/>
<Plane DeltaT="66.641" DeltaTUnit="s" TheC="0" TheT="0" TheZ="25"/>
<Plane DeltaT="66.641" DeltaTUnit="s" TheC="1" TheT="0" TheZ="25"/>
<Plane DeltaT="69.313" DeltaTUnit="s" TheC="0" TheT="0" TheZ="26"/>
<Plane DeltaT="69.313" DeltaTUnit="s" TheC="1" TheT="0" TheZ="26"/>
<Plane DeltaT="71.969" DeltaTUnit="s" TheC="0" TheT="0" TheZ="27"/>
<Plane DeltaT="71.969" DeltaTUnit="s" TheC="1" TheT="0" TheZ="27"/>
<Plane DeltaT="74.641" DeltaTUnit="s" TheC="0" TheT="0" TheZ="28"/>
<Plane DeltaT="74.641" DeltaTUnit="s" TheC="1" TheT="0" TheZ="28"/>
<Plane DeltaT="77.297" DeltaTUnit="s" TheC="0" TheT="0" TheZ="29"/>
<Plane DeltaT="77.297" DeltaTUnit="s" TheC="1" TheT="0" TheZ="29"/>
<Plane DeltaT="79.969" DeltaTUnit="s" TheC="0" TheT="0" TheZ="30"/>
<Plane DeltaT="79.969" DeltaTUnit="s" TheC="1" TheT="0" TheZ="30"/>
<Plane DeltaT="82.641" DeltaTUnit="s" TheC="0" TheT="0" TheZ="31"/>
<Plane DeltaT="82.641" DeltaTUnit="s" TheC="1" TheT="0" TheZ="31"/>
<Plane DeltaT="85.297" DeltaTUnit="s" TheC="0" TheT="0" TheZ="32"/>
<Plane DeltaT="85.297" DeltaTUnit="s" TheC="1" TheT="0" TheZ="32"/>
<Plane DeltaT="87.969" DeltaTUnit="s" TheC="0" TheT="0" TheZ="33"/>
<Plane DeltaT="87.969" DeltaTUnit="s" TheC="1" TheT="0" TheZ="33"/>
<Plane DeltaT="90.625" DeltaTUnit="s" TheC="0" TheT="0" TheZ="34"/>
<Plane DeltaT="90.625" DeltaTUnit="s" TheC="1" TheT="0" TheZ="34"/>
<Plane DeltaT="93.297" DeltaTUnit="s" TheC="0" TheT="0" TheZ="35"/>
<Plane DeltaT="93.297" DeltaTUnit="s" TheC="1" TheT="0" TheZ="35"/>
<Plane DeltaT="95.969" DeltaTUnit="s" TheC="0" TheT="0" TheZ="36"/>
<Plane DeltaT="95.969" DeltaTUnit="s" TheC="1" TheT="0" TheZ="36"/>
<Plane DeltaT="98.625" DeltaTUnit="s" TheC="0" TheT="0" TheZ="37"/>
<Plane DeltaT="98.625" DeltaTUnit="s" TheC="1" TheT="0" TheZ="37"/>
<Plane DeltaT="101.297" DeltaTUnit="s" TheC="0" TheT="0" TheZ="38"/>
<Plane DeltaT="101.297" DeltaTUnit="s" TheC="1" TheT="0" TheZ="38"/>
<Plane DeltaT="103.969" DeltaTUnit="s" TheC="0" TheT="0" TheZ="39"/>
<Plane DeltaT="103.969" DeltaTUnit="s" TheC="1" TheT="0" TheZ="39"/>
<Plane DeltaT="106.625" DeltaTUnit="s" TheC="0" TheT="0" TheZ="40"/>
<Plane DeltaT="106.625" DeltaTUnit="s" TheC="1" TheT="0" TheZ="40"/>
<Plane DeltaT="109.297" DeltaTUnit="s" TheC="0" TheT="0" TheZ="41"/>
<Plane DeltaT="109.297" DeltaTUnit="s" TheC="1" TheT="0" TheZ="41"/>
<Plane DeltaT="111.953" DeltaTUnit="s" TheC="0" TheT="0" TheZ="42"/>
<Plane DeltaT="111.953" DeltaTUnit="s" TheC="1" TheT="0" TheZ="42"/>
<Plane DeltaT="114.625" DeltaTUnit="s" TheC="0" TheT="0" TheZ="43"/>
<Plane DeltaT="114.625" DeltaTUnit="s" TheC="1" TheT="0" TheZ="43"/>
<Plane DeltaT="117.297" DeltaTUnit="s" TheC="0" TheT="0" TheZ="44"/>
<Plane DeltaT="117.297" DeltaTUnit="s" TheC="1" TheT="0" TheZ="44"/>
<Plane DeltaT="119.953" DeltaTUnit="s" TheC="0" TheT="0" TheZ="45"/>
<Plane DeltaT="119.953" DeltaTUnit="s" TheC="1" TheT="0" TheZ="45"/>
<Plane DeltaT="122.625" DeltaTUnit="s" TheC="0" TheT="0" TheZ="46"/>
<Plane DeltaT="122.625" DeltaTUnit="s" TheC="1" TheT="0" TheZ="46"/>
<Plane DeltaT="125.281" DeltaTUnit="s" TheC="0" TheT="0" TheZ="47"/>
<Plane DeltaT="125.281" DeltaTUnit="s" TheC="1" TheT="0" TheZ="47"/>
<Plane DeltaT="127.953" DeltaTUnit="s" TheC="0" TheT="0" TheZ="48"/>
<Plane DeltaT="127.953" DeltaTUnit="s" TheC="1" TheT="0" TheZ="48"/>
<Plane DeltaT="130.625" DeltaTUnit="s" TheC="0" TheT="0" TheZ="49"/>
<Plane DeltaT="130.625" DeltaTUnit="s" TheC="1" TheT="0" TheZ="49"/>
</Pixels>
</Image>
<Image ID="Image:1" Name="XY-Ch-trans">
<Description/>
<InstrumentRef ID="Instrument:1"/>
<ObjectiveSettings ID="Objective:1:0"/>
<StageLabel Name="Position" Z="-9.422826440898726" ZUnit="reference frame"/>
<Pixels BigEndian="false" DimensionOrder="XYCZT" ID="Pixels:1" Interleaved="false" PhysicalSizeX="0.23251488095238093" PhysicalSizeXUnit="µm" PhysicalSizeY="0.2325148809523809" PhysicalSizeYUnit="µm" PhysicalSizeZ="0.04070335395636601" PhysicalSizeZUnit="µm" SignificantBits="8" SizeC="3" SizeT="1" SizeX="512" SizeY="512" SizeZ="1" Type="uint8">
<Channel Color="16711935" ID="Channel:1:0" Name="PMT 1" PinholeSize="20.420594633792604" PinholeSizeUnit="µm" SamplesPerPixel="1">
<DetectorSettings ID="Detector:1:0"/>
<LightPath>
<EmissionFilterRef ID="Filter:1:0"/>
</LightPath>
</Channel>
<Channel Color="-16776961" ID="Channel:1:1" Name="PMT 2" PinholeSize="20.420594633792604" PinholeSizeUnit="µm" SamplesPerPixel="1">
<DetectorSettings ID="Detector:1:1"/>
<LightPath>
<EmissionFilterRef ID="Filter:1:1"/>
</LightPath>
</Channel>
<Channel Color="-1" ID="Channel:1:2" Name="PMT Trans" PinholeSize="20.420594633792604" PinholeSizeUnit="µm" SamplesPerPixel="1">
<DetectorSettings ID="Detector:1:2"/>
<LightPath/>
</Channel>
<MetadataOnly/>
<Plane DeltaT="0.0" DeltaTUnit="s" TheC="0" TheT="0" TheZ="0"/>
<Plane DeltaT="0.0" DeltaTUnit="s" TheC="1" TheT="0" TheZ="0"/>
<Plane DeltaT="0.0" DeltaTUnit="s" TheC="2" TheT="0" TheZ="0"/>
</Pixels>
</Image>
<Image ID="Image:2" Name="XZY-Ch-trans">
<Description/>
<InstrumentRef ID="Instrument:2"/>
<ObjectiveSettings ID="Objective:2:0"/>
<StageLabel Name="Position" Z="0.38668186258547055" ZUnit="reference frame"/>
<Pixels BigEndian="false" DimensionOrder="XYZCT" ID="Pixels:2" Interleaved="false" PhysicalSizeX="0.11625744047619045" PhysicalSizeXUnit="µm" PhysicalSizeY="0.11625744047619047" PhysicalSizeYUnit="µm" PhysicalSizeZ="1.5874308042982737" PhysicalSizeZUnit="µm" SignificantBits="8" SizeC="3" SizeT="1" SizeX="512" SizeY="512" SizeZ="1" Type="uint8">
<Channel Color="-16776961" ID="Channel:2:0" Name="TEXAS RED" PinholeSize="243.3357505438723" PinholeSizeUnit="µm" SamplesPerPixel="1">
<DetectorSettings ID="Detector:2:0"/>
<LightPath>
<EmissionFilterRef ID="Filter:2:1"/>
</LightPath>
</Channel>
<Channel Color="16711935" ID="Channel:2:1" Name="FITC" PinholeSize="243.3357505438723" PinholeSizeUnit="µm" SamplesPerPixel="1">
<DetectorSettings ID="Detector:2:1"/>
<LightPath>
<EmissionFilterRef ID="Filter:2:0"/>
</LightPath>
</Channel>
<Channel Color="-1" ID="Channel:2:2" Name="PMT Trans" PinholeSize="243.3357505438723" PinholeSizeUnit="µm" SamplesPerPixel="1">
<DetectorSettings ID="Detector:2:2"/>
<LightPath/>
</Channel>
<MetadataOnly/>
<Plane DeltaT="0.0" DeltaTUnit="s" TheC="0" TheT="0" TheZ="0"/>
<Plane DeltaT="3.844" DeltaTUnit="s" TheC="1" TheT="0" TheZ="0"/>
<Plane DeltaT="3.844" DeltaTUnit="s" TheC="2" TheT="0" TheZ="0"/>
</Pixels>
</Image>
<Image ID="Image:3" Name="XY-lambda">
<Description/>
<InstrumentRef ID="Instrument:3"/>
<ObjectiveSettings ID="Objective:3:0"/>
<StageLabel Name="Position" Z="-0.020351676978177666" ZUnit="reference frame"/>
<Pixels BigEndian="false" DimensionOrder="XYCTZ" ID="Pixels:3" Interleaved="false" PhysicalSizeX="2.9296875" PhysicalSizeXUnit="µm" PhysicalSizeY="2.9296875" PhysicalSizeYUnit="µm" SignificantBits="8" SizeC="1" SizeT="50" SizeX="128" SizeY="128" SizeZ="1" Type="uint8">
<Channel Color="-1" ID="Channel:3:0" Name="PMT 3" PinholeSize="600.0" PinholeSizeUnit="µm" SamplesPerPixel="1">
<DetectorSettings ID="Detector:3:0"/>
<LightPath>
<EmissionFilterRef ID="Filter:3:2"/>
</LightPath>
</Channel>
<MetadataOnly/>
<Plane DeltaT="0.0" DeltaTUnit="s" TheC="0" TheT="0" TheZ="0"/>
<Plane DeltaT="0.266" DeltaTUnit="s" TheC="0" TheT="1" TheZ="0"/>
<Plane DeltaT="0.532" DeltaTUnit="s" TheC="0" TheT="2" TheZ="0"/>
<Plane DeltaT="0.797" DeltaTUnit="s" TheC="0" TheT="3" TheZ="0"/>
<Plane DeltaT="1.063" DeltaTUnit="s" TheC="0" TheT="4" TheZ="0"/>
<Plane DeltaT="1.657" DeltaTUnit="s" TheC="0" TheT="5" TheZ="0"/>
<Plane DeltaT="2.25" DeltaTUnit="s" TheC="0" TheT="6" TheZ="0"/>
<Plane DeltaT="2.719" DeltaTUnit="s" TheC="0" TheT="7" TheZ="0"/>
<Plane DeltaT="3.188" DeltaTUnit="s" TheC="0" TheT="8" TheZ="0"/>
<Plane DeltaT="3.657" DeltaTUnit="s" TheC="0" TheT="9" TheZ="0"/>
<Plane DeltaT="4.125" DeltaTUnit="s" TheC="0" TheT="10" TheZ="0"/>
<Plane DeltaT="4.594" DeltaTUnit="s" TheC="0" TheT="11" TheZ="0"/>
<Plane DeltaT="5.063" DeltaTUnit="s" TheC="0" TheT="12" TheZ="0"/>
<Plane DeltaT="5.532" DeltaTUnit="s" TheC="0" TheT="13" TheZ="0"/>
<Plane DeltaT="5.891" DeltaTUnit="s" TheC="0" TheT="14" TheZ="0"/>
<Plane DeltaT="6.25" DeltaTUnit="s" TheC="0" TheT="15" TheZ="0"/>
<Plane DeltaT="6.61" DeltaTUnit="s" TheC="0" TheT="16" TheZ="0"/>
<Plane DeltaT="6.969" DeltaTUnit="s" TheC="0" TheT="17" TheZ="0"/>
<Plane DeltaT="7.328" DeltaTUnit="s" TheC="0" TheT="18" TheZ="0"/>
<Plane DeltaT="7.688" DeltaTUnit="s" TheC="0" TheT="19" TheZ="0"/>
<Plane DeltaT="8.047" DeltaTUnit="s" TheC="0" TheT="20" TheZ="0"/>
<Plane DeltaT="8.407" DeltaTUnit="s" TheC="0" TheT="21" TheZ="0"/>
<Plane DeltaT="8.75" DeltaTUnit="s" TheC="0" TheT="22" TheZ="0"/>
<Plane DeltaT="9.11" DeltaTUnit="s" TheC="0" TheT="23" TheZ="0"/>
<Plane DeltaT="9.469" DeltaTUnit="s" TheC="0" TheT="24" TheZ="0"/>
<Plane DeltaT="9.828" DeltaTUnit="s" TheC="0" TheT="25" TheZ="0"/>
<Plane DeltaT="10.188" DeltaTUnit="s" TheC="0" TheT="26" TheZ="0"/>
<Plane DeltaT="10.547" DeltaTUnit="s" TheC="0" TheT="27" TheZ="0"/>
<Plane DeltaT="10.891" DeltaTUnit="s" TheC="0" TheT="28" TheZ="0"/>
<Plane DeltaT="11.25" DeltaTUnit="s" TheC="0" TheT="29" TheZ="0"/>
<Plane DeltaT="11.61" DeltaTUnit="s" TheC="0" TheT="30" TheZ="0"/>
<Plane DeltaT="11.969" DeltaTUnit="s" TheC="0" TheT="31" TheZ="0"/>
<Plane DeltaT="12.328" DeltaTUnit="s" TheC="0" TheT="32" TheZ="0"/>
<Plane DeltaT="12.688" DeltaTUnit="s" TheC="0" TheT="33" TheZ="0"/>
<Plane DeltaT="13.047" DeltaTUnit="s" TheC="0" TheT="34" TheZ="0"/>
<Plane DeltaT="13.391" DeltaTUnit="s" TheC="0" TheT="35" TheZ="0"/>
<Plane DeltaT="13.75" DeltaTUnit="s" TheC="0" TheT="36" TheZ="0"/>
<Plane DeltaT="14.11" DeltaTUnit="s" TheC="0" TheT="37" TheZ="0"/>
<Plane DeltaT="14.469" DeltaTUnit="s" TheC="0" TheT="38" TheZ="0"/>
<Plane DeltaT="14.828" DeltaTUnit="s" TheC="0" TheT="39" TheZ="0"/>
<Plane DeltaT="15.094" DeltaTUnit="s" TheC="0" TheT="40" TheZ="0"/>
<Plane DeltaT="15.36" DeltaTUnit="s" TheC="0" TheT="41" TheZ="0"/>
<Plane DeltaT="15.625" DeltaTUnit="s" TheC="0" TheT="42" TheZ="0"/>
<Plane DeltaT="15.891" DeltaTUnit="s" TheC="0" TheT="43" TheZ="0"/>
<Plane DeltaT="16.157" DeltaTUnit="s" TheC="0" TheT="44" TheZ="0"/>
<Plane DeltaT="16.422" DeltaTUnit="s" TheC="0" TheT="45" TheZ="0"/>
<Plane DeltaT="16.688" DeltaTUnit="s" TheC="0" TheT="46" TheZ="0"/>
<Plane DeltaT="16.953" DeltaTUnit="s" TheC="0" TheT="47" TheZ="0"/>
<Plane DeltaT="17.219" DeltaTUnit="s" TheC="0" TheT="48" TheZ="0"/>
<Plane DeltaT="17.485" DeltaTUnit="s" TheC="0" TheT="49" TheZ="0"/>
</Pixels>
</Image>
</OME>