243 lines
8.7 KiB
Python
243 lines
8.7 KiB
Python
# Copyright (c) 2021-2025 The University of Texas Southwestern Medical Center.
|
|
# All rights reserved.
|
|
|
|
# Redistribution and use in source and binary forms, with or without
|
|
# modification, are permitted for academic and research use only (subject to the
|
|
# limitations in the disclaimer below) provided that the following conditions are met:
|
|
|
|
# * Redistributions of source code must retain the above copyright notice,
|
|
# this list of conditions and the following disclaimer.
|
|
|
|
# * Redistributions in binary form must reproduce the above copyright
|
|
# notice, this list of conditions and the following disclaimer in the
|
|
# documentation and/or other materials provided with the distribution.
|
|
|
|
# * Neither the name of the copyright holders nor the names of its
|
|
# contributors may be used to endorse or promote products derived from this
|
|
# software without specific prior written permission.
|
|
|
|
# NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY
|
|
# THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
|
# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
|
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
|
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
|
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
|
|
# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
# POSSIBILITY OF SUCH DAMAGE.
|
|
#
|
|
|
|
import pytest
|
|
import random
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def dummy_microscope(dummy_model):
|
|
from navigate.model.microscope import Microscope
|
|
from navigate.model.device_startup_functions import load_devices
|
|
|
|
devices_dict = load_devices(
|
|
dummy_model.active_microscope_name, dummy_model.configuration, is_synthetic=True
|
|
)
|
|
|
|
return Microscope(
|
|
dummy_model.active_microscope_name,
|
|
dummy_model.configuration,
|
|
devices_dict,
|
|
is_synthetic=True,
|
|
is_virtual=False,
|
|
)
|
|
|
|
|
|
def test_prepare_acquisition(dummy_microscope):
|
|
waveform_dict = dummy_microscope.prepare_acquisition()
|
|
|
|
channels = dummy_microscope.configuration["experiment"]["MicroscopeState"][
|
|
"channels"
|
|
]
|
|
|
|
assert dummy_microscope.current_channel == 0
|
|
assert dummy_microscope.central_focus is None
|
|
assert dummy_microscope.available_channels == list(
|
|
map(
|
|
lambda c: int(c[len("channel_") :]),
|
|
filter(lambda k: channels[k]["is_selected"], channels.keys()),
|
|
)
|
|
)
|
|
assert dummy_microscope.camera.is_acquiring is True
|
|
assert dummy_microscope.shutter.shutter_state is True
|
|
assert isinstance(waveform_dict, dict)
|
|
assert [
|
|
k in waveform_dict.keys()
|
|
for k in ["camera_waveform", "remote_focus_waveform", "galvo_waveform"]
|
|
]
|
|
|
|
def test_move_stage(dummy_microscope):
|
|
import numpy as np
|
|
|
|
acquisition_mode = dummy_microscope.configuration["experiment"]["MicroscopeState"][
|
|
"image_mode"
|
|
]
|
|
|
|
expected_device_flag = {
|
|
"continous": True,
|
|
"single": True,
|
|
"z-stack": False,
|
|
"customized": False,
|
|
}
|
|
for mode in expected_device_flag:
|
|
dummy_microscope.configuration["experiment"]["MicroscopeState"][
|
|
"image_mode"
|
|
] = mode
|
|
|
|
# move stage to random position
|
|
axes = ["x", "y", "z", "theta", "f"]
|
|
for i in range(5):
|
|
test_axes = random.sample(axes, i+1)
|
|
pos_dict = {
|
|
f"{k}_abs": v
|
|
for k, v in zip(test_axes, np.random.rand(len(test_axes)) * 100)
|
|
}
|
|
dummy_microscope.move_stage(pos_dict, wait_until_done=True)
|
|
|
|
assert dummy_microscope.ask_stage_for_position == expected_device_flag[mode]
|
|
|
|
if expected_device_flag[mode] == False:
|
|
# assert position is cached
|
|
for axis in test_axes:
|
|
assert round(dummy_microscope.ret_pos_dict[axis + "_pos"], 2) == round(
|
|
pos_dict[f"{axis}_abs"], 2
|
|
)
|
|
|
|
# set back acquisition mode
|
|
dummy_microscope.configuration["experiment"]["MicroscopeState"][
|
|
"image_mode"
|
|
] = acquisition_mode
|
|
|
|
def test_get_stage_position(dummy_microscope):
|
|
import numpy as np
|
|
|
|
acquisition_mode = dummy_microscope.configuration["experiment"]["MicroscopeState"][
|
|
"image_mode"
|
|
]
|
|
|
|
report_position_funcs = {}
|
|
axes_dict = {}
|
|
for stage, axes in dummy_microscope.stages_list:
|
|
for axis in axes:
|
|
axes_dict[axis] = axes
|
|
report_position_funcs[axis] = stage.report_position
|
|
|
|
is_called = dict([(axis, False) for axis in dummy_microscope.stages])
|
|
def report_position_mock(axis):
|
|
def func():
|
|
for a in axes_dict[axis]:
|
|
is_called[a] = True
|
|
return report_position_funcs[axis]()
|
|
return func
|
|
|
|
for axis in dummy_microscope.stages:
|
|
dummy_microscope.stages[axis].report_position = report_position_mock(axis)
|
|
|
|
expected_device_flag = {
|
|
"continous": True,
|
|
"single": True,
|
|
"z-stack": False,
|
|
"customized": False,
|
|
}
|
|
for mode in expected_device_flag:
|
|
dummy_microscope.configuration["experiment"]["MicroscopeState"][
|
|
"image_mode"
|
|
] = mode
|
|
|
|
# move stage to random position
|
|
pos_dict = {
|
|
f"{k}_abs": v
|
|
for k, v in zip(["x", "y", "z", "theta", "f"], np.random.rand(5) * 100)
|
|
}
|
|
dummy_microscope.move_stage(pos_dict, wait_until_done=True)
|
|
|
|
assert dummy_microscope.ask_stage_for_position == expected_device_flag[mode]
|
|
|
|
for axis in is_called:
|
|
is_called[axis] = False
|
|
|
|
stage_dict = dummy_microscope.get_stage_position()
|
|
|
|
# verify if report_position is called according to mode
|
|
for axis in is_called:
|
|
assert is_called[axis] == expected_device_flag[mode]
|
|
|
|
ret_pos_dict = {}
|
|
for axis in dummy_microscope.stages:
|
|
pos_axis = axis + "_pos"
|
|
temp_pos = dummy_microscope.stages[axis].report_position()
|
|
ret_pos_dict[pos_axis] = round(temp_pos[pos_axis], 2)
|
|
|
|
assert isinstance(stage_dict, dict)
|
|
assert ret_pos_dict == stage_dict
|
|
|
|
# Check caching
|
|
assert dummy_microscope.ask_stage_for_position is False
|
|
for axis in is_called:
|
|
is_called[axis] = False
|
|
stage_dict = dummy_microscope.get_stage_position()
|
|
assert ret_pos_dict == stage_dict
|
|
assert dummy_microscope.ask_stage_for_position is False
|
|
for axis in is_called:
|
|
assert is_called[axis] is False
|
|
|
|
|
|
# set back acquisition mode
|
|
dummy_microscope.configuration["experiment"]["MicroscopeState"][
|
|
"image_mode"
|
|
] = acquisition_mode
|
|
|
|
# restore report position functions
|
|
for axis in dummy_microscope.stages:
|
|
dummy_microscope.stages[axis].report_position = report_position_funcs[axis]
|
|
|
|
|
|
def test_prepare_next_channel(dummy_microscope):
|
|
dummy_microscope.prepare_acquisition()
|
|
|
|
current_channel = dummy_microscope.available_channels[0]
|
|
channel_key = f"channel_{current_channel}"
|
|
channel_dict = dummy_microscope.configuration["experiment"]["MicroscopeState"][
|
|
"channels"
|
|
][channel_key]
|
|
channel_dict["defocus"] = random.randint(1, 10)
|
|
|
|
dummy_microscope.prepare_next_channel()
|
|
|
|
assert dummy_microscope.current_channel == current_channel
|
|
assert dummy_microscope.get_stage_position()["f_pos"] == (
|
|
dummy_microscope.central_focus + channel_dict["defocus"]
|
|
)
|
|
|
|
|
|
def test_calculate_all_waveform(dummy_microscope):
|
|
# set waveform template to default
|
|
dummy_microscope.configuration["experiment"]["MicroscopeState"][
|
|
"waveform_template"
|
|
] = "Default"
|
|
waveform_dict = dummy_microscope.calculate_all_waveform()
|
|
# verify the waveform lengths
|
|
sweep_times = dummy_microscope.sweep_times
|
|
sample_rate = dummy_microscope.configuration["configuration"]["microscopes"][
|
|
dummy_microscope.microscope_name
|
|
]["daq"]["sample_rate"]
|
|
for channel_key in sweep_times:
|
|
waveform_length = int(sweep_times[channel_key] * sample_rate)
|
|
assert waveform_dict["camera_waveform"][channel_key].shape == (waveform_length,)
|
|
assert waveform_dict["remote_focus_waveform"][channel_key].shape == (
|
|
waveform_length,
|
|
)
|
|
for i in range(len(waveform_dict["galvo_waveform"])):
|
|
assert waveform_dict["galvo_waveform"][i][channel_key].shape == (
|
|
waveform_length,
|
|
)
|