feat: init
This commit is contained in:
855
test/config/test_config.py
Normal file
855
test/config/test_config.py
Normal file
@@ -0,0 +1,855 @@
|
||||
# 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 Imports
|
||||
import pathlib
|
||||
import unittest
|
||||
from unittest.mock import patch, MagicMock
|
||||
from multiprocessing import Manager
|
||||
from multiprocessing.managers import ListProxy, DictProxy
|
||||
import os
|
||||
import time
|
||||
import random
|
||||
import yaml
|
||||
import sys
|
||||
|
||||
# Third Party Imports
|
||||
|
||||
# Local Imports
|
||||
import navigate.config.config as config
|
||||
from navigate.tools.file_functions import save_yaml_file, delete_folder, load_yaml_file
|
||||
|
||||
|
||||
def test_config_methods():
|
||||
methods = dir(config)
|
||||
desired_methods = [
|
||||
"DictProxy",
|
||||
"ListProxy",
|
||||
"Path",
|
||||
"__builtins__",
|
||||
"__cached__",
|
||||
"__doc__",
|
||||
"__file__",
|
||||
"__loader__",
|
||||
"__name__",
|
||||
"__package__",
|
||||
"__spec__",
|
||||
"build_nested_dict",
|
||||
"build_ref_name",
|
||||
"load_param_from_module",
|
||||
"save_yaml_file",
|
||||
"get_configuration_paths",
|
||||
"get_navigate_path",
|
||||
"isfile",
|
||||
"load_configs",
|
||||
"os",
|
||||
"platform",
|
||||
"shutil",
|
||||
"sys",
|
||||
"time",
|
||||
"update_config_dict",
|
||||
"verify_experiment_config",
|
||||
"verify_waveform_constants",
|
||||
"verify_positions_config",
|
||||
"verify_configuration",
|
||||
"support_deceased_configuration",
|
||||
"yaml",
|
||||
"logging",
|
||||
"logger",
|
||||
"p",
|
||||
"Union",
|
||||
"multiprocessing",
|
||||
]
|
||||
for method in methods:
|
||||
assert method in desired_methods
|
||||
|
||||
|
||||
def test_get_navigate_path():
|
||||
"""Test that the Navigate path is a string."""
|
||||
assert isinstance(config.get_navigate_path(), str)
|
||||
path_string = config.get_navigate_path()
|
||||
assert ".navigate" in path_string
|
||||
|
||||
|
||||
def test_get_navigate_path_windows(monkeypatch):
|
||||
"""Test that the Navigate path is a string."""
|
||||
monkeypatch.setattr(config.platform, "system", lambda: "Windows")
|
||||
monkeypatch.setattr(config.os, "getenv", lambda x: "LOCALAPPDATA")
|
||||
monkeypatch.setattr(config.os.path, "exists", lambda x: True)
|
||||
assert isinstance(config.get_navigate_path(), str)
|
||||
path_string = config.get_navigate_path()
|
||||
assert path_string.startswith("LOCALAPPDATA")
|
||||
assert path_string.endswith(".navigate")
|
||||
|
||||
|
||||
def test_get_navigate_path_mac(monkeypatch):
|
||||
"""Test that the Navigate path is a string."""
|
||||
monkeypatch.setattr(config.platform, "system", lambda: "Darwin")
|
||||
monkeypatch.setattr(config.os, "getenv", lambda x: "HOME")
|
||||
monkeypatch.setattr(config.os.path, "exists", lambda x: True)
|
||||
assert isinstance(config.get_navigate_path(), str)
|
||||
path_string = config.get_navigate_path()
|
||||
assert path_string.startswith("HOME")
|
||||
assert path_string.endswith(".navigate")
|
||||
|
||||
|
||||
# Write a test for config.get_configuration_paths()
|
||||
def test_get_configuration_paths():
|
||||
"""Test that the configuration paths are a list."""
|
||||
paths = config.get_configuration_paths()
|
||||
for path in paths:
|
||||
assert isinstance(path, pathlib.Path)
|
||||
assert len(paths) == 7
|
||||
|
||||
|
||||
def test_get_configuration_paths_create_dir(monkeypatch):
|
||||
"""Test that the configuration path is created,
|
||||
and that they are a list."""
|
||||
monkeypatch.setattr(config, "get_navigate_path", lambda: "TESTPATH")
|
||||
paths = config.get_configuration_paths()
|
||||
for path in paths:
|
||||
assert isinstance(path, pathlib.Path)
|
||||
assert os.path.exists(path), "Each configuration yaml file is copied"
|
||||
assert path.suffix.lower() in [".yml", ".yaml"]
|
||||
# delete generated folder
|
||||
delete_folder("TESTPATH")
|
||||
|
||||
|
||||
# test that the system is exited if no file is provided to load_yaml_config
|
||||
def test_load_yaml_config_no_file():
|
||||
"""Test that the system exits if no file is provided."""
|
||||
from unittest import mock
|
||||
|
||||
with mock.patch("sys.exit") as mock_sys_exit:
|
||||
config.load_configs(manager=Manager(), **{})
|
||||
mock_sys_exit.assert_called_once()
|
||||
|
||||
|
||||
class TestLoadConfigsWithYAMLError(unittest.TestCase):
|
||||
"""Test the load_configs function.
|
||||
|
||||
Target is the yaml.YAMLError exception clause.
|
||||
"""
|
||||
|
||||
@patch("yaml.load")
|
||||
@patch("builtins.open")
|
||||
@patch("pathlib.Path.exists")
|
||||
def test_yaml_error(self, mock_exists, mock_open, mock_yaml_load):
|
||||
# Set up the mocks
|
||||
mock_exists.return_value = True
|
||||
mock_open.return_value = MagicMock()
|
||||
mock_yaml_load.side_effect = yaml.YAMLError("Test YAMLError")
|
||||
|
||||
# Mocking sys.exit to prevent the test runner from exiting
|
||||
with patch.object(sys, "exit") as mock_exit:
|
||||
manager = MagicMock()
|
||||
config.load_configs(manager, config1="path/to/config1.yaml")
|
||||
|
||||
# Check if sys.exit was called with the expected argument
|
||||
mock_exit.assert_called_with(1)
|
||||
|
||||
# Check if the YAMLError was triggered
|
||||
mock_yaml_load.assert_called_once()
|
||||
|
||||
|
||||
class TestBuildNestedDict(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.manager = Manager()
|
||||
self.parent_dict = {}
|
||||
self.key_name = "nested_dict"
|
||||
|
||||
def tearDown(self):
|
||||
self.manager.shutdown()
|
||||
|
||||
def test_build_nested_dict_with_string_data(self):
|
||||
string_data = "string"
|
||||
expected_result = {"nested_dict": "string"}
|
||||
|
||||
config.build_nested_dict(
|
||||
self.manager, self.parent_dict, self.key_name, string_data
|
||||
)
|
||||
|
||||
self.assertEqual(self.parent_dict, expected_result)
|
||||
self.assertEqual(self.parent_dict[self.key_name], "string")
|
||||
assert isinstance(self.parent_dict, dict)
|
||||
|
||||
def test_build_nested_dict_with_list_data(self):
|
||||
list_data = ["string1", "string2"]
|
||||
|
||||
config.build_nested_dict(
|
||||
self.manager, self.parent_dict, self.key_name, list_data
|
||||
)
|
||||
|
||||
assert isinstance(self.parent_dict, dict)
|
||||
assert isinstance(self.parent_dict[self.key_name], ListProxy)
|
||||
for i in range(2):
|
||||
assert self.parent_dict[self.key_name][i] == list_data[i]
|
||||
|
||||
def test_build_nested_dict_with_dict_data(self):
|
||||
dict_data = {"key1": "string1", "key2": "string2"}
|
||||
|
||||
config.build_nested_dict(
|
||||
self.manager, self.parent_dict, self.key_name, dict_data
|
||||
)
|
||||
|
||||
assert isinstance(self.parent_dict, dict)
|
||||
assert isinstance(self.parent_dict[self.key_name], DictProxy)
|
||||
for key in dict_data.keys():
|
||||
assert self.parent_dict[self.key_name][key] == dict_data[key]
|
||||
|
||||
def test_update_config_dict_with_bad_file_name(self):
|
||||
test_entry = "string"
|
||||
dict_data = {"key1": "string1", "key2": "string2"}
|
||||
# Build the nested config
|
||||
config.build_nested_dict(
|
||||
self.manager, self.parent_dict, self.key_name, dict_data
|
||||
)
|
||||
|
||||
# Update the nested config
|
||||
result = config.update_config_dict(
|
||||
self.manager, self.parent_dict, self.key_name, test_entry
|
||||
)
|
||||
assert result is False
|
||||
|
||||
def test_update_config_dict_with_file_name(self):
|
||||
test_entry = "test.yml"
|
||||
# create an yaml file
|
||||
test_yaml_data = {"test_key1": "test_string1", "test_key2": "test_string2"}
|
||||
save_yaml_file("", test_yaml_data, test_entry)
|
||||
|
||||
dict_data = {"key1": "string1", "key2": "string2"}
|
||||
# Build the nested config
|
||||
config.build_nested_dict(
|
||||
self.manager, self.parent_dict, self.key_name, dict_data
|
||||
)
|
||||
|
||||
# Update the nested config
|
||||
result = config.update_config_dict(
|
||||
self.manager, self.parent_dict, self.key_name, test_entry
|
||||
)
|
||||
|
||||
assert result is True
|
||||
assert isinstance(self.parent_dict[self.key_name], DictProxy)
|
||||
for k in self.parent_dict[self.key_name].keys():
|
||||
assert self.parent_dict[self.key_name][k] == test_yaml_data[k]
|
||||
|
||||
# delete test yaml file
|
||||
os.remove(test_entry)
|
||||
|
||||
|
||||
class TestVerifyExperimentConfig(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.manager = Manager()
|
||||
current_path = os.path.abspath(os.path.dirname(__file__))
|
||||
root_path = os.path.dirname(os.path.dirname(current_path))
|
||||
self.config_path = os.path.join(root_path, "src", "navigate", "config")
|
||||
self.test_root = "test_dir"
|
||||
os.mkdir(self.test_root)
|
||||
|
||||
configuration = config.load_configs(
|
||||
self.manager,
|
||||
configuration=os.path.join(self.config_path, "configuration.yaml"),
|
||||
)
|
||||
config.verify_configuration(self.manager, configuration)
|
||||
saving_dict_sample = {
|
||||
"root_directory": config.get_navigate_path(),
|
||||
"save_directory": config.get_navigate_path(),
|
||||
"user": "Kevin",
|
||||
"tissue": "Lung",
|
||||
"celltype": "MV3",
|
||||
"label": "GFP",
|
||||
"file_type": "TIFF",
|
||||
"date": time.strftime("%Y-%m-%d"),
|
||||
"solvent": "BABB",
|
||||
}
|
||||
|
||||
camera_parameters_dict_sample = {
|
||||
"x_pixels": 2048,
|
||||
"y_pixels": 2048,
|
||||
"img_x_pixels": 2048,
|
||||
"img_y_pixels": 2048,
|
||||
"sensor_mode": "Normal",
|
||||
"readout_direction": "Top-to-Bottom",
|
||||
"number_of_pixels": 10,
|
||||
"binning": "1x1",
|
||||
"frames_to_average": 1,
|
||||
"databuffer_size": 100,
|
||||
}
|
||||
|
||||
# Autofocus
|
||||
# autofocus_sample = {
|
||||
# "coarse_range": 500,
|
||||
# "coarse_step_size": 50,
|
||||
# "coarse_selected": True,
|
||||
# "fine_range": 50,
|
||||
# "fine_step_size": 5,
|
||||
# "fine_selected": True,
|
||||
# "robust_fit": False,
|
||||
# }
|
||||
|
||||
stage_parameters_dict_sample = {
|
||||
"limits": True,
|
||||
}
|
||||
for microscope_name in configuration["configuration"]["microscopes"].keys():
|
||||
stage_parameters_dict_sample[microscope_name] = {}
|
||||
for k in ["theta_step", "f_step", "z_step"]:
|
||||
stage_parameters_dict_sample[microscope_name][k] = configuration[
|
||||
"configuration"
|
||||
]["microscopes"][microscope_name]["stage"].get(k, 30)
|
||||
stage_parameters_dict_sample[microscope_name]["xy_step"] = min(
|
||||
configuration["configuration"]["microscopes"][microscope_name][
|
||||
"stage"
|
||||
].get("x_step", 500),
|
||||
configuration["configuration"]["microscopes"][microscope_name][
|
||||
"stage"
|
||||
].get("y_step", 500),
|
||||
)
|
||||
|
||||
microscope_name = configuration["configuration"]["microscopes"].keys()[0]
|
||||
zoom = configuration["configuration"]["microscopes"][microscope_name]["zoom"][
|
||||
"position"
|
||||
].keys()[0]
|
||||
microscope_parameters_dict_sample = {
|
||||
"microscope_name": microscope_name,
|
||||
"image_mode": "live",
|
||||
"zoom": zoom,
|
||||
"stack_cycling_mode": "per_stack",
|
||||
"start_position": 0.0,
|
||||
"end_position": 100.0,
|
||||
"step_size": 20.0,
|
||||
"number_z_steps": 5,
|
||||
"timepoints": 1,
|
||||
"stack_pause": 0.0,
|
||||
"is_save": False,
|
||||
"stack_acq_time": 1.0,
|
||||
"timepoint_interval": 0,
|
||||
"experiment_duration": 1.03,
|
||||
"is_multiposition": False,
|
||||
"stack_z_origin": 0,
|
||||
"stack_focus_origin": 0,
|
||||
"start_focus": 0.0,
|
||||
"end_focus": 0.0,
|
||||
"abs_z_start": 0.0,
|
||||
"abs_z_end": 100.0,
|
||||
"waveform_template": "Default",
|
||||
}
|
||||
|
||||
# multipositions_sample = [[10.0, 10.0, 10.0, 10.0, 10.0]]
|
||||
|
||||
self.experiment_sample = {
|
||||
"Saving": saving_dict_sample,
|
||||
"CameraParameters": camera_parameters_dict_sample,
|
||||
"StageParameters": stage_parameters_dict_sample,
|
||||
"MicroscopeState": microscope_parameters_dict_sample,
|
||||
}
|
||||
|
||||
def tearDown(self):
|
||||
delete_folder(self.test_root)
|
||||
self.manager.shutdown()
|
||||
|
||||
def assert_equal_dict(self, dict1, dict2):
|
||||
# dict1 and dict2 are not nested dict
|
||||
for k in dict1.keys():
|
||||
assert dict1[k] == dict2[k], f"{k}: {dict1[k]} -- {dict2[k]}"
|
||||
|
||||
def test_load_empty_experiment_file(self):
|
||||
experiment_file_path = os.path.join(self.test_root, "experiment.yml")
|
||||
with open(experiment_file_path, "w") as f:
|
||||
f.write("")
|
||||
configuration = config.load_configs(
|
||||
self.manager,
|
||||
configuration=os.path.join(self.config_path, "configuration.yaml"),
|
||||
experiment=experiment_file_path,
|
||||
)
|
||||
config.verify_configuration(self.manager, configuration)
|
||||
config.verify_experiment_config(self.manager, configuration)
|
||||
|
||||
experiement_config = configuration["experiment"]
|
||||
assert type(experiement_config) == DictProxy
|
||||
|
||||
# Saving parameters
|
||||
self.assert_equal_dict(
|
||||
self.experiment_sample["Saving"], experiement_config["Saving"]
|
||||
)
|
||||
|
||||
# Camera parameters
|
||||
self.assert_equal_dict(
|
||||
self.experiment_sample["CameraParameters"],
|
||||
experiement_config["CameraParameters"],
|
||||
)
|
||||
|
||||
# AutoFocusParameters
|
||||
|
||||
# Stage parameters
|
||||
for k, value in self.experiment_sample["StageParameters"].items():
|
||||
if type(value) == dict:
|
||||
assert k in experiement_config["StageParameters"].keys()
|
||||
self.assert_equal_dict(value, experiement_config["StageParameters"][k])
|
||||
else:
|
||||
assert value == experiement_config["StageParameters"][k]
|
||||
|
||||
# MicroscopeState parameters
|
||||
self.assert_equal_dict(
|
||||
self.experiment_sample["MicroscopeState"],
|
||||
experiement_config["MicroscopeState"],
|
||||
)
|
||||
|
||||
# # MultiPositions
|
||||
# for i, position in enumerate(self.experiment_sample["MultiPositions"]):
|
||||
# assert position == experiement_config["MultiPositions"][i]
|
||||
|
||||
def test_load_experiment_file_with_missing_parameters(self):
|
||||
experiment = load_yaml_file(os.path.join(self.config_path, "experiment.yml"))
|
||||
# Saving prameters
|
||||
saving_parameters = list(self.experiment_sample["Saving"].keys())
|
||||
saving_parameters_deleted = self.delete_random_entries_from_dict(
|
||||
saving_parameters, experiment["Saving"]
|
||||
)
|
||||
|
||||
# Camera parameters
|
||||
camera_parameters = list(self.experiment_sample["CameraParameters"].keys())
|
||||
camera_parameters.append("img_x_pixels")
|
||||
camera_parameters.append("img_y_pixels")
|
||||
camera_parameters_deleted = self.delete_random_entries_from_dict(
|
||||
camera_parameters, experiment["CameraParameters"]
|
||||
)
|
||||
|
||||
# StageParameters
|
||||
configuration = load_yaml_file(
|
||||
os.path.join(self.config_path, "configuration.yaml")
|
||||
)
|
||||
# delete limits
|
||||
if "limits" in experiment["StageParameters"].keys():
|
||||
del experiment["StageParameters"]["limits"]
|
||||
# delete part of stage parameters of one microscope
|
||||
microscope_names = list(configuration["microscopes"].keys())
|
||||
if microscope_names[0] not in experiment["StageParameters"]:
|
||||
experiment["StageParameters"][microscope_names[0]] = {
|
||||
"z_step": 100.0,
|
||||
"theta_step": 10.0,
|
||||
}
|
||||
# delete all stage parameter of another microscope
|
||||
if microscope_names[1] in experiment["StageParameters"].keys():
|
||||
del experiment["StageParameters"][microscope_names[1]]
|
||||
|
||||
# MicroscopeState
|
||||
micrscope_parameters = list(self.experiment_sample["MicroscopeState"].keys())
|
||||
micrscope_parameters.append("channels")
|
||||
micrscope_parameters_deleted = self.delete_random_entries_from_dict(
|
||||
micrscope_parameters, experiment["MicroscopeState"]
|
||||
)
|
||||
|
||||
save_yaml_file(self.test_root, experiment, "experiment_missing_parameters.yml")
|
||||
configuration = config.load_configs(
|
||||
self.manager,
|
||||
configuration=os.path.join(self.config_path, "configuration.yaml"),
|
||||
experiment=os.path.join(
|
||||
self.test_root, "experiment_missing_parameters.yml"
|
||||
),
|
||||
)
|
||||
config.verify_configuration(self.manager, configuration)
|
||||
config.verify_experiment_config(self.manager, configuration)
|
||||
# verify Saving parameters are added
|
||||
for k in saving_parameters_deleted:
|
||||
assert (
|
||||
k in configuration["experiment"]["Saving"].keys()
|
||||
), f"parameter {k} should be added to Saving parameters"
|
||||
|
||||
# verify CameraParameters are added
|
||||
for k in camera_parameters_deleted:
|
||||
assert (
|
||||
k in configuration["experiment"]["CameraParameters"].keys()
|
||||
), f"parameter {k} should be added into CameraParameters"
|
||||
|
||||
# verify MicroscopeState parameters are added
|
||||
for k in micrscope_parameters_deleted:
|
||||
assert (
|
||||
k in configuration["experiment"]["MicroscopeState"].keys()
|
||||
), f"parameter {k} should be added to MicroscopeState"
|
||||
|
||||
# verify Stage parameters are added
|
||||
assert (
|
||||
"limits" in configuration["experiment"]["StageParameters"].keys()
|
||||
), "limits should be added to Stage parameters"
|
||||
for microscope_name in microscope_names:
|
||||
for k in ["xy_step", "z_step", "f_step", "theta_step"]:
|
||||
assert (
|
||||
k in configuration["experiment"]["StageParameters"][microscope_name]
|
||||
)
|
||||
|
||||
def test_load_experiment_file_with_wrong_parameter_values(self):
|
||||
configuration = config.load_configs(
|
||||
self.manager,
|
||||
configuration=os.path.join(self.config_path, "configuration.yaml"),
|
||||
experiment=os.path.join(self.config_path, "experiment.yml"),
|
||||
)
|
||||
config.verify_configuration(self.manager, configuration)
|
||||
experiment = configuration["experiment"]
|
||||
# Saving parameters
|
||||
# check if root_directory and save_directory exist
|
||||
experiment["Saving"]["root_directory"] = self.config_path
|
||||
experiment["Saving"]["save_directory"] = os.path.join(
|
||||
self.test_root, "not_exist", "not_exist"
|
||||
)
|
||||
config.verify_experiment_config(self.manager, configuration)
|
||||
assert experiment["Saving"]["root_directory"] == self.config_path
|
||||
assert os.path.exists(experiment["Saving"]["save_directory"])
|
||||
assert experiment["Saving"]["save_directory"] != os.path.join(
|
||||
self.test_root, "not_exist", "not_exist"
|
||||
)
|
||||
|
||||
# CameraParameters
|
||||
# x_pixels, y_pixels
|
||||
experiment["CameraParameters"]["x_pixels"] = -10
|
||||
experiment["CameraParameters"]["y_pixels"] = "abcd"
|
||||
config.verify_experiment_config(self.manager, configuration)
|
||||
assert (
|
||||
experiment["CameraParameters"]["x_pixels"]
|
||||
== self.experiment_sample["CameraParameters"]["x_pixels"]
|
||||
)
|
||||
assert (
|
||||
experiment["CameraParameters"]["y_pixels"]
|
||||
== self.experiment_sample["CameraParameters"]["y_pixels"]
|
||||
)
|
||||
binning = int(experiment["CameraParameters"]["binning"][0])
|
||||
assert (
|
||||
experiment["CameraParameters"]["img_x_pixels"]
|
||||
== experiment["CameraParameters"]["x_pixels"] // binning
|
||||
)
|
||||
assert (
|
||||
experiment["CameraParameters"]["img_y_pixels"]
|
||||
== experiment["CameraParameters"]["y_pixels"] // binning
|
||||
)
|
||||
|
||||
# binning
|
||||
for v in ["abcd", "3x3", "12.4"]:
|
||||
experiment["CameraParameters"]["binning"] = v
|
||||
config.verify_experiment_config(self.manager, configuration)
|
||||
assert experiment["CameraParameters"]["binning"] == "1x1"
|
||||
assert (
|
||||
experiment["CameraParameters"]["img_x_pixels"]
|
||||
== experiment["CameraParameters"]["x_pixels"]
|
||||
)
|
||||
assert (
|
||||
experiment["CameraParameters"]["img_y_pixels"]
|
||||
== experiment["CameraParameters"]["y_pixels"]
|
||||
)
|
||||
|
||||
# sensor_mode
|
||||
experiment["CameraParameters"]["sensor_mode"] = "None"
|
||||
config.verify_experiment_config(self.manager, configuration)
|
||||
assert experiment["CameraParameters"]["sensor_mode"] == "Normal"
|
||||
experiment["CameraParameters"]["sensor_mode"] = "Lightsheet"
|
||||
config.verify_experiment_config(self.manager, configuration)
|
||||
assert experiment["CameraParameters"]["sensor_mode"] == "Normal"
|
||||
experiment["CameraParameters"]["sensor_mode"] = "Light-Sheet"
|
||||
config.verify_experiment_config(self.manager, configuration)
|
||||
assert experiment["CameraParameters"]["sensor_mode"] == "Light-Sheet"
|
||||
|
||||
# readout_direction
|
||||
for v in ["abcd", 123, None]:
|
||||
experiment["CameraParameters"]["readout_direction"] = v
|
||||
config.verify_experiment_config(self.manager, configuration)
|
||||
assert (
|
||||
experiment["CameraParameters"]["readout_direction"] == "Top-to-Bottom"
|
||||
)
|
||||
|
||||
experiment["CameraParameters"]["readout_direction"] = "Bottom-to-Top"
|
||||
config.verify_experiment_config(self.manager, configuration)
|
||||
assert experiment["CameraParameters"]["readout_direction"] == "Bottom-to-Top"
|
||||
|
||||
# other parameters should be int
|
||||
for k in ["number_of_pixels", "databuffer_size", "frames_to_average"]:
|
||||
for v in ["abc", -10, 0]:
|
||||
experiment["CameraParameters"][k] = v
|
||||
config.verify_experiment_config(self.manager, configuration)
|
||||
assert (
|
||||
experiment["CameraParameters"][k]
|
||||
== self.experiment_sample["CameraParameters"][k]
|
||||
)
|
||||
|
||||
# StageParameters
|
||||
experiment["StageParameters"]["limits"] = "abc"
|
||||
config.verify_experiment_config(self.manager, configuration)
|
||||
assert experiment["StageParameters"]["limits"] is True
|
||||
|
||||
microscope_names = list(configuration["configuration"]["microscopes"].keys())
|
||||
for microscope_name in microscope_names:
|
||||
for k in ["xy_step", "z_step", "f_step", "theta_step"]:
|
||||
experiment["StageParameters"][microscope_name][k] = "abc"
|
||||
config.verify_experiment_config(self.manager, configuration)
|
||||
assert isinstance(
|
||||
experiment["StageParameters"][microscope_name][k], int
|
||||
)
|
||||
|
||||
# MicroscopeState
|
||||
experiment["MicroscopeState"]["microscope_name"] = "nonexist_microscope"
|
||||
config.verify_experiment_config(self.manager, configuration)
|
||||
assert experiment["MicroscopeState"]["microscope_name"] == microscope_names[0]
|
||||
|
||||
experiment["MicroscopeState"]["zoom"] = "abc"
|
||||
config.verify_experiment_config(self.manager, configuration)
|
||||
assert (
|
||||
experiment["MicroscopeState"]["zoom"]
|
||||
== list(
|
||||
configuration["configuration"]["microscopes"][microscope_names[0]][
|
||||
"zoom"
|
||||
]["position"].keys()
|
||||
)[0]
|
||||
)
|
||||
|
||||
for k in [
|
||||
"start_position",
|
||||
"end_position",
|
||||
"step_size",
|
||||
"number_z_steps",
|
||||
"timepoints",
|
||||
"stack_acq_time",
|
||||
"timepoint_interval",
|
||||
"experiment_duration",
|
||||
"stack_z_origin",
|
||||
"stack_focus_origin",
|
||||
"start_focus",
|
||||
"end_focus",
|
||||
"abs_z_start",
|
||||
"abs_z_end",
|
||||
]:
|
||||
experiment["MicroscopeState"][k] = "nonsense_value"
|
||||
config.verify_experiment_config(self.manager, configuration)
|
||||
assert isinstance(experiment["MicroscopeState"][k], int) or isinstance(
|
||||
experiment["MicroscopeState"][k], float
|
||||
)
|
||||
|
||||
# channels
|
||||
experiment["MicroscopeState"]["channels"] = [
|
||||
{
|
||||
"is_selected": True,
|
||||
"laser": "488nm",
|
||||
"laser_index": 0,
|
||||
"filter": "Empty-Alignment",
|
||||
"filter_position": 0,
|
||||
"camera_exposure_time": 200.0,
|
||||
"laser_power": 20.0,
|
||||
"interval_time": 5.0,
|
||||
"defocus": 0.0,
|
||||
}
|
||||
]
|
||||
# number_of_filter_wheels =
|
||||
|
||||
config.verify_experiment_config(self.manager, configuration)
|
||||
assert type(experiment["MicroscopeState"]["channels"]) is DictProxy
|
||||
assert len(list(experiment["MicroscopeState"]["channels"].keys())) == 0
|
||||
|
||||
experiment["MicroscopeState"]["channels"] = {
|
||||
"channel_0": {
|
||||
"is_selected": True,
|
||||
"laser": "488nm",
|
||||
"laser_index": 0,
|
||||
"filter": "Empty-Alignment",
|
||||
"filter_position": 0,
|
||||
"camera_exposure_time": 200.0,
|
||||
"laser_power": 20.0,
|
||||
"interval_time": 5.0,
|
||||
"defocus": 0.0,
|
||||
}
|
||||
}
|
||||
config.verify_experiment_config(self.manager, configuration)
|
||||
assert type(experiment["MicroscopeState"]["channels"]) is DictProxy
|
||||
assert len(list(experiment["MicroscopeState"]["channels"].keys())) == 0
|
||||
|
||||
experiment["MicroscopeState"]["channels"] = {
|
||||
"channel_100": {
|
||||
"is_selected": True,
|
||||
"laser": "488nm",
|
||||
"laser_index": 0,
|
||||
"filter": "Empty-Alignment",
|
||||
"filter_position": 0,
|
||||
"camera_exposure_time": 200.0,
|
||||
"laser_power": 20.0,
|
||||
"interval_time": 5.0,
|
||||
"defocus": 0.0,
|
||||
}
|
||||
}
|
||||
config.verify_experiment_config(self.manager, configuration)
|
||||
assert type(experiment["MicroscopeState"]["channels"]) is DictProxy
|
||||
assert len(list(experiment["MicroscopeState"]["channels"].keys())) == 0
|
||||
|
||||
microscope_name = experiment["MicroscopeState"]["microscope_name"]
|
||||
lasers = [
|
||||
f"{laser['wavelength']}nm"
|
||||
for laser in configuration["configuration"]["microscopes"][microscope_name][
|
||||
"laser"
|
||||
]
|
||||
]
|
||||
filterwheels = list(
|
||||
configuration["configuration"]["microscopes"][microscope_name][
|
||||
"filter_wheel"
|
||||
][0]["available_filters"].keys()
|
||||
)
|
||||
config.update_config_dict(
|
||||
self.manager,
|
||||
experiment["MicroscopeState"]["channels"],
|
||||
"channel_2",
|
||||
{
|
||||
"is_selected": 1,
|
||||
"laser": "48nm",
|
||||
"laser_index": -1,
|
||||
"filter_wheel_0": "nonexsit_filter_***",
|
||||
"filter_position_0": 1,
|
||||
"camera_exposure_time": -200.0,
|
||||
"laser_power": "a",
|
||||
"interval_time": -3,
|
||||
"defocus": "a",
|
||||
},
|
||||
)
|
||||
expected_value = {
|
||||
"is_selected": False,
|
||||
"laser": lasers[0],
|
||||
"laser_index": 0,
|
||||
"filter_wheel_0": filterwheels[0],
|
||||
"filter_position_0": 0,
|
||||
"camera_exposure_time": 200.0,
|
||||
"laser_power": 20.0,
|
||||
"interval_time": 0.0,
|
||||
"defocus": 0.0,
|
||||
}
|
||||
config.verify_experiment_config(self.manager, configuration)
|
||||
assert type(experiment["MicroscopeState"]["channels"]) is DictProxy
|
||||
assert "channel_2" in experiment["MicroscopeState"]["channels"].keys()
|
||||
for k in expected_value:
|
||||
assert (
|
||||
experiment["MicroscopeState"]["channels"]["channel_2"][k]
|
||||
== expected_value[k]
|
||||
)
|
||||
|
||||
config.update_config_dict(
|
||||
self.manager,
|
||||
experiment["MicroscopeState"]["channels"],
|
||||
"channel_2",
|
||||
{
|
||||
"is_selected": 1,
|
||||
"laser": lasers[1],
|
||||
"laser_index": 3,
|
||||
"filter_wheel_0": filterwheels[2],
|
||||
"filter_position_0": 1,
|
||||
"camera_exposure_time": -200.0,
|
||||
"laser_power": "a",
|
||||
"interval_time": -3,
|
||||
"defocus": "a",
|
||||
},
|
||||
)
|
||||
expected_value = {
|
||||
"is_selected": False,
|
||||
"laser": lasers[1],
|
||||
"laser_index": 1,
|
||||
"filter_wheel_0": filterwheels[2],
|
||||
"filter_position_0": 2,
|
||||
"camera_exposure_time": 200.0,
|
||||
"laser_power": 20.0,
|
||||
"interval_time": 0.0,
|
||||
"defocus": 0.0,
|
||||
}
|
||||
config.verify_experiment_config(self.manager, configuration)
|
||||
assert type(experiment["MicroscopeState"]["channels"]) is DictProxy
|
||||
assert "channel_2" in experiment["MicroscopeState"]["channels"].keys()
|
||||
for k in expected_value:
|
||||
assert (
|
||||
experiment["MicroscopeState"]["channels"]["channel_2"][k]
|
||||
== expected_value[k]
|
||||
)
|
||||
|
||||
def select_random_entries_from_list(self, parameter_list):
|
||||
n = random.randint(1, len(parameter_list))
|
||||
return random.choices(parameter_list, k=n)
|
||||
|
||||
def delete_random_entries_from_dict(self, parameter_list, parameter_dict):
|
||||
n = random.randint(1, len(parameter_list))
|
||||
deleted_parameters = random.choices(parameter_list, k=n)
|
||||
for k in deleted_parameters:
|
||||
if k in parameter_dict.keys():
|
||||
del parameter_dict[k]
|
||||
return deleted_parameters
|
||||
|
||||
def test_load_empty_multi_positions(self):
|
||||
positions_file_path = os.path.join(self.test_root, "multi_positions.yml")
|
||||
with open(positions_file_path, "w") as f:
|
||||
f.write("")
|
||||
positions = load_yaml_file(positions_file_path)
|
||||
new_positions = config.verify_positions_config(positions)
|
||||
assert isinstance(new_positions, list)
|
||||
assert len(new_positions) == 0
|
||||
|
||||
def test_load_multi_positions_with_corrupted_values(self):
|
||||
positions = [
|
||||
[1, 2, 3],
|
||||
["a", "b", "c", 1, 2],
|
||||
[
|
||||
10,
|
||||
"a",
|
||||
30,
|
||||
40,
|
||||
],
|
||||
]
|
||||
new_positions = config.verify_positions_config(positions)
|
||||
assert isinstance(new_positions, list)
|
||||
assert len(new_positions) == 1
|
||||
|
||||
positions = [
|
||||
[1, 2, 3],
|
||||
["a", "b", "c", 1, 2],
|
||||
[1, 2, 3, 4, 5],
|
||||
[
|
||||
10,
|
||||
"a",
|
||||
30,
|
||||
40,
|
||||
],
|
||||
]
|
||||
new_positions = config.verify_positions_config(positions)
|
||||
assert isinstance(new_positions, list)
|
||||
assert len(new_positions) == 2
|
||||
|
||||
positions = [
|
||||
[1, 2, 3],
|
||||
["a", "b", "c", 1, 2],
|
||||
[1, 2, 3, 4, 5],
|
||||
[
|
||||
10,
|
||||
"a",
|
||||
30,
|
||||
40,
|
||||
],
|
||||
[1, 2, 3, 4, 5, 6],
|
||||
]
|
||||
new_positions = config.verify_positions_config(positions)
|
||||
assert isinstance(new_positions, list)
|
||||
assert len(new_positions) == 3
|
||||
426
test/config/test_configuration.py
Normal file
426
test/config/test_configuration.py
Normal file
@@ -0,0 +1,426 @@
|
||||
# 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 Imports
|
||||
import unittest
|
||||
import yaml
|
||||
import os
|
||||
|
||||
# Third Party Imports
|
||||
|
||||
# Local Imports
|
||||
|
||||
|
||||
class TestConfiguration(unittest.TestCase):
|
||||
def setUp(self):
|
||||
current_path = os.path.abspath(os.path.dirname(__file__))
|
||||
root_path = os.path.dirname(os.path.dirname(current_path))
|
||||
yaml_path = os.path.join(
|
||||
root_path, "src", "navigate", "config", "configuration.yaml"
|
||||
)
|
||||
|
||||
with open(yaml_path) as file:
|
||||
self.data = yaml.safe_load(file)
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
# # hardware head section has been removed
|
||||
# def test_hardware_section(self):
|
||||
# expected_hardware = ["daq", "camera", "filter_wheel", "stage", "zoom"]
|
||||
|
||||
# hardware_types = self.data["hardware"].keys()
|
||||
# for hardware_type in hardware_types:
|
||||
# self.assertIn(hardware_type, expected_hardware)
|
||||
# if isinstance(self.data["hardware"][hardware_type], dict):
|
||||
# hardware_keys = self.data["hardware"][hardware_type].keys()
|
||||
# for key in hardware_keys:
|
||||
# self.assertIn("type", self.data["hardware"][hardware_type])
|
||||
# elif isinstance(self.data["hardware"][hardware_type], list):
|
||||
# for i in range(len(self.data["hardware"][hardware_type])):
|
||||
# self.assertIn("type", self.data["hardware"][hardware_type][i])
|
||||
|
||||
def test_gui_section(self):
|
||||
expected_keys = ["channels"]
|
||||
expected_channel_keys = [
|
||||
"count",
|
||||
# "laser_power",
|
||||
# "exposure_time",
|
||||
# "interval_time",
|
||||
]
|
||||
expected_stack_keys = ["step_size", "start_pos", "end_pos"]
|
||||
min_max_step_keys = ["min", "max", "step"]
|
||||
|
||||
gui_keys = self.data["gui"].keys()
|
||||
for key in gui_keys:
|
||||
self.assertIn(key, expected_keys)
|
||||
|
||||
# Channels Entry
|
||||
if key == "channels":
|
||||
channel_keys = self.data["gui"][key].keys()
|
||||
for channel_key in channel_keys:
|
||||
self.assertIn(channel_key, expected_channel_keys)
|
||||
if channel_key != "count":
|
||||
channel_key_keys = self.data["gui"][key][channel_key].keys()
|
||||
for channel_key_key in channel_key_keys:
|
||||
self.assertIn(channel_key_key, min_max_step_keys)
|
||||
|
||||
# Stack Acquisition Entry
|
||||
elif key == "stack_acquisition":
|
||||
stack_keys = self.data["gui"][key].keys()
|
||||
for stack_key in stack_keys:
|
||||
self.assertIn(stack_key, expected_stack_keys)
|
||||
stack_key_keys = self.data["gui"][key][stack_key].keys()
|
||||
for stack_key_key in stack_key_keys:
|
||||
self.assertIn(stack_key_key, min_max_step_keys)
|
||||
|
||||
# Timepoint Entry
|
||||
elif key == "timepoint":
|
||||
timepoint_keys = self.data["gui"][key].keys()
|
||||
for timepoint_key in timepoint_keys:
|
||||
timepoint_key_keys = self.data["gui"][key][timepoint_key].keys()
|
||||
for timepoint_key_key in timepoint_key_keys:
|
||||
self.assertIn(timepoint_key_key, min_max_step_keys)
|
||||
|
||||
else:
|
||||
raise ValueError("Unexpected key in gui section")
|
||||
|
||||
def test_microscope_section(self):
|
||||
expected_hardware = [
|
||||
"daq",
|
||||
"camera",
|
||||
"remote_focus",
|
||||
"galvo",
|
||||
"shutter",
|
||||
"laser",
|
||||
"filter_wheel",
|
||||
"stage",
|
||||
"zoom",
|
||||
]
|
||||
|
||||
microscopes = self.data["microscopes"].keys()
|
||||
for microscope in microscopes:
|
||||
hardware = self.data["microscopes"][microscope].keys()
|
||||
for hardware_type in hardware:
|
||||
self.assertIn(hardware_type, expected_hardware)
|
||||
|
||||
if hardware_type == "daq":
|
||||
self.daq_section(microscope=microscope, hardware_type=hardware_type)
|
||||
|
||||
elif hardware_type == "camera":
|
||||
self.camera_section(
|
||||
microscope=microscope, hardware_type=hardware_type
|
||||
)
|
||||
|
||||
elif hardware_type == "remote_focus":
|
||||
self.remote_focus_section(
|
||||
microscope=microscope, hardware_type=hardware_type
|
||||
)
|
||||
|
||||
elif hardware_type == "galvo":
|
||||
self.galvo_section(
|
||||
microscope=microscope, hardware_type=hardware_type
|
||||
)
|
||||
|
||||
elif hardware_type == "filter_wheel":
|
||||
self.filter_wheel_section(
|
||||
microscope=microscope, hardware_type=hardware_type
|
||||
)
|
||||
elif hardware_type == "stage":
|
||||
self.stage_section(
|
||||
microscope=microscope, hardware_type=hardware_type
|
||||
)
|
||||
|
||||
elif hardware_type == "zoom":
|
||||
self.zoom_section(
|
||||
microscope=microscope, hardware_type=hardware_type
|
||||
)
|
||||
|
||||
elif hardware_type == "shutter":
|
||||
self.shutter_section(
|
||||
microscope=microscope, hardware_type=hardware_type
|
||||
)
|
||||
|
||||
elif hardware_type == "laser":
|
||||
self.laser_section(
|
||||
microscope=microscope, hardware_type=hardware_type
|
||||
)
|
||||
|
||||
else:
|
||||
raise ValueError("Unexpected hardware type")
|
||||
|
||||
def daq_section(self, microscope, hardware_type):
|
||||
expected_daq_keys = [
|
||||
"hardware",
|
||||
"sample_rate",
|
||||
"sweep_time",
|
||||
"master_trigger_out_line",
|
||||
"camera_trigger_out_line",
|
||||
"trigger_source",
|
||||
"laser_port_switcher",
|
||||
"laser_switch_state",
|
||||
]
|
||||
type_keys = ["type"]
|
||||
|
||||
daq_keys = self.data["microscopes"][microscope][hardware_type].keys()
|
||||
for key in daq_keys:
|
||||
if key == "hardware":
|
||||
for type_key in type_keys:
|
||||
self.assertIn(
|
||||
type_key,
|
||||
self.data["microscopes"][microscope][hardware_type][key],
|
||||
)
|
||||
else:
|
||||
self.assertIn(key, expected_daq_keys)
|
||||
|
||||
def camera_section(self, microscope, hardware_type):
|
||||
expected_keys = [
|
||||
"hardware",
|
||||
"defect_correct_mode",
|
||||
"delay",
|
||||
"settle_down",
|
||||
"flip_x",
|
||||
"flip_y",
|
||||
]
|
||||
type_keys = ["type", "serial_number", "camera_connection"]
|
||||
|
||||
camera_keys = self.data["microscopes"][microscope][hardware_type].keys()
|
||||
for key in camera_keys:
|
||||
if key == "hardware":
|
||||
for type_key in type_keys:
|
||||
self.assertIn(
|
||||
type_key,
|
||||
self.data["microscopes"][microscope][hardware_type][key],
|
||||
)
|
||||
else:
|
||||
self.assertIn(key, expected_keys)
|
||||
|
||||
def remote_focus_section(self, microscope, hardware_type):
|
||||
expected_keys = [
|
||||
"hardware",
|
||||
]
|
||||
type_keys = ["type", "channel", "min", "max", "port", "baudrate"]
|
||||
remote_focus_keys = self.data["microscopes"][microscope][hardware_type].keys()
|
||||
for key in remote_focus_keys:
|
||||
if key == "hardware":
|
||||
for type_key in type_keys:
|
||||
self.assertIn(
|
||||
type_key,
|
||||
self.data["microscopes"][microscope][hardware_type][key],
|
||||
)
|
||||
else:
|
||||
self.assertIn(key, expected_keys)
|
||||
|
||||
def galvo_section(self, microscope, hardware_type):
|
||||
expected_keys = [
|
||||
"hardware",
|
||||
"waveform",
|
||||
"phase",
|
||||
]
|
||||
type_keys = ["type", "channel", "min", "max"]
|
||||
|
||||
if isinstance(self.data["microscopes"][microscope][hardware_type], list):
|
||||
for i in range(len(self.data["microscopes"][microscope][hardware_type])):
|
||||
galvo_keys = self.data["microscopes"][microscope][hardware_type][
|
||||
i
|
||||
].keys()
|
||||
for key in galvo_keys:
|
||||
if key == "hardware":
|
||||
for type_key in type_keys:
|
||||
self.assertIn(
|
||||
type_key,
|
||||
self.data["microscopes"][microscope][hardware_type][i][
|
||||
key
|
||||
],
|
||||
)
|
||||
else:
|
||||
self.assertIn(key, expected_keys)
|
||||
else:
|
||||
raise ValueError("Galvo section is not a list")
|
||||
|
||||
def filter_wheel_section(self, microscope, hardware_type):
|
||||
expected_keys = [
|
||||
"hardware",
|
||||
"filter_wheel_delay",
|
||||
"available_filters",
|
||||
]
|
||||
type_keys = ["type", "wheel_number", "port", "baudrate"]
|
||||
|
||||
keys = self.data["microscopes"][microscope][hardware_type].keys()
|
||||
for key in keys:
|
||||
if key == "hardware":
|
||||
for type_key in type_keys:
|
||||
self.assertIn(
|
||||
type_key,
|
||||
self.data["microscopes"][microscope][hardware_type][key],
|
||||
)
|
||||
elif key == "available_filters":
|
||||
assert isinstance(
|
||||
self.data["microscopes"][microscope][hardware_type][key], dict
|
||||
)
|
||||
else:
|
||||
self.assertIn(key, expected_keys)
|
||||
|
||||
def stage_section(self, microscope, hardware_type):
|
||||
expected_keys = [
|
||||
"hardware",
|
||||
"x_max",
|
||||
"x_min",
|
||||
"y_max",
|
||||
"y_min",
|
||||
"z_max",
|
||||
"z_min",
|
||||
"f_max",
|
||||
"f_min",
|
||||
"theta_max",
|
||||
"theta_min",
|
||||
"x_offset",
|
||||
"y_offset",
|
||||
"z_offset",
|
||||
"theta_offset",
|
||||
"f_offset",
|
||||
"joystick_axes",
|
||||
"flip_x",
|
||||
"flip_y",
|
||||
"flip_z",
|
||||
"flip_f",
|
||||
]
|
||||
type_keys = [
|
||||
"type",
|
||||
"serial_number",
|
||||
"axes",
|
||||
"volts_per_micron",
|
||||
"axes_mapping",
|
||||
"max",
|
||||
"min",
|
||||
"controllername",
|
||||
"stages",
|
||||
"refmode",
|
||||
"port",
|
||||
"baudrate",
|
||||
"timeout",
|
||||
]
|
||||
|
||||
for key in self.data["microscopes"][microscope][hardware_type].keys():
|
||||
if key == "hardware":
|
||||
if isinstance(
|
||||
self.data["microscopes"][microscope][hardware_type][key], list
|
||||
):
|
||||
for i in range(
|
||||
len(self.data["microscopes"][microscope][hardware_type][key])
|
||||
):
|
||||
for type_key in type_keys:
|
||||
self.assertIn(
|
||||
type_key,
|
||||
self.data["microscopes"][microscope][hardware_type][
|
||||
key
|
||||
][i],
|
||||
)
|
||||
elif isinstance(
|
||||
self.data["microscopes"][microscope][hardware_type][key], dict
|
||||
):
|
||||
for type_key in type_keys:
|
||||
self.assertIn(
|
||||
type_key,
|
||||
self.data["microscopes"][microscope][hardware_type][key],
|
||||
)
|
||||
else:
|
||||
raise ValueError("Stage hardware is not a list or dict")
|
||||
else:
|
||||
self.assertIn(key, expected_keys)
|
||||
|
||||
def zoom_section(self, microscope, hardware_type):
|
||||
expected_keys = ["hardware", "position", "pixel_size", "stage_positions"]
|
||||
type_keys = ["type", "servo_id", "port", "baudrate"]
|
||||
|
||||
for key in self.data["microscopes"][microscope][hardware_type].keys():
|
||||
if key == "hardware":
|
||||
for type_key in type_keys:
|
||||
self.assertIn(
|
||||
type_key,
|
||||
self.data["microscopes"][microscope][hardware_type][key],
|
||||
)
|
||||
elif key == "position":
|
||||
assert isinstance(
|
||||
self.data["microscopes"][microscope][hardware_type][key], dict
|
||||
)
|
||||
elif key == "pixel_size":
|
||||
assert isinstance(
|
||||
self.data["microscopes"][microscope][hardware_type][key], dict
|
||||
)
|
||||
elif key == "stage_positions":
|
||||
assert isinstance(
|
||||
self.data["microscopes"][microscope][hardware_type][key], dict
|
||||
)
|
||||
else:
|
||||
self.assertIn(key, expected_keys)
|
||||
|
||||
def shutter_section(self, microscope, hardware_type):
|
||||
expected_keys = ["hardware"]
|
||||
type_keys = ["type", "channel", "min", "max"]
|
||||
|
||||
for key in self.data["microscopes"][microscope][hardware_type].keys():
|
||||
if key == "hardware":
|
||||
for type_key in type_keys:
|
||||
self.assertIn(
|
||||
type_key,
|
||||
self.data["microscopes"][microscope][hardware_type][key],
|
||||
)
|
||||
else:
|
||||
self.assertIn(key, expected_keys)
|
||||
|
||||
def laser_section(self, microscope, hardware_type):
|
||||
expected_keys = [
|
||||
"wavelength",
|
||||
"onoff",
|
||||
"power",
|
||||
"type",
|
||||
]
|
||||
|
||||
hardware_keys = ["type", "channel", "min", "max"]
|
||||
|
||||
if isinstance(self.data["microscopes"][microscope][hardware_type], list):
|
||||
for i in range(len(self.data["microscopes"][microscope][hardware_type])):
|
||||
laser_keys = self.data["microscopes"][microscope][hardware_type][
|
||||
i
|
||||
].keys()
|
||||
for key in laser_keys:
|
||||
if key == "onoff" or key == "power":
|
||||
onoff_keys = self.data["microscopes"][microscope][
|
||||
hardware_type
|
||||
][i][key]["hardware"].keys()
|
||||
for onoff_key in onoff_keys:
|
||||
self.assertIn(onoff_key, hardware_keys)
|
||||
else:
|
||||
self.assertIn(key, expected_keys)
|
||||
else:
|
||||
raise ValueError("Laser section is not a list")
|
||||
203
test/config/test_experiment.py
Normal file
203
test/config/test_experiment.py
Normal file
@@ -0,0 +1,203 @@
|
||||
# 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 Imports
|
||||
import unittest
|
||||
import yaml
|
||||
import os
|
||||
import datetime
|
||||
|
||||
# Third Party Imports
|
||||
|
||||
# Local Imports
|
||||
|
||||
|
||||
class TextExperimentFile(unittest.TestCase):
|
||||
"""Test the experiment configuration file."""
|
||||
|
||||
def setUp(self):
|
||||
current_path = os.path.abspath(os.path.dirname(__file__))
|
||||
root_path = os.path.dirname(os.path.dirname(current_path))
|
||||
yaml_path = os.path.join(
|
||||
root_path, "src", "navigate", "config", "experiment.yml"
|
||||
)
|
||||
|
||||
with open(yaml_path) as file:
|
||||
self.data = yaml.safe_load(file)
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
def parse_entries(self, section, expected_values):
|
||||
"""Parse the entries in the configuration file.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
section : str
|
||||
The section of the configuration file to parse
|
||||
expected_values : dict
|
||||
A dictionary of expected values for the section
|
||||
|
||||
|
||||
Raises
|
||||
------
|
||||
AssertionError
|
||||
If the key is not in the section or if the value is not the expected type
|
||||
"""
|
||||
keys = self.data[section].keys()
|
||||
for key in keys:
|
||||
self.assertIn(key, expected_values)
|
||||
assert isinstance(
|
||||
self.data[section][key], expected_values[key]
|
||||
), f"{key} is not of type {expected_values[key]}"
|
||||
|
||||
def test_user(self):
|
||||
expected_values = {"name": str}
|
||||
|
||||
self.parse_entries(section="User", expected_values=expected_values)
|
||||
|
||||
def test_saving(self):
|
||||
expected_values = {
|
||||
"root_directory": str,
|
||||
"save_directory": str,
|
||||
"user": str,
|
||||
"tissue": str,
|
||||
"celltype": str,
|
||||
"label": str,
|
||||
"file_type": str,
|
||||
"date": datetime.date,
|
||||
"solvent": str,
|
||||
}
|
||||
|
||||
self.parse_entries(section="Saving", expected_values=expected_values)
|
||||
|
||||
def test_camera_parameters(self):
|
||||
expected_values = {
|
||||
"x_pixels": int,
|
||||
"y_pixels": int,
|
||||
"sensor_mode": str,
|
||||
"readout_direction": str,
|
||||
"number_of_pixels": int,
|
||||
"binning": str,
|
||||
"pixel_size": float,
|
||||
"frames_to_average": float,
|
||||
"databuffer_size": int,
|
||||
}
|
||||
|
||||
self.parse_entries(section="CameraParameters", expected_values=expected_values)
|
||||
|
||||
def test_autofocus_parameters(self):
|
||||
expected_values = {
|
||||
"coarse_range": int,
|
||||
"coarse_step_size": int,
|
||||
"coarse_selected": int,
|
||||
"fine_range": int,
|
||||
"fine_step_size": int,
|
||||
"fine_selected": bool,
|
||||
"robust_fit": bool,
|
||||
"spline_fit": bool,
|
||||
"test_significance": bool,
|
||||
}
|
||||
|
||||
self.parse_entries(
|
||||
section="AutoFocusParameters", expected_values=expected_values
|
||||
)
|
||||
|
||||
def test_stage_parameters(self):
|
||||
expected_values = {
|
||||
"xy_step": float,
|
||||
"z_step": float,
|
||||
"theta_step": float,
|
||||
"f_step": float,
|
||||
"x": float,
|
||||
"y": float,
|
||||
"z": float,
|
||||
"theta": float,
|
||||
"f": float,
|
||||
"limits": bool,
|
||||
}
|
||||
|
||||
self.parse_entries(section="StageParameters", expected_values=expected_values)
|
||||
|
||||
def test_microscope_state(self):
|
||||
expected_values = {
|
||||
"microscope_name": str,
|
||||
"image_mode": str,
|
||||
"zoom": str,
|
||||
"stack_cycling_mode": str,
|
||||
"start_position": float,
|
||||
"end_position": float,
|
||||
"step_size": float,
|
||||
"number_z_steps": float,
|
||||
"timepoints": int,
|
||||
"stack_pause": float,
|
||||
"is_save": bool,
|
||||
"stack_acq_time": float,
|
||||
"timepoint_interval": int,
|
||||
"experiment_duration": float,
|
||||
"is_multiposition": bool,
|
||||
"channels": dict,
|
||||
"stack_z_origin": float,
|
||||
"stack_focus_origin": float,
|
||||
"start_focus": float,
|
||||
"end_focus": float,
|
||||
"abs_z_start": float,
|
||||
"abs_z_end": float,
|
||||
"waveform_template": str,
|
||||
}
|
||||
|
||||
self.parse_entries(section="MicroscopeState", expected_values=expected_values)
|
||||
|
||||
# Check that the channels dictionary has the correct keys
|
||||
channel_keys = self.data["MicroscopeState"]["channels"].keys()
|
||||
for key in channel_keys:
|
||||
# Number of channels can vary depending upon the experiment.
|
||||
assert "channel_" in key
|
||||
expected_values = {
|
||||
"is_selected": bool,
|
||||
"laser": str,
|
||||
"laser_index": int,
|
||||
"filter": str,
|
||||
"filter_position": int,
|
||||
"camera_exposure_time": float,
|
||||
"laser_power": str,
|
||||
"interval_time": str,
|
||||
"defocus": float,
|
||||
}
|
||||
|
||||
key_keys = self.data["MicroscopeState"]["channels"][key].keys()
|
||||
for key_key in key_keys:
|
||||
self.assertIn(key_key, expected_values)
|
||||
assert isinstance(
|
||||
self.data["MicroscopeState"]["channels"][key][key_key],
|
||||
expected_values[key_key],
|
||||
), f"{key_key} is not of type {expected_values[key_key]}"
|
||||
62
test/config/test_rest_api_config.py
Normal file
62
test/config/test_rest_api_config.py
Normal file
@@ -0,0 +1,62 @@
|
||||
# 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 Imports
|
||||
import unittest
|
||||
import yaml
|
||||
import os
|
||||
|
||||
# Third Party Imports
|
||||
|
||||
# Local Imports
|
||||
|
||||
|
||||
class TestAPIConfiguration(unittest.TestCase):
|
||||
def setUp(self):
|
||||
current_path = os.path.abspath(os.path.dirname(__file__))
|
||||
root_path = os.path.dirname(os.path.dirname(current_path))
|
||||
yaml_path = os.path.join(
|
||||
root_path, "src", "navigate", "config", "rest_api_config.yml"
|
||||
)
|
||||
|
||||
with open(yaml_path) as file:
|
||||
self.data = yaml.safe_load(file)
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
def test_api_config(self):
|
||||
keys = self.data["Ilastik"].keys()
|
||||
self.assertIn("url", keys)
|
||||
ilastik_path = self.data["Ilastik"]["url"]
|
||||
assert "http" in ilastik_path, "Ilastik path not found in config file."
|
||||
assert "ilastik" in ilastik_path, "Ilastik path not found in config file."
|
||||
110
test/config/test_waveform_constants.py
Normal file
110
test/config/test_waveform_constants.py
Normal file
@@ -0,0 +1,110 @@
|
||||
# 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 Imports
|
||||
import unittest
|
||||
import yaml
|
||||
import os
|
||||
|
||||
|
||||
# Third Party Imports
|
||||
|
||||
# Local Imports
|
||||
|
||||
|
||||
class TestWaveformConstants(unittest.TestCase):
|
||||
def test_yaml_structure(self):
|
||||
current_path = os.path.abspath(os.path.dirname(__file__))
|
||||
root_path = os.path.dirname(os.path.dirname(current_path))
|
||||
yaml_path = os.path.join(
|
||||
root_path, "src", "navigate", "config", "waveform_constants.yml"
|
||||
)
|
||||
|
||||
# Load the YAML file
|
||||
with open(yaml_path) as file:
|
||||
data = yaml.safe_load(file)
|
||||
|
||||
expected_keys = ["amplitude", "offset"]
|
||||
|
||||
microscope_names = data["remote_focus_constants"].keys()
|
||||
for microscope_name in microscope_names:
|
||||
assert isinstance(microscope_name, str)
|
||||
|
||||
magnifications = data["remote_focus_constants"][microscope_name].keys()
|
||||
for magnification in magnifications:
|
||||
assert isinstance(magnification, str)
|
||||
|
||||
wavelengths = data["remote_focus_constants"][microscope_name][
|
||||
magnification
|
||||
].keys()
|
||||
for wavelength in wavelengths:
|
||||
assert isinstance(wavelength, str)
|
||||
|
||||
for key in expected_keys:
|
||||
assert (
|
||||
key
|
||||
in data["remote_focus_constants"][microscope_name][
|
||||
magnification
|
||||
][wavelength].keys()
|
||||
)
|
||||
|
||||
expected_keys = ["amplitude", "offset", "frequency"]
|
||||
|
||||
galvos = data["galvo_constants"].keys()
|
||||
for galvo in galvos:
|
||||
assert isinstance(galvo, str)
|
||||
|
||||
for microscope_name in microscope_names:
|
||||
assert microscope_name in data["galvo_constants"][galvo].keys()
|
||||
|
||||
magnifications = data["galvo_constants"][galvo][microscope_name].keys()
|
||||
for magnification in magnifications:
|
||||
assert (
|
||||
magnification
|
||||
in data["galvo_constants"][galvo][microscope_name].keys()
|
||||
)
|
||||
for key in expected_keys:
|
||||
assert (
|
||||
key
|
||||
in data["galvo_constants"][galvo][microscope_name][
|
||||
magnification
|
||||
].keys()
|
||||
)
|
||||
|
||||
other_constants = data["other_constants"].keys()
|
||||
assert "remote_focus_settle_duration" in other_constants
|
||||
# assert "percent_smoothing" in other_constants
|
||||
# assert "remote_focus_delay" in other_constants
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
64
test/config/test_waveform_templates.py
Normal file
64
test/config/test_waveform_templates.py
Normal file
@@ -0,0 +1,64 @@
|
||||
# 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 Imports
|
||||
import unittest
|
||||
import yaml
|
||||
import os
|
||||
|
||||
# Third Party Imports
|
||||
|
||||
# Local Imports
|
||||
|
||||
|
||||
class TestWaveformTemplates(unittest.TestCase):
|
||||
def test_yaml_structure(self):
|
||||
current_path = os.path.abspath(os.path.dirname(__file__))
|
||||
root_path = os.path.dirname(os.path.dirname(current_path))
|
||||
yaml_path = os.path.join(
|
||||
root_path, "src", "navigate", "config", "waveform_templates.yml"
|
||||
)
|
||||
|
||||
with open(yaml_path) as file:
|
||||
data = yaml.safe_load(file)
|
||||
|
||||
expected_keys = ["Default", "Confocal-Projection", "Bidirectional"]
|
||||
|
||||
waveform_keys = data.keys()
|
||||
for key in waveform_keys:
|
||||
assert key in expected_keys
|
||||
|
||||
for subkey in data[key].keys():
|
||||
assert subkey in ["repeat", "expand"]
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user