feat: init
This commit is contained in:
476
test/model/features/test_common_features.py
Normal file
476
test/model/features/test_common_features.py
Normal file
@@ -0,0 +1,476 @@
|
||||
# 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.
|
||||
|
||||
|
||||
import random
|
||||
import pytest
|
||||
from navigate.model.features.common_features import ZStackAcquisition
|
||||
|
||||
|
||||
class TestZStack:
|
||||
@pytest.fixture(autouse=True)
|
||||
def _prepare_test(self, dummy_model_to_test_features):
|
||||
self.model = dummy_model_to_test_features
|
||||
self.model.virtual_microscopes = {}
|
||||
self.config = self.model.configuration["experiment"]["MicroscopeState"]
|
||||
self.record_num = 0
|
||||
self.feature_list = [[{"name": ZStackAcquisition}]]
|
||||
|
||||
self.config["start_position"] = 0
|
||||
self.config["end_position"] = 200
|
||||
self.config["number_z_steps"] = 5
|
||||
self.config["step_size"] = (
|
||||
self.config["end_position"] - self.config["start_position"]
|
||||
) / self.config["number_z_steps"]
|
||||
|
||||
position_list = self.model.configuration["multi_positions"]
|
||||
if len(position_list) < 5:
|
||||
for i in range(5):
|
||||
pos = [0] * len(position_list[0])
|
||||
for i in range(len(position_list[0])):
|
||||
pos[i] = random.randint(1, 10000)
|
||||
position_list.append(pos)
|
||||
|
||||
def get_next_record(self, record_prefix, idx):
|
||||
idx += 1
|
||||
while self.model.signal_records[idx][0] != record_prefix:
|
||||
idx += 1
|
||||
if idx >= self.record_num:
|
||||
assert False, "Some device movements are missed!"
|
||||
return idx
|
||||
|
||||
def exist_record(self, record_prefix, idx_start, idx_end):
|
||||
for i in range(idx_start, idx_end + 1):
|
||||
if self.model.signal_records[i][0] == record_prefix:
|
||||
return True
|
||||
return False
|
||||
|
||||
def z_stack_verification(self):
|
||||
self.record_num = len(self.model.signal_records)
|
||||
change_channel_func_str = "active_microscope.prepare_next_channel"
|
||||
close_daq_tasks_str = "active_microscope.daq.stop_acquisition"
|
||||
create_daq_tasks_str = "active_microscope.daq.prepare_acquisition"
|
||||
# save all the selected channels
|
||||
selected_channels = []
|
||||
for channel_key in self.config["channels"].keys():
|
||||
if self.config["channels"][channel_key]["is_selected"]:
|
||||
selected_channels.append(dict(self.config["channels"][channel_key]))
|
||||
selected_channels[-1]["id"] = int(channel_key[len("channel_") :])
|
||||
|
||||
# restore z and f
|
||||
pos_dict = self.model.get_stage_position()
|
||||
restore_z = pos_dict["z_pos"]
|
||||
restore_f = pos_dict["f_pos"]
|
||||
|
||||
mode = self.config["stack_cycling_mode"] # per_z/pre_stack
|
||||
is_multiposition = self.config["is_multiposition"]
|
||||
if is_multiposition:
|
||||
positions = self.model.configuration["multi_positions"][1:]
|
||||
else:
|
||||
pos_dict = self.model.configuration["experiment"]["StageParameters"]
|
||||
positions = [
|
||||
[
|
||||
pos_dict["x"],
|
||||
pos_dict["y"],
|
||||
self.config.get("stack_z_origin", pos_dict["z"]),
|
||||
pos_dict["theta"],
|
||||
self.config.get("stack_focus_origin", pos_dict["f"]),
|
||||
]
|
||||
]
|
||||
|
||||
z_step = self.config["step_size"]
|
||||
f_step = (self.config["end_focus"] - self.config["start_focus"]) / self.config[
|
||||
"number_z_steps"
|
||||
]
|
||||
|
||||
frame_id = 0
|
||||
idx = -1
|
||||
z_moved_times = 0
|
||||
if mode == "per_z":
|
||||
z_should_move_times = len(positions) * int(self.config["number_z_steps"])
|
||||
else:
|
||||
z_should_move_times = (
|
||||
len(selected_channels)
|
||||
* len(positions)
|
||||
* int(self.config["number_z_steps"])
|
||||
)
|
||||
|
||||
has_ni_galvo_stage = self.model.configuration["configuration"]["microscopes"][
|
||||
self.config["microscope_name"]
|
||||
]["stage"]["has_ni_galvo_stage"]
|
||||
prepared_next_channel = False
|
||||
|
||||
# prepare first channel in pre_signal_func
|
||||
idx = self.get_next_record(change_channel_func_str, idx)
|
||||
prepared_next_channel = True
|
||||
pre_change_channel_idx = idx
|
||||
assert (
|
||||
self.model.signal_records[idx][2]["__test_frame_id_completed"] == -1
|
||||
), "prepare first channel should happen before 0"
|
||||
assert (
|
||||
self.model.signal_records[idx][2]["__test_frame_id"] == 0
|
||||
), "prepare first channel should happen for frame: 0"
|
||||
|
||||
for i, pos in enumerate(positions):
|
||||
|
||||
idx = self.get_next_record("move_stage", idx)
|
||||
|
||||
# x, y, theta
|
||||
pos_moved = self.model.signal_records[idx][1][0]
|
||||
for i, axis in [(0, "x"), (1, "y"), (3, "theta")]:
|
||||
assert pos[i] == pos_moved[axis + "_abs"], (
|
||||
f"should move to {axis}: {pos[i]}, "
|
||||
f"but moved to {pos_moved[axis + '_abs']}"
|
||||
)
|
||||
|
||||
# (x, y, z, theta, f)
|
||||
z_pos = pos[2] + self.config["start_position"]
|
||||
f_pos = pos[4] + self.config["start_focus"]
|
||||
|
||||
if mode == "per_z":
|
||||
f_pos += selected_channels[0]["defocus"]
|
||||
for j in range(self.config["number_z_steps"]):
|
||||
idx = self.get_next_record("move_stage", idx)
|
||||
|
||||
pos_moved = self.model.signal_records[idx][1][0]
|
||||
# z, f
|
||||
assert pos_moved["z_abs"] == z_pos + j * z_step, (
|
||||
f"should move to z: {z_pos + j * z_step}, "
|
||||
f"but moved to {pos_moved['z_abs']}"
|
||||
)
|
||||
assert pos_moved["f_abs"] == f_pos + j * f_step, (
|
||||
f"should move to f: {f_pos + j * f_step}, "
|
||||
f"but moved to {pos_moved['f_abs']}"
|
||||
)
|
||||
z_moved_times += 1
|
||||
|
||||
# if the system has NIGalvo stage, should close the DAQ tasks and
|
||||
# then create new tasks to override the new waveforms
|
||||
if has_ni_galvo_stage and prepared_next_channel:
|
||||
idx = self.get_next_record(close_daq_tasks_str, idx)
|
||||
pre_change_channel_idx = idx
|
||||
assert (
|
||||
self.model.signal_records[idx][2]["__test_frame_id"]
|
||||
== frame_id
|
||||
), f"close DAQ tasks should happen before {frame_id}"
|
||||
|
||||
idx = self.get_next_record(create_daq_tasks_str, idx)
|
||||
pre_change_channel_idx = idx
|
||||
assert (
|
||||
self.model.signal_records[idx][2]["__test_frame_id"]
|
||||
== frame_id
|
||||
), f"create DAQ tasks should happen before {frame_id}"
|
||||
|
||||
# channel
|
||||
for k in range(len(selected_channels)):
|
||||
idx = self.get_next_record(change_channel_func_str, idx)
|
||||
prepared_next_channel = True
|
||||
assert (
|
||||
self.model.signal_records[idx][2]["__test_frame_id"]
|
||||
== frame_id
|
||||
), (
|
||||
"prepare next channel (change channel) "
|
||||
f"should happen after {frame_id}"
|
||||
)
|
||||
|
||||
assert (
|
||||
self.model.signal_records[idx][2][
|
||||
"__test_frame_id_completed"
|
||||
]
|
||||
== self.model.signal_records[idx][2]["__test_frame_id"]
|
||||
), (
|
||||
"prepare next channel (change channel) "
|
||||
"should happen inside signal_end_func()"
|
||||
)
|
||||
|
||||
assert (
|
||||
self.exist_record(
|
||||
change_channel_func_str,
|
||||
pre_change_channel_idx + 1,
|
||||
idx - 1,
|
||||
)
|
||||
is False
|
||||
), (
|
||||
"prepare next channel (change channel) "
|
||||
"should not happen more than once"
|
||||
)
|
||||
pre_change_channel_idx = idx
|
||||
frame_id += 1
|
||||
|
||||
else: # per_stack
|
||||
for k in range(len(selected_channels)):
|
||||
# z
|
||||
f_pos += selected_channels[k]["defocus"]
|
||||
for j in range(self.config["number_z_steps"]):
|
||||
idx = self.get_next_record("move_stage", idx)
|
||||
|
||||
pos_moved = self.model.signal_records[idx][1][0]
|
||||
# z, f
|
||||
assert pos_moved["z_abs"] == z_pos + j * z_step, (
|
||||
f"should move to z: {z_pos + j * z_step}, "
|
||||
f"but moved to {pos_moved['z_abs']}"
|
||||
)
|
||||
assert pos_moved["f_abs"] == f_pos + j * f_step, (
|
||||
f"should move to f: {f_pos + j * f_step}, "
|
||||
f"but moved to {pos_moved['f_abs']}"
|
||||
)
|
||||
z_moved_times += 1
|
||||
frame_id += 1
|
||||
f_pos -= selected_channels[k]["defocus"]
|
||||
idx = self.get_next_record(change_channel_func_str, idx)
|
||||
prepared_next_channel = True
|
||||
assert (
|
||||
self.model.signal_records[idx][2]["__test_frame_id"]
|
||||
== frame_id - 1
|
||||
), (
|
||||
"prepare next channel (change channel) "
|
||||
f"should happen at {frame_id - 1}"
|
||||
)
|
||||
|
||||
# restore z, f
|
||||
idx = self.get_next_record("move_stage", idx)
|
||||
pos_moved = self.model.signal_records[idx][1][0]
|
||||
assert (
|
||||
pos_moved["z_abs"] == restore_z
|
||||
), f"should restore z to {restore_z}, but moved to {pos_moved['z_abs']}"
|
||||
assert (
|
||||
pos_moved["f_abs"] == restore_f
|
||||
), f"should restore f to {restore_f}, but moved to {pos_moved['f_abs']}"
|
||||
|
||||
assert z_moved_times == z_should_move_times, (
|
||||
f"should verify all the stage movements! {z_moved_times} -- "
|
||||
f"{z_should_move_times}"
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize("has_ni_galvo_stage", [False])
|
||||
def test_single_position_one_channel_per_z(self, has_ni_galvo_stage):
|
||||
# single position
|
||||
self.config["is_multiposition"] = False
|
||||
self.model.configuration["configuration"]["microscopes"][
|
||||
self.config["microscope_name"]
|
||||
]["stage"]["has_ni_galvo_stage"] = has_ni_galvo_stage
|
||||
|
||||
# 1 channel per_z
|
||||
self.config["stack_cycling_mode"] = "per_z"
|
||||
self.config["channels"]["channel_1"]["is_selected"] = True
|
||||
self.config["channels"]["channel_2"]["is_selected"] = False
|
||||
self.config["channels"]["channel_3"]["is_selected"] = False
|
||||
self.model.start(self.feature_list)
|
||||
print(self.model.signal_records)
|
||||
self.z_stack_verification()
|
||||
|
||||
@pytest.mark.parametrize("has_ni_galvo_stage", [False])
|
||||
def test_single_position_one_channel_per_stack(self, has_ni_galvo_stage):
|
||||
# single position
|
||||
self.config["is_multiposition"] = False
|
||||
self.model.configuration["configuration"]["microscopes"][
|
||||
self.config["microscope_name"]
|
||||
]["stage"]["has_ni_galvo_stage"] = has_ni_galvo_stage
|
||||
|
||||
# 1 channel per_stack
|
||||
self.config["stack_cycling_mode"] = "per_stack"
|
||||
self.config["channels"]["channel_1"]["is_selected"] = True
|
||||
self.config["channels"]["channel_2"]["is_selected"] = False
|
||||
self.config["channels"]["channel_3"]["is_selected"] = False
|
||||
self.model.start(self.feature_list)
|
||||
self.z_stack_verification()
|
||||
|
||||
@pytest.mark.parametrize("has_ni_galvo_stage", [False])
|
||||
def test_single_position_two_channels_per_z(self, has_ni_galvo_stage):
|
||||
# single position
|
||||
self.config["is_multiposition"] = False
|
||||
self.model.configuration["configuration"]["microscopes"][
|
||||
self.config["microscope_name"]
|
||||
]["stage"]["has_ni_galvo_stage"] = has_ni_galvo_stage
|
||||
|
||||
# 2 channels per_z
|
||||
self.config["stack_cycling_mode"] = "per_z"
|
||||
for i in range(3):
|
||||
for j in range(3):
|
||||
self.config["channels"]["channel_" + str(j + 1)]["is_selected"] = True
|
||||
self.config["channels"]["channel_" + str(i + 1)]["is_selected"] = False
|
||||
self.model.start(self.feature_list)
|
||||
self.z_stack_verification()
|
||||
|
||||
@pytest.mark.parametrize("has_ni_galvo_stage", [False])
|
||||
def test_single_position_two_channels_per_stack(self, has_ni_galvo_stage):
|
||||
# single position
|
||||
self.config["is_multiposition"] = False
|
||||
self.model.configuration["configuration"]["microscopes"][
|
||||
self.config["microscope_name"]
|
||||
]["stage"]["has_ni_galvo_stage"] = has_ni_galvo_stage
|
||||
|
||||
# 2 channels per_stack
|
||||
self.config["stack_cycling_mode"] = "per_stack"
|
||||
for i in range(3):
|
||||
for j in range(3):
|
||||
self.config["channels"]["channel_" + str(j + 1)]["is_selected"] = True
|
||||
self.config["channels"]["channel_" + str(i + 1)]["is_selected"] = False
|
||||
self.model.start(self.feature_list)
|
||||
self.z_stack_verification()
|
||||
|
||||
@pytest.mark.parametrize("has_ni_galvo_stage", [False])
|
||||
def test_single_position_three_channels_per_stack(self, has_ni_galvo_stage):
|
||||
# single position
|
||||
self.config["is_multiposition"] = False
|
||||
self.model.configuration["configuration"]["microscopes"][
|
||||
self.config["microscope_name"]
|
||||
]["stage"]["has_ni_galvo_stage"] = has_ni_galvo_stage
|
||||
|
||||
# 3 channels per_stack
|
||||
self.config["channels"]["channel_1"]["is_selected"] = True
|
||||
self.config["channels"]["channel_2"]["is_selected"] = True
|
||||
self.config["channels"]["channel_3"]["is_selected"] = True
|
||||
self.config["stack_cycling_mode"] = "per_stack"
|
||||
self.model.start(self.feature_list)
|
||||
self.z_stack_verification()
|
||||
|
||||
@pytest.mark.parametrize("has_ni_galvo_stage", [False])
|
||||
def test_single_position_three_channels_per_z(self, has_ni_galvo_stage):
|
||||
# single position
|
||||
self.config["is_multiposition"] = False
|
||||
self.model.configuration["configuration"]["microscopes"][
|
||||
self.config["microscope_name"]
|
||||
]["stage"]["has_ni_galvo_stage"] = has_ni_galvo_stage
|
||||
|
||||
# 3 channels per_z
|
||||
self.config["channels"]["channel_1"]["is_selected"] = True
|
||||
self.config["channels"]["channel_2"]["is_selected"] = True
|
||||
self.config["channels"]["channel_3"]["is_selected"] = True
|
||||
self.config["stack_cycling_mode"] = "per_z"
|
||||
self.model.start(self.feature_list)
|
||||
self.z_stack_verification()
|
||||
|
||||
@pytest.mark.parametrize("has_ni_galvo_stage", [False])
|
||||
def test_multi_position_one_channel_per_z(self, has_ni_galvo_stage):
|
||||
# multi position
|
||||
self.config["is_multiposition"] = True
|
||||
self.model.configuration["configuration"]["microscopes"][
|
||||
self.config["microscope_name"]
|
||||
]["stage"]["has_ni_galvo_stage"] = has_ni_galvo_stage
|
||||
|
||||
# 1 channel per_z
|
||||
self.config["stack_cycling_mode"] = "per_z"
|
||||
self.config["channels"]["channel_1"]["is_selected"] = True
|
||||
self.config["channels"]["channel_2"]["is_selected"] = False
|
||||
self.config["channels"]["channel_3"]["is_selected"] = False
|
||||
self.model.start(self.feature_list)
|
||||
self.z_stack_verification()
|
||||
|
||||
self.config["is_multiposition"] = False
|
||||
|
||||
@pytest.mark.parametrize("has_ni_galvo_stage", [False])
|
||||
def test_multi_position_one_channel_per_stack(self, has_ni_galvo_stage):
|
||||
self.config["is_multiposition"] = True
|
||||
self.model.configuration["configuration"]["microscopes"][
|
||||
self.config["microscope_name"]
|
||||
]["stage"]["has_ni_galvo_stage"] = has_ni_galvo_stage
|
||||
|
||||
# 1 channel per_stack
|
||||
self.config["stack_cycling_mode"] = "per_stack"
|
||||
self.config["channels"]["channel_1"]["is_selected"] = True
|
||||
self.config["channels"]["channel_2"]["is_selected"] = False
|
||||
self.config["channels"]["channel_3"]["is_selected"] = False
|
||||
self.model.start(self.feature_list)
|
||||
self.z_stack_verification()
|
||||
|
||||
self.config["is_multiposition"] = False
|
||||
|
||||
@pytest.mark.parametrize("has_ni_galvo_stage", [False])
|
||||
def test_multi_position_two_channels_per_z(self, has_ni_galvo_stage):
|
||||
self.config["is_multiposition"] = True
|
||||
self.model.configuration["configuration"]["microscopes"][
|
||||
self.config["microscope_name"]
|
||||
]["stage"]["has_ni_galvo_stage"] = has_ni_galvo_stage
|
||||
|
||||
# 2 channels per_z
|
||||
self.config["stack_cycling_mode"] = "per_z"
|
||||
for i in range(3):
|
||||
for j in range(3):
|
||||
self.config["channels"]["channel_" + str(j + 1)]["is_selected"] = True
|
||||
self.config["channels"]["channel_" + str(i + 1)]["is_selected"] = False
|
||||
self.model.start(self.feature_list)
|
||||
self.z_stack_verification()
|
||||
|
||||
self.config["is_multiposition"] = False
|
||||
|
||||
@pytest.mark.parametrize("has_ni_galvo_stage", [False])
|
||||
def test_multi_position_two_channels_per_stack(self, has_ni_galvo_stage):
|
||||
self.config["is_multiposition"] = True
|
||||
self.model.configuration["configuration"]["microscopes"][
|
||||
self.config["microscope_name"]
|
||||
]["stage"]["has_ni_galvo_stage"] = has_ni_galvo_stage
|
||||
|
||||
# 2 channels per_stack
|
||||
self.config["stack_cycling_mode"] = "per_stack"
|
||||
for i in range(3):
|
||||
for j in range(3):
|
||||
self.config["channels"]["channel_" + str(j + 1)]["is_selected"] = True
|
||||
self.config["channels"]["channel_" + str(i + 1)]["is_selected"] = False
|
||||
self.model.start(self.feature_list)
|
||||
self.z_stack_verification()
|
||||
|
||||
self.config["is_multiposition"] = False
|
||||
|
||||
@pytest.mark.parametrize("has_ni_galvo_stage", [False])
|
||||
def test_multi_position_three_channels_per_stack(self, has_ni_galvo_stage):
|
||||
self.config["is_multiposition"] = True
|
||||
self.model.configuration["configuration"]["microscopes"][
|
||||
self.config["microscope_name"]
|
||||
]["stage"]["has_ni_galvo_stage"] = has_ni_galvo_stage
|
||||
|
||||
# 3 channels per_stack
|
||||
self.config["channels"]["channel_1"]["is_selected"] = True
|
||||
self.config["channels"]["channel_2"]["is_selected"] = True
|
||||
self.config["channels"]["channel_3"]["is_selected"] = True
|
||||
self.config["stack_cycling_mode"] = "per_stack"
|
||||
self.model.start(self.feature_list)
|
||||
self.z_stack_verification()
|
||||
|
||||
self.config["is_multiposition"] = False
|
||||
|
||||
@pytest.mark.parametrize("has_ni_galvo_stage", [False])
|
||||
def test_multi_position_three_channels_per_z(self, has_ni_galvo_stage):
|
||||
self.config["is_multiposition"] = True
|
||||
self.model.configuration["configuration"]["microscopes"][
|
||||
self.config["microscope_name"]
|
||||
]["stage"]["has_ni_galvo_stage"] = has_ni_galvo_stage
|
||||
|
||||
# 3 channels per_z
|
||||
self.config["channels"]["channel_1"]["is_selected"] = True
|
||||
self.config["channels"]["channel_2"]["is_selected"] = True
|
||||
self.config["channels"]["channel_3"]["is_selected"] = True
|
||||
self.config["stack_cycling_mode"] = "per_z"
|
||||
self.model.start(self.feature_list)
|
||||
self.z_stack_verification()
|
||||
|
||||
self.config["is_multiposition"] = False
|
||||
Reference in New Issue
Block a user