feat: init
This commit is contained in:
0
test/controller/__init__.py
Normal file
0
test/controller/__init__.py
Normal file
0
test/controller/sub_controllers/__init__.py
Normal file
0
test/controller/sub_controllers/__init__.py
Normal file
617
test/controller/sub_controllers/test_acquire_bar.py
Normal file
617
test/controller/sub_controllers/test_acquire_bar.py
Normal file
@@ -0,0 +1,617 @@
|
||||
# Copyright (c) 2021-2025 The University of Texas Southwestern Medical Center.
|
||||
# All rights reserved.
|
||||
import tkinter
|
||||
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted for academic and research use only
|
||||
# (subject to the limitations in the disclaimer below)
|
||||
# provided that the following conditions are met:
|
||||
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
|
||||
# * Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
|
||||
# * Neither the name of the copyright holders nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from this
|
||||
# software without specific prior written permission.
|
||||
|
||||
# NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY
|
||||
# THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||
# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
||||
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
|
||||
# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
|
||||
# Standard library imports
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
# Third party imports
|
||||
import pytest
|
||||
|
||||
# Local imports
|
||||
from navigate.controller.sub_controllers import AcquireBarController
|
||||
from navigate.view.popups.acquire_popup import (
|
||||
AcquirePopUp,
|
||||
)
|
||||
from navigate.model.data_sources import FILE_TYPES
|
||||
|
||||
|
||||
class TestAcquireBarController:
|
||||
"""Tests for the AcquireBarController class"""
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup_class(self, dummy_controller):
|
||||
"""Setup for the TestAcquireBarController class
|
||||
|
||||
Parameters
|
||||
----------
|
||||
dummy_controller : DummyController
|
||||
Instance of the DummyController class
|
||||
"""
|
||||
c = dummy_controller
|
||||
v = dummy_controller.view
|
||||
|
||||
self.acquire_bar_controller = AcquireBarController(
|
||||
view=v.acquire_bar, parent_controller=c
|
||||
)
|
||||
self.acquire_bar_controller.populate_experiment_values()
|
||||
c.channels_tab_controller.populate_experiment_values()
|
||||
c.camera_setting_controller = MagicMock()
|
||||
|
||||
def test_init(self):
|
||||
"""Tests the initialization of the AcquireBarController class
|
||||
|
||||
Raises
|
||||
------
|
||||
AssertionError
|
||||
If the initialization of the AcquireBarController class is not correct
|
||||
"""
|
||||
assert isinstance(self.acquire_bar_controller, AcquireBarController)
|
||||
|
||||
def test_attr(self):
|
||||
"""Tests the attributes of the AcquireBarController class
|
||||
|
||||
Raises
|
||||
------
|
||||
AssertionError
|
||||
If the attributes of the AcquireBarController class are not correct
|
||||
"""
|
||||
# Listing off attributes to check existence
|
||||
attrs = ["mode", "is_save", "mode_dict"]
|
||||
|
||||
for attr in attrs:
|
||||
assert hasattr(self.acquire_bar_controller, attr)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"mode,mode_expected,value_expected",
|
||||
[
|
||||
("live", "indeterminate", None),
|
||||
("single", "determinate", 0),
|
||||
("customized", "indeterminate", None),
|
||||
("z-stack", "determinate", 0),
|
||||
],
|
||||
)
|
||||
def test_progress_bar(self, mode, mode_expected, value_expected):
|
||||
"""Tests the progress bar of the AcquireBarController class
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mode : str
|
||||
Mode of the progress bar
|
||||
mode_expected : str
|
||||
Expected mode of the progress bar
|
||||
value_expected : int
|
||||
Expected value of the progress bar
|
||||
|
||||
|
||||
Raises
|
||||
------
|
||||
AssertionError
|
||||
If the progress bar of the AcquireBarController class is not correct
|
||||
"""
|
||||
|
||||
# Startup progress bars
|
||||
images_received = 0
|
||||
mode = mode
|
||||
stop = False
|
||||
self.acquire_bar_controller.progress_bar(
|
||||
images_received=images_received,
|
||||
microscope_state=self.acquire_bar_controller.parent_controller.configuration[
|
||||
"experiment"
|
||||
][
|
||||
"MicroscopeState"
|
||||
],
|
||||
mode=mode,
|
||||
stop=stop,
|
||||
)
|
||||
|
||||
progress_mode = str(self.acquire_bar_controller.view.CurAcq["mode"])
|
||||
ovr_mode = str(self.acquire_bar_controller.view.OvrAcq["mode"])
|
||||
|
||||
assert progress_mode == mode_expected, (
|
||||
f"Wrong progress bar mode ({progress_mode}) "
|
||||
f"relative to microscope mode ({mode})"
|
||||
)
|
||||
assert ovr_mode == mode_expected, (
|
||||
f"Wrong progress bar mode ({progress_mode}) "
|
||||
f"relative to microscope mode ({mode})"
|
||||
)
|
||||
|
||||
if value_expected is not None:
|
||||
progress_start = int(self.acquire_bar_controller.view.CurAcq["value"])
|
||||
ovr_start = int(self.acquire_bar_controller.view.OvrAcq["value"])
|
||||
assert (
|
||||
progress_start == value_expected
|
||||
), "Wrong starting value for progress bar"
|
||||
assert ovr_start == value_expected, "Wrong starting value for progress bar"
|
||||
|
||||
# Updating progress bar
|
||||
images_received = 1
|
||||
while images_received < 6:
|
||||
self.acquire_bar_controller.progress_bar(
|
||||
images_received=images_received,
|
||||
microscope_state=self.acquire_bar_controller.parent_controller.configuration[
|
||||
"experiment"
|
||||
][
|
||||
"MicroscopeState"
|
||||
],
|
||||
mode=mode,
|
||||
stop=stop,
|
||||
)
|
||||
making_progress = float(self.acquire_bar_controller.view.CurAcq["value"])
|
||||
ovr_progress = float(self.acquire_bar_controller.view.OvrAcq["value"])
|
||||
assert (
|
||||
making_progress > 0
|
||||
), f"Progress bar should be moving in {mode} mode (making_progress)"
|
||||
assert (
|
||||
ovr_progress > 0
|
||||
), f"Progress bar should be moving in {mode} mode (ovr_progress)"
|
||||
images_received += 1
|
||||
|
||||
# Stopping progress bar
|
||||
self.acquire_bar_controller.progress_bar(
|
||||
images_received=images_received,
|
||||
microscope_state=self.acquire_bar_controller.parent_controller.configuration[
|
||||
"experiment"
|
||||
][
|
||||
"MicroscopeState"
|
||||
],
|
||||
mode=mode,
|
||||
stop=True,
|
||||
)
|
||||
|
||||
after_stop = float(self.acquire_bar_controller.view.CurAcq["value"])
|
||||
after_ovr = float(self.acquire_bar_controller.view.OvrAcq["value"])
|
||||
|
||||
assert after_stop == 0, "Progress Bar did not stop"
|
||||
assert after_ovr == 0, "Progress Bar did not stop"
|
||||
|
||||
@pytest.mark.parametrize("mode", ["live", "single", "z-stack", "customized"])
|
||||
def test_get_set_mode(self, mode):
|
||||
"""Tests the get_mode and set_mode methods of the AcquireBarController class
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mode : str
|
||||
Mode of the progress bar
|
||||
|
||||
|
||||
Raises
|
||||
------
|
||||
AssertionError
|
||||
If the get_mode and set_mode methods of the
|
||||
AcquireBarController class are not correct
|
||||
"""
|
||||
self.acquire_bar_controller.set_mode(mode)
|
||||
test = self.acquire_bar_controller.get_mode()
|
||||
assert test == mode, "Mode not set correctly"
|
||||
# assert imaging mode is updated in the experiment
|
||||
assert (
|
||||
self.acquire_bar_controller.parent_controller.configuration["experiment"][
|
||||
"MicroscopeState"
|
||||
]["image_mode"]
|
||||
== mode
|
||||
)
|
||||
|
||||
def test_set_save(self):
|
||||
"""Tests the set_save method of the AcquireBarController class
|
||||
|
||||
|
||||
Raises
|
||||
------
|
||||
AssertionError
|
||||
If the set_save method of the AcquireBarController class is not correct
|
||||
"""
|
||||
|
||||
# Assuming save state starts as False
|
||||
self.acquire_bar_controller.set_save_option(True)
|
||||
assert self.acquire_bar_controller.is_save is True, "Save option not correct"
|
||||
|
||||
# Return value to False
|
||||
self.acquire_bar_controller.set_save_option(False)
|
||||
assert (
|
||||
self.acquire_bar_controller.is_save is False
|
||||
), "Save option did not return to original value"
|
||||
|
||||
def test_stop_acquire(self):
|
||||
"""Tests the stop_acquire method of the AcquireBarController class
|
||||
|
||||
|
||||
Raises
|
||||
------
|
||||
AssertionError
|
||||
If the stop_acquire method of the AcquireBarController class is not correct
|
||||
"""
|
||||
|
||||
# Stopping acquisition
|
||||
self.acquire_bar_controller.stop_acquire()
|
||||
assert self.acquire_bar_controller.view.acquire_btn["text"] == "Acquire"
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"user_mode,expected_mode",
|
||||
[
|
||||
("Continuous Scan", "live"),
|
||||
("Z-Stack", "z-stack"),
|
||||
("Single Acquisition", "single"),
|
||||
("Customized", "customized"),
|
||||
],
|
||||
)
|
||||
def test_update_microscope_mode(self, user_mode, expected_mode):
|
||||
"""Tests the update_microscope_mode method of the AcquireBarController class
|
||||
|
||||
Parameters
|
||||
----------
|
||||
user_mode : str
|
||||
Mode of the progress bar
|
||||
expected_mode : str
|
||||
Expected state of the progress bar
|
||||
|
||||
|
||||
Raises
|
||||
------
|
||||
AssertionError
|
||||
If the update_microscope_mode method of
|
||||
the AcquireBarController class is not correct
|
||||
"""
|
||||
# Assuming mode starts on live
|
||||
self.acquire_bar_controller.mode = "live"
|
||||
|
||||
# Setting to mode specified by user
|
||||
self.acquire_bar_controller.view.pull_down.set(user_mode)
|
||||
|
||||
# Generate event that calls update microscope mode
|
||||
self.acquire_bar_controller.view.pull_down.event_generate(
|
||||
"<<ComboboxSelected>>"
|
||||
)
|
||||
|
||||
# Checking that new mode gets set by function
|
||||
assert self.acquire_bar_controller.mode == expected_mode
|
||||
assert (
|
||||
self.acquire_bar_controller.parent_controller.configuration["experiment"][
|
||||
"MicroscopeState"
|
||||
]["image_mode"]
|
||||
== expected_mode
|
||||
)
|
||||
|
||||
# Resetting to live
|
||||
self.acquire_bar_controller.view.pull_down.set("Continuous Scan")
|
||||
self.acquire_bar_controller.view.pull_down.event_generate(
|
||||
"<<ComboboxSelected>>"
|
||||
)
|
||||
assert self.acquire_bar_controller.mode == "live"
|
||||
|
||||
def test_populate_experiment_values(self):
|
||||
"""Tests the populate_experiment_values method of the AcquireBarController class
|
||||
|
||||
|
||||
Raises
|
||||
------
|
||||
AssertionError
|
||||
If the populate_experiment_values method
|
||||
of the AcquireBarController class is not correct
|
||||
"""
|
||||
|
||||
# Calling function to populate values
|
||||
self.acquire_bar_controller.populate_experiment_values()
|
||||
|
||||
# Checking values are what we expect
|
||||
for key, value in self.acquire_bar_controller.saving_settings.items():
|
||||
assert (
|
||||
self.acquire_bar_controller.saving_settings[key]
|
||||
== self.acquire_bar_controller.parent_controller.configuration[
|
||||
"experiment"
|
||||
]["Saving"][key]
|
||||
)
|
||||
|
||||
# Assuming default value in exp file,
|
||||
# can be altered TODO maybe set default to current date
|
||||
assert self.acquire_bar_controller.saving_settings["date"] == "2022-06-07"
|
||||
assert (
|
||||
self.acquire_bar_controller.mode
|
||||
== self.acquire_bar_controller.parent_controller.configuration[
|
||||
"experiment"
|
||||
]["MicroscopeState"]["image_mode"]
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"text,is_acquiring, save,mode,file_types,choice",
|
||||
[
|
||||
("Stop", False, None, "live", [], None),
|
||||
("Stop", True, None, "live", [], None),
|
||||
("Acquire", True, True, "live", [], None),
|
||||
("Acquire", False, True, "live", [], None),
|
||||
("Acquire", False, False, "z-stack", [], None),
|
||||
("Acquire", False, True, "z-stack", FILE_TYPES, "Done"),
|
||||
("Acquire", False, True, "z-stack", FILE_TYPES, "Cancel"),
|
||||
],
|
||||
)
|
||||
def test_launch_popup_window(
|
||||
self, text, is_acquiring, save, mode, file_types, choice
|
||||
):
|
||||
"""Tests the launch_popup_window method of the AcquireBarController class
|
||||
|
||||
This is the largest test for this controller.
|
||||
It will test multiple functions that are all used together
|
||||
and difficult to isolate.
|
||||
|
||||
Funcs Tested:
|
||||
launch_popup_window
|
||||
update_file_type
|
||||
launch_acquisition
|
||||
update_experiment_values
|
||||
acquire_pop.popup.dismiss # This will be double tested in view
|
||||
|
||||
Parameters
|
||||
----------
|
||||
text : str
|
||||
Text of the button that is clicked
|
||||
save : bool
|
||||
Whether or not to save the image
|
||||
mode : str
|
||||
Mode of the progress bar
|
||||
file_types : list
|
||||
List of file types to save as
|
||||
choice : str
|
||||
Choice of the user in the popup window
|
||||
|
||||
|
||||
Raises
|
||||
------
|
||||
AssertionError
|
||||
If the launch_popup_window method of the
|
||||
AcquireBarController class is not correct
|
||||
"""
|
||||
|
||||
# Setup Gui for test
|
||||
self.acquire_bar_controller.view.acquire_btn.configure(state="normal")
|
||||
self.acquire_bar_controller.view.acquire_btn.configure(text=text)
|
||||
self.acquire_bar_controller.is_save = save
|
||||
self.acquire_bar_controller.set_mode(mode)
|
||||
self.acquire_bar_controller.is_acquiring = is_acquiring
|
||||
|
||||
# Test based on setup, launches popup
|
||||
self.acquire_bar_controller.view.acquire_btn.invoke()
|
||||
|
||||
# Checking things are what we expect
|
||||
if text == "Stop":
|
||||
assert self.acquire_bar_controller.view.acquire_btn["text"] == "Stop"
|
||||
if is_acquiring:
|
||||
assert (
|
||||
str(self.acquire_bar_controller.view.acquire_btn["state"])
|
||||
== "disabled"
|
||||
)
|
||||
res = self.acquire_bar_controller.parent_controller.pop()
|
||||
assert res == "stop_acquire"
|
||||
else:
|
||||
assert (
|
||||
str(self.acquire_bar_controller.view.acquire_btn["state"])
|
||||
== "normal"
|
||||
)
|
||||
res = self.acquire_bar_controller.parent_controller.pop()
|
||||
assert res == "Empty command list"
|
||||
|
||||
if text == "Acquire":
|
||||
if is_acquiring:
|
||||
assert (
|
||||
str(self.acquire_bar_controller.view.acquire_btn["state"])
|
||||
== "normal"
|
||||
)
|
||||
res = self.acquire_bar_controller.parent_controller.pop()
|
||||
assert res == "Empty command list"
|
||||
return
|
||||
# First scenario Save is on and in live mode
|
||||
if save is True and mode == "live":
|
||||
assert self.acquire_bar_controller.view.acquire_btn["text"] == "Acquire"
|
||||
assert (
|
||||
str(self.acquire_bar_controller.view.acquire_btn["state"])
|
||||
== "disabled"
|
||||
)
|
||||
res = self.acquire_bar_controller.parent_controller.pop()
|
||||
print(res)
|
||||
print(self.acquire_bar_controller.parent_controller.pop())
|
||||
assert res == "acquire"
|
||||
|
||||
# Second scenario Save is off and mode is not live
|
||||
if save is False and mode != "live":
|
||||
assert self.acquire_bar_controller.view.acquire_btn["text"] == "Acquire"
|
||||
assert (
|
||||
str(self.acquire_bar_controller.view.acquire_btn["state"])
|
||||
== "disabled"
|
||||
)
|
||||
res = self.acquire_bar_controller.parent_controller.pop()
|
||||
assert res == "acquire"
|
||||
|
||||
# Third and final scenario Save is on and mode is not live
|
||||
if save is True and mode != "live":
|
||||
|
||||
# Checking if popup created
|
||||
assert isinstance(self.acquire_bar_controller.acquire_pop, AcquirePopUp)
|
||||
assert self.acquire_bar_controller.acquire_pop.popup.winfo_exists() == 1
|
||||
|
||||
# Testing update_file_type if list exists
|
||||
widgets = self.acquire_bar_controller.acquire_pop.get_widgets()
|
||||
if len(file_types) > 0:
|
||||
for file in file_types:
|
||||
widgets["file_type"].set(file)
|
||||
assert (
|
||||
self.acquire_bar_controller.saving_settings["file_type"]
|
||||
== file
|
||||
)
|
||||
# Resetting file type back to orginal
|
||||
widgets["file_type"].set("TIFF")
|
||||
assert (
|
||||
self.acquire_bar_controller.saving_settings["file_type"]
|
||||
== "TIFF"
|
||||
)
|
||||
|
||||
# Check that loop thru saving settings is correct
|
||||
for k, v in self.acquire_bar_controller.saving_settings.items():
|
||||
if widgets.get(k, None):
|
||||
value = widgets[k].get().strip()
|
||||
assert value == v
|
||||
|
||||
# Grabbing buttons to test
|
||||
buttons = self.acquire_bar_controller.acquire_pop.get_buttons()
|
||||
|
||||
if choice == "Cancel":
|
||||
# Testing cancel button
|
||||
|
||||
buttons["Cancel"].invoke() # Call to dismiss popup
|
||||
# Check toplevel gone
|
||||
assert (
|
||||
self.acquire_bar_controller.acquire_pop.popup.winfo_exists()
|
||||
== 0
|
||||
)
|
||||
assert (
|
||||
str(self.acquire_bar_controller.view.acquire_btn["state"])
|
||||
== "normal"
|
||||
)
|
||||
elif choice == "Done":
|
||||
# Testing done button
|
||||
|
||||
# Update experiment values test
|
||||
# Changing popup vals to test update
|
||||
# experiment values inside launch acquisition
|
||||
widgets["user"].set("John")
|
||||
widgets["tissue"].set("Heart")
|
||||
widgets["celltype"].set("34T")
|
||||
widgets["label"].set("BCB")
|
||||
widgets["solvent"].set("uDISCO")
|
||||
widgets["file_type"].set("OME-TIFF")
|
||||
|
||||
# Tab frame
|
||||
for i in range(100):
|
||||
self.acquire_bar_controller.acquire_pop.tab_frame.inputs[
|
||||
"misc"
|
||||
].insert(tkinter.END, f"L{i}")
|
||||
|
||||
self.acquire_bar_controller.acquire_pop.tab_frame.inputs[
|
||||
"shear_data"
|
||||
].set(True)
|
||||
self.acquire_bar_controller.acquire_pop.tab_frame.inputs[
|
||||
"shear_dimension"
|
||||
].set("XZ")
|
||||
self.acquire_bar_controller.acquire_pop.tab_frame.inputs[
|
||||
"shear_angle"
|
||||
].set(45)
|
||||
self.acquire_bar_controller.acquire_pop.tab_frame.inputs[
|
||||
"rotate_data"
|
||||
].set(True)
|
||||
self.acquire_bar_controller.acquire_pop.tab_frame.inputs[
|
||||
"rotate_angle_x"
|
||||
].set(90)
|
||||
self.acquire_bar_controller.acquire_pop.tab_frame.inputs[
|
||||
"rotate_angle_y"
|
||||
].set(90)
|
||||
self.acquire_bar_controller.acquire_pop.tab_frame.inputs[
|
||||
"rotate_angle_z"
|
||||
].set(90)
|
||||
self.acquire_bar_controller.acquire_pop.tab_frame.inputs[
|
||||
"down_sample_data"
|
||||
].set(True)
|
||||
self.acquire_bar_controller.acquire_pop.tab_frame.inputs[
|
||||
"lateral_down_sample"
|
||||
].set("2x")
|
||||
self.acquire_bar_controller.acquire_pop.tab_frame.inputs[
|
||||
"axial_down_sample"
|
||||
].set("2x")
|
||||
|
||||
# Launch acquisition start/test
|
||||
buttons["Done"].invoke() # Call to launch acquisition
|
||||
|
||||
# Check if update experiment values works correctly
|
||||
pop_vals = self.acquire_bar_controller.acquire_pop.get_variables()
|
||||
for k, v in self.acquire_bar_controller.saving_settings.items():
|
||||
if pop_vals.get(k, None):
|
||||
value = pop_vals[k].strip()
|
||||
assert value == v
|
||||
|
||||
# Check command sent to controller
|
||||
# and if acquire button changed to Stop
|
||||
res = self.acquire_bar_controller.parent_controller.pop()
|
||||
assert res == "acquire_and_save"
|
||||
assert (
|
||||
str(self.acquire_bar_controller.view.acquire_btn["state"])
|
||||
== "disabled"
|
||||
)
|
||||
assert (
|
||||
self.acquire_bar_controller.acquire_pop.popup.winfo_exists()
|
||||
== 0
|
||||
)
|
||||
|
||||
def test_frequent_start_and_stop_acquisition(self):
|
||||
# set up
|
||||
self.acquire_bar_controller.view.acquire_btn.configure(state="normal")
|
||||
self.acquire_bar_controller.view.acquire_btn.configure(text="Acquire")
|
||||
self.acquire_bar_controller.is_save = False
|
||||
self.acquire_bar_controller.set_mode("live")
|
||||
self.acquire_bar_controller.is_acquiring = False
|
||||
|
||||
# start acquisition
|
||||
self.acquire_bar_controller.view.acquire_btn.invoke()
|
||||
assert self.acquire_bar_controller.view.acquire_btn["text"] == "Acquire"
|
||||
assert str(self.acquire_bar_controller.view.acquire_btn["state"]) == "disabled"
|
||||
assert self.acquire_bar_controller.is_acquiring is True
|
||||
# assert dummy_controller_to_test_acquire_bar.acquisition_count == 1
|
||||
res = self.acquire_bar_controller.parent_controller.pop()
|
||||
assert res == "acquire"
|
||||
|
||||
# not in acquisition, click the "Acquire" button several times
|
||||
self.acquire_bar_controller.view.acquire_btn.invoke()
|
||||
assert self.acquire_bar_controller.view.acquire_btn["text"] == "Acquire"
|
||||
assert str(self.acquire_bar_controller.view.acquire_btn["state"]) == "disabled"
|
||||
res = self.acquire_bar_controller.parent_controller.pop()
|
||||
assert res == "Empty command list"
|
||||
self.acquire_bar_controller.view.acquire_btn.invoke()
|
||||
res = self.acquire_bar_controller.parent_controller.pop()
|
||||
assert res == "Empty command list"
|
||||
|
||||
# in acquisition, click "Stop" button several times
|
||||
self.acquire_bar_controller.view.acquire_btn.configure(state="normal")
|
||||
self.acquire_bar_controller.view.acquire_btn.configure(text="Stop")
|
||||
self.acquire_bar_controller.is_acquiring = True
|
||||
|
||||
self.acquire_bar_controller.view.acquire_btn.invoke()
|
||||
assert self.acquire_bar_controller.view.acquire_btn["text"] == "Stop"
|
||||
assert str(self.acquire_bar_controller.view.acquire_btn["state"]) == "disabled"
|
||||
res = self.acquire_bar_controller.parent_controller.pop()
|
||||
assert res == "stop_acquire"
|
||||
self.acquire_bar_controller.view.acquire_btn.invoke()
|
||||
res = self.acquire_bar_controller.parent_controller.pop()
|
||||
assert res == "Empty command list"
|
||||
self.acquire_bar_controller.view.acquire_btn.invoke()
|
||||
res = self.acquire_bar_controller.parent_controller.pop()
|
||||
assert res == "Empty command list"
|
||||
self.acquire_bar_controller.view.acquire_btn.invoke()
|
||||
res = self.acquire_bar_controller.parent_controller.pop()
|
||||
assert res == "Empty command list"
|
||||
202
test/controller/sub_controllers/test_autofocus.py
Normal file
202
test/controller/sub_controllers/test_autofocus.py
Normal file
@@ -0,0 +1,202 @@
|
||||
# Copyright (c) 2021-2025 The University of Texas Southwestern Medical Center.
|
||||
# All rights reserved.
|
||||
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted for academic and research use only
|
||||
# (subject to the limitations in the disclaimer below)
|
||||
# provided that the following conditions are met:
|
||||
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
|
||||
# * Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
|
||||
# * Neither the name of the copyright holders nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from this
|
||||
# software without specific prior written permission.
|
||||
|
||||
# NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY
|
||||
# THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||
# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
||||
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
|
||||
# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
|
||||
# Standard library imports
|
||||
|
||||
# Third party imports
|
||||
import pytest
|
||||
import numpy as np
|
||||
|
||||
# Local application imports
|
||||
from navigate.controller.sub_controllers import AutofocusPopupController
|
||||
from navigate.view.popups.autofocus_setting_popup import AutofocusPopup
|
||||
|
||||
|
||||
class TestAutofocusPopupController:
|
||||
"""Class for testing autofocus popup controller
|
||||
|
||||
Methods
|
||||
-------
|
||||
test_init()
|
||||
Tests that the controller is initialized correctly
|
||||
test_attr()
|
||||
Tests that the attributes are initialized correctly
|
||||
test_populate_experiment_values()
|
||||
Tests that the values are populated correctly
|
||||
test_update_experiment_values()
|
||||
Tests that the values are updated correctly
|
||||
test_start_autofocus()
|
||||
Tests that the start autofocus function works correctly
|
||||
test_display_plot()
|
||||
Tests that the display plot function works correctly
|
||||
"""
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup_class(self, dummy_controller):
|
||||
"""Setup for testing autofocus popup controller
|
||||
|
||||
Parameters
|
||||
----------
|
||||
dummy_controller : DummyController
|
||||
Dummy controller for testing
|
||||
|
||||
"""
|
||||
autofocus_popup = AutofocusPopup(dummy_controller.view)
|
||||
self.autofocus_controller = AutofocusPopupController(
|
||||
autofocus_popup, dummy_controller
|
||||
)
|
||||
|
||||
def test_init(self):
|
||||
"""Tests that the controller is initialized correctly
|
||||
|
||||
Raises
|
||||
------
|
||||
AssertionError
|
||||
If the controller is not initialized correctly
|
||||
"""
|
||||
assert isinstance(self.autofocus_controller, AutofocusPopupController)
|
||||
assert self.autofocus_controller.view.popup.winfo_exists() == 1
|
||||
|
||||
def test_attr(self):
|
||||
"""Tests that the attributes are initialized correctly
|
||||
|
||||
Raises
|
||||
------
|
||||
AssertionError
|
||||
If the attributes are not initialized correctly
|
||||
"""
|
||||
|
||||
# Listing off attributes to check existence
|
||||
attrs = [
|
||||
"autofocus_fig",
|
||||
"autofocus_coarse",
|
||||
"widgets",
|
||||
"setting_dict",
|
||||
]
|
||||
|
||||
for attr in attrs:
|
||||
assert hasattr(self.autofocus_controller, attr)
|
||||
|
||||
def test_populate_experiment_values(self):
|
||||
"""Tests that the values are populated correctly
|
||||
|
||||
Raises
|
||||
------
|
||||
AssertionError
|
||||
If the values are not populated correctly
|
||||
"""
|
||||
microscope_name = self.autofocus_controller.microscope_name
|
||||
device = self.autofocus_controller.widgets["device"].get()
|
||||
device_ref = self.autofocus_controller.widgets["device_ref"].get()
|
||||
for k in self.autofocus_controller.widgets:
|
||||
if k != "device" and k != "device_ref":
|
||||
assert self.autofocus_controller.widgets[k].get() == str(
|
||||
self.autofocus_controller.setting_dict[microscope_name][device][
|
||||
device_ref
|
||||
][k]
|
||||
)
|
||||
# Some values are ints but Tkinter only uses strings
|
||||
|
||||
def test_update_experiment_values(self):
|
||||
"""Tests that the values are updated correctly
|
||||
|
||||
Raises
|
||||
------
|
||||
AssertionError
|
||||
If the values are not updated correctly
|
||||
"""
|
||||
# Changing values
|
||||
self.autofocus_controller.widgets["coarse_range"].set(200)
|
||||
self.autofocus_controller.widgets["coarse_step_size"].set(30)
|
||||
self.autofocus_controller.view.setting_vars["coarse_selected"].set(False)
|
||||
self.autofocus_controller.widgets["fine_range"].set(25)
|
||||
self.autofocus_controller.widgets["fine_step_size"].set(2)
|
||||
self.autofocus_controller.view.setting_vars["fine_selected"].set(False)
|
||||
|
||||
microscope_name = self.autofocus_controller.microscope_name
|
||||
device = self.autofocus_controller.widgets["device"].get()
|
||||
device_ref = self.autofocus_controller.widgets["device_ref"].get()
|
||||
|
||||
# Checking values match
|
||||
for k in self.autofocus_controller.widgets:
|
||||
if k != "device" and k != "device_ref":
|
||||
assert self.autofocus_controller.widgets[k].get() == str(
|
||||
self.autofocus_controller.setting_dict[microscope_name][device][
|
||||
device_ref
|
||||
][k]
|
||||
)
|
||||
for k in self.autofocus_controller.view.setting_vars:
|
||||
assert (
|
||||
self.autofocus_controller.view.setting_vars[k].get()
|
||||
== self.autofocus_controller.setting_dict[microscope_name][device][
|
||||
device_ref
|
||||
][k]
|
||||
)
|
||||
|
||||
def test_start_autofocus(self):
|
||||
"""Tests that the start autofocus function works correctly
|
||||
|
||||
Raises
|
||||
------
|
||||
AssertionError
|
||||
If the start autofocus function does not work correctly
|
||||
"""
|
||||
|
||||
# Calling function
|
||||
self.autofocus_controller.start_autofocus()
|
||||
|
||||
# Checking message sent
|
||||
res = self.autofocus_controller.parent_controller.pop()
|
||||
assert res == "autofocus"
|
||||
|
||||
self.autofocus_controller.parent_controller.clear()
|
||||
|
||||
def test_display_plot(self):
|
||||
"""Tests that the display plot function works correctly
|
||||
|
||||
Todo: Retrieve data from axessubplot instance and
|
||||
check that it is correct
|
||||
|
||||
Raises
|
||||
------
|
||||
AssertionError
|
||||
If the display plot function does not work correctly
|
||||
"""
|
||||
# Make this robust by sending data and then
|
||||
# checking each plot is plotting correct data low priority
|
||||
|
||||
x_data = np.linspace(start=69750.0, stop=70250.0, num=101)
|
||||
y_data = np.random.rand(101)
|
||||
data = [x_data, y_data]
|
||||
self.autofocus_controller.display_plot([data, False, True])
|
||||
pass
|
||||
607
test/controller/sub_controllers/test_camera_settings.py
Normal file
607
test/controller/sub_controllers/test_camera_settings.py
Normal file
@@ -0,0 +1,607 @@
|
||||
# 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
|
||||
|
||||
# Third Party Imports
|
||||
import pytest
|
||||
import random
|
||||
|
||||
# Local Imports
|
||||
from navigate.controller.sub_controllers.camera_settings import (
|
||||
CameraSettingController,
|
||||
)
|
||||
|
||||
|
||||
class TestCameraSettingController:
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup_class(self, dummy_controller):
|
||||
c = dummy_controller
|
||||
v = dummy_controller.view
|
||||
self.camera_settings = CameraSettingController(
|
||||
v.settings.camera_settings_tab, c
|
||||
)
|
||||
|
||||
def test_init(self):
|
||||
|
||||
assert isinstance(self.camera_settings, CameraSettingController)
|
||||
|
||||
# Setup, going to check what the default values widgets are set too
|
||||
microscope_name = self.camera_settings.parent_controller.configuration[
|
||||
"experiment"
|
||||
]["MicroscopeState"]["microscope_name"]
|
||||
camera_config_dict = self.camera_settings.parent_controller.configuration[
|
||||
"configuration"
|
||||
]["microscopes"][microscope_name]["camera"]
|
||||
|
||||
# Default Values
|
||||
assert (
|
||||
self.camera_settings.default_pixel_size
|
||||
== camera_config_dict["pixel_size_in_microns"]
|
||||
)
|
||||
assert self.camera_settings.default_height == camera_config_dict["y_pixels"]
|
||||
assert self.camera_settings.default_width == camera_config_dict["x_pixels"]
|
||||
|
||||
# Camera Mode
|
||||
assert list(self.camera_settings.mode_widgets["Sensor"].widget["values"]) == [
|
||||
"Normal",
|
||||
"Light-Sheet",
|
||||
]
|
||||
assert (
|
||||
str(self.camera_settings.mode_widgets["Sensor"].widget["state"])
|
||||
== "readonly"
|
||||
)
|
||||
|
||||
# Readout Mode
|
||||
assert list(self.camera_settings.mode_widgets["Readout"].widget["values"]) == [
|
||||
"Top-to-Bottom",
|
||||
"Bottom-to-Top",
|
||||
"Bidirectional",
|
||||
"Rev. Bidirectional",
|
||||
]
|
||||
assert (
|
||||
str(self.camera_settings.mode_widgets["Readout"].widget["state"])
|
||||
== "disabled"
|
||||
)
|
||||
|
||||
# Pixels
|
||||
assert (
|
||||
str(self.camera_settings.mode_widgets["Pixels"].widget["state"])
|
||||
== "disabled"
|
||||
)
|
||||
assert self.camera_settings.mode_widgets["Pixels"].widget.get() == ""
|
||||
assert self.camera_settings.mode_widgets["Pixels"].widget.cget("from") == 1
|
||||
assert (
|
||||
self.camera_settings.mode_widgets["Pixels"].widget.cget("to")
|
||||
== self.camera_settings.default_height / 2
|
||||
)
|
||||
assert self.camera_settings.mode_widgets["Pixels"].widget.cget("increment") == 1
|
||||
|
||||
# Framerate
|
||||
assert (
|
||||
str(self.camera_settings.framerate_widgets["exposure_time"].widget["state"])
|
||||
== "disabled"
|
||||
)
|
||||
assert (
|
||||
str(self.camera_settings.framerate_widgets["readout_time"].widget["state"])
|
||||
== "disabled"
|
||||
)
|
||||
assert (
|
||||
str(self.camera_settings.framerate_widgets["max_framerate"].widget["state"])
|
||||
== "disabled"
|
||||
)
|
||||
|
||||
# Set range value
|
||||
assert (
|
||||
self.camera_settings.roi_widgets["Width"].widget.cget("to")
|
||||
== self.camera_settings.default_width
|
||||
)
|
||||
assert self.camera_settings.roi_widgets["Width"].widget.cget("from") == 2
|
||||
assert self.camera_settings.roi_widgets["Width"].widget.cget("increment") == 2
|
||||
assert (
|
||||
self.camera_settings.roi_widgets["Height"].widget.cget("to")
|
||||
== self.camera_settings.default_height
|
||||
)
|
||||
assert self.camera_settings.roi_widgets["Height"].widget.cget("from") == 2
|
||||
assert self.camera_settings.roi_widgets["Height"].widget.cget("increment") == 2
|
||||
|
||||
# Set binning options
|
||||
assert list(self.camera_settings.roi_widgets["Binning"].widget["values"]) == [
|
||||
"{}x{}".format(i, i) for i in [1, 2, 4]
|
||||
]
|
||||
assert (
|
||||
str(self.camera_settings.roi_widgets["Binning"].widget["state"])
|
||||
== "readonly"
|
||||
)
|
||||
|
||||
# FOV
|
||||
assert (
|
||||
str(self.camera_settings.roi_widgets["FOV_X"].widget["state"]) == "disabled"
|
||||
)
|
||||
assert (
|
||||
str(self.camera_settings.roi_widgets["FOV_Y"].widget["state"]) == "disabled"
|
||||
)
|
||||
|
||||
def test_attr(self):
|
||||
|
||||
attrs = [
|
||||
"in_initialization",
|
||||
"resolution_value",
|
||||
"mode",
|
||||
"solvent",
|
||||
"mode_widgets",
|
||||
"framerate_widgets",
|
||||
"roi_widgets",
|
||||
"roi_btns",
|
||||
"default_pixel_size",
|
||||
"default_width",
|
||||
"default_height",
|
||||
"pixel_event_id",
|
||||
]
|
||||
|
||||
for attr in attrs:
|
||||
assert hasattr(self.camera_settings, attr)
|
||||
|
||||
def test_populate_experiment_values(self):
|
||||
microscope_name = self.camera_settings.parent_controller.configuration[
|
||||
"experiment"
|
||||
]["MicroscopeState"]["microscope_name"]
|
||||
self.camera_settings.parent_controller.configuration["experiment"][
|
||||
"CameraParameters"
|
||||
][microscope_name]["readout_time"] = 0.1
|
||||
# Populate widgets with values from experiment file and check
|
||||
self.camera_settings.populate_experiment_values()
|
||||
camera_setting_dict = self.camera_settings.parent_controller.configuration[
|
||||
"experiment"
|
||||
]["CameraParameters"][microscope_name]
|
||||
|
||||
# Checking values altered are correct
|
||||
assert dict(self.camera_settings.camera_setting_dict) == dict(
|
||||
self.camera_settings.parent_controller.configuration["experiment"][
|
||||
"CameraParameters"
|
||||
][microscope_name]
|
||||
)
|
||||
assert (
|
||||
str(self.camera_settings.mode_widgets["Sensor"].get())
|
||||
== camera_setting_dict["sensor_mode"]
|
||||
)
|
||||
if camera_setting_dict["sensor_mode"] == "Normal":
|
||||
pass
|
||||
# assert str(self.camera_settings.mode_widgets[
|
||||
# "Readout"].get()) == ""
|
||||
# assert str(self.camera_settings.mode_widgets[
|
||||
# "Pixels"].get()) == ""
|
||||
elif camera_setting_dict["sensor_mode"] == "Light-Sheet":
|
||||
assert (
|
||||
str(self.camera_settings.mode_widgets["Readout"].get())
|
||||
== self.camera_settings.camera_setting_dict["readout_direction"]
|
||||
)
|
||||
assert (
|
||||
str(self.camera_settings.mode_widgets["Pixels"].get())
|
||||
== self.camera_settings.camera_setting_dict["number_of_pixels"]
|
||||
)
|
||||
|
||||
# ROI
|
||||
assert (
|
||||
self.camera_settings.roi_widgets["Width"].get()
|
||||
== camera_setting_dict["x_pixels"]
|
||||
)
|
||||
assert (
|
||||
self.camera_settings.roi_widgets["Height"].get()
|
||||
== camera_setting_dict["y_pixels"]
|
||||
)
|
||||
|
||||
assert self.camera_settings.roi_widgets[
|
||||
"Top_X"
|
||||
].get() == camera_setting_dict.get("top_x", 0)
|
||||
assert self.camera_settings.roi_widgets[
|
||||
"Top_Y"
|
||||
].get() == camera_setting_dict.get("top_y", 0)
|
||||
if camera_setting_dict.get("is_centered", True):
|
||||
assert (
|
||||
str(self.camera_settings.roi_widgets["Top_X"].widget["state"])
|
||||
== "disabled"
|
||||
)
|
||||
assert (
|
||||
str(self.camera_settings.roi_widgets["Top_Y"].widget["state"])
|
||||
== "disabled"
|
||||
)
|
||||
|
||||
# Binning
|
||||
assert (
|
||||
str(self.camera_settings.roi_widgets["Binning"].get())
|
||||
== camera_setting_dict["binning"]
|
||||
)
|
||||
|
||||
# Exposure Time
|
||||
channels = self.camera_settings.parent_controller.configuration["experiment"][
|
||||
"MicroscopeState"
|
||||
]["channels"]
|
||||
exposure_time = channels[list(channels.keys())[0]]["camera_exposure_time"]
|
||||
assert (
|
||||
self.camera_settings.framerate_widgets["exposure_time"].get()
|
||||
== exposure_time
|
||||
)
|
||||
assert (
|
||||
self.camera_settings.framerate_widgets["frames_to_average"].get()
|
||||
== camera_setting_dict["frames_to_average"]
|
||||
)
|
||||
assert self.camera_settings.in_initialization is False
|
||||
|
||||
@pytest.mark.parametrize("mode", ["Normal", "Light-Sheet"])
|
||||
def test_update_experiment_values(self, mode):
|
||||
|
||||
microscope_name = self.camera_settings.parent_controller.configuration[
|
||||
"experiment"
|
||||
]["MicroscopeState"]["microscope_name"]
|
||||
# Setup basic default experiment
|
||||
self.camera_settings.camera_setting_dict = (
|
||||
self.camera_settings.parent_controller.configuration["experiment"][
|
||||
"CameraParameters"
|
||||
][microscope_name]
|
||||
)
|
||||
|
||||
# Setting up new values in widgets
|
||||
self.camera_settings.mode_widgets["Sensor"].set(mode)
|
||||
self.camera_settings.roi_widgets["Binning"].set("4x4")
|
||||
if mode == "Light-Sheet":
|
||||
self.camera_settings.mode_widgets["Readout"].set("Bottom-to-Top")
|
||||
self.camera_settings.mode_widgets["Pixels"].set(15)
|
||||
self.camera_settings.roi_widgets["Binning"].set("1x1")
|
||||
width, height = random.randint(1, 2000), random.randint(1, 2000)
|
||||
self.camera_settings.roi_widgets["Width"].set(width)
|
||||
self.camera_settings.roi_widgets["Height"].set(height)
|
||||
self.camera_settings.framerate_widgets["frames_to_average"].set(5)
|
||||
|
||||
# Update experiment dict and assert
|
||||
self.camera_settings.update_experiment_values()
|
||||
assert self.camera_settings.camera_setting_dict["sensor_mode"] == mode
|
||||
if mode == "Light-Sheet":
|
||||
assert (
|
||||
self.camera_settings.camera_setting_dict["readout_direction"]
|
||||
== "Bottom-to-Top"
|
||||
)
|
||||
assert (
|
||||
int(self.camera_settings.camera_setting_dict["number_of_pixels"]) == 15
|
||||
)
|
||||
step_width = self.camera_settings.step_width
|
||||
step_height = self.camera_settings.step_height
|
||||
set_width = int(width // step_width) * step_width
|
||||
set_height = int(height // step_height) * step_height
|
||||
if mode == "Light-Sheet":
|
||||
assert self.camera_settings.camera_setting_dict["binning"] == "1x1"
|
||||
assert self.camera_settings.camera_setting_dict["img_x_pixels"] == set_width
|
||||
assert (
|
||||
self.camera_settings.camera_setting_dict["img_y_pixels"] == set_height
|
||||
)
|
||||
binning = 1
|
||||
else:
|
||||
assert self.camera_settings.camera_setting_dict["binning"] == "4x4"
|
||||
# make sure image size is divisible by step_width and step_height
|
||||
assert self.camera_settings.camera_setting_dict["img_x_pixels"] == (
|
||||
set_width // 4
|
||||
) - (set_width // 4 % step_width)
|
||||
assert self.camera_settings.camera_setting_dict["img_y_pixels"] == (
|
||||
set_height // 4
|
||||
) - (set_height // 4 % step_height)
|
||||
binning = 4
|
||||
|
||||
# make sure x, y pixels are img_x, img_y pixels * binning
|
||||
assert (
|
||||
self.camera_settings.camera_setting_dict["x_pixels"]
|
||||
== self.camera_settings.camera_setting_dict["img_x_pixels"] * binning
|
||||
)
|
||||
assert (
|
||||
self.camera_settings.camera_setting_dict["y_pixels"]
|
||||
== self.camera_settings.camera_setting_dict["img_y_pixels"] * binning
|
||||
)
|
||||
assert (
|
||||
self.camera_settings.camera_setting_dict["pixel_size"]
|
||||
== self.camera_settings.default_pixel_size
|
||||
)
|
||||
assert self.camera_settings.camera_setting_dict["frames_to_average"] == 5
|
||||
|
||||
@pytest.mark.parametrize("mode", ["Normal", "Light-Sheet"])
|
||||
def test_update_sensor_mode(self, mode):
|
||||
self.camera_settings.populate_experiment_values()
|
||||
microscope_name = self.camera_settings.parent_controller.configuration[
|
||||
"experiment"
|
||||
]["MicroscopeState"]["microscope_name"]
|
||||
camera_setting_dict = self.camera_settings.parent_controller.configuration[
|
||||
"experiment"
|
||||
]["CameraParameters"][microscope_name]
|
||||
|
||||
# Set mode
|
||||
self.camera_settings.mode_widgets["Sensor"].widget.set(mode)
|
||||
self.camera_settings.mode_widgets["Sensor"].widget.event_generate(
|
||||
"<<ComboboxSelected>>"
|
||||
)
|
||||
|
||||
# Call update
|
||||
# self.camera_settings.update_sensor_mode()
|
||||
|
||||
# Check values
|
||||
if mode == "Normal":
|
||||
assert str(self.camera_settings.mode_widgets["Readout"].get()) == " "
|
||||
assert (
|
||||
str(self.camera_settings.mode_widgets["Readout"].widget["state"])
|
||||
== "disabled"
|
||||
)
|
||||
assert (
|
||||
str(self.camera_settings.mode_widgets["Pixels"].widget["state"])
|
||||
== "disabled"
|
||||
)
|
||||
assert str(self.camera_settings.mode_widgets["Pixels"].widget.get()) == ""
|
||||
|
||||
if mode == "Light-Sheet":
|
||||
assert (
|
||||
str(self.camera_settings.mode_widgets["Readout"].get())
|
||||
== camera_setting_dict["readout_direction"]
|
||||
)
|
||||
assert (
|
||||
str(self.camera_settings.mode_widgets["Readout"].widget["state"])
|
||||
== "readonly"
|
||||
)
|
||||
assert (
|
||||
str(self.camera_settings.mode_widgets["Pixels"].widget["state"])
|
||||
== "normal"
|
||||
)
|
||||
assert int(self.camera_settings.mode_widgets["Pixels"].widget.get()) == int(
|
||||
self.camera_settings.camera_setting_dict["number_of_pixels"]
|
||||
)
|
||||
|
||||
def test_update_exposure_time(self):
|
||||
|
||||
# Call funciton
|
||||
self.camera_settings.update_exposure_time(35)
|
||||
|
||||
# Check
|
||||
assert self.camera_settings.framerate_widgets["exposure_time"].get() == 35
|
||||
|
||||
@pytest.mark.parametrize("name", ["All", "1600", "1024", "512"])
|
||||
def test_update_roi(self, name):
|
||||
|
||||
# Call button to check if handler setup correctly
|
||||
self.camera_settings.roi_btns[name].invoke()
|
||||
|
||||
# Check
|
||||
if name == "All":
|
||||
name = "2048"
|
||||
assert str(self.camera_settings.roi_widgets["Width"].get()) == name
|
||||
assert str(self.camera_settings.roi_widgets["Height"].get()) == name
|
||||
|
||||
def test_update_fov(self):
|
||||
self.camera_settings.populate_experiment_values()
|
||||
|
||||
# Change invoke
|
||||
self.camera_settings.in_initialization = False
|
||||
self.camera_settings.roi_widgets["Width"].widget.set(2048)
|
||||
self.camera_settings.roi_widgets["Height"].widget.set(2048)
|
||||
xFov = int(self.camera_settings.roi_widgets["FOV_X"].get())
|
||||
yFov = int(self.camera_settings.roi_widgets["FOV_Y"].get())
|
||||
self.camera_settings.roi_widgets["Width"].widget.set(1600)
|
||||
self.camera_settings.roi_widgets["Height"].widget.set(1600)
|
||||
|
||||
# need these since we switched to read events
|
||||
self.camera_settings.roi_widgets["Width"].get_variable().get()
|
||||
self.camera_settings.roi_widgets["Height"].get_variable().get()
|
||||
|
||||
# Check
|
||||
assert xFov != int(self.camera_settings.roi_widgets["FOV_X"].get())
|
||||
assert yFov != int(self.camera_settings.roi_widgets["FOV_Y"].get())
|
||||
|
||||
# Reset
|
||||
self.camera_settings.roi_widgets["Width"].widget.set(2048)
|
||||
self.camera_settings.roi_widgets["Height"].widget.set(2048)
|
||||
|
||||
# need these since we switched to read events
|
||||
self.camera_settings.roi_widgets["Width"].get_variable().get()
|
||||
self.camera_settings.roi_widgets["Height"].get_variable().get()
|
||||
|
||||
assert int(self.camera_settings.roi_widgets["FOV_X"].get()) == 13066
|
||||
assert int(self.camera_settings.roi_widgets["FOV_Y"].get()) == 13066
|
||||
|
||||
@pytest.mark.parametrize("mode", ["live", "z-stack", "stop", "single"])
|
||||
@pytest.mark.parametrize("readout", ["Normal", "Light-Sheet"])
|
||||
def test_set_mode(self, mode, readout):
|
||||
|
||||
# Populate widgets with values from experiment file
|
||||
self.camera_settings.populate_experiment_values()
|
||||
|
||||
# Set mode
|
||||
self.camera_settings.mode_widgets["Sensor"].widget.set(readout)
|
||||
self.camera_settings.update_sensor_mode()
|
||||
self.camera_settings.set_mode(mode)
|
||||
|
||||
# Check
|
||||
assert self.camera_settings.mode == mode
|
||||
if mode != "stop":
|
||||
state = "disabled"
|
||||
else:
|
||||
state = "normal"
|
||||
if mode != "stop":
|
||||
state_readonly = "disabled"
|
||||
else:
|
||||
state_readonly = "readonly"
|
||||
|
||||
assert (
|
||||
str(self.camera_settings.mode_widgets["Sensor"].widget["state"])
|
||||
== state_readonly
|
||||
)
|
||||
if str(self.camera_settings.mode_widgets["Sensor"].get()) == "Light-Sheet":
|
||||
assert (
|
||||
str(self.camera_settings.mode_widgets["Readout"].widget["state"])
|
||||
== state_readonly
|
||||
)
|
||||
if mode == "live":
|
||||
assert (
|
||||
str(self.camera_settings.mode_widgets["Pixels"].widget["state"])
|
||||
== "normal"
|
||||
)
|
||||
else:
|
||||
assert (
|
||||
str(self.camera_settings.mode_widgets["Pixels"].widget["state"])
|
||||
== state
|
||||
)
|
||||
else:
|
||||
assert (
|
||||
str(self.camera_settings.mode_widgets["Readout"].widget["state"])
|
||||
== "disabled"
|
||||
)
|
||||
assert (
|
||||
str(self.camera_settings.mode_widgets["Pixels"].widget["state"])
|
||||
== "disabled"
|
||||
)
|
||||
assert (
|
||||
str(
|
||||
self.camera_settings.framerate_widgets["frames_to_average"].widget[
|
||||
"state"
|
||||
]
|
||||
)
|
||||
== state
|
||||
)
|
||||
assert str(self.camera_settings.roi_widgets["Width"].widget["state"]) == state
|
||||
assert str(self.camera_settings.roi_widgets["Height"].widget["state"]) == state
|
||||
assert (
|
||||
str(self.camera_settings.roi_widgets["Binning"].widget["state"])
|
||||
== state_readonly
|
||||
)
|
||||
for btn_name in self.camera_settings.roi_btns:
|
||||
assert str(self.camera_settings.roi_btns[btn_name]["state"]) == state
|
||||
|
||||
@pytest.mark.parametrize("zoom", ["0.63x", "1x", "2x", "3x", "4x", "5x", "6x"])
|
||||
def test_calculate_physical_dimensions(self, zoom):
|
||||
self.camera_settings.parent_controller.configuration["experiment"][
|
||||
"MicroscopeState"
|
||||
]["zoom"] = zoom
|
||||
|
||||
self.camera_settings.populate_experiment_values()
|
||||
|
||||
# Calling
|
||||
self.camera_settings.calculate_physical_dimensions()
|
||||
|
||||
pixel_size = self.camera_settings.default_pixel_size
|
||||
x_pixel = float(self.camera_settings.roi_widgets["Width"].get())
|
||||
y_pixel = float(self.camera_settings.roi_widgets["Height"].get())
|
||||
|
||||
microscope_state_dict = self.camera_settings.parent_controller.configuration[
|
||||
"experiment"
|
||||
]["MicroscopeState"]
|
||||
zoom = microscope_state_dict["zoom"]
|
||||
microscope_name = microscope_state_dict["microscope_name"]
|
||||
pixel_size = self.camera_settings.parent_controller.configuration[
|
||||
"configuration"
|
||||
]["microscopes"][microscope_name]["zoom"]["pixel_size"][zoom]
|
||||
|
||||
dim_x = x_pixel * pixel_size
|
||||
dim_y = y_pixel * pixel_size
|
||||
|
||||
assert float(self.camera_settings.roi_widgets["FOV_X"].get()) == float(
|
||||
int(dim_x)
|
||||
)
|
||||
assert float(self.camera_settings.roi_widgets["FOV_Y"].get()) == float(
|
||||
int(dim_y)
|
||||
)
|
||||
|
||||
# Reset to zoom of 1
|
||||
self.camera_settings.parent_controller.configuration["experiment"][
|
||||
"MicroscopeState"
|
||||
]["zoom"] = "1x"
|
||||
assert (
|
||||
self.camera_settings.parent_controller.configuration["experiment"][
|
||||
"MicroscopeState"
|
||||
]["zoom"]
|
||||
== "1x"
|
||||
)
|
||||
|
||||
def test_calculate_readout_time(self):
|
||||
"""
|
||||
TODO need more info about camera before testing
|
||||
"""
|
||||
pass
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"mode", ["single", "live", "customized", "z-stack", "stop"]
|
||||
)
|
||||
def test_update_number_of_pixels(self, mode):
|
||||
import random
|
||||
|
||||
self.camera_settings.populate_experiment_values()
|
||||
|
||||
self.camera_settings.mode = mode
|
||||
|
||||
self.camera_settings.mode_widgets["Pixels"].set("")
|
||||
assert self.camera_settings.camera_setting_dict["number_of_pixels"] != ""
|
||||
|
||||
n_pixels = random.randint(1, 100)
|
||||
self.camera_settings.mode_widgets["Pixels"].set(n_pixels)
|
||||
|
||||
# Check
|
||||
assert self.camera_settings.camera_setting_dict["number_of_pixels"] == int(
|
||||
n_pixels
|
||||
)
|
||||
if mode != "live" and mode != "stop":
|
||||
assert (
|
||||
self.camera_settings.camera_setting_dict["number_of_pixels"] == n_pixels
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"x_pixels, y_pixels", [(512, 512), (4096, 4096), (2304, 1024), (1024, 2048)]
|
||||
)
|
||||
def test_update_camera_device_related_setting(self, x_pixels, y_pixels):
|
||||
self.camera_settings.populate_experiment_values()
|
||||
|
||||
microscope_name = self.camera_settings.parent_controller.configuration[
|
||||
"experiment"
|
||||
]["MicroscopeState"]["microscope_name"]
|
||||
camera_config = self.camera_settings.parent_controller.configuration[
|
||||
"configuration"
|
||||
]["microscopes"][microscope_name]["camera"]
|
||||
default_x_pixels = camera_config["x_pixels"]
|
||||
default_y_pixels = camera_config["y_pixels"]
|
||||
|
||||
camera_config["x_pixels"] = x_pixels
|
||||
camera_config["y_pixels"] = y_pixels
|
||||
self.camera_settings.update_camera_device_related_setting()
|
||||
|
||||
assert self.camera_settings.roi_widgets["Width"].get() == min(
|
||||
self.camera_settings.camera_setting_dict["x_pixels"], x_pixels
|
||||
)
|
||||
assert self.camera_settings.roi_widgets["Height"].get() == min(
|
||||
self.camera_settings.camera_setting_dict["y_pixels"], y_pixels
|
||||
)
|
||||
assert self.camera_settings.roi_widgets["Width"].widget["to"] == x_pixels
|
||||
assert self.camera_settings.roi_widgets["Height"].widget["to"] == y_pixels
|
||||
|
||||
camera_config["x_pixels"] = default_x_pixels
|
||||
camera_config["y_pixels"] = default_y_pixels
|
||||
813
test/controller/sub_controllers/test_camera_view.py
Normal file
813
test/controller/sub_controllers/test_camera_view.py
Normal file
@@ -0,0 +1,813 @@
|
||||
# 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.
|
||||
#
|
||||
|
||||
from navigate.controller.sub_controllers.camera_view import CameraViewController
|
||||
import pytest
|
||||
import random
|
||||
from unittest.mock import MagicMock
|
||||
import numpy as np
|
||||
|
||||
|
||||
class TestCameraViewController:
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup_class(self, dummy_controller):
|
||||
c = dummy_controller
|
||||
self.v = dummy_controller.view
|
||||
c.model = MagicMock()
|
||||
c.model.get_offset_variance_maps = MagicMock(return_value=[None, None])
|
||||
|
||||
self.camera_view = CameraViewController(self.v.camera_waveform.camera_tab, c)
|
||||
|
||||
self.microscope_state = {
|
||||
"channels": {
|
||||
"channel_1": {
|
||||
"is_selected": True,
|
||||
"laser": "488nm",
|
||||
"laser_index": 0,
|
||||
"camera_exposure_time": 200.0,
|
||||
"laser_power": 20.0,
|
||||
"interval_time": 1.0,
|
||||
"defocus": 100.0,
|
||||
"filter_wheel_0": "Empty-Alignment",
|
||||
"filter_position_0": 0,
|
||||
"filter_wheel_1": "Empty-Alignment",
|
||||
"filter_position_1": 0,
|
||||
},
|
||||
"channel_2": {
|
||||
"is_selected": True,
|
||||
"laser": "488nm",
|
||||
"laser_index": 0,
|
||||
"camera_exposure_time": 200.0,
|
||||
"laser_power": 20.0,
|
||||
"interval_time": 1.0,
|
||||
"defocus": 100.0,
|
||||
"filter_wheel_0": "Empty-Alignment",
|
||||
"filter_position_0": 0,
|
||||
"filter_wheel_1": "Empty-Alignment",
|
||||
"filter_position_1": 0,
|
||||
},
|
||||
"channel_3": {
|
||||
"is_selected": True,
|
||||
"laser": "488nm",
|
||||
"laser_index": 0,
|
||||
"camera_exposure_time": 200.0,
|
||||
"laser_power": 20.0,
|
||||
"interval_time": 1.0,
|
||||
"defocus": 100.0,
|
||||
"filter_wheel_0": "Empty-Alignment",
|
||||
"filter_position_0": 0,
|
||||
"filter_wheel_1": "Empty-Alignment",
|
||||
"filter_position_1": 0,
|
||||
},
|
||||
},
|
||||
"number_z_steps": np.random.randint(10, 100),
|
||||
"stack_cycling_mode": "per_stack",
|
||||
"image_mode": "z-stack",
|
||||
}
|
||||
|
||||
def test_init(self):
|
||||
|
||||
assert isinstance(self.camera_view, CameraViewController)
|
||||
|
||||
def test_update_display_state(self):
|
||||
pass
|
||||
|
||||
def test_get_absolute_position(self, monkeypatch):
|
||||
def mock_winfo_pointerx():
|
||||
self.x = int(random.random())
|
||||
return self.x
|
||||
|
||||
def mock_winfo_pointery():
|
||||
self.y = int(random.random())
|
||||
return self.y
|
||||
|
||||
monkeypatch.setattr(self.v, "winfo_pointerx", mock_winfo_pointerx)
|
||||
monkeypatch.setattr(self.v, "winfo_pointery", mock_winfo_pointery)
|
||||
|
||||
# call the function under test
|
||||
x, y = self.camera_view.get_absolute_position()
|
||||
|
||||
# make assertions about the return value
|
||||
assert x == self.x
|
||||
assert y == self.y
|
||||
|
||||
def test_popup_menu(self, monkeypatch):
|
||||
|
||||
# create a fake event object
|
||||
self.startx = int(random.random())
|
||||
self.starty = int(random.random())
|
||||
event = type("Event", (object,), {"x": self.startx, "y": self.starty})()
|
||||
self.grab_released = False
|
||||
self.x = int(random.random())
|
||||
self.y = int(random.random())
|
||||
self.absx = int(random.random())
|
||||
self.absy = int(random.random())
|
||||
|
||||
# monkey patch the get_absolute_position method to return specific values
|
||||
def mock_get_absolute_position():
|
||||
self.absx = int(random.random())
|
||||
self.absy = int(random.random())
|
||||
return self.absx, self.absy
|
||||
|
||||
monkeypatch.setattr(
|
||||
self.camera_view, "get_absolute_position", mock_get_absolute_position
|
||||
)
|
||||
|
||||
def mock_tk_popup(x, y):
|
||||
self.x = x
|
||||
self.y = y
|
||||
|
||||
def mock_grab_release():
|
||||
self.grab_released = True
|
||||
|
||||
monkeypatch.setattr(self.camera_view.menu, "tk_popup", mock_tk_popup)
|
||||
monkeypatch.setattr(self.camera_view.menu, "grab_release", mock_grab_release)
|
||||
|
||||
# call the function under test
|
||||
self.camera_view.popup_menu(event)
|
||||
|
||||
# make assertions about the state of the view object
|
||||
assert self.camera_view.move_to_x == self.startx
|
||||
assert self.camera_view.move_to_y == self.starty
|
||||
assert self.x == self.absx
|
||||
assert self.y == self.absy
|
||||
assert self.grab_released is True
|
||||
|
||||
@pytest.mark.parametrize("name", ["minmax", "image"])
|
||||
@pytest.mark.parametrize("data", [[random.randint(0, 49), random.randint(50, 100)]])
|
||||
def test_initialize(self, name, data):
|
||||
|
||||
self.camera_view.initialize(name, data)
|
||||
|
||||
# Checking values
|
||||
if name == "minmax":
|
||||
assert self.camera_view.image_palette["Min"].get() == data[0]
|
||||
assert self.camera_view.image_palette["Max"].get() == data[1]
|
||||
if name == "image":
|
||||
assert self.camera_view.image_metrics["Frames"].get() == data[0]
|
||||
|
||||
def test_set_mode(self):
|
||||
|
||||
# Test default mode
|
||||
self.camera_view.set_mode()
|
||||
assert self.camera_view.mode == ""
|
||||
assert self.camera_view.menu.entrycget("Move Here", "state") == "disabled"
|
||||
|
||||
# Test 'live' mode
|
||||
self.camera_view.set_mode("live")
|
||||
assert self.camera_view.mode == "live"
|
||||
assert self.camera_view.menu.entrycget("Move Here", "state") == "normal"
|
||||
|
||||
# Test 'stop' mode
|
||||
self.camera_view.set_mode("stop")
|
||||
assert self.camera_view.mode == "stop"
|
||||
assert self.camera_view.menu.entrycget("Move Here", "state") == "normal"
|
||||
|
||||
# Test invalid mode
|
||||
self.camera_view.set_mode("invalid")
|
||||
assert self.camera_view.mode == "invalid"
|
||||
assert self.camera_view.menu.entrycget("Move Here", "state") == "disabled"
|
||||
|
||||
@pytest.mark.parametrize("mode", ["stop", "live"])
|
||||
def test_move_stage(self, mode):
|
||||
|
||||
# Setup to check formula inside func is correct
|
||||
microscope_name = self.camera_view.parent_controller.configuration[
|
||||
"experiment"
|
||||
]["MicroscopeState"]["microscope_name"]
|
||||
zoom_value = self.camera_view.parent_controller.configuration["experiment"][
|
||||
"MicroscopeState"
|
||||
]["zoom"]
|
||||
pixel_size = self.camera_view.parent_controller.configuration["configuration"][
|
||||
"microscopes"
|
||||
][microscope_name]["zoom"]["pixel_size"][zoom_value]
|
||||
|
||||
current_center_x = (
|
||||
self.camera_view.zoom_rect[0][0] + self.camera_view.zoom_rect[0][1]
|
||||
) / 2
|
||||
current_center_y = (
|
||||
self.camera_view.zoom_rect[1][0] + self.camera_view.zoom_rect[1][1]
|
||||
) / 2
|
||||
|
||||
self.camera_view.move_to_x = int(random.random())
|
||||
self.camera_view.move_to_y = int(random.random())
|
||||
|
||||
# This is the formula to check
|
||||
offset_x = (
|
||||
(self.camera_view.move_to_x - current_center_x)
|
||||
/ self.camera_view.zoom_scale
|
||||
* self.camera_view.canvas_width_scale
|
||||
* pixel_size
|
||||
)
|
||||
offset_y = (
|
||||
(self.camera_view.move_to_y - current_center_y)
|
||||
/ self.camera_view.zoom_scale
|
||||
* self.camera_view.canvas_width_scale
|
||||
* pixel_size
|
||||
)
|
||||
|
||||
# Set the mode to check if statements
|
||||
self.camera_view.mode = mode
|
||||
|
||||
# Act
|
||||
self.camera_view.move_stage()
|
||||
|
||||
# Check
|
||||
assert self.camera_view.parent_controller.pop() == "get_stage_position"
|
||||
res = self.camera_view.parent_controller.pop()
|
||||
if mode == "stop":
|
||||
assert res == "move_stage_and_acquire_image"
|
||||
else:
|
||||
assert res == "move_stage_and_update_info"
|
||||
# Checking that move stage properly changed pos
|
||||
new_pos = self.camera_view.parent_controller.pop()
|
||||
self.camera_view.parent_controller.stage_pos["x"] -= offset_x
|
||||
self.camera_view.parent_controller.stage_pos["y"] += offset_y
|
||||
assert new_pos == self.camera_view.parent_controller.stage_pos
|
||||
|
||||
def test_reset_display(self, monkeypatch):
|
||||
|
||||
# Mock process image function
|
||||
process_image_called = False
|
||||
|
||||
def mock_process_image():
|
||||
nonlocal process_image_called
|
||||
process_image_called = True
|
||||
|
||||
monkeypatch.setattr(self.camera_view, "process_image", mock_process_image)
|
||||
|
||||
# Reset and check
|
||||
self.camera_view.reset_display()
|
||||
assert np.array_equal(
|
||||
self.camera_view.zoom_rect,
|
||||
np.array(
|
||||
[
|
||||
[0, self.camera_view.view.canvas_width],
|
||||
[0, self.camera_view.view.canvas_height],
|
||||
]
|
||||
),
|
||||
)
|
||||
assert np.array_equal(self.camera_view.zoom_offset, np.array([[0], [0]]))
|
||||
assert self.camera_view.zoom_value == 1
|
||||
assert self.camera_view.zoom_scale == 1
|
||||
assert self.camera_view.zoom_width == self.camera_view.view.canvas_width
|
||||
assert self.camera_view.zoom_height == self.camera_view.view.canvas_height
|
||||
assert process_image_called is True
|
||||
|
||||
def test_process_image(self):
|
||||
self.camera_view.image = np.random.randint(0, 256, (600, 800))
|
||||
self.camera_view.digital_zoom = MagicMock()
|
||||
# self.camera_view.detect_saturation = MagicMock()
|
||||
self.camera_view.down_sample_image = MagicMock()
|
||||
self.camera_view.scale_image_intensity = MagicMock()
|
||||
self.camera_view.add_crosshair = MagicMock()
|
||||
self.camera_view.apply_lut = MagicMock()
|
||||
self.camera_view.populate_image = MagicMock()
|
||||
|
||||
self.camera_view.process_image()
|
||||
|
||||
self.camera_view.digital_zoom.assert_called()
|
||||
# self.camera_view.detect_saturation.assert_called()
|
||||
self.camera_view.down_sample_image.assert_called()
|
||||
self.camera_view.scale_image_intensity.assert_called()
|
||||
self.camera_view.add_crosshair.assert_called()
|
||||
self.camera_view.apply_lut.assert_called()
|
||||
self.camera_view.populate_image.assert_called()
|
||||
|
||||
@pytest.mark.parametrize("num,value", [(4, 0.95), (5, 1.05)])
|
||||
def test_mouse_wheel(self, num, value):
|
||||
|
||||
# Test zoom in and out
|
||||
event = MagicMock()
|
||||
event.num = num
|
||||
event.x = int(random.random())
|
||||
event.y = int(random.random())
|
||||
event.delta = 120
|
||||
self.camera_view.zoom_value = 1
|
||||
self.camera_view.zoom_scale = 1
|
||||
self.camera_view.zoom_width = self.camera_view.view.canvas_width
|
||||
self.camera_view.zoom_height = self.camera_view.view.canvas_height
|
||||
self.camera_view.reset_display = MagicMock()
|
||||
self.camera_view.process_image = MagicMock()
|
||||
self.camera_view.mouse_wheel(event)
|
||||
|
||||
assert self.camera_view.zoom_value == value
|
||||
assert self.camera_view.zoom_scale == value
|
||||
assert self.camera_view.zoom_width == self.camera_view.view.canvas_width / value
|
||||
assert (
|
||||
self.camera_view.zoom_height == self.camera_view.view.canvas_height / value
|
||||
)
|
||||
|
||||
if (
|
||||
self.camera_view.zoom_width > self.camera_view.view.canvas_width
|
||||
or self.camera_view.zoom_height > self.camera_view.view.canvas_height
|
||||
):
|
||||
assert self.camera_view.reset_display.called is True
|
||||
self.camera_view.process_image.assert_called()
|
||||
else:
|
||||
assert self.camera_view.reset_display.called is False
|
||||
not self.camera_view.process_image.assert_called()
|
||||
|
||||
@pytest.mark.skip("AssertionError: Expected 'mock' to have been called.")
|
||||
def test_digital_zoom(self):
|
||||
|
||||
# Setup
|
||||
a, b, c, d, e, f = [random.randint(1, 100) for _ in range(6)]
|
||||
g, h = [random.randint(100, 400) for _ in range(2)]
|
||||
i, j = [random.randint(500, 1000) for _ in range(2)]
|
||||
val, scale, widthsc, heightsc = [random.randint(2, 4) for _ in range(4)]
|
||||
self.camera_view.zoom_rect = np.array([[a, b], [c, d]])
|
||||
self.camera_view.zoom_offset = np.array([[e], [f]])
|
||||
self.camera_view.zoom_value = val
|
||||
self.camera_view.zoom_scale = scale
|
||||
self.camera_view.zoom_width = g # 300
|
||||
self.camera_view.zoom_height = h # 400
|
||||
self.camera_view.view.canvas_width = i # 800
|
||||
self.camera_view.view.canvas_height = j # 600
|
||||
self.camera_view.canvas_width_scale = widthsc
|
||||
self.camera_view.canvas_height_scale = heightsc
|
||||
self.camera_view.image = np.random.randint(0, 256, (600, 800))
|
||||
self.camera_view.reset_display = MagicMock()
|
||||
|
||||
# Preprocess
|
||||
new_zoom_rec = self.camera_view.zoom_rect - self.camera_view.zoom_offset
|
||||
new_zoom_rec *= self.camera_view.zoom_value
|
||||
new_zoom_rec += self.camera_view.zoom_offset
|
||||
|
||||
x_start_index = int(-new_zoom_rec[0][0] / self.camera_view.zoom_scale)
|
||||
x_end_index = int(x_start_index + self.camera_view.zoom_width)
|
||||
y_start_index = int(-new_zoom_rec[1][0] / self.camera_view.zoom_scale)
|
||||
y_end_index = int(y_start_index + self.camera_view.zoom_height)
|
||||
|
||||
crosshair_x = (new_zoom_rec[0][0] + new_zoom_rec[0][1]) / 2
|
||||
crosshair_y = (new_zoom_rec[1][0] + new_zoom_rec[1][1]) / 2
|
||||
if crosshair_x < 0 or crosshair_x >= self.camera_view.view.canvas_width:
|
||||
crosshair_x = -1
|
||||
if crosshair_y < 0 or crosshair_y >= self.camera_view.view.canvas_height:
|
||||
crosshair_y = -1
|
||||
|
||||
new_image = self.camera_view.image[
|
||||
y_start_index
|
||||
* self.camera_view.canvas_height_scale : y_end_index
|
||||
* self.camera_view.canvas_height_scale,
|
||||
x_start_index
|
||||
* self.camera_view.canvas_width_scale : x_end_index
|
||||
* self.camera_view.canvas_width_scale,
|
||||
]
|
||||
|
||||
# Call func
|
||||
self.camera_view.digital_zoom()
|
||||
|
||||
# Check zoom rec
|
||||
assert np.array_equal(self.camera_view.zoom_rect, new_zoom_rec)
|
||||
# Check zoom offset
|
||||
assert np.array_equal(self.camera_view.zoom_offset, np.array([[0], [0]]))
|
||||
# Check zoom_value
|
||||
assert self.camera_view.zoom_value == 1
|
||||
# Check zoom_image
|
||||
assert np.array_equal(self.camera_view.zoom_image, new_image)
|
||||
# Check crosshairs
|
||||
assert self.camera_view.crosshair_x == int(crosshair_x)
|
||||
assert self.camera_view.crosshair_y == int(crosshair_y)
|
||||
# Check reset display
|
||||
self.camera_view.reset_display.assert_called()
|
||||
|
||||
@pytest.mark.parametrize("onoff", [True, False])
|
||||
def test_left_click(self, onoff):
|
||||
self.camera_view.add_crosshair = MagicMock()
|
||||
self.camera_view.digital_zoom = MagicMock()
|
||||
# self.camera_view.detect_saturation = MagicMock()
|
||||
self.camera_view.down_sample_image = MagicMock()
|
||||
self.camera_view.transpose_image = MagicMock()
|
||||
self.camera_view.scale_image_intensity = MagicMock()
|
||||
self.camera_view.apply_lut = MagicMock()
|
||||
self.camera_view.populate_image = MagicMock()
|
||||
event = MagicMock()
|
||||
|
||||
self.camera_view.image = np.random.randint(0, 256, (600, 800))
|
||||
self.camera_view.apply_cross_hair = onoff
|
||||
|
||||
self.camera_view.left_click(event)
|
||||
|
||||
self.camera_view.add_crosshair.assert_called()
|
||||
self.camera_view.populate_image.assert_called()
|
||||
assert self.camera_view.apply_cross_hair == (not onoff)
|
||||
|
||||
@pytest.mark.parametrize("frames", [0, 1, 2, 10])
|
||||
def test_update_max_count(self, frames):
|
||||
self.camera_view.image_metrics["Frames"].set(frames)
|
||||
|
||||
idx = 0
|
||||
temp = [0] * 40
|
||||
|
||||
while idx < 40:
|
||||
self.camera_view._last_frame_display_max = np.random.randint(0, 500)
|
||||
temp[idx] = self.camera_view._last_frame_display_max
|
||||
idx += 1
|
||||
# Act
|
||||
self.camera_view.update_max_counts()
|
||||
|
||||
assert self.camera_view._max_intensity_history_idx == idx % 32
|
||||
assert (
|
||||
self.camera_view.max_intensity_history[
|
||||
self.camera_view._max_intensity_history_idx - 1
|
||||
]
|
||||
== self.camera_view._last_frame_display_max
|
||||
)
|
||||
|
||||
# Assert
|
||||
if frames == 0:
|
||||
assert self.camera_view.image_metrics["Frames"].get() == 1
|
||||
frame_num = 1
|
||||
else:
|
||||
frame_num = frames
|
||||
|
||||
if frame_num <= idx:
|
||||
rolling_average = sum(temp[idx - frame_num : idx]) / frame_num
|
||||
else:
|
||||
rolling_average = sum(temp[:idx]) / frame_num
|
||||
|
||||
assert self.camera_view.image_metrics["Image"].get() == round(
|
||||
rolling_average, 0
|
||||
)
|
||||
|
||||
def test_down_sample_image(self, monkeypatch):
|
||||
import cv2
|
||||
|
||||
# create a test image
|
||||
test_image = np.random.rand(100, 100)
|
||||
self.zoom_image = test_image
|
||||
|
||||
# set the widget size
|
||||
widget = type("MyWidget", (object,), {"widget": self.camera_view.view})
|
||||
event = type(
|
||||
"MyEvent",
|
||||
(object,),
|
||||
{
|
||||
"widget": widget,
|
||||
"width": np.random.randint(5, 1000),
|
||||
"height": np.random.randint(5, 1000),
|
||||
},
|
||||
)
|
||||
self.camera_view.resize(event)
|
||||
|
||||
# monkeypatch cv2.resize
|
||||
def mocked_resize(src, dsize, interpolation=1):
|
||||
return np.ones((dsize[0], dsize[1]))
|
||||
|
||||
monkeypatch.setattr(cv2, "resize", mocked_resize)
|
||||
|
||||
# call the function
|
||||
down_sampled_image = self.camera_view.down_sample_image(test_image)
|
||||
|
||||
# assert that the image has been resized correctly
|
||||
assert np.shape(down_sampled_image) == (
|
||||
self.camera_view.view.canvas_width,
|
||||
self.camera_view.view.canvas_height,
|
||||
)
|
||||
|
||||
# assert that the image has not been modified
|
||||
assert not np.array_equal(down_sampled_image, test_image)
|
||||
|
||||
@pytest.mark.parametrize("auto", [True, False])
|
||||
def test_scale_image_intensity(self, auto):
|
||||
# Create a test image
|
||||
test_image = np.random.rand(100, 100)
|
||||
|
||||
# Set autoscale to True
|
||||
self.camera_view.autoscale = auto
|
||||
|
||||
if auto is False:
|
||||
self.camera_view.max_counts = 1.5
|
||||
self.camera_view.min_counts = 0.5
|
||||
|
||||
# Call the function
|
||||
scaled_image = self.camera_view.scale_image_intensity(test_image)
|
||||
|
||||
# Assert that max_counts and min_counts have been set correctly
|
||||
if auto is True:
|
||||
assert self.camera_view._last_frame_display_max == np.max(test_image)
|
||||
|
||||
# Assert that the image has been scaled correctly
|
||||
assert np.min(scaled_image) >= 0
|
||||
assert np.max(scaled_image) <= 255
|
||||
|
||||
def test_populate_image(self, monkeypatch):
|
||||
from PIL import Image, ImageTk
|
||||
import cv2
|
||||
|
||||
# Create test image
|
||||
self.camera_view.cross_hair_image = np.random.rand(100, 100)
|
||||
self.camera_view.ilastik_seg_mask = np.random.rand(100, 100)
|
||||
|
||||
# Set display_mask_flag to True
|
||||
self.camera_view.display_mask_flag = True
|
||||
|
||||
# Monkeypatch the Image.fromarray() method of PIL
|
||||
def mocked_fromarray(arr):
|
||||
return arr
|
||||
|
||||
monkeypatch.setattr(Image, "fromarray", mocked_fromarray)
|
||||
|
||||
# Monkeypatch the cv2.resize() function
|
||||
def mocked_resize(arr, size):
|
||||
return arr
|
||||
|
||||
monkeypatch.setattr(cv2, "resize", mocked_resize)
|
||||
|
||||
# Monkeypatch the Image.blend() method of PIL
|
||||
def mocked_blend(img1, img2, alpha):
|
||||
return img1 * alpha + img2 * (1 - alpha)
|
||||
|
||||
monkeypatch.setattr(Image, "blend", mocked_blend)
|
||||
|
||||
def mocked_PhotoImage(img):
|
||||
mock_photo = MagicMock()
|
||||
# Set up width and height methods to return appropriate dimensions
|
||||
mock_photo.width.return_value = 100
|
||||
mock_photo.height.return_value = 100
|
||||
return mock_photo
|
||||
|
||||
monkeypatch.setattr(ImageTk, "PhotoImage", mocked_PhotoImage)
|
||||
|
||||
self.camera_view.canvas.create_image = MagicMock()
|
||||
self.camera_view.image_cache_flag = True
|
||||
|
||||
# Call the function
|
||||
self.camera_view.populate_image(self.camera_view.cross_hair_image)
|
||||
|
||||
# Assert that the tk_image has been created correctly
|
||||
assert self.camera_view._img_buf is not None
|
||||
|
||||
# Set display_mask_flag to True
|
||||
self.camera_view.display_mask_flag = False
|
||||
|
||||
# Call the function
|
||||
self.camera_view.populate_image(self.camera_view.cross_hair_image)
|
||||
|
||||
def test_initialize_non_live_display(self):
|
||||
# Create test buffer and microscope_state
|
||||
camera_parameters = {
|
||||
"img_x_pixels": np.random.randint(1, 200),
|
||||
"img_y_pixels": np.random.randint(1, 200),
|
||||
}
|
||||
|
||||
# Call the function
|
||||
self.camera_view.initialize_non_live_display(
|
||||
self.microscope_state, camera_parameters
|
||||
)
|
||||
|
||||
# Assert that the variables have been set correctly
|
||||
assert self.camera_view.image_count == 0
|
||||
assert self.camera_view.slice_index == 0
|
||||
assert self.camera_view.number_of_channels == len(
|
||||
self.microscope_state["channels"]
|
||||
)
|
||||
assert (
|
||||
self.camera_view.number_of_slices == self.microscope_state["number_z_steps"]
|
||||
)
|
||||
assert (
|
||||
self.camera_view.total_images_per_volume
|
||||
== self.camera_view.number_of_channels * self.camera_view.number_of_slices
|
||||
)
|
||||
assert self.camera_view.original_image_width == int(
|
||||
camera_parameters["img_x_pixels"]
|
||||
)
|
||||
assert self.camera_view.original_image_height == int(
|
||||
camera_parameters["img_y_pixels"]
|
||||
)
|
||||
assert self.camera_view.canvas_width_scale == float(
|
||||
self.camera_view.original_image_width / self.camera_view.canvas_width
|
||||
)
|
||||
assert self.camera_view.canvas_height_scale == float(
|
||||
self.camera_view.original_image_height / self.camera_view.canvas_height
|
||||
)
|
||||
|
||||
def test_identify_channel_index_and_slice(self):
|
||||
# Not currently in use
|
||||
pass
|
||||
|
||||
def test_retrieve_image_slice_from_volume(self):
|
||||
# Not currently in use
|
||||
pass
|
||||
|
||||
@pytest.mark.parametrize("transpose", [True, False])
|
||||
def test_display_image(self, transpose):
|
||||
"""Test the display of an image on the camera view
|
||||
|
||||
TODO: The recent refactor makes this test non-functional. It needs to be updated
|
||||
The newer code does not use the camera_view.image attribute after the
|
||||
transpose operation, so any transpose/image flipping is not reflected in the
|
||||
final image. The test should be updated to reflect this.,
|
||||
"""
|
||||
|
||||
self.camera_view.initialize_non_live_display(
|
||||
self.microscope_state, {"img_x_pixels": 50, "img_y_pixels": 100}
|
||||
)
|
||||
self.camera_view.digital_zoom = MagicMock()
|
||||
# self.camera_view.detect_saturation = MagicMock()
|
||||
self.camera_view.down_sample_image = MagicMock()
|
||||
self.camera_view.scale_image_intensity = MagicMock()
|
||||
self.camera_view.apply_lut = MagicMock()
|
||||
self.camera_view.populate_image = MagicMock()
|
||||
images = np.random.rand(10, 100, 50)
|
||||
|
||||
self.camera_view.transpose = transpose
|
||||
count = 0
|
||||
self.camera_view.image_count = count
|
||||
self.camera_view.image_metrics = {"Channel": MagicMock()}
|
||||
self.camera_view.update_max_counts = MagicMock()
|
||||
self.camera_view.flip_flags = {"x": False, "y": False}
|
||||
|
||||
image_id = np.random.randint(0, 10)
|
||||
self.camera_view.try_to_display_image(images[image_id])
|
||||
|
||||
assert (
|
||||
self.camera_view.spooled_images.size_y,
|
||||
self.camera_view.spooled_images.size_x,
|
||||
) == np.shape(images[image_id])
|
||||
assert self.camera_view.image_count == count + 1
|
||||
|
||||
self.camera_view.flip_flags = {"x": True, "y": False}
|
||||
image_id = np.random.randint(0, 10)
|
||||
self.camera_view.try_to_display_image(images[image_id])
|
||||
# assert np.shape(self.camera_view.image) == np.shape(images[image_id])
|
||||
# if not transpose:
|
||||
# assert (self.camera_view.image == images[image_id][:, ::-1]).all()
|
||||
# else:
|
||||
# assert (self.camera_view.image == images[image_id][:, ::-1].T).all()
|
||||
assert self.camera_view.image_count == count + 2
|
||||
|
||||
self.camera_view.flip_flags = {"x": False, "y": True}
|
||||
image_id = np.random.randint(0, 10)
|
||||
self.camera_view.try_to_display_image(images[image_id])
|
||||
# assert np.shape(self.camera_view.image) == np.shape(images[image_id])
|
||||
# if not transpose:
|
||||
# assert (self.camera_view.image == images[image_id][::-1, :]).all()
|
||||
# else:
|
||||
# assert (self.camera_view.image == images[image_id][::-1, :].T).all()
|
||||
assert self.camera_view.image_count == count + 3
|
||||
|
||||
self.camera_view.flip_flags = {"x": True, "y": True}
|
||||
image_id = np.random.randint(0, 10)
|
||||
self.camera_view.try_to_display_image(images[image_id])
|
||||
# assert np.shape(self.camera_view.image) == np.shape(images[image_id])
|
||||
# if not transpose:
|
||||
# assert (self.camera_view.image == images[image_id][::-1, ::-1]).all()
|
||||
# else:
|
||||
# assert (self.camera_view.image == images[image_id][::-1, ::-1].T).all()
|
||||
assert self.camera_view.image_count == count + 4
|
||||
|
||||
def test_add_crosshair(self):
|
||||
|
||||
# Arrange
|
||||
x = self.camera_view.canvas_width
|
||||
y = self.camera_view.canvas_height
|
||||
image = np.random.rand(x, y)
|
||||
self.camera_view.apply_cross_hair = True
|
||||
|
||||
# Act
|
||||
image2 = self.camera_view.add_crosshair(image)
|
||||
|
||||
# Assert
|
||||
assert np.all(image2[:, self.camera_view.zoom_rect[0][1] // 2] == 255)
|
||||
assert np.all(image2[self.camera_view.zoom_rect[1][1] // 2, :] == 255)
|
||||
|
||||
def test_apply_LUT(self):
|
||||
# Someone else with better numpy understanding will need to do this TODO
|
||||
|
||||
pass
|
||||
|
||||
def test_update_LUT(self):
|
||||
# Same as apply LUT TODO
|
||||
pass
|
||||
|
||||
def test_toggle_min_max_button(self):
|
||||
|
||||
# Setup for true path
|
||||
self.camera_view.image_palette["Autoscale"].set(True)
|
||||
|
||||
# Act by calling function
|
||||
self.camera_view.toggle_min_max_buttons()
|
||||
|
||||
# Assert things are correct
|
||||
assert str(self.camera_view.image_palette["Min"].widget["state"]) == "disabled"
|
||||
assert str(self.camera_view.image_palette["Max"].widget["state"]) == "disabled"
|
||||
|
||||
# Setup for false path
|
||||
self.camera_view.image_palette["Autoscale"].set(False)
|
||||
|
||||
# Mock function call to isolate
|
||||
self.camera_view.update_min_max_counts = MagicMock()
|
||||
|
||||
# Act by calling function
|
||||
self.camera_view.toggle_min_max_buttons()
|
||||
|
||||
# Assert things are correct and called
|
||||
assert str(self.camera_view.image_palette["Min"].widget["state"]) == "normal"
|
||||
assert str(self.camera_view.image_palette["Max"].widget["state"]) == "normal"
|
||||
self.camera_view.update_min_max_counts.assert_called()
|
||||
|
||||
def test_transpose_image(self):
|
||||
# Create test data
|
||||
self.camera_view.image_palette["Flip XY"].set(True)
|
||||
self.camera_view.transpose = None
|
||||
|
||||
# Call the function
|
||||
self.camera_view.update_transpose_state()
|
||||
|
||||
# Assert the output
|
||||
assert self.camera_view.transpose is True
|
||||
|
||||
# Create test data
|
||||
self.camera_view.image_palette["Flip XY"].set(False)
|
||||
self.camera_view.transpose = None
|
||||
|
||||
# Call the function
|
||||
self.camera_view.update_transpose_state()
|
||||
|
||||
# Assert the output
|
||||
assert self.camera_view.transpose is False
|
||||
|
||||
def test_update_min_max_counts(self):
|
||||
# Create test data
|
||||
min = np.random.randint(0, 10)
|
||||
max = np.random.randint(0, 10)
|
||||
self.camera_view.image_palette["Min"].set(min)
|
||||
self.camera_view.image_palette["Max"].set(max)
|
||||
|
||||
self.camera_view.min_counts = None
|
||||
self.camera_view.max_counts = None
|
||||
|
||||
# Call the function
|
||||
self.camera_view.update_min_max_counts()
|
||||
|
||||
# Assert the output
|
||||
assert self.camera_view.min_counts == min
|
||||
assert self.camera_view.max_counts == max
|
||||
|
||||
def test_set_mask_color_table(self):
|
||||
# This is beyond me currently TODO
|
||||
pass
|
||||
|
||||
def test_display_mask(self, monkeypatch):
|
||||
import cv2
|
||||
|
||||
# Create test data
|
||||
self.camera_view.ilastik_seg_mask = None
|
||||
self.camera_view.ilastik_mask_ready_lock.acquire()
|
||||
mask = np.zeros((5, 5), dtype=np.uint8)
|
||||
self.camera_view.mask_color_table = np.zeros((256, 1, 3), dtype=np.uint8)
|
||||
|
||||
# Define the monkeypatch
|
||||
def mock_applyColorMap(mask, mask_color_table):
|
||||
return mask
|
||||
|
||||
# Apply the monkeypatch
|
||||
monkeypatch.setattr(cv2, "applyColorMap", mock_applyColorMap)
|
||||
|
||||
# Call the function
|
||||
self.camera_view.display_mask(mask)
|
||||
|
||||
# Assert the output
|
||||
assert (self.camera_view.ilastik_seg_mask == mask).all()
|
||||
assert not self.camera_view.ilastik_mask_ready_lock.locked()
|
||||
|
||||
def test_update_canvas_size(self):
|
||||
self.camera_view.view.canvas["width"] = random.randint(1, 2000)
|
||||
self.camera_view.view.canvas["height"] = random.randint(1, 2000)
|
||||
|
||||
self.camera_view.update_canvas_size()
|
||||
|
||||
assert self.camera_view.canvas_width > 0
|
||||
assert self.camera_view.canvas_height > 0
|
||||
168
test/controller/sub_controllers/test_channels_settings.py
Normal file
168
test/controller/sub_controllers/test_channels_settings.py
Normal file
@@ -0,0 +1,168 @@
|
||||
# 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
|
||||
|
||||
|
||||
class TestChannelSettingController:
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup_class(self, dummy_controller):
|
||||
from navigate.controller.sub_controllers.channels_tab import (
|
||||
ChannelsTabController,
|
||||
)
|
||||
from navigate.controller.sub_controllers.channels_settings import (
|
||||
ChannelSettingController,
|
||||
)
|
||||
|
||||
self.ctc = ChannelsTabController(
|
||||
dummy_controller.view.settings.channels_tab, dummy_controller
|
||||
)
|
||||
self.ctc.commands = []
|
||||
self.ctc.execute = lambda command: self.ctc.commands.append(command)
|
||||
|
||||
self.channel_setting = ChannelSettingController(
|
||||
self.ctc.view.channel_widgets_frame,
|
||||
self.ctc,
|
||||
dummy_controller.configuration_controller,
|
||||
)
|
||||
self.channel_setting.populate_experiment_values(
|
||||
dummy_controller.configuration["experiment"]["MicroscopeState"]["channels"]
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"mode,state,state_readonly",
|
||||
[("stop", "normal", "readonly"), ("live", "disabled", "readonly")],
|
||||
)
|
||||
def test_set_mode(self, mode, state, state_readonly):
|
||||
|
||||
self.channel_setting.set_mode(mode)
|
||||
|
||||
for i in range(5):
|
||||
assert str(self.channel_setting.view.channel_checks[i]["state"]) == state
|
||||
# interval widget is disabled now
|
||||
assert (
|
||||
str(self.channel_setting.view.interval_spins[i]["state"]) == "disabled"
|
||||
)
|
||||
if mode == "stop":
|
||||
assert (
|
||||
str(self.channel_setting.view.laser_pulldowns[i]["state"])
|
||||
== state_readonly
|
||||
)
|
||||
else:
|
||||
assert (
|
||||
str(self.channel_setting.view.laser_pulldowns[i]["state"])
|
||||
== "disabled"
|
||||
)
|
||||
if self.channel_setting.mode != "live" or (
|
||||
self.channel_setting.mode == "live"
|
||||
and not self.channel_setting.view.channel_variables[i].get()
|
||||
):
|
||||
assert (
|
||||
str(self.channel_setting.view.exptime_pulldowns[i]["state"])
|
||||
== state
|
||||
)
|
||||
if not self.channel_setting.view.channel_variables[i].get():
|
||||
assert (
|
||||
str(self.channel_setting.view.laserpower_pulldowns[i]["state"])
|
||||
== state
|
||||
)
|
||||
assert (
|
||||
str(self.channel_setting.view.filterwheel_pulldowns[i]["state"])
|
||||
== state_readonly
|
||||
)
|
||||
assert str(self.channel_setting.view.defocus_spins[i]["state"]) == state
|
||||
|
||||
def test_channel_callback(self):
|
||||
import random
|
||||
|
||||
self.channel_setting.in_initialization = False
|
||||
channel_dict = (
|
||||
self.channel_setting.parent_controller.parent_controller.configuration[
|
||||
"experiment"
|
||||
]["MicroscopeState"]["channels"]
|
||||
)
|
||||
# shuffle the channels
|
||||
new_channel_dict = {
|
||||
k: v
|
||||
for k, v in zip(
|
||||
channel_dict.keys(),
|
||||
random.choices(channel_dict.values(), k=len(channel_dict.keys())),
|
||||
)
|
||||
}
|
||||
|
||||
self.channel_setting.populate_experiment_values(channel_dict)
|
||||
for channel_id in range(self.channel_setting.num):
|
||||
vals = self.channel_setting.get_vals_by_channel(channel_id)
|
||||
channel_key = f"channel_{str(channel_id + 1)}"
|
||||
try:
|
||||
setting_dict = channel_dict[channel_key]
|
||||
new_setting_dict = new_channel_dict[channel_key]
|
||||
except KeyError:
|
||||
continue
|
||||
|
||||
# Test channel callback through trace writes
|
||||
for k in setting_dict.keys():
|
||||
if k == "laser_index" or k.startswith("filter_position"):
|
||||
continue
|
||||
if k == "defocus":
|
||||
new_val = float(random.randint(1, 10))
|
||||
else:
|
||||
new_val = new_setting_dict[k]
|
||||
|
||||
vals[k].set(new_val)
|
||||
|
||||
assert str(vals[k].get()) == str(new_val)
|
||||
if k != "defocus":
|
||||
assert setting_dict[k] == new_setting_dict[k]
|
||||
|
||||
if k == "laser":
|
||||
assert (
|
||||
setting_dict["laser_index"] == new_setting_dict["laser_index"]
|
||||
)
|
||||
elif k == "filter":
|
||||
assert (
|
||||
setting_dict["filter_position"]
|
||||
== new_setting_dict["filter_position"]
|
||||
)
|
||||
elif k == "camera_exposure_time" or k == "is_selected":
|
||||
assert (
|
||||
self.channel_setting.parent_controller.commands.pop()
|
||||
== "recalculate_timepoint"
|
||||
)
|
||||
self.channel_setting.parent_controller.commands = [] # reset
|
||||
|
||||
def test_get_vals_by_channel(self):
|
||||
# Not needed to test IMO
|
||||
pass
|
||||
|
||||
def test_get_index(self):
|
||||
# Not needed to test IMO
|
||||
pass
|
||||
385
test/controller/sub_controllers/test_channels_tab.py
Normal file
385
test/controller/sub_controllers/test_channels_tab.py
Normal file
@@ -0,0 +1,385 @@
|
||||
# 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 copy
|
||||
|
||||
import pytest
|
||||
import numpy as np
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def channels_tab_controller(dummy_controller):
|
||||
from navigate.controller.sub_controllers.channels_tab import (
|
||||
ChannelsTabController,
|
||||
)
|
||||
|
||||
return ChannelsTabController(
|
||||
dummy_controller.view.settings.channels_tab, dummy_controller
|
||||
)
|
||||
|
||||
|
||||
def test_update_z_steps(channels_tab_controller):
|
||||
# Calculate params
|
||||
z_start, f_start = random.randint(1, 1000), random.randint(1, 1000)
|
||||
z_end, f_end = random.randint(1, 1000), random.randint(1, 1000)
|
||||
if z_end < z_start:
|
||||
# Sort so we are always going low to high
|
||||
tmp = z_start
|
||||
tmp_f = f_start
|
||||
z_start = z_end
|
||||
f_start = f_end
|
||||
z_end = tmp
|
||||
f_end = tmp_f
|
||||
step_size = max(1, min(random.randint(1, 10), (z_end - z_start) // 2))
|
||||
|
||||
# Set params
|
||||
channels_tab_controller.microscope_state_dict = (
|
||||
channels_tab_controller.parent_controller.configuration["experiment"][
|
||||
"MicroscopeState"
|
||||
]
|
||||
)
|
||||
channels_tab_controller.in_initialization = False
|
||||
channels_tab_controller.stack_acq_vals["start_position"].set(z_start)
|
||||
channels_tab_controller.stack_acq_vals["start_focus"].set(f_start)
|
||||
channels_tab_controller.stack_acq_vals["end_position"].set(z_end)
|
||||
channels_tab_controller.stack_acq_vals["end_focus"].set(f_end)
|
||||
channels_tab_controller.stack_acq_vals["step_size"].set(step_size)
|
||||
|
||||
# Run
|
||||
channels_tab_controller.update_z_steps()
|
||||
|
||||
# Verify
|
||||
number_z_steps = int(np.ceil(np.abs((z_start - z_end) / step_size)))
|
||||
assert (
|
||||
int(channels_tab_controller.stack_acq_vals["number_z_steps"].get())
|
||||
== number_z_steps
|
||||
)
|
||||
|
||||
# test flip_z is True
|
||||
microscope_name = (
|
||||
channels_tab_controller.parent_controller.configuration_controller.microscope_name
|
||||
)
|
||||
stage_config = channels_tab_controller.parent_controller.configuration[
|
||||
"configuration"
|
||||
]["microscopes"][microscope_name]["stage"]
|
||||
stage_config["flip_z"] = True
|
||||
channels_tab_controller.z_origin = (z_start + z_end) / 2
|
||||
channels_tab_controller.stack_acq_vals["start_position"].set(z_end)
|
||||
channels_tab_controller.stack_acq_vals["start_focus"].set(f_end)
|
||||
channels_tab_controller.stack_acq_vals["end_position"].set(z_start)
|
||||
channels_tab_controller.stack_acq_vals["end_focus"].set(f_start)
|
||||
channels_tab_controller.update_z_steps()
|
||||
assert channels_tab_controller.stack_acq_vals["step_size"].get() == step_size
|
||||
assert channels_tab_controller.microscope_state_dict["step_size"] == -1 * step_size
|
||||
assert (
|
||||
channels_tab_controller.stack_acq_vals["number_z_steps"].get() == number_z_steps
|
||||
)
|
||||
stage_config["flip_z"] = False
|
||||
|
||||
|
||||
def test_update_start_position(channels_tab_controller):
|
||||
z, f = random.randint(0, 1000), random.randint(0, 1000)
|
||||
channels_tab_controller.parent_controller.configuration["experiment"][
|
||||
"StageParameters"
|
||||
]["z"] = z
|
||||
channels_tab_controller.parent_controller.configuration["experiment"][
|
||||
"StageParameters"
|
||||
]["f"] = f
|
||||
|
||||
channels_tab_controller.update_start_position()
|
||||
|
||||
assert channels_tab_controller.z_origin == z
|
||||
assert channels_tab_controller.focus_origin == f
|
||||
assert int(channels_tab_controller.stack_acq_vals["start_position"].get()) == 0
|
||||
assert int(channels_tab_controller.stack_acq_vals["start_focus"].get()) == 0
|
||||
|
||||
# test flip_z is True
|
||||
microscope_name = (
|
||||
channels_tab_controller.parent_controller.configuration_controller.microscope_name
|
||||
)
|
||||
stage_config = channels_tab_controller.parent_controller.configuration[
|
||||
"configuration"
|
||||
]["microscopes"][microscope_name]["stage"]
|
||||
stage_config["flip_z"] = True
|
||||
channels_tab_controller.update_start_position()
|
||||
|
||||
assert channels_tab_controller.z_origin == z
|
||||
assert channels_tab_controller.focus_origin == f
|
||||
assert int(channels_tab_controller.stack_acq_vals["end_position"].get()) == 0
|
||||
assert int(channels_tab_controller.stack_acq_vals["end_focus"].get()) == 0
|
||||
stage_config["flip_z"] = False
|
||||
|
||||
|
||||
def test_update_end_position(channels_tab_controller):
|
||||
configuration = channels_tab_controller.parent_controller.configuration
|
||||
|
||||
# Initialize
|
||||
z, f = random.randint(0, 1000), random.randint(0, 1000)
|
||||
z_shift, f_shift = random.randint(1, 500), random.randint(1, 500)
|
||||
configuration["experiment"]["StageParameters"]["z"] = z + z_shift
|
||||
configuration["experiment"]["StageParameters"]["f"] = f + f_shift
|
||||
|
||||
print(f"z: {z} z-shift: {z_shift} f: {f} f-shift: {f_shift}")
|
||||
print(f'z-dict: {configuration["experiment"]["StageParameters"]["z"]}')
|
||||
print(f'f-dict: {configuration["experiment"]["StageParameters"]["f"]}')
|
||||
|
||||
# Step backwards and record results
|
||||
channels_tab_controller.z_origin = z - z_shift
|
||||
channels_tab_controller.focus_origin = f - f_shift
|
||||
channels_tab_controller.update_end_position()
|
||||
z_origin_minus = copy.deepcopy(channels_tab_controller.z_origin)
|
||||
f_origin_minus = copy.deepcopy(channels_tab_controller.focus_origin)
|
||||
start_position_minus = copy.deepcopy(
|
||||
channels_tab_controller.stack_acq_vals["start_position"].get()
|
||||
)
|
||||
end_position_minus = copy.deepcopy(
|
||||
channels_tab_controller.stack_acq_vals["end_position"].get()
|
||||
)
|
||||
start_focus_minus = copy.deepcopy(
|
||||
channels_tab_controller.stack_acq_vals["start_focus"].get()
|
||||
)
|
||||
end_focus_minus = copy.deepcopy(
|
||||
channels_tab_controller.stack_acq_vals["end_focus"].get()
|
||||
)
|
||||
|
||||
print("back")
|
||||
print(f"z: {z} z-shift: {z_shift} f: {f} f-shift: {f_shift}")
|
||||
print(f'z-dict: {configuration["experiment"]["StageParameters"]["z"]}')
|
||||
print(f'f-dict: {configuration["experiment"]["StageParameters"]["f"]}')
|
||||
|
||||
# Step forward
|
||||
configuration["experiment"]["StageParameters"]["z"] = z - z_shift
|
||||
configuration["experiment"]["StageParameters"]["f"] = f - f_shift
|
||||
channels_tab_controller.z_origin = z + z_shift
|
||||
channels_tab_controller.focus_origin = f + f_shift
|
||||
channels_tab_controller.update_end_position()
|
||||
|
||||
print("forward")
|
||||
print(f"z: {z} z-shift: {z_shift} f: {f} f-shift: {f_shift}")
|
||||
print(f'z-dict: {configuration["experiment"]["StageParameters"]["z"]}')
|
||||
print(f'f-dict: {configuration["experiment"]["StageParameters"]["f"]}')
|
||||
|
||||
# Ensure we achieve the same origin
|
||||
assert channels_tab_controller.z_origin == z_origin_minus
|
||||
assert channels_tab_controller.focus_origin == f_origin_minus
|
||||
assert (
|
||||
channels_tab_controller.stack_acq_vals["start_position"].get()
|
||||
== start_position_minus
|
||||
)
|
||||
assert (
|
||||
channels_tab_controller.stack_acq_vals["end_position"].get()
|
||||
== end_position_minus
|
||||
)
|
||||
assert (
|
||||
channels_tab_controller.stack_acq_vals["start_focus"].get() == start_focus_minus
|
||||
)
|
||||
assert channels_tab_controller.stack_acq_vals["end_focus"].get() == end_focus_minus
|
||||
|
||||
# test flip_z is True
|
||||
microscope_name = (
|
||||
channels_tab_controller.parent_controller.configuration_controller.microscope_name
|
||||
)
|
||||
stage_config = channels_tab_controller.parent_controller.configuration[
|
||||
"configuration"
|
||||
]["microscopes"][microscope_name]["stage"]
|
||||
stage_config["flip_z"] = True
|
||||
# forward
|
||||
channels_tab_controller.z_origin = z
|
||||
channels_tab_controller.focus_origin = f
|
||||
configuration["experiment"]["StageParameters"]["z"] = z - 2 * z_shift
|
||||
configuration["experiment"]["StageParameters"]["f"] = f - 2 * f_shift
|
||||
channels_tab_controller.update_end_position()
|
||||
assert channels_tab_controller.z_origin == z - z_shift
|
||||
assert channels_tab_controller.focus_origin == f - f_shift
|
||||
assert channels_tab_controller.stack_acq_vals["start_position"].get() == z_shift
|
||||
assert channels_tab_controller.stack_acq_vals["end_position"].get() == -1 * z_shift
|
||||
assert channels_tab_controller.stack_acq_vals["start_focus"].get() == f_shift
|
||||
assert channels_tab_controller.stack_acq_vals["end_focus"].get() == -1 * f_shift
|
||||
|
||||
# backward
|
||||
channels_tab_controller.z_origin = z
|
||||
channels_tab_controller.focus_origin = f
|
||||
configuration["experiment"]["StageParameters"]["z"] = z + 2 * z_shift
|
||||
configuration["experiment"]["StageParameters"]["f"] = f + 2 * f_shift
|
||||
channels_tab_controller.update_end_position()
|
||||
assert channels_tab_controller.z_origin == z + z_shift
|
||||
assert channels_tab_controller.focus_origin == f + f_shift
|
||||
assert channels_tab_controller.stack_acq_vals["start_position"].get() == z_shift
|
||||
assert channels_tab_controller.stack_acq_vals["end_position"].get() == -1 * z_shift
|
||||
assert channels_tab_controller.stack_acq_vals["start_focus"].get() == f_shift
|
||||
assert channels_tab_controller.stack_acq_vals["end_focus"].get() == -1 * f_shift
|
||||
stage_config["flip_z"] = False
|
||||
|
||||
|
||||
def test_update_start_update_end_position(channels_tab_controller):
|
||||
configuration = channels_tab_controller.parent_controller.configuration
|
||||
channels_tab_controller.microscope_state_dict = configuration["experiment"][
|
||||
"MicroscopeState"
|
||||
]
|
||||
channels_tab_controller.in_initialization = False
|
||||
|
||||
# Initialize
|
||||
z, f = random.randint(0, 1000), random.randint(0, 1000)
|
||||
z_shift, f_shift = random.randint(1, 500), random.randint(1, 500)
|
||||
configuration["experiment"]["StageParameters"]["z"] = z - z_shift
|
||||
configuration["experiment"]["StageParameters"]["f"] = f - f_shift
|
||||
channels_tab_controller.update_start_position()
|
||||
|
||||
print(f"z: {z} z-shift: {z_shift} f: {f} f-shift: {f_shift}")
|
||||
print(f'z-dict: {configuration["experiment"]["StageParameters"]["z"]}')
|
||||
print(f'f-dict: {configuration["experiment"]["StageParameters"]["f"]}')
|
||||
|
||||
# Step forward and record results
|
||||
configuration["experiment"]["StageParameters"]["z"] = z + z_shift
|
||||
configuration["experiment"]["StageParameters"]["f"] = f + f_shift
|
||||
channels_tab_controller.update_end_position()
|
||||
z_origin_minus = copy.deepcopy(channels_tab_controller.z_origin)
|
||||
f_origin_minus = copy.deepcopy(channels_tab_controller.focus_origin)
|
||||
start_position_minus = copy.deepcopy(
|
||||
channels_tab_controller.stack_acq_vals["start_position"].get()
|
||||
)
|
||||
end_position_minus = copy.deepcopy(
|
||||
channels_tab_controller.stack_acq_vals["end_position"].get()
|
||||
)
|
||||
start_focus_minus = copy.deepcopy(
|
||||
channels_tab_controller.stack_acq_vals["start_focus"].get()
|
||||
)
|
||||
end_focus_minus = copy.deepcopy(
|
||||
channels_tab_controller.stack_acq_vals["end_focus"].get()
|
||||
)
|
||||
|
||||
print("back")
|
||||
print(f"z: {z} z-shift: {z_shift} f: {f} f-shift: {f_shift}")
|
||||
print(f'z-dict: {configuration["experiment"]["StageParameters"]["z"]}')
|
||||
print(f'f-dict: {configuration["experiment"]["StageParameters"]["f"]}')
|
||||
|
||||
channels_tab_controller.update_start_position()
|
||||
|
||||
# Step back
|
||||
configuration["experiment"]["StageParameters"]["z"] = z - z_shift
|
||||
configuration["experiment"]["StageParameters"]["f"] = f - f_shift
|
||||
channels_tab_controller.update_end_position()
|
||||
|
||||
print("forward")
|
||||
print(f"z: {z} z-shift: {z_shift} f: {f} f-shift: {f_shift}")
|
||||
print(f'z-dict: {configuration["experiment"]["StageParameters"]["z"]}')
|
||||
print(f'f-dict: {configuration["experiment"]["StageParameters"]["f"]}')
|
||||
|
||||
# Ensure we achieve the same origin
|
||||
assert channels_tab_controller.z_origin == z_origin_minus
|
||||
assert channels_tab_controller.focus_origin == f_origin_minus
|
||||
assert (
|
||||
channels_tab_controller.stack_acq_vals["start_position"].get()
|
||||
== start_position_minus
|
||||
)
|
||||
assert (
|
||||
channels_tab_controller.stack_acq_vals["end_position"].get()
|
||||
== end_position_minus
|
||||
)
|
||||
assert (
|
||||
channels_tab_controller.stack_acq_vals["start_focus"].get() == start_focus_minus
|
||||
)
|
||||
assert channels_tab_controller.stack_acq_vals["end_focus"].get() == end_focus_minus
|
||||
|
||||
# test flip_z is true
|
||||
microscope_name = (
|
||||
channels_tab_controller.parent_controller.configuration_controller.microscope_name
|
||||
)
|
||||
stage_config = channels_tab_controller.parent_controller.configuration[
|
||||
"configuration"
|
||||
]["microscopes"][microscope_name]["stage"]
|
||||
stage_config["flip_z"] = True
|
||||
configuration = channels_tab_controller.parent_controller.configuration
|
||||
z, f = random.randint(0, 1000), random.randint(0, 1000)
|
||||
z_shift, f_shift = random.randint(1, 500), random.randint(1, 500)
|
||||
configuration["experiment"]["StageParameters"]["z"] = z - z_shift
|
||||
configuration["experiment"]["StageParameters"]["f"] = f - f_shift
|
||||
channels_tab_controller.update_start_position()
|
||||
configuration["experiment"]["StageParameters"]["z"] = z + z_shift
|
||||
configuration["experiment"]["StageParameters"]["f"] = f + f_shift
|
||||
channels_tab_controller.update_end_position()
|
||||
|
||||
assert channels_tab_controller.z_origin == z
|
||||
assert channels_tab_controller.focus_origin == f
|
||||
assert channels_tab_controller.stack_acq_vals["start_position"].get() == z_shift
|
||||
assert channels_tab_controller.stack_acq_vals["end_position"].get() == -1 * z_shift
|
||||
assert channels_tab_controller.stack_acq_vals["start_focus"].get() == f_shift
|
||||
assert channels_tab_controller.stack_acq_vals["end_focus"].get() == -1 * f_shift
|
||||
|
||||
assert configuration["experiment"]["MicroscopeState"]["start_position"] == z_shift
|
||||
assert (
|
||||
configuration["experiment"]["MicroscopeState"]["end_position"] == -1 * z_shift
|
||||
)
|
||||
assert configuration["experiment"]["MicroscopeState"]["abs_z_start"] == z - z_shift
|
||||
assert configuration["experiment"]["MicroscopeState"]["abs_z_end"] == z + z_shift
|
||||
assert configuration["experiment"]["MicroscopeState"]["start_focus"] == f_shift
|
||||
assert configuration["experiment"]["MicroscopeState"]["end_focus"] == -1 * f_shift
|
||||
|
||||
configuration["experiment"]["StageParameters"]["z"] = z + z_shift
|
||||
configuration["experiment"]["StageParameters"]["f"] = f + f_shift
|
||||
channels_tab_controller.update_start_position()
|
||||
configuration["experiment"]["StageParameters"]["z"] = z - z_shift
|
||||
configuration["experiment"]["StageParameters"]["f"] = f - f_shift
|
||||
channels_tab_controller.update_end_position()
|
||||
|
||||
assert channels_tab_controller.z_origin == z
|
||||
assert channels_tab_controller.focus_origin == f
|
||||
assert channels_tab_controller.stack_acq_vals["start_position"].get() == z_shift
|
||||
assert channels_tab_controller.stack_acq_vals["end_position"].get() == -1 * z_shift
|
||||
assert channels_tab_controller.stack_acq_vals["start_focus"].get() == f_shift
|
||||
assert channels_tab_controller.stack_acq_vals["end_focus"].get() == -1 * f_shift
|
||||
|
||||
assert configuration["experiment"]["MicroscopeState"]["start_position"] == z_shift
|
||||
assert (
|
||||
configuration["experiment"]["MicroscopeState"]["end_position"] == -1 * z_shift
|
||||
)
|
||||
assert configuration["experiment"]["MicroscopeState"]["abs_z_start"] == z - z_shift
|
||||
assert configuration["experiment"]["MicroscopeState"]["abs_z_end"] == z + z_shift
|
||||
assert configuration["experiment"]["MicroscopeState"]["start_focus"] == f_shift
|
||||
assert configuration["experiment"]["MicroscopeState"]["end_focus"] == -1 * f_shift
|
||||
stage_config["flip_z"] = False
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_multiposition", [True, False])
|
||||
def test_toggle_multiposition(channels_tab_controller, is_multiposition):
|
||||
channels_tab_controller.populate_experiment_values()
|
||||
channels_tab_controller.is_multiposition_val.set(is_multiposition)
|
||||
with patch.object(channels_tab_controller, "update_timepoint_setting") as uts:
|
||||
channels_tab_controller.toggle_multiposition()
|
||||
assert channels_tab_controller.is_multiposition == is_multiposition
|
||||
assert (
|
||||
channels_tab_controller.microscope_state_dict["is_multiposition"]
|
||||
== is_multiposition
|
||||
)
|
||||
uts.assert_called()
|
||||
259
test/controller/sub_controllers/test_menus.py
Normal file
259
test/controller/sub_controllers/test_menus.py
Normal file
@@ -0,0 +1,259 @@
|
||||
# Copyright (c) 2021-2025 The University of Texas Southwestern Medical Center.
|
||||
# All rights reserved.
|
||||
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted for academic and research use only
|
||||
# (subject to the limitations in the disclaimer below)
|
||||
# provided that the following conditions are met:
|
||||
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
|
||||
# * Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
|
||||
# * Neither the name of the copyright holders nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from this
|
||||
# software without specific prior written permission.
|
||||
|
||||
# NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY
|
||||
# THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||
# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
||||
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
|
||||
# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
# Standard Library Imports
|
||||
import unittest
|
||||
from unittest.mock import MagicMock, patch
|
||||
import tkinter as tk
|
||||
|
||||
# Third Party Imports
|
||||
import pytest
|
||||
|
||||
# Local Imports
|
||||
from navigate.controller.sub_controllers.menus import (
|
||||
MenuController,
|
||||
FakeEvent,
|
||||
)
|
||||
|
||||
|
||||
class TestFakeEvent(unittest.TestCase):
|
||||
def test_fake_event_creation(self):
|
||||
fake_event = FakeEvent(char="a", keysym="A")
|
||||
self.assertEqual(fake_event.char, "a")
|
||||
self.assertEqual(fake_event.keysym, "A")
|
||||
self.assertEqual(fake_event.state, 0)
|
||||
|
||||
|
||||
class TestStageMovement(unittest.TestCase):
|
||||
def setUp(self):
|
||||
# Create a mock parent controller and view
|
||||
self.root = tk.Tk()
|
||||
self.parent_controller = MagicMock()
|
||||
self.parent_controller.stage_controller = MagicMock()
|
||||
self.view = MagicMock()
|
||||
self.view.root = self.root
|
||||
|
||||
# Initialize the menu controller
|
||||
self.mc = MenuController(self.view, self.parent_controller)
|
||||
|
||||
# Mock the histogram configuration entry.
|
||||
self.parent_controller.configuration["gui"]["histogram"] = MagicMock()
|
||||
self.parent_controller.configuration["gui"]["histogram"].get.return_value = True
|
||||
|
||||
def tearDown(self):
|
||||
self.root.destroy()
|
||||
|
||||
def test_initialize_menus(self):
|
||||
self.mc.initialize_menus()
|
||||
|
||||
def test_stage_movement_with_ttk_entry(self):
|
||||
self.mc.parent_controller.view.focus_get.return_value = MagicMock(
|
||||
widgetName="ttk::entry"
|
||||
)
|
||||
self.mc.stage_movement("a")
|
||||
self.mc.parent_controller.stage_controller.stage_key_press.assert_not_called()
|
||||
|
||||
def test_stage_movement_with_ttk_combobox(self):
|
||||
self.mc.parent_controller.view.focus_get.return_value = MagicMock(
|
||||
widgetName="ttk::combobox"
|
||||
)
|
||||
self.mc.stage_movement("a")
|
||||
self.mc.parent_controller.stage_controller.stage_key_press.assert_not_called()
|
||||
|
||||
def test_stage_movement_with_other_widget(self):
|
||||
self.mc.parent_controller.view.focus_get.return_value = MagicMock(
|
||||
widgetName="other_widget"
|
||||
)
|
||||
self.mc.stage_movement("a")
|
||||
self.mc.parent_controller.stage_controller.stage_key_press.assert_called_with(
|
||||
self.mc.fake_event
|
||||
)
|
||||
|
||||
def test_stage_movement_with_key_error(self):
|
||||
self.mc.parent_controller.view.focus_get.side_effect = KeyError
|
||||
# Test that no exception is raised
|
||||
try:
|
||||
self.mc.stage_movement("a")
|
||||
except KeyError:
|
||||
self.fail("stage_movement() raised KeyError unexpectedly!")
|
||||
|
||||
def test_stage_movement_with_no_focus(self):
|
||||
self.mc.parent_controller.view.focus_get.return_value = None
|
||||
self.mc.stage_movement("a")
|
||||
self.mc.parent_controller.stage_controller.stage_key_press.assert_called_with(
|
||||
self.mc.fake_event
|
||||
)
|
||||
|
||||
|
||||
class TestMenuController(unittest.TestCase):
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup_class(self, dummy_controller):
|
||||
c = dummy_controller
|
||||
v = dummy_controller.view
|
||||
self.menu_controller = MenuController(v, c)
|
||||
|
||||
def test_attributes(self):
|
||||
methods = dir(MenuController)
|
||||
desired_methods = [
|
||||
"initialize_menus",
|
||||
"populate_menu",
|
||||
"new_experiment",
|
||||
"load_experiment",
|
||||
"save_experiment",
|
||||
"load_images",
|
||||
"popup_camera_map_setting",
|
||||
"popup_ilastik_setting",
|
||||
"popup_help",
|
||||
"toggle_stage_limits",
|
||||
"popup_autofocus_setting",
|
||||
"popup_waveform_setting",
|
||||
"popup_microscope_setting",
|
||||
"toggle_save",
|
||||
"acquire_data",
|
||||
"not_implemented",
|
||||
"stage_movement",
|
||||
"switch_tabs",
|
||||
]
|
||||
|
||||
for method in desired_methods:
|
||||
assert method in methods
|
||||
|
||||
def test_popup_camera_map_setting(self):
|
||||
assert (
|
||||
hasattr(
|
||||
self.menu_controller.parent_controller, "camera_map_popup_controller"
|
||||
)
|
||||
is False
|
||||
)
|
||||
self.menu_controller.popup_camera_map_setting()
|
||||
assert (
|
||||
hasattr(
|
||||
self.menu_controller.parent_controller, "camera_map_popup_controller"
|
||||
)
|
||||
is True
|
||||
)
|
||||
|
||||
def test_autofocus_settings(self):
|
||||
assert (
|
||||
hasattr(self.menu_controller.parent_controller, "af_popup_controller")
|
||||
is False
|
||||
)
|
||||
self.menu_controller.popup_autofocus_setting()
|
||||
assert (
|
||||
hasattr(self.menu_controller.parent_controller, "af_popup_controller")
|
||||
is True
|
||||
)
|
||||
|
||||
def test_popup_waveform_setting(self):
|
||||
# TODO: Incomplete.
|
||||
assert (
|
||||
hasattr(self.menu_controller.parent_controller, "waveform_popup_controller")
|
||||
is False
|
||||
)
|
||||
|
||||
def test_popup_microscope_setting(self):
|
||||
# TODO: Incomplete. DummyController has no attribute 'model'
|
||||
assert (
|
||||
hasattr(
|
||||
self.menu_controller.parent_controller, "microscope_popup_controller"
|
||||
)
|
||||
is False
|
||||
)
|
||||
|
||||
def test_toggle_save(self):
|
||||
class MockWidget:
|
||||
def __int__(self):
|
||||
self.value = False
|
||||
|
||||
def set(self, value):
|
||||
self.value = value
|
||||
|
||||
def get(self):
|
||||
return self.value
|
||||
|
||||
channel_tab_controller = MagicMock()
|
||||
self.menu_controller.parent_controller.channels_tab_controller = (
|
||||
channel_tab_controller
|
||||
)
|
||||
channel_tab_controller.timepoint_vals = {"is_save": MockWidget()}
|
||||
temp = self.menu_controller.view.settings.channels_tab.stack_timepoint_frame
|
||||
temp.save_data.get = MagicMock(return_value=False)
|
||||
self.menu_controller.toggle_save()
|
||||
assert channel_tab_controller.timepoint_vals["is_save"].get() is True
|
||||
|
||||
temp = self.menu_controller.view.settings.channels_tab.stack_timepoint_frame
|
||||
temp.save_data.get = MagicMock(return_value=True)
|
||||
self.menu_controller.toggle_save()
|
||||
assert channel_tab_controller.timepoint_vals["is_save"].get() is False
|
||||
|
||||
def test_stage_movement(self):
|
||||
# TODO: DummyController does not have a stage controller.
|
||||
pass
|
||||
|
||||
def test_switch_tabs(self):
|
||||
for i in range(1, 4):
|
||||
self.menu_controller.switch_tabs(window="left", tab=i)
|
||||
assert (
|
||||
self.menu_controller.parent_controller.view.settings.index("current")
|
||||
== i - 1
|
||||
)
|
||||
|
||||
@patch("src.navigate.controller.sub_controllers.menus.platform.system")
|
||||
@patch("src.navigate.controller.sub_controllers.menus.subprocess.check_call")
|
||||
def test_open_folder(self, mock_check_call, mock_system):
|
||||
mock_system.return_value = "Darwin"
|
||||
self.menu_controller.open_folder("test_path")
|
||||
mock_check_call.assert_called_once_with(["open", "--", "test_path"])
|
||||
|
||||
mock_check_call.reset_mock()
|
||||
mock_system.return_value = "Windows"
|
||||
self.menu_controller.open_folder("test_path")
|
||||
mock_check_call.assert_called_once_with(["explorer", "test_path"])
|
||||
|
||||
mock_check_call.reset_mock()
|
||||
mock_system.return_value = "Linux"
|
||||
self.menu_controller.open_folder("test_path")
|
||||
self.assertEqual(mock_check_call.call_count, 0)
|
||||
|
||||
@patch("src.navigate.controller.sub_controllers.menus.os.path.join")
|
||||
def test_open_log_files(self, mock_join):
|
||||
with patch.object(self.menu_controller, "open_folder") as mock_open_folder:
|
||||
mock_join.return_value = "joined_path"
|
||||
self.menu_controller.open_log_files()
|
||||
mock_open_folder.assert_called_once_with("joined_path")
|
||||
|
||||
@patch("src.navigate.controller.sub_controllers.menus.os.path.join")
|
||||
def test_open_configuration_files(self, mock_join):
|
||||
with patch.object(self.menu_controller, "open_folder") as mock_open_folder:
|
||||
mock_join.return_value = "joined_path"
|
||||
self.menu_controller.open_configuration_files()
|
||||
mock_open_folder.assert_called_once_with("joined_path")
|
||||
171
test/controller/sub_controllers/test_multiposition.py
Normal file
171
test/controller/sub_controllers/test_multiposition.py
Normal file
@@ -0,0 +1,171 @@
|
||||
# Copyright (c) 2021-2025 The University of Texas Southwestern Medical Center.
|
||||
# All rights reserved.
|
||||
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted for academic and research use only
|
||||
# (subject to the limitations in the disclaimer below)
|
||||
# provided that the following conditions are met:
|
||||
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
|
||||
# * Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
|
||||
# * Neither the name of the copyright holders nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from this
|
||||
# software without specific prior written permission.
|
||||
|
||||
# NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY
|
||||
# THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||
# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
||||
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
|
||||
# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
|
||||
# Standard library imports
|
||||
|
||||
# Third party imports
|
||||
import pytest
|
||||
import unittest
|
||||
from unittest.mock import MagicMock, patch
|
||||
import pandas as pd
|
||||
|
||||
# Local application imports
|
||||
from navigate.controller.sub_controllers.multiposition import MultiPositionController
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def multiposition_controller(dummy_controller):
|
||||
# Create a copy/clone of the dummy_controller to avoid side effects
|
||||
isolated_controller = MagicMock()
|
||||
isolated_controller.configuration = dummy_controller.configuration
|
||||
|
||||
# Create a mock pt attribute for the multiposition_tab
|
||||
isolated_controller.view.settings.multiposition_tab.pt = MagicMock()
|
||||
isolated_controller.view.settings.multiposition_tab.pt.model = MagicMock()
|
||||
isolated_controller.view.settings.multiposition_tab.pt.model.df = pd.DataFrame()
|
||||
|
||||
# Add other required mock attributes and methods
|
||||
isolated_controller.view.settings.multiposition_tab.pt.redraw = MagicMock()
|
||||
isolated_controller.view.settings.multiposition_tab.pt.tableChanged = MagicMock()
|
||||
isolated_controller.view.settings.multiposition_tab.pt.resetColors = MagicMock()
|
||||
isolated_controller.view.settings.multiposition_tab.pt.update_rowcolors = (
|
||||
MagicMock()
|
||||
)
|
||||
|
||||
# Mock the master and tiling buttons
|
||||
isolated_controller.view.settings.multiposition_tab.master = MagicMock()
|
||||
isolated_controller.view.settings.multiposition_tab.master.tiling_buttons = (
|
||||
MagicMock()
|
||||
)
|
||||
isolated_controller.view.settings.multiposition_tab.master.tiling_buttons.buttons = {
|
||||
"tiling": MagicMock(),
|
||||
"save_data": MagicMock(),
|
||||
"load_data": MagicMock(),
|
||||
"eliminate_tiles": MagicMock(),
|
||||
}
|
||||
|
||||
# This is the important part - configure the stage axes
|
||||
isolated_controller.configuration_controller = MagicMock()
|
||||
isolated_controller.configuration_controller.stage_axes = [
|
||||
"x",
|
||||
"y",
|
||||
"z",
|
||||
"theta",
|
||||
"f",
|
||||
]
|
||||
|
||||
return MultiPositionController(
|
||||
isolated_controller.view.settings.multiposition_tab, isolated_controller
|
||||
)
|
||||
|
||||
|
||||
@patch("navigate.controller.sub_controllers.multiposition.filedialog.askopenfilenames")
|
||||
@patch("navigate.controller.sub_controllers.multiposition.yaml.safe_load")
|
||||
@patch("builtins.open", new_callable=unittest.mock.mock_open, read_data="dummy content")
|
||||
def test_load_positions_yaml(
|
||||
mock_file, mock_safe_load, mock_askopen, multiposition_controller
|
||||
):
|
||||
"""Test loading positions from YAML"""
|
||||
controller = multiposition_controller
|
||||
table = controller.table
|
||||
|
||||
mock_askopen.return_value = ("dummy_file.yml",)
|
||||
mock_safe_load.return_value = [
|
||||
["X", "Y", "Z", "THETA", "F"],
|
||||
[0, 0, 0, 0, 0],
|
||||
[100, 200, 300, 400, 500],
|
||||
]
|
||||
|
||||
controller.load_positions()
|
||||
|
||||
mock_file.assert_called_once_with("dummy_file.yml", "r")
|
||||
|
||||
expected = pd.DataFrame(
|
||||
[[0, 0, 0, 0, 0], [100, 200, 300, 400, 500]],
|
||||
columns=["X", "Y", "Z", "THETA", "F"],
|
||||
)
|
||||
pd.testing.assert_frame_equal(table.model.df, expected)
|
||||
|
||||
|
||||
@patch("navigate.controller.sub_controllers.multiposition.filedialog.askopenfilenames")
|
||||
@patch("navigate.controller.sub_controllers.multiposition.pd.read_csv")
|
||||
def test_load_positions_csv(mock_read_csv, mock_askopen, multiposition_controller):
|
||||
"""Test loading positions from CSV"""
|
||||
controller = multiposition_controller
|
||||
table = controller.table
|
||||
|
||||
mock_askopen.return_value = ("dummy_file.csv",)
|
||||
mock_read_csv.return_value = pd.DataFrame(
|
||||
{"X": [1, 2], "Y": [3, 4], "Z": [5, 6], "THETA": [0, 0], "F": [0, 0]}
|
||||
)
|
||||
|
||||
controller.load_positions()
|
||||
|
||||
expected = pd.DataFrame(
|
||||
{"X": [1, 2], "Y": [3, 4], "Z": [5, 6], "THETA": [0, 0], "F": [0, 0]}
|
||||
)
|
||||
pd.testing.assert_frame_equal(table.model.df, expected)
|
||||
|
||||
|
||||
@patch("navigate.controller.sub_controllers.multiposition.filedialog.asksaveasfilename")
|
||||
@patch("navigate.controller.sub_controllers.multiposition.save_yaml_file")
|
||||
def test_export_positions_yaml(mock_save_yaml, mock_asksave, multiposition_controller):
|
||||
"""Test exporting positions to YAML"""
|
||||
controller = multiposition_controller
|
||||
table = controller.table
|
||||
|
||||
table.model.df = pd.DataFrame(
|
||||
{"X": [1, 2], "Y": [3, 4], "Z": [5, 6], "THETA": [0, 0], "F": [0, 0]}
|
||||
)
|
||||
mock_asksave.return_value = "/tmp/output.yml"
|
||||
|
||||
controller.export_positions()
|
||||
mock_save_yaml.assert_called_once()
|
||||
|
||||
|
||||
@patch("navigate.controller.sub_controllers.multiposition.filedialog.asksaveasfilename")
|
||||
def test_export_positions_csv(mock_asksave, multiposition_controller):
|
||||
"""Test exporting positions to CSV"""
|
||||
controller = multiposition_controller
|
||||
table = controller.table
|
||||
|
||||
df = pd.DataFrame(
|
||||
{"X": [1, 2], "Y": [3, 4], "Z": [5, 6], "THETA": [0, 0], "F": [0, 0]}
|
||||
)
|
||||
table.model.df = df
|
||||
table.model.df.to_csv = MagicMock()
|
||||
|
||||
mock_asksave.return_value = "/tmp/output.csv"
|
||||
|
||||
controller.export_positions()
|
||||
table.model.df.to_csv.assert_called_once_with("/tmp/output.csv", index=False)
|
||||
376
test/controller/sub_controllers/test_stages.py
Normal file
376
test/controller/sub_controllers/test_stages.py
Normal file
@@ -0,0 +1,376 @@
|
||||
# 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
|
||||
from unittest.mock import MagicMock, call
|
||||
import numpy as np
|
||||
|
||||
AXES = ["x", "y", "z", "theta", "f"]
|
||||
CAXES = ["xy", "z", "theta", "f"]
|
||||
|
||||
|
||||
def pos_dict(v, axes=AXES):
|
||||
return {k: v for k in axes}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def stage_controller(dummy_controller):
|
||||
from navigate.controller.sub_controllers.stages import StageController
|
||||
|
||||
dummy_controller.camera_view_controller = MagicMock()
|
||||
|
||||
stage_controller = StageController(
|
||||
dummy_controller.view.settings.stage_control_tab,
|
||||
dummy_controller,
|
||||
)
|
||||
|
||||
dummy_controller.view.settings.stage_control_tab.focus_get = MagicMock(
|
||||
return_value=True
|
||||
)
|
||||
|
||||
return stage_controller
|
||||
|
||||
|
||||
# test before set position variables to MagicMock()
|
||||
def test_set_position(stage_controller):
|
||||
|
||||
widgets = stage_controller.view.get_widgets()
|
||||
vals = {}
|
||||
for axis in AXES:
|
||||
widgets[axis].widget.trigger_focusout_validation = MagicMock()
|
||||
vals[axis] = np.random.randint(0, 9)
|
||||
|
||||
stage_controller.view.get_widgets = MagicMock(return_value=widgets)
|
||||
stage_controller.show_verbose_info = MagicMock()
|
||||
position = {
|
||||
"x": np.random.random(),
|
||||
"y": np.random.random(),
|
||||
"z": np.random.random(),
|
||||
}
|
||||
stage_controller.set_position(position)
|
||||
for axis in position.keys():
|
||||
assert float(stage_controller.widget_vals[axis].get()) == position[axis]
|
||||
assert widgets[axis].widget.trigger_focusout_validation.called
|
||||
assert stage_controller.stage_setting_dict[axis] == position.get(axis, 0)
|
||||
stage_controller.show_verbose_info.assert_has_calls(
|
||||
[call("Stage position changed"), call("Set stage position")]
|
||||
)
|
||||
|
||||
|
||||
def test_set_position_silent(stage_controller):
|
||||
|
||||
widgets = stage_controller.view.get_widgets()
|
||||
vals = {}
|
||||
for axis in AXES:
|
||||
widgets[axis].widget.trigger_focusout_validation = MagicMock()
|
||||
vals[axis] = np.random.randint(0, 9)
|
||||
|
||||
stage_controller.view.get_widgets = MagicMock(return_value=widgets)
|
||||
stage_controller.show_verbose_info = MagicMock()
|
||||
position = {
|
||||
"x": np.random.random(),
|
||||
"y": np.random.random(),
|
||||
"z": np.random.random(),
|
||||
}
|
||||
stage_controller.set_position_silent(position)
|
||||
for axis in position.keys():
|
||||
assert float(stage_controller.widget_vals[axis].get()) == position[axis]
|
||||
widgets[axis].widget.trigger_focusout_validation.assert_called_once()
|
||||
assert stage_controller.stage_setting_dict[axis] == position.get(axis, 0)
|
||||
stage_controller.show_verbose_info.assert_has_calls([call("Set stage position")])
|
||||
assert (
|
||||
call("Stage position changed")
|
||||
not in stage_controller.show_verbose_info.mock_calls
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"flip_x, flip_y",
|
||||
[(False, False), (True, False), (True, True), (False, True), (True, True)],
|
||||
)
|
||||
def test_stage_key_press(stage_controller, flip_x, flip_y):
|
||||
microscope_name = (
|
||||
stage_controller.parent_controller.configuration_controller.microscope_name
|
||||
)
|
||||
stage_config = stage_controller.parent_controller.configuration["configuration"][
|
||||
"microscopes"
|
||||
][microscope_name]["stage"]
|
||||
stage_config["flip_x"] = flip_x
|
||||
stage_config["flip_y"] = flip_y
|
||||
stage_controller.initialize()
|
||||
x = round(np.random.random(), 1)
|
||||
y = round(np.random.random(), 1)
|
||||
increment = round(np.random.random() + 1, 1)
|
||||
stage_controller.widget_vals["xy_step"].get = MagicMock(return_value=increment)
|
||||
stage_controller.widget_vals["x"].get = MagicMock(return_value=x)
|
||||
stage_controller.widget_vals["x"].set = MagicMock()
|
||||
stage_controller.widget_vals["y"].get = MagicMock(return_value=y)
|
||||
stage_controller.widget_vals["y"].set = MagicMock()
|
||||
event = MagicMock()
|
||||
|
||||
axes_map = {"w": "y", "a": "x", "s": "y", "d": "x"}
|
||||
|
||||
for char, xs, ys in zip(
|
||||
["w", "a", "s", "d"],
|
||||
[0, -increment, 0, increment],
|
||||
[increment, 0, -increment, 0],
|
||||
):
|
||||
event.char = char
|
||||
# <a> instead of <Control+a>
|
||||
event.state = 0
|
||||
axis = axes_map[char]
|
||||
if axis == "x":
|
||||
temp = x + xs * (-1 if flip_x else 1)
|
||||
else:
|
||||
temp = y + ys * (-1 if flip_y else 1)
|
||||
stage_controller.stage_key_press(event)
|
||||
stage_controller.widget_vals[axis].set.assert_called_once_with(temp)
|
||||
stage_controller.widget_vals[axis].set.reset_mock()
|
||||
stage_controller.widget_vals[axis].get.reset_mock()
|
||||
stage_controller.widget_vals["xy_step"].get.reset_mock()
|
||||
|
||||
stage_config["flip_x"] = False
|
||||
stage_config["flip_y"] = False
|
||||
|
||||
|
||||
def test_get_position(stage_controller):
|
||||
import tkinter as tk
|
||||
|
||||
vals = {}
|
||||
for axis in AXES:
|
||||
vals[axis] = np.random.randint(0, 9)
|
||||
stage_controller.widget_vals[axis].get = MagicMock(return_value=vals[axis])
|
||||
|
||||
step_vals = {}
|
||||
for axis in CAXES:
|
||||
step_vals[axis] = np.random.randint(1, 9)
|
||||
stage_controller.widget_vals[axis + "_step"].get = MagicMock(
|
||||
return_value=step_vals[axis]
|
||||
)
|
||||
|
||||
stage_controller.position_min = pos_dict(0)
|
||||
stage_controller.position_max = pos_dict(10)
|
||||
position = stage_controller.get_position()
|
||||
assert position == {k: vals[k] for k in AXES}
|
||||
|
||||
stage_controller.position_min = pos_dict(2)
|
||||
|
||||
vals = {}
|
||||
for axis in AXES:
|
||||
vals[axis] = np.random.choice(
|
||||
np.concatenate((np.arange(-9, 0), np.arange(10, 20)))
|
||||
)
|
||||
stage_controller.widget_vals[axis].get = MagicMock(return_value=vals[axis])
|
||||
|
||||
position = stage_controller.get_position()
|
||||
assert position is None
|
||||
|
||||
stage_controller.widget_vals["x"].get.side_effect = tk.TclError
|
||||
position = stage_controller.get_position()
|
||||
assert position is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"flip_x, flip_y, flip_z",
|
||||
[
|
||||
(False, False, False),
|
||||
(True, False, False),
|
||||
(True, True, False),
|
||||
(False, True, True),
|
||||
(True, True, True),
|
||||
],
|
||||
)
|
||||
def test_up_btn_handler(stage_controller, flip_x, flip_y, flip_z):
|
||||
microscope_name = (
|
||||
stage_controller.parent_controller.configuration_controller.microscope_name
|
||||
)
|
||||
stage_config = stage_controller.parent_controller.configuration["configuration"][
|
||||
"microscopes"
|
||||
][microscope_name]["stage"]
|
||||
stage_config["flip_x"] = flip_x
|
||||
stage_config["flip_y"] = flip_y
|
||||
stage_config["flip_z"] = flip_z
|
||||
stage_controller.initialize()
|
||||
flip_flags = (
|
||||
stage_controller.parent_controller.configuration_controller.stage_flip_flags
|
||||
)
|
||||
|
||||
vals = {}
|
||||
for axis in AXES:
|
||||
vals[axis] = np.random.randint(1, 9)
|
||||
stage_controller.widget_vals[axis].get = MagicMock(return_value=vals[axis])
|
||||
stage_controller.widget_vals[axis].set = MagicMock()
|
||||
|
||||
step_vals = {}
|
||||
for axis in CAXES:
|
||||
step_vals[axis] = np.random.randint(1, 9)
|
||||
stage_controller.widget_vals[axis + "_step"].get = MagicMock(
|
||||
return_value=step_vals[axis]
|
||||
)
|
||||
|
||||
stage_controller.position_max = pos_dict(10)
|
||||
|
||||
# Test for each axis
|
||||
for axis in AXES:
|
||||
pos = stage_controller.widget_vals[axis].get()
|
||||
if axis == "x" or axis == "y":
|
||||
step = stage_controller.widget_vals["xy_step"].get()
|
||||
else:
|
||||
step = stage_controller.widget_vals[axis + "_step"].get()
|
||||
temp = pos + step * (-1 if flip_flags[axis] else 1)
|
||||
if temp > stage_controller.position_max[axis]:
|
||||
temp = stage_controller.position_max[axis]
|
||||
stage_controller.up_btn_handler(axis)()
|
||||
stage_controller.widget_vals[axis].set.assert_called_once_with(temp)
|
||||
|
||||
# Test for out of limit condition
|
||||
for axis in AXES:
|
||||
stage_controller.widget_vals[axis].set.reset_mock()
|
||||
stage_controller.widget_vals[axis].get.return_value = 10
|
||||
stage_controller.up_btn_handler(axis)()
|
||||
if flip_flags[axis] is False:
|
||||
stage_controller.widget_vals[axis].set.assert_not_called()
|
||||
|
||||
stage_config["flip_x"] = False
|
||||
stage_config["flip_y"] = False
|
||||
stage_config["flip_z"] = False
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"flip_x, flip_y, flip_z",
|
||||
[
|
||||
(False, False, False),
|
||||
(True, False, False),
|
||||
(True, True, False),
|
||||
(False, True, True),
|
||||
(True, True, True),
|
||||
],
|
||||
)
|
||||
def test_down_btn_handler(stage_controller, flip_x, flip_y, flip_z):
|
||||
microscope_name = (
|
||||
stage_controller.parent_controller.configuration_controller.microscope_name
|
||||
)
|
||||
stage_config = stage_controller.parent_controller.configuration["configuration"][
|
||||
"microscopes"
|
||||
][microscope_name]["stage"]
|
||||
stage_config["flip_x"] = flip_x
|
||||
stage_config["flip_y"] = flip_y
|
||||
stage_config["flip_z"] = flip_z
|
||||
stage_controller.initialize()
|
||||
flip_flags = (
|
||||
stage_controller.parent_controller.configuration_controller.stage_flip_flags
|
||||
)
|
||||
vals = {}
|
||||
for axis in AXES:
|
||||
vals[axis] = np.random.randint(1, 9)
|
||||
stage_controller.widget_vals[axis].get = MagicMock(return_value=vals[axis])
|
||||
stage_controller.widget_vals[axis].set = MagicMock()
|
||||
|
||||
step_vals = {}
|
||||
for axis in CAXES:
|
||||
step_vals[axis] = np.random.randint(1, 9)
|
||||
stage_controller.widget_vals[axis + "_step"].get = MagicMock(
|
||||
return_value=step_vals[axis]
|
||||
)
|
||||
|
||||
stage_controller.position_min = pos_dict(0)
|
||||
|
||||
# Test for each axis
|
||||
for axis in AXES:
|
||||
pos = stage_controller.widget_vals[axis].get()
|
||||
if axis == "x" or axis == "y":
|
||||
step = stage_controller.widget_vals["xy_step"].get()
|
||||
else:
|
||||
step = stage_controller.widget_vals[axis + "_step"].get()
|
||||
temp = pos - step * (-1 if flip_flags[axis] else 1)
|
||||
if temp < stage_controller.position_min[axis]:
|
||||
temp = stage_controller.position_min[axis]
|
||||
stage_controller.down_btn_handler(axis)()
|
||||
stage_controller.widget_vals[axis].set.assert_called_once_with(temp)
|
||||
|
||||
# Test for out of limit condition
|
||||
for axis in ["x", "y", "z", "theta", "f"]:
|
||||
stage_controller.widget_vals[axis].set.reset_mock()
|
||||
stage_controller.widget_vals[axis].get.return_value = 0
|
||||
stage_controller.down_btn_handler(axis)()
|
||||
if flip_flags[axis] is False:
|
||||
stage_controller.widget_vals[axis].set.assert_not_called()
|
||||
|
||||
stage_config["flip_x"] = False
|
||||
stage_config["flip_y"] = False
|
||||
stage_config["flip_z"] = False
|
||||
|
||||
|
||||
def test_stop_button_handler(stage_controller):
|
||||
|
||||
stage_controller.view.after = MagicMock()
|
||||
|
||||
stage_controller.stop_button_handler()
|
||||
|
||||
stage_controller.view.after.assert_called_once()
|
||||
|
||||
|
||||
def test_position_callback(stage_controller):
|
||||
|
||||
stage_controller.show_verbose_info = MagicMock()
|
||||
|
||||
stage_controller.view.after = MagicMock()
|
||||
|
||||
vals = {}
|
||||
widgets = stage_controller.view.get_widgets()
|
||||
for axis in AXES:
|
||||
vals[axis] = np.random.randint(1, 9)
|
||||
stage_controller.widget_vals[axis].get = MagicMock(return_value=vals[axis])
|
||||
stage_controller.widget_vals[axis].set = MagicMock()
|
||||
widgets[axis].widget.set(vals[axis])
|
||||
widgets[axis].widget.trigger_focusout_validation = MagicMock()
|
||||
|
||||
stage_controller.position_min = pos_dict(0)
|
||||
stage_controller.position_max = pos_dict(10)
|
||||
stage_controller.stage_setting_dict = {}
|
||||
|
||||
for axis in AXES:
|
||||
|
||||
callback = stage_controller.position_callback(axis)
|
||||
|
||||
# Test case 1: Position variable is within limits
|
||||
widgets[axis].widget.get = MagicMock(return_value=vals[axis])
|
||||
callback()
|
||||
stage_controller.view.after.assert_called()
|
||||
stage_controller.view.after.reset_mock()
|
||||
assert stage_controller.stage_setting_dict[axis] == vals[axis]
|
||||
|
||||
# Test case 2: Position variable is outside limits
|
||||
widgets[axis].widget.get = MagicMock(return_value=11)
|
||||
callback()
|
||||
stage_controller.view.after.assert_called_once()
|
||||
stage_controller.view.after.reset_mock()
|
||||
215
test/controller/sub_controllers/test_tiling.py
Normal file
215
test/controller/sub_controllers/test_tiling.py
Normal file
@@ -0,0 +1,215 @@
|
||||
import random
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def tiling_wizard_controller(dummy_view, dummy_controller):
|
||||
from navigate.view.popups.tiling_wizard_popup2 import TilingWizardPopup
|
||||
from navigate.controller.sub_controllers.tiling import (
|
||||
TilingWizardController,
|
||||
)
|
||||
|
||||
tiling_wizard = TilingWizardPopup(dummy_view)
|
||||
|
||||
class SubController:
|
||||
def __init__(self):
|
||||
self.parent_controller = dummy_controller
|
||||
|
||||
return TilingWizardController(tiling_wizard, SubController())
|
||||
|
||||
|
||||
def test_traces(tiling_wizard_controller):
|
||||
"""TODO: Find a way to access the actual lambda functions.
|
||||
|
||||
If we can, inspect.getsource(myfunc) should provide us the lambda definition.
|
||||
"""
|
||||
|
||||
def assert_one_trace(var):
|
||||
tinfo = var.trace_info()
|
||||
assert len(tinfo) >= 1
|
||||
assert tinfo[0][0][0] == "write"
|
||||
assert "lambda" in tinfo[0][1]
|
||||
|
||||
for ax in ["x", "y", "z", "f"]:
|
||||
# self.variables["x_start"], etc. should all be bound to two lambda functions
|
||||
# calling calculate_distance() and update_fov()
|
||||
for bound in ["start", "end"]:
|
||||
tinfo = tiling_wizard_controller.variables[f"{ax}_{bound}"].trace_info()
|
||||
assert len(tinfo) >= 1
|
||||
for ti in tinfo:
|
||||
assert ti[0][0] == "write"
|
||||
assert "lambda" in ti[1]
|
||||
|
||||
# fov should be bound to one lambda, calling calculate_tiles()
|
||||
assert_one_trace(tiling_wizard_controller.variables[f"{ax}_fov"])
|
||||
|
||||
# dist should be bound to one lambda, calling calculate_tiles()
|
||||
assert_one_trace(tiling_wizard_controller.variables[f"{ax}_dist"])
|
||||
|
||||
# Special cases
|
||||
assert_one_trace(tiling_wizard_controller.variables["percent_overlap"])
|
||||
assert_one_trace(
|
||||
tiling_wizard_controller.cam_settings_widgets["FOV_X"].get_variable()
|
||||
)
|
||||
assert_one_trace(
|
||||
tiling_wizard_controller.cam_settings_widgets["FOV_Y"].get_variable()
|
||||
)
|
||||
assert_one_trace(
|
||||
tiling_wizard_controller.stack_acq_widgets["start_position"].get_variable()
|
||||
)
|
||||
assert_one_trace(
|
||||
tiling_wizard_controller.stack_acq_widgets["end_position"].get_variable()
|
||||
)
|
||||
# Channels tab controller binds these a bunch
|
||||
# assert_one_trace(
|
||||
# tiling_wizard_controller.stack_acq_widgets["start_focus"].get_variable()
|
||||
# )
|
||||
# assert_one_trace(
|
||||
# tiling_wizard_controller.stack_acq_widgets["end_focus"].get_variable()
|
||||
# )
|
||||
|
||||
|
||||
def test_update_total_tiles(tiling_wizard_controller):
|
||||
tiling_wizard_controller.update_total_tiles()
|
||||
|
||||
assert True
|
||||
|
||||
|
||||
@pytest.mark.parametrize("axis", ["x", "y", "z", "f"])
|
||||
def test_calculate_tiles(tiling_wizard_controller, axis):
|
||||
from navigate.tools.multipos_table_tools import calc_num_tiles
|
||||
|
||||
ov, dist, fov = random.random(), random.random() * 100, random.random() * 10
|
||||
tiling_wizard_controller._percent_overlap = ov * 100
|
||||
tiling_wizard_controller.variables[f"{axis}_dist"].set(dist)
|
||||
tiling_wizard_controller.variables[f"{axis}_fov"].set(fov)
|
||||
tiling_wizard_controller.calculate_tiles(axis)
|
||||
|
||||
if axis == "x" or axis == "y":
|
||||
dist += fov
|
||||
|
||||
assert int(
|
||||
tiling_wizard_controller.variables[f"{axis}_tiles"].get()
|
||||
) == calc_num_tiles(dist, ov, fov)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("axis", ["x", "y", "z", "f"])
|
||||
def test_calculate_distance(tiling_wizard_controller, axis):
|
||||
start, end = random.random() * 10, random.random() * 100
|
||||
tiling_wizard_controller.variables[axis + "_start"].set(start)
|
||||
tiling_wizard_controller.variables[axis + "_end"].set(end)
|
||||
tiling_wizard_controller.calculate_distance(axis)
|
||||
assert float(tiling_wizard_controller.variables[axis + "_dist"].get()) == abs(
|
||||
start - end
|
||||
)
|
||||
|
||||
|
||||
def test_update_overlap(tiling_wizard_controller):
|
||||
tiling_wizard_controller.variables["percent_overlap"].set("")
|
||||
tiling_wizard_controller.update_overlap()
|
||||
tiling_wizard_controller.variables["percent_overlap"].set("10")
|
||||
tiling_wizard_controller.update_overlap()
|
||||
|
||||
assert True
|
||||
|
||||
|
||||
@pytest.mark.parametrize("axis", ["x", "y", "z", "f"])
|
||||
def test_update_fov(tiling_wizard_controller, axis):
|
||||
import random
|
||||
from navigate.tools.multipos_table_tools import sign
|
||||
|
||||
if axis == "y":
|
||||
tiling_wizard_controller.cam_settings_widgets["FOV_X"].set(
|
||||
int(random.random() * 1000)
|
||||
)
|
||||
tiling_wizard_controller.variables["x_start"].set(random.random() * 10)
|
||||
tiling_wizard_controller.variables["x_end"].set(random.random() * 1000)
|
||||
var = float(
|
||||
tiling_wizard_controller.cam_settings_widgets["FOV_X"].get()
|
||||
) * sign(
|
||||
float(tiling_wizard_controller.variables["x_end"].get())
|
||||
- float(tiling_wizard_controller.variables["x_start"].get())
|
||||
)
|
||||
elif axis == "x":
|
||||
tiling_wizard_controller.cam_settings_widgets["FOV_Y"].set(
|
||||
int(random.random() * 1000)
|
||||
)
|
||||
tiling_wizard_controller.variables["y_start"].set(random.random() * 10)
|
||||
tiling_wizard_controller.variables["y_end"].set(random.random() * 1000)
|
||||
var = float(
|
||||
tiling_wizard_controller.cam_settings_widgets["FOV_Y"].get()
|
||||
) * sign(
|
||||
float(tiling_wizard_controller.variables["y_end"].get())
|
||||
- float(tiling_wizard_controller.variables["y_start"].get())
|
||||
)
|
||||
elif axis == "z":
|
||||
tiling_wizard_controller.stack_acq_widgets["start_position"].set(
|
||||
random.random() * 10
|
||||
)
|
||||
tiling_wizard_controller.stack_acq_widgets["end_position"].set(
|
||||
random.random() * 1000
|
||||
)
|
||||
var = float(
|
||||
tiling_wizard_controller.stack_acq_widgets["end_position"].get()
|
||||
) - float(tiling_wizard_controller.stack_acq_widgets["start_position"].get())
|
||||
elif axis == "f":
|
||||
tiling_wizard_controller.stack_acq_widgets["start_focus"].set(
|
||||
random.random() * 10
|
||||
)
|
||||
tiling_wizard_controller.stack_acq_widgets["end_focus"].set(
|
||||
random.random() * 1000
|
||||
)
|
||||
var = float(
|
||||
tiling_wizard_controller.stack_acq_widgets["end_focus"].get()
|
||||
) - float(tiling_wizard_controller.stack_acq_widgets["start_focus"].get())
|
||||
tiling_wizard_controller.update_fov(axis)
|
||||
|
||||
assert float(tiling_wizard_controller.variables[f"{axis}_fov"].get()) == abs(var)
|
||||
|
||||
|
||||
def test_set_table(tiling_wizard_controller):
|
||||
# from navigate.tools.multipos_table_tools import compute_tiles_from_bounding_box
|
||||
tiling_wizard_controller.set_table()
|
||||
|
||||
x_start = float(tiling_wizard_controller.variables["x_start"].get())
|
||||
x_stop = float(tiling_wizard_controller.variables["x_end"].get())
|
||||
|
||||
y_start = float(tiling_wizard_controller.variables["y_start"].get())
|
||||
y_stop = float(tiling_wizard_controller.variables["y_end"].get())
|
||||
|
||||
# shift z by coordinate origin of local z-stack
|
||||
z_start = float(tiling_wizard_controller.variables["z_start"].get()) - float(
|
||||
tiling_wizard_controller.stack_acq_widgets["start_position"].get()
|
||||
)
|
||||
z_stop = float(tiling_wizard_controller.variables["z_end"].get()) - float(
|
||||
tiling_wizard_controller.stack_acq_widgets["end_position"].get()
|
||||
)
|
||||
|
||||
# Default to fixed theta
|
||||
r_start = tiling_wizard_controller.stage_position_vars["theta"].get()
|
||||
r_stop = tiling_wizard_controller.stage_position_vars["theta"].get()
|
||||
|
||||
f_start = float(tiling_wizard_controller.variables["f_start"].get()) - float(
|
||||
tiling_wizard_controller.stack_acq_widgets["start_focus"].get()
|
||||
)
|
||||
f_stop = float(tiling_wizard_controller.variables["f_end"].get()) - float(
|
||||
tiling_wizard_controller.stack_acq_widgets["end_focus"].get()
|
||||
)
|
||||
|
||||
# for consistency, always go from low to high
|
||||
def sort_vars(a, b):
|
||||
if a > b:
|
||||
return b, a
|
||||
return a, b
|
||||
|
||||
x_start, x_stop = sort_vars(x_start, x_stop)
|
||||
y_start, y_stop = sort_vars(y_start, y_stop)
|
||||
z_start, z_stop = sort_vars(z_start, z_stop)
|
||||
r_start, r_stop = sort_vars(r_start, r_stop)
|
||||
f_start, f_stop = sort_vars(f_start, f_stop)
|
||||
|
||||
assert tiling_wizard_controller.multipoint_table.model.df["X"].min() == x_start
|
||||
assert tiling_wizard_controller.multipoint_table.model.df["Y"].min() == y_start
|
||||
assert tiling_wizard_controller.multipoint_table.model.df["Z"].min() == z_start
|
||||
assert tiling_wizard_controller.multipoint_table.model.df["THETA"].min() == r_start
|
||||
assert tiling_wizard_controller.multipoint_table.model.df["F"].min() == f_start
|
||||
178
test/controller/sub_controllers/test_waveform_popup.py
Normal file
178
test/controller/sub_controllers/test_waveform_popup.py
Normal file
@@ -0,0 +1,178 @@
|
||||
import pytest
|
||||
import random
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def waveform_popup_controller(dummy_view, dummy_controller):
|
||||
from navigate.controller.sub_controllers.waveform_popup import (
|
||||
WaveformPopupController,
|
||||
)
|
||||
from navigate.view.popups.waveform_parameter_popup_window import (
|
||||
WaveformParameterPopupWindow,
|
||||
)
|
||||
|
||||
waveform_constants_popup = WaveformParameterPopupWindow(
|
||||
dummy_view, dummy_controller.configuration_controller
|
||||
)
|
||||
|
||||
return WaveformPopupController(
|
||||
waveform_constants_popup,
|
||||
dummy_controller,
|
||||
dummy_controller.waveform_constants_path,
|
||||
)
|
||||
|
||||
|
||||
def test_populate_experiment_values(waveform_popup_controller):
|
||||
exp_dict = waveform_popup_controller.parent_controller.configuration["experiment"][
|
||||
"MicroscopeState"
|
||||
]
|
||||
resolution = exp_dict["microscope_name"]
|
||||
zoom = exp_dict["zoom"]
|
||||
waveform_constants = waveform_popup_controller.parent_controller.configuration[
|
||||
"waveform_constants"
|
||||
]
|
||||
widgets = waveform_popup_controller.view.get_widgets()
|
||||
|
||||
def assert_widget_values():
|
||||
resolution = exp_dict["microscope_name"]
|
||||
zoom = exp_dict["zoom"]
|
||||
assert widgets["Mode"].get() == resolution
|
||||
assert widgets["Mag"].get() == zoom
|
||||
|
||||
# remote focus
|
||||
remote_focus_dict = waveform_constants["remote_focus_constants"][resolution][
|
||||
zoom
|
||||
]
|
||||
for k in remote_focus_dict.keys():
|
||||
assert widgets[k + " Amp"].get() == remote_focus_dict[k]["amplitude"]
|
||||
assert widgets[k + " Off"].get() == remote_focus_dict[k]["offset"]
|
||||
|
||||
# galvo
|
||||
galvo_dict = waveform_constants["galvo_constants"]
|
||||
for g in galvo_dict.keys():
|
||||
if resolution in [galvo_dict[g].keys()]:
|
||||
galvo_info = galvo_dict[g][resolution][zoom]
|
||||
assert widgets[g + " Amp"].get() == galvo_info["amplitude"]
|
||||
assert widgets[g + " Off"].get() == galvo_info["offset"]
|
||||
|
||||
# delay, fly back time, settle duraation, smoothing
|
||||
assert widgets["Delay"].get() == str(
|
||||
waveform_constants["other_constants"]["remote_focus_delay"]
|
||||
)
|
||||
assert widgets["Ramp_falling"].get() == str(
|
||||
waveform_constants["other_constants"]["remote_focus_ramp_falling"]
|
||||
)
|
||||
assert widgets["Duty"].get() == str(
|
||||
waveform_constants["other_constants"]["remote_focus_settle_duration"]
|
||||
)
|
||||
assert widgets["Smoothing"].get() == str(
|
||||
waveform_constants["other_constants"]["percent_smoothing"]
|
||||
)
|
||||
|
||||
# default values
|
||||
waveform_popup_controller.populate_experiment_values()
|
||||
assert_widget_values()
|
||||
|
||||
# change resolution and/or zoom
|
||||
for microscope_name in waveform_constants["remote_focus_constants"].keys():
|
||||
for z in waveform_constants["remote_focus_constants"][microscope_name].keys():
|
||||
exp_dict["microscope_name"] = microscope_name
|
||||
exp_dict["zoom"] = z
|
||||
waveform_popup_controller.populate_experiment_values()
|
||||
assert_widget_values()
|
||||
|
||||
exp_dict["microscope_name"] = resolution
|
||||
exp_dict["zoom"] = zoom
|
||||
waveform_popup_controller.populate_experiment_values()
|
||||
assert_widget_values()
|
||||
|
||||
# update waveform_constants
|
||||
for k in waveform_constants["remote_focus_constants"][resolution][zoom].keys():
|
||||
amplitude = round(random.random() * 5, 2)
|
||||
offset = round(random.random() * 5, 2)
|
||||
temp = waveform_constants["remote_focus_constants"][resolution][zoom][k]
|
||||
temp["amplitude"] = amplitude
|
||||
temp["offset"] = offset
|
||||
|
||||
# update galvo
|
||||
for g in waveform_constants["galvo_constants"].keys():
|
||||
amplitude = round(random.random() * 5, 2)
|
||||
offset = round(random.random() * 5, 2)
|
||||
temp = waveform_constants["galvo_constants"][g][resolution][zoom]
|
||||
temp["amplitude"] = amplitude
|
||||
temp["offset"] = offset
|
||||
|
||||
for k in [
|
||||
"remote_focus_ramp_falling",
|
||||
"remote_focus_settle_duration",
|
||||
"percent_smoothing",
|
||||
"remote_focus_delay",
|
||||
]:
|
||||
waveform_constants["other_constants"][k] = round(random.random() * 100, 2)
|
||||
|
||||
waveform_popup_controller.populate_experiment_values(force_update=True)
|
||||
assert_widget_values()
|
||||
|
||||
|
||||
def test_show_laser_info(waveform_popup_controller):
|
||||
waveform_popup_controller.show_laser_info()
|
||||
assert True
|
||||
|
||||
|
||||
def test_configure_widget_range(waveform_popup_controller):
|
||||
waveform_popup_controller.configure_widget_range()
|
||||
assert True
|
||||
|
||||
|
||||
def test_estimate_galvo_setting_empty_string(waveform_popup_controller):
|
||||
"""Test if the function returns without calling the camera setting controller."""
|
||||
# Galvo name
|
||||
galvo_name = "galvo_0"
|
||||
|
||||
# Mocked camera setting controller
|
||||
waveform_popup_controller.parent_controller.camera_setting_controller = MagicMock()
|
||||
waveform_popup_controller.parent_controller.camera_setting_controller.mode_widgets[
|
||||
"Pixels"
|
||||
].get = MagicMock(return_value="")
|
||||
waveform_popup_controller.parent_controller.camera_setting_controller.framerate_widgets[
|
||||
"exposure_time"
|
||||
].get = MagicMock()
|
||||
|
||||
waveform_popup_controller.estimate_galvo_setting(galvo_name)
|
||||
waveform_popup_controller.parent_controller.camera_setting_controller.framerate_widgets[
|
||||
"exposure_time"
|
||||
].get.assert_not_called()
|
||||
|
||||
|
||||
def test_estimate_galvo_setting_with_string(waveform_popup_controller):
|
||||
"""Test if the function calls the camera setting controller."""
|
||||
# Galvo name
|
||||
galvo_name = "galvo_0"
|
||||
number_of_pixels = 50
|
||||
|
||||
# Mocked camera setting controller
|
||||
waveform_popup_controller.parent_controller.camera_setting_controller = MagicMock()
|
||||
waveform_popup_controller.parent_controller.camera_setting_controller.mode_widgets[
|
||||
"Pixels"
|
||||
].get = MagicMock(return_value=str(number_of_pixels))
|
||||
waveform_popup_controller.parent_controller.camera_setting_controller.framerate_widgets[
|
||||
"exposure_time"
|
||||
].get = MagicMock()
|
||||
|
||||
# Mocked model
|
||||
waveform_popup_controller.parent_controller.model = MagicMock()
|
||||
mock_model = waveform_popup_controller.parent_controller.model
|
||||
mock_model.get_camera_line_interval_and_exposure_time = MagicMock(
|
||||
return_value=(0.05, 50, 500)
|
||||
)
|
||||
|
||||
# Mocked view
|
||||
waveform_popup_controller.view = MagicMock()
|
||||
waveform_popup_controller.view.inputs[galvo_name].widget.set = MagicMock()
|
||||
|
||||
# Call the function
|
||||
waveform_popup_controller.estimate_galvo_setting(galvo_name)
|
||||
|
||||
# Check to see what the view was called with.
|
||||
waveform_popup_controller.view.inputs[galvo_name].widget.set.assert_called_once()
|
||||
787
test/controller/test_controller.py
Normal file
787
test/controller/test_controller.py
Normal file
@@ -0,0 +1,787 @@
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock, ANY
|
||||
import pytest
|
||||
import numpy
|
||||
import multiprocessing as mp
|
||||
import logging
|
||||
import platform
|
||||
from logging.handlers import QueueHandler
|
||||
|
||||
from navigate.log_files.log_functions import log_setup
|
||||
|
||||
|
||||
class _NullQueue:
|
||||
"""Minimal queue-like sink for logging; avoids mp feeder threads on Windows."""
|
||||
|
||||
def put(self, _): # QueueHandler calls .put()
|
||||
pass
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
def cancel_join_thread(self):
|
||||
pass
|
||||
|
||||
|
||||
class DummySplashScreen:
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
|
||||
def _normalize_log_setup(start_listener):
|
||||
"""Call log_setup and normalize its return to (log_queue, log_listener)."""
|
||||
from navigate.log_files.log_functions import log_setup
|
||||
|
||||
try:
|
||||
res = log_setup("logging.yml", logging_path=None, start_listener=start_listener)
|
||||
except Exception:
|
||||
res = None
|
||||
|
||||
# Accept (queue, listener), or queue-only, or None.
|
||||
if isinstance(res, tuple) and len(res) == 2:
|
||||
return res[0], res[1]
|
||||
if res is not None and hasattr(res, "put"):
|
||||
return res, None
|
||||
|
||||
# Fallbacks:
|
||||
if platform.system() == "Windows":
|
||||
# Avoid mp.Queue on Windows to prevent hangs in CI.
|
||||
return _NullQueue(), None
|
||||
# Non-Windows: a real mp.Queue is fine without a listener.
|
||||
try:
|
||||
return mp.Queue(), None
|
||||
except Exception:
|
||||
return _NullQueue(), None
|
||||
|
||||
|
||||
def _remove_queue_handlers(target_queue=None):
|
||||
# Detach and close any QueueHandler that targets target_queue.
|
||||
def strip_handlers(logger):
|
||||
for h in list(getattr(logger, "handlers", [])):
|
||||
if isinstance(h, QueueHandler) and (
|
||||
target_queue is None or getattr(h, "queue", None) is target_queue
|
||||
):
|
||||
try:
|
||||
logger.removeHandler(h)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
h.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
strip_handlers(logging.getLogger())
|
||||
for name, obj in logging.root.manager.loggerDict.items():
|
||||
if isinstance(obj, logging.Logger):
|
||||
strip_handlers(logging.getLogger(name))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def controller(tk_root):
|
||||
from navigate.controller.controller import Controller
|
||||
|
||||
base_directory = Path.joinpath(
|
||||
Path(__file__).resolve().parent.parent.parent, "src", "navigate"
|
||||
)
|
||||
configuration_directory = Path.joinpath(base_directory, "config")
|
||||
|
||||
configuration_path = Path.joinpath(configuration_directory, "configuration.yaml")
|
||||
experiment_path = Path.joinpath(configuration_directory, "experiment.yml")
|
||||
waveform_constants_path = Path.joinpath(
|
||||
configuration_directory, "waveform_constants.yml"
|
||||
)
|
||||
rest_api_path = Path.joinpath(configuration_directory, "rest_api_config.yml")
|
||||
waveform_templates_path = Path.joinpath(
|
||||
configuration_directory, "waveform_templates.yml"
|
||||
)
|
||||
gui_configuration_path = Path.joinpath(
|
||||
configuration_directory, "gui_configuration.yml"
|
||||
)
|
||||
multi_positions_path = Path.joinpath(configuration_directory, "multi_positions.yml")
|
||||
args = SimpleNamespace(synthetic_hardware=True)
|
||||
|
||||
start_listener = platform.system() != "Windows"
|
||||
log_queue, log_listener = _normalize_log_setup(start_listener)
|
||||
|
||||
controller = Controller(
|
||||
root=tk_root,
|
||||
splash_screen=DummySplashScreen(),
|
||||
configuration_path=configuration_path,
|
||||
experiment_path=experiment_path,
|
||||
waveform_constants_path=waveform_constants_path,
|
||||
rest_api_path=rest_api_path,
|
||||
waveform_templates_path=waveform_templates_path,
|
||||
gui_configuration_path=gui_configuration_path,
|
||||
multi_positions_path=multi_positions_path,
|
||||
log_queue=log_queue,
|
||||
args=args,
|
||||
)
|
||||
|
||||
# To make sure the testcases won't hang on because of the model.event_queue
|
||||
# The changes here won't affect other testcases,
|
||||
# because the testcases from other files use DummyController
|
||||
# and DummyModel instead of this controller fixture
|
||||
controller.model = MagicMock()
|
||||
controller.threads_pool = MagicMock()
|
||||
controller.model.get_offset_variance_maps.return_value = (None, None)
|
||||
|
||||
yield controller
|
||||
|
||||
try:
|
||||
controller.execute("exit")
|
||||
except SystemExit:
|
||||
pass
|
||||
|
||||
# Tear down the controller properly
|
||||
q = getattr(controller, "event_queue", None)
|
||||
if q is not None:
|
||||
try:
|
||||
q.close()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
q.cancel_join_thread()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Close any Pipes
|
||||
if getattr(controller, "show_img_pipe", None):
|
||||
try:
|
||||
controller.show_img_pipe.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
controller.manager.shutdown()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Detach QueueHandlers first so no more puts go to log_queue
|
||||
_remove_queue_handlers(log_queue)
|
||||
|
||||
# Stop the queue listener (only if started)
|
||||
try:
|
||||
if start_listener and log_listener:
|
||||
try:
|
||||
log_listener.enqueue_sentinel()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
log_listener.stop()
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Close the logging queue and skip join on Windows
|
||||
if platform.system() == "Windows":
|
||||
|
||||
try:
|
||||
log_queue.close()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
log_queue.cancel_join_thread()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
logging.shutdown()
|
||||
|
||||
# As a last resort on Windows, hard-terminate any alive mp children
|
||||
if platform.system() == "Windows":
|
||||
try:
|
||||
children = list(mp.active_children())
|
||||
for p in children:
|
||||
try:
|
||||
p.terminate()
|
||||
except Exception:
|
||||
pass
|
||||
for p in children:
|
||||
try:
|
||||
p.join(timeout=5)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def test_update_buffer(controller):
|
||||
camera_parameters = controller.configuration["experiment"]["CameraParameters"]
|
||||
controller.update_buffer()
|
||||
assert controller.img_width == camera_parameters["img_x_pixels"]
|
||||
assert controller.img_height == camera_parameters["img_y_pixels"]
|
||||
|
||||
# Make sure that the get_data_buffer method is not called.
|
||||
assert controller.model.get_data_buffer.called is False
|
||||
|
||||
# Change the buffer size
|
||||
microscope_name = controller.configuration["experiment"]["MicroscopeState"][
|
||||
"microscope_name"
|
||||
]
|
||||
controller.configuration["experiment"]["CameraParameters"][microscope_name][
|
||||
"img_x_pixels"
|
||||
] = 100
|
||||
controller.configuration["experiment"]["CameraParameters"][microscope_name][
|
||||
"img_y_pixels"
|
||||
] = 100
|
||||
controller.update_buffer()
|
||||
|
||||
# Make sure that the get_data_buffer method is called.
|
||||
assert controller.model.get_data_buffer.called is True
|
||||
|
||||
# Confirm that the buffer size has been updated.
|
||||
assert controller.img_width == 100
|
||||
assert controller.img_height == 100
|
||||
|
||||
assert True
|
||||
|
||||
|
||||
def test_change_microscope(controller):
|
||||
# Get the microscopes from the configuration file
|
||||
microscopes = controller.configuration["configuration"]["microscopes"]
|
||||
|
||||
# Iterate through the microscopes and change the microscope
|
||||
for microscope_name in microscopes.keys():
|
||||
|
||||
# Patch the configuration_controller
|
||||
controller.configuration_controller.change_microscope = MagicMock()
|
||||
|
||||
# Default zoom is '0.63x'
|
||||
zoom = microscopes[microscope_name]["zoom"]["position"].keys()[0]
|
||||
controller.configuration["experiment"]["MicroscopeState"]["zoom"] = zoom
|
||||
|
||||
# Change the microscope without passing the zoom
|
||||
controller.change_microscope(microscope_name)
|
||||
assert (
|
||||
controller.configuration["experiment"]["MicroscopeState"]["microscope_name"]
|
||||
== microscope_name
|
||||
)
|
||||
|
||||
# Confirm that the zoom has not changed.
|
||||
assert controller.configuration["experiment"]["MicroscopeState"]["zoom"] == zoom
|
||||
|
||||
# Call it and pass the zoom
|
||||
zoom = microscopes[microscope_name]["zoom"]["position"].keys()[-1]
|
||||
controller.change_microscope(microscope_name, zoom)
|
||||
assert controller.configuration["experiment"]["MicroscopeState"]["zoom"] == zoom
|
||||
|
||||
# Make sure that the configuration_controller has been called.
|
||||
assert controller.configuration_controller.change_microscope.called is True
|
||||
|
||||
# Have configuration controller return False
|
||||
controller.configuration_controller.change_microscope.return_value = False
|
||||
|
||||
# Patch the stage controller, channels_tab_controller...
|
||||
controller.stage_controller.initialize = MagicMock()
|
||||
controller.channels_tab_controller.initialize = MagicMock()
|
||||
camera_setting_controller = controller.camera_setting_controller
|
||||
camera_setting_controller.update_camera_device_related_setting = MagicMock()
|
||||
camera_setting_controller.calculate_physical_dimensions = MagicMock()
|
||||
controller.camera_view_controller.update_snr = MagicMock()
|
||||
|
||||
# Call change microscope, assert patched methods are not called
|
||||
controller.change_microscope(microscope_name)
|
||||
assert controller.stage_controller.initialize.called is False
|
||||
assert controller.channels_tab_controller.initialize.called is False
|
||||
assert (
|
||||
camera_setting_controller.update_camera_device_related_setting.called
|
||||
is False
|
||||
)
|
||||
assert camera_setting_controller.calculate_physical_dimensions.called is False
|
||||
assert controller.camera_view_controller.update_snr.called is False
|
||||
|
||||
# Have configuration controller return True
|
||||
controller.configuration_controller.change_microscope.return_value = True
|
||||
|
||||
# Call change microscope, assert patched methods are called
|
||||
controller.change_microscope(microscope_name)
|
||||
assert controller.stage_controller.initialize.called is True
|
||||
assert controller.channels_tab_controller.initialize.called is True
|
||||
assert (
|
||||
camera_setting_controller.update_camera_device_related_setting.called
|
||||
is True
|
||||
)
|
||||
assert camera_setting_controller.calculate_physical_dimensions.called is True
|
||||
assert controller.camera_view_controller.update_snr.called is True
|
||||
|
||||
# Test waveform popup controller.
|
||||
controller.waveform_popup_controller = MagicMock()
|
||||
controller.change_microscope(microscope_name)
|
||||
assert (
|
||||
controller.waveform_popup_controller.populate_experiment_values.called
|
||||
is True
|
||||
)
|
||||
|
||||
assert True
|
||||
|
||||
|
||||
def test_initialize_cam_view(controller):
|
||||
minmax_values = [0, 2**16 - 1]
|
||||
image_metrics = [1, 0, 0]
|
||||
controller.initialize_cam_view()
|
||||
|
||||
assert (
|
||||
controller.camera_view_controller.image_palette["Min"].get() == minmax_values[0]
|
||||
)
|
||||
assert (
|
||||
controller.camera_view_controller.image_palette["Max"].get() == minmax_values[1]
|
||||
)
|
||||
assert (
|
||||
controller.camera_view_controller.image_metrics["Frames"].get()
|
||||
== image_metrics[0]
|
||||
)
|
||||
|
||||
assert True
|
||||
|
||||
|
||||
def test_populate_experiment_setting(controller):
|
||||
controller.populate_experiment_setting(in_initialize=False)
|
||||
assert True
|
||||
|
||||
|
||||
def test_prepare_acquire_data(controller):
|
||||
|
||||
# Test without warning message
|
||||
controller.set_mode_of_sub = MagicMock()
|
||||
assert controller.prepare_acquire_data() is True
|
||||
assert controller.set_mode_of_sub.called is True
|
||||
|
||||
# Test with warning message. Challenging since controller is local.
|
||||
# with patch('controller.tkinter.messagebox.showerror') as mock_showerror:
|
||||
# controller.update_experiment_setting.return_value = "Warning!"
|
||||
# assert controller.prepare_acquire_data() is False
|
||||
# mock_showerror.assert_called_once()
|
||||
|
||||
assert True
|
||||
|
||||
|
||||
def test_set_mode_of_sub(controller):
|
||||
modes = ["customized", "stop", "live"]
|
||||
|
||||
for mode in modes:
|
||||
controller.set_mode_of_sub(mode=mode)
|
||||
|
||||
assert True
|
||||
|
||||
|
||||
def test_execute_stop_stage(controller):
|
||||
controller.threads_pool.createThread = MagicMock()
|
||||
controller.execute(command="stop_stage")
|
||||
assert controller.threads_pool.createThread.called is True
|
||||
|
||||
assert True
|
||||
|
||||
|
||||
def test_execute_move_stage_and_update_info(controller):
|
||||
positions = {"x": 51, "y": 52.0, "z": -530.3, "theta": 1, "f": 0}
|
||||
|
||||
controller.execute("move_stage_and_update_info", positions)
|
||||
for axis, value in positions.items():
|
||||
assert (
|
||||
float(controller.stage_controller.widget_vals[axis].get())
|
||||
== positions[axis]
|
||||
)
|
||||
|
||||
assert True
|
||||
|
||||
|
||||
def test_execute_move_stage_and_acquire_image(controller):
|
||||
positions = {"x": 51, "y": 52.0, "z": -530.3, "theta": 1, "f": 0}
|
||||
controller.model.move_stage = MagicMock()
|
||||
controller.threads_pool.createThread = MagicMock()
|
||||
controller.execute("move_stage_and_acquire_image", positions)
|
||||
|
||||
assert controller.model.move_stage.called is True
|
||||
|
||||
for axis, value in positions.items():
|
||||
assert (
|
||||
float(controller.stage_controller.widget_vals[axis].get())
|
||||
== positions[axis]
|
||||
)
|
||||
|
||||
assert controller.threads_pool.createThread.called is True
|
||||
assert True
|
||||
|
||||
|
||||
def test_execute_get_stage_position(controller):
|
||||
# Set the positions in the GUI
|
||||
set_positions = {"x": 51, "y": 52.0, "z": -530.3, "theta": 1, "f": 0}
|
||||
controller.execute("move_stage_and_update_info", set_positions)
|
||||
|
||||
# Get the positions from the GUI
|
||||
get_positions = controller.execute("get_stage_position")
|
||||
assert type(get_positions) is dict
|
||||
|
||||
axes = ["x", "y", "z", "theta", "f"]
|
||||
for axis in axes:
|
||||
assert axis in get_positions.keys()
|
||||
|
||||
# assert that get_positions is equal to set_positions
|
||||
assert get_positions == set_positions
|
||||
|
||||
assert True
|
||||
|
||||
|
||||
def test_execute_mark_position(controller):
|
||||
|
||||
set_positions = {"x": 51, "y": 52.0, "z": -530.3, "theta": 1, "f": 0}
|
||||
controller.execute("mark_position", set_positions)
|
||||
|
||||
# Get the positions from the multiposition table. Returns a list of lists.
|
||||
get_positions = controller.multiposition_tab_controller.get_positions()
|
||||
|
||||
# Assert that the last position in get_positions is equal to set_positions
|
||||
assert get_positions[-1] == [
|
||||
set_positions["x"],
|
||||
set_positions["y"],
|
||||
set_positions["z"],
|
||||
set_positions["theta"],
|
||||
set_positions["f"],
|
||||
]
|
||||
|
||||
assert True
|
||||
|
||||
|
||||
def test_execute_resolution(controller):
|
||||
pass
|
||||
|
||||
|
||||
def test_execute_set_save(controller):
|
||||
|
||||
for save_data in [True, False]:
|
||||
controller.execute("set_save", save_data)
|
||||
assert controller.acquire_bar_controller.is_save == save_data
|
||||
assert (
|
||||
controller.configuration["experiment"]["MicroscopeState"]["is_save"]
|
||||
== save_data
|
||||
)
|
||||
|
||||
assert True
|
||||
|
||||
|
||||
def test_execute_update_setting(controller):
|
||||
controller.threads_pool.createThread = MagicMock()
|
||||
args = ["resolution", {"resolution_mode", "1x"}]
|
||||
controller.execute("update_setting", args)
|
||||
assert controller.threads_pool.createThread.called is True
|
||||
|
||||
assert True
|
||||
|
||||
|
||||
def test_execute_stage_limits(controller):
|
||||
controller.threads_pool.createThread = MagicMock()
|
||||
for stage_limits in [True, False]:
|
||||
controller.threads_pool.createThread.reset_mock()
|
||||
controller.execute("stage_limits", stage_limits)
|
||||
assert controller.stage_controller.stage_limits == stage_limits
|
||||
assert controller.threads_pool.createThread.called is True
|
||||
|
||||
assert True
|
||||
|
||||
|
||||
def test_execute_autofocus(controller):
|
||||
# Create mock objects
|
||||
controller.threads_pool.createThread = MagicMock()
|
||||
|
||||
# Test non-acquiring case
|
||||
controller.acquire_bar_controller.is_acquiring = False
|
||||
controller.execute("autofocus")
|
||||
controller.threads_pool.createThread.assert_called_with(
|
||||
resourceName="camera",
|
||||
target=controller.capture_image,
|
||||
args=("autofocus", "live"),
|
||||
)
|
||||
|
||||
# Test the acquiring case
|
||||
controller.acquire_bar_controller.mode = "live"
|
||||
controller.acquire_bar_controller.is_acquiring = True
|
||||
controller.threads_pool.createThread.reset_mock()
|
||||
controller.execute("autofocus")
|
||||
controller.threads_pool.createThread.assert_called_once()
|
||||
controller.threads_pool.createThread.assert_any_call(
|
||||
resourceName="model", target=ANY
|
||||
)
|
||||
args, kwargs = controller.threads_pool.createThread.call_args
|
||||
assert kwargs["resourceName"] == "model"
|
||||
|
||||
# Confirm that the lambda is callable.
|
||||
assert callable(kwargs["target"])
|
||||
|
||||
assert True
|
||||
|
||||
|
||||
def test_execute_eliminate_tiles(controller):
|
||||
controller.threads_pool.createThread = MagicMock()
|
||||
|
||||
# Populate Feature List
|
||||
controller.menu_controller.feature_list_names = ["Remove Empty Tiles"]
|
||||
|
||||
# Set the mode to live
|
||||
controller.acquire_bar_controller.set_mode("live")
|
||||
|
||||
# Execute the command
|
||||
controller.execute("eliminate_tiles")
|
||||
assert controller.acquire_bar_controller.get_mode() == "customized"
|
||||
|
||||
# Assert that the thread pool is called
|
||||
assert controller.threads_pool.createThread.called is True
|
||||
|
||||
assert True
|
||||
|
||||
|
||||
def test_execute_load_features(controller):
|
||||
controller.threads_pool.createThread = MagicMock()
|
||||
|
||||
controller.execute("load_features")
|
||||
controller.threads_pool.createThread.assert_any_call("model", ANY)
|
||||
args, kwargs = controller.threads_pool.createThread.call_args
|
||||
assert args[0] == "model"
|
||||
assert callable(args[1])
|
||||
|
||||
assert True
|
||||
|
||||
|
||||
def test_execute_acquire_and_save_return(controller):
|
||||
|
||||
# Prepare mock objects.
|
||||
controller.prepare_acquire_data = MagicMock()
|
||||
controller.acquire_bar_controller.stop_acquire = MagicMock()
|
||||
controller.camera_setting_controller.calculate_physical_dimensions = MagicMock()
|
||||
|
||||
# Prepare mock returns
|
||||
controller.prepare_acquire_data.return_value = False
|
||||
|
||||
# Test and make sure return is called
|
||||
controller.execute("acquire_and_save")
|
||||
assert controller.acquire_bar_controller.stop_acquire.called is True
|
||||
assert (
|
||||
controller.camera_setting_controller.calculate_physical_dimensions.called
|
||||
is False
|
||||
)
|
||||
|
||||
assert True
|
||||
|
||||
|
||||
def test_execute_acquire_and_acquire_and_save(controller):
|
||||
# The modes "customized" & "live" results in the thread not being called.
|
||||
# TODO: Figure out why the thread is not being called.
|
||||
|
||||
# controller.plugin_acquisition_modes = {}
|
||||
# controller.threads_pool.createThread = MagicMock()
|
||||
#
|
||||
# for statement in ["acquire", "acquire_and_save"]:
|
||||
# for mode in ["z-stack", "single"]:
|
||||
# controller.acquire_bar_controller.mode = mode
|
||||
# controller.execute(statement)
|
||||
# controller.threads_pool.createThread.assert_called_with(
|
||||
# "camera",
|
||||
# controller.capture_image,
|
||||
# args=(
|
||||
# "acquire",
|
||||
# controller.acquire_bar_controller.mode,
|
||||
# ),
|
||||
# )
|
||||
# controller.stop_acquisition_flag = True
|
||||
# controller.threads_pool.createThread.reset_mock()
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def test_execute_stop_acquire(controller):
|
||||
# TODO: Currently hangs indefinitely.
|
||||
# Prepare mock objects.
|
||||
# controller.show_img_pipe.poll = MagicMock()
|
||||
# controller.show_img_pipe.recv = MagicMock()
|
||||
# controller.sloppy_stop = MagicMock()
|
||||
# controller.threads_pool.createThread = MagicMock()
|
||||
#
|
||||
# # Prepare mock returns
|
||||
# controller.show_img_pipe.poll.return_value = True
|
||||
#
|
||||
# # Test and make sure return is called
|
||||
# controller.stop_acquisition_flag = False
|
||||
# controller.execute("acquire", "continuous")
|
||||
# controller.execute("stop_acquire")
|
||||
pass
|
||||
|
||||
|
||||
def test_execute_exit(controller):
|
||||
# Essentially already tested by teardown of controller fixture.
|
||||
pass
|
||||
|
||||
|
||||
def test_execute_adaptive_optics(controller):
|
||||
controller.threads_pool.createThread = MagicMock()
|
||||
for command in [
|
||||
"flatten_mirror",
|
||||
"zero_mirror",
|
||||
"set_mirror",
|
||||
"set_mirror_from_wcs",
|
||||
]:
|
||||
controller.execute(command)
|
||||
controller.threads_pool.createThread.assert_called_with("model", ANY)
|
||||
args, kwargs = controller.threads_pool.createThread.call_args
|
||||
assert args[0] == "model"
|
||||
assert callable(args[1])
|
||||
|
||||
controller.execute("tony_wilson")
|
||||
controller.threads_pool.createThread.assert_called_with(
|
||||
"camera", controller.capture_image, args=("tony_wilson", "live")
|
||||
)
|
||||
|
||||
|
||||
def test_execute_random(controller):
|
||||
controller.threads_pool.createThread = MagicMock()
|
||||
for command in ["random1", "random2"]:
|
||||
controller.execute(command)
|
||||
controller.threads_pool.createThread.assert_called_with("model", ANY)
|
||||
args, kwargs = controller.threads_pool.createThread.call_args
|
||||
assert args[0] == "model"
|
||||
assert callable(args[1])
|
||||
|
||||
assert True
|
||||
|
||||
|
||||
def test_capture_image(controller):
|
||||
|
||||
count = 0
|
||||
|
||||
def get_image_id():
|
||||
nonlocal count
|
||||
count += 1
|
||||
if count >= 10:
|
||||
return "stop"
|
||||
return numpy.random.randint(0, 10)
|
||||
|
||||
microscope_name = controller.configuration["experiment"]["MicroscopeState"][
|
||||
"microscope_name"
|
||||
]
|
||||
width = controller.configuration["experiment"]["CameraParameters"][microscope_name][
|
||||
"img_x_pixels"
|
||||
]
|
||||
height = controller.configuration["experiment"]["CameraParameters"][
|
||||
microscope_name
|
||||
]["img_y_pixels"]
|
||||
images = numpy.random.rand(10, width, height)
|
||||
controller.data_buffer = images
|
||||
work_thread = MagicMock()
|
||||
work_thread.join = MagicMock()
|
||||
controller.threads_pool.createThread = MagicMock()
|
||||
controller.threads_pool.createThread.return_value = work_thread
|
||||
controller.show_img_pipe.recv = get_image_id
|
||||
controller.show_img_pipe.poll = MagicMock()
|
||||
controller.show_img_pipe.poll.return_value = False
|
||||
|
||||
# Deal with stop_acquire
|
||||
controller.sloppy_stop = MagicMock()
|
||||
controller.menu_controller.feature_id_val.set = MagicMock()
|
||||
|
||||
# Deal with camera view controller trying to launch a thread
|
||||
controller.camera_view_controller.is_displaying_image = MagicMock()
|
||||
controller.camera_view_controller.is_displaying_image.return_value = True
|
||||
|
||||
for command in ["acquire"]: # "autofocus"
|
||||
for mode in ["continuous", "live", "z-stack", "single"]:
|
||||
controller.capture_image(command, mode)
|
||||
|
||||
# Evaluate calls
|
||||
controller.threads_pool.createThread.assert_called_with("model", ANY)
|
||||
args, kwargs = controller.threads_pool.createThread.call_args
|
||||
assert args[0] == "model"
|
||||
assert callable(args[1])
|
||||
|
||||
controller.stop_acquisition_flag = True
|
||||
controller.threads_pool.createThread.reset_mock()
|
||||
|
||||
assert controller.acquire_bar_controller.framerate != 0
|
||||
assert controller.camera_setting_controller.framerate_widgets[
|
||||
"max_framerate"
|
||||
].get() != str(0)
|
||||
|
||||
assert True
|
||||
|
||||
|
||||
def test_launch_additional_microscope():
|
||||
# This looks awful to test...
|
||||
pass
|
||||
|
||||
|
||||
def test_move_stage(controller):
|
||||
pos_dict = {"x": 1, "y": 2.0, "z": 3.14, "theta": 400, "f": 5.01}
|
||||
controller.model.move_stage = MagicMock()
|
||||
controller.move_stage(pos_dict)
|
||||
controller.model.move_stage.assert_called_with(pos_dict)
|
||||
|
||||
|
||||
def test_stop_stage(controller):
|
||||
controller.model.stop_stage = MagicMock()
|
||||
controller.stop_stage()
|
||||
controller.model.stop_stage.assert_called_with()
|
||||
|
||||
|
||||
def test_update_stage_controller_silent(controller):
|
||||
pos_dict = {"x": 1, "y": 2.0, "z": 3.14, "theta": 400, "f": 5.01}
|
||||
controller.update_stage_controller_silent(pos_dict)
|
||||
for axis, value in pos_dict.items():
|
||||
assert (
|
||||
float(controller.stage_controller.widget_vals[axis].get()) == pos_dict[axis]
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"acquisition_mode, sensor_mode, readout_direction, template_name, "
|
||||
"expected_template_name",
|
||||
[
|
||||
("live", "Normal", "", "Bidirectional", "Default"),
|
||||
("z-stack", "Normal", "", "Bidirectional", "Default"),
|
||||
("customized", "Normal", "", "Bidirectional", "Bidirectional"),
|
||||
("live", "Light-Sheet", "Top-To-Bottom", "Bidirectional", "Default"),
|
||||
("live", "Light-Sheet", "Bidirectional", "Bidirectional", "Bidirectional"),
|
||||
(
|
||||
"customized",
|
||||
"Light-Sheet",
|
||||
"Bidirectional",
|
||||
"Bidirectional",
|
||||
"Bidirectional",
|
||||
),
|
||||
("z-stack", "Light-Sheet", "Bidirectional", "Default", "Bidirectional"),
|
||||
("z-stack", "Light-Sheet", "Top-To-Bottom", "Default", "Default"),
|
||||
],
|
||||
)
|
||||
def test_waveform_template(
|
||||
controller,
|
||||
acquisition_mode,
|
||||
sensor_mode,
|
||||
readout_direction,
|
||||
template_name,
|
||||
expected_template_name,
|
||||
):
|
||||
controller.configuration["experiment"]["MicroscopeState"][
|
||||
"waveform_template"
|
||||
] = template_name
|
||||
controller.configuration["experiment"]["MicroscopeState"][
|
||||
"image_mode"
|
||||
] = acquisition_mode
|
||||
microscope_name = controller.configuration["experiment"]["MicroscopeState"][
|
||||
"microscope_name"
|
||||
]
|
||||
controller.configuration["experiment"]["CameraParameters"][microscope_name][
|
||||
"number_of_pixels"
|
||||
] = 10
|
||||
controller.populate_experiment_setting(in_initialize=True)
|
||||
|
||||
controller.camera_setting_controller.mode_widgets["Readout"].set(readout_direction)
|
||||
controller.camera_setting_controller.mode_widgets["Sensor"].set(sensor_mode)
|
||||
controller.update_experiment_setting()
|
||||
|
||||
assert (
|
||||
controller.configuration["experiment"]["MicroscopeState"]["waveform_template"]
|
||||
== expected_template_name
|
||||
)
|
||||
|
||||
assert True
|
||||
Reference in New Issue
Block a user