feat: init
This commit is contained in:
647
test/model/dummy.py
Normal file
647
test/model/dummy.py
Normal file
@@ -0,0 +1,647 @@
|
||||
# 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 pathlib import Path
|
||||
import multiprocessing as mp
|
||||
from multiprocessing import Manager
|
||||
import threading
|
||||
import time
|
||||
|
||||
# Third Party Imports
|
||||
import numpy as np
|
||||
import random
|
||||
|
||||
# Local Imports
|
||||
from navigate.config.config import (
|
||||
load_configs,
|
||||
verify_experiment_config,
|
||||
verify_waveform_constants,
|
||||
verify_configuration,
|
||||
verify_positions_config,
|
||||
)
|
||||
from navigate.model.devices.camera.synthetic import (
|
||||
SyntheticCamera,
|
||||
SyntheticCameraController,
|
||||
)
|
||||
from navigate.model.features.feature_container import (
|
||||
load_features,
|
||||
)
|
||||
from navigate.tools.file_functions import load_yaml_file
|
||||
|
||||
|
||||
class DummyController:
|
||||
"""Dummy Controller"""
|
||||
|
||||
def __init__(self, view):
|
||||
"""Initialize the Dummy controller.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
view : DummyView
|
||||
The view to be controlled by this controller.
|
||||
|
||||
Example
|
||||
-------
|
||||
>>> controller = DummyController(view)
|
||||
"""
|
||||
from navigate.controller.configuration_controller import ConfigurationController
|
||||
from navigate.controller.sub_controllers import MenuController
|
||||
from navigate.controller.sub_controllers.multiposition import (
|
||||
MultiPositionController,
|
||||
)
|
||||
from navigate.controller.sub_controllers.channels_tab import (
|
||||
ChannelsTabController,
|
||||
)
|
||||
|
||||
#: dict: The configuration dictionary.
|
||||
self.configuration = DummyModel().configuration
|
||||
|
||||
#: list: The list of commands.
|
||||
self.commands = []
|
||||
|
||||
#: dict: The custom events
|
||||
self.event_listeners = {}
|
||||
|
||||
self.manager = Manager()
|
||||
|
||||
#: DummyView: The view to be controlled by this controller.
|
||||
self.view = view
|
||||
|
||||
#: ConfigurationController: The configuration controller.
|
||||
self.configuration_controller = ConfigurationController(self.configuration)
|
||||
|
||||
#: MenuController: The menu controller.
|
||||
self.menu_controller = MenuController(view=self.view, parent_controller=self)
|
||||
|
||||
#: ChannelsTabController: The channels tab controller.
|
||||
self.channels_tab_controller = ChannelsTabController(
|
||||
self.view.settings.channels_tab, self
|
||||
)
|
||||
|
||||
#: MultiPositionController: The multiposition tab controller.
|
||||
self.multiposition_tab_controller = MultiPositionController(
|
||||
self.view.settings.multiposition_tab.multipoint_list, self
|
||||
)
|
||||
|
||||
#: dict: The stage positions.
|
||||
self.stage_pos = {}
|
||||
|
||||
#: dict: The stage offset positions.
|
||||
self.off_stage_pos = {}
|
||||
base_directory = Path.joinpath(Path(__file__).resolve().parent.parent)
|
||||
configuration_directory = Path.joinpath(base_directory, "config")
|
||||
self.waveform_constants_path = Path.joinpath(
|
||||
configuration_directory, "waveform_constants.yml"
|
||||
)
|
||||
|
||||
#: bool: Flag to indicate if the resize is ready.
|
||||
self.resize_ready_flag = True
|
||||
|
||||
def execute(self, str, *args, sec=None):
|
||||
"""Execute a command.
|
||||
|
||||
Appends commands sent via execute,
|
||||
first element is oldest command/first to pop off
|
||||
|
||||
Parameters
|
||||
----------
|
||||
str : str
|
||||
The command to be executed.
|
||||
sec : float
|
||||
The time to wait before executing the command.
|
||||
|
||||
Example
|
||||
-------
|
||||
>>> controller.execute('move_stage', 1)
|
||||
"""
|
||||
|
||||
self.commands.append(str)
|
||||
if str in ["move_stage_and_acquire_image", "move_stage_and_update_info"]:
|
||||
self.commands.append(*args)
|
||||
if sec is not None:
|
||||
self.commands.append(sec)
|
||||
|
||||
if str == "get_stage_position":
|
||||
self.stage_pos["x"] = int(random.random())
|
||||
self.stage_pos["y"] = int(random.random())
|
||||
return self.stage_pos
|
||||
|
||||
def pop(self):
|
||||
"""Pop the oldest command.
|
||||
|
||||
Use this method in testing code to grab the next command.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
The oldest command.
|
||||
|
||||
Example
|
||||
-------
|
||||
>>> controller.pop()
|
||||
"""
|
||||
|
||||
if len(self.commands) > 0:
|
||||
return self.commands.pop(0)
|
||||
else:
|
||||
return "Empty command list"
|
||||
|
||||
def clear(self):
|
||||
"""Clear command list"""
|
||||
self.commands = []
|
||||
|
||||
|
||||
class DummyModel:
|
||||
"""Dummy Model - This class is used to test the controller and view."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the Dummy model."""
|
||||
# Set up the model, experiment, waveform dictionaries
|
||||
base_directory = Path(__file__).resolve().parent.parent.parent
|
||||
configuration_directory = Path.joinpath(
|
||||
base_directory, "src", "navigate", "config"
|
||||
)
|
||||
|
||||
config = Path.joinpath(configuration_directory, "configuration.yaml")
|
||||
experiment = Path.joinpath(configuration_directory, "experiment.yml")
|
||||
waveform_constants = Path.joinpath(
|
||||
configuration_directory, "waveform_constants.yml"
|
||||
)
|
||||
gui_configuration_path = Path.joinpath(
|
||||
configuration_directory, "gui_configuration.yml"
|
||||
)
|
||||
multi_positions_path = Path.joinpath(
|
||||
configuration_directory, "multi_positions.yml"
|
||||
)
|
||||
|
||||
#: Manager: The manager.
|
||||
self.manager = Manager()
|
||||
#: dict: The configuration dictionary.
|
||||
self.configuration = load_configs(
|
||||
self.manager,
|
||||
configuration=config,
|
||||
experiment=experiment,
|
||||
waveform_constants=waveform_constants,
|
||||
gui=gui_configuration_path,
|
||||
)
|
||||
|
||||
verify_configuration(self.manager, self.configuration)
|
||||
verify_experiment_config(self.manager, self.configuration)
|
||||
verify_waveform_constants(self.manager, self.configuration)
|
||||
|
||||
positions = load_yaml_file(multi_positions_path)
|
||||
positions = verify_positions_config(positions)
|
||||
self.configuration["multi_positions"] = positions
|
||||
|
||||
#: DummyDevice: The device.
|
||||
self.device = DummyDevice()
|
||||
#: Pipe: The pipe for sending signals.
|
||||
self.signal_pipe, self.data_pipe = None, None
|
||||
#: DummyMicroscope: The microscope.
|
||||
self.active_microscope = DummyMicroscope(
|
||||
"Mesoscale", self.configuration, devices_dict={}, is_synthetic=True
|
||||
)
|
||||
#: Object: The signal container.
|
||||
self.signal_container = None
|
||||
#: Object: The data container.
|
||||
self.data_container = None
|
||||
#: Thread: The signal thread.
|
||||
self.signal_thread = None
|
||||
#: Thread: The data thread.
|
||||
self.data_thread = None
|
||||
|
||||
#: bool: The flag for stopping the model.
|
||||
self.stop_flag = False
|
||||
#: int: The frame id.
|
||||
self.frame_id = 0 # signal_num
|
||||
#: list: The list of data.
|
||||
self.data = []
|
||||
#: list: The list of signal records.
|
||||
self.signal_records = []
|
||||
#: list: The list of data records.
|
||||
self.data_records = []
|
||||
#: int: The image width.
|
||||
self.img_width = int(
|
||||
self.configuration["experiment"]["CameraParameters"]["x_pixels"]
|
||||
)
|
||||
#: int: The image height.
|
||||
self.img_height = int(
|
||||
self.configuration["experiment"]["CameraParameters"]["y_pixels"]
|
||||
)
|
||||
#: int: The number of frames in the data buffer.
|
||||
self.number_of_frames = 10
|
||||
#: ndarray: The data buffer.
|
||||
self.data_buffer = np.zeros(
|
||||
(self.number_of_frames, self.img_width, self.img_height)
|
||||
)
|
||||
#: ndarray: The data buffer positions.
|
||||
self.data_buffer_positions = np.zeros(
|
||||
shape=(self.number_of_frames, 5), dtype=float
|
||||
) # z-index, x, y, z, theta, f
|
||||
#: dict: The camera dictionary.
|
||||
self.camera = {}
|
||||
#: str: The active microscope name.
|
||||
self.active_microscope_name = self.configuration["experiment"][
|
||||
"MicroscopeState"
|
||||
]["microscope_name"]
|
||||
for k in self.configuration["configuration"]["microscopes"].keys():
|
||||
self.camera[k] = SyntheticCamera(
|
||||
self.active_microscope_name,
|
||||
SyntheticCameraController(),
|
||||
self.configuration,
|
||||
)
|
||||
self.camera[k].initialize_image_series(
|
||||
self.data_buffer, self.number_of_frames
|
||||
)
|
||||
|
||||
def signal_func(self):
|
||||
"""Perform signal-related functionality.
|
||||
|
||||
This method is responsible for signal processing operations. It resets the
|
||||
signal container and continues processing signals until the end flag is set.
|
||||
During each iteration, it runs the signal container and communicates with
|
||||
a separate process using a signal pipe. The `frame_id` is incremented after
|
||||
each signal processing step.
|
||||
|
||||
Note
|
||||
----
|
||||
- The function utilizes a signal container and a signal pipe for communication.
|
||||
- It terminates when the `end_flag` is set and sends a "shutdown" signal.
|
||||
"""
|
||||
|
||||
self.signal_container.reset()
|
||||
while not self.signal_container.end_flag:
|
||||
if self.signal_container:
|
||||
self.signal_container.run()
|
||||
|
||||
self.signal_pipe.send("signal")
|
||||
self.signal_pipe.recv()
|
||||
|
||||
if self.signal_container:
|
||||
self.signal_container.run(wait_response=True)
|
||||
|
||||
self.frame_id += 1 # signal_num
|
||||
|
||||
self.signal_pipe.send("shutdown")
|
||||
|
||||
self.stop_flag = True
|
||||
|
||||
def data_func(self):
|
||||
"""The function responsible for sending and processing data.
|
||||
|
||||
This method continuously sends data requests using a data pipe and receives
|
||||
corresponding frame IDs. It appends the received frame IDs to the data storage
|
||||
and runs data processing operations if a data container is available.
|
||||
|
||||
Notes
|
||||
-----
|
||||
- The function operates in a loop until the `stop_flag` is set.
|
||||
- It communicates with a separate process using a data pipe for data retrieval.
|
||||
- Received frame IDs are appended to the data storage and processed if
|
||||
applicable.
|
||||
- The method terminates by sending a "shutdown" signal.
|
||||
|
||||
"""
|
||||
while not self.stop_flag:
|
||||
self.data_pipe.send("getData")
|
||||
frame_ids = self.data_pipe.recv()
|
||||
print("receive: ", frame_ids)
|
||||
if not frame_ids:
|
||||
continue
|
||||
|
||||
self.data.append(frame_ids)
|
||||
|
||||
if self.data_container:
|
||||
self.data_container.run(frame_ids)
|
||||
self.data_pipe.send("shutdown")
|
||||
|
||||
def start(self, feature_list):
|
||||
"""Start the model.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
feature_list : list
|
||||
The list of features to be used.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
True if the model is started successfully, False otherwise.
|
||||
|
||||
Example
|
||||
-------
|
||||
>>> model.start(['signal', 'data'])
|
||||
"""
|
||||
|
||||
if feature_list is None:
|
||||
return False
|
||||
self.data = []
|
||||
self.signal_records = []
|
||||
self.data_records = []
|
||||
self.stop_flag = False
|
||||
self.frame_id = 0 # signal_num
|
||||
|
||||
self.signal_pipe, self.data_pipe = self.device.setup()
|
||||
|
||||
self.signal_container, self.data_container = load_features(self, feature_list)
|
||||
self.signal_thread = threading.Thread(target=self.signal_func, name="signal")
|
||||
self.data_thread = threading.Thread(target=self.data_func, name="data")
|
||||
self.signal_thread.start()
|
||||
self.data_thread.start()
|
||||
|
||||
self.signal_thread.join()
|
||||
self.stop_flag = True
|
||||
self.data_thread.join()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class DummyDevice:
|
||||
"""Dummy Device - class is used to test the controller and view."""
|
||||
|
||||
def __init__(self, timecost=0.2):
|
||||
"""Initialize the Dummy device.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
timecost : float
|
||||
The time cost for generating a message.
|
||||
"""
|
||||
|
||||
#: int: The message count.
|
||||
self.msg_count = mp.Value("i", 0)
|
||||
#: int: The sendout message count.
|
||||
self.sendout_msg_count = 0
|
||||
#: Pipe: The pipe for sending signals.
|
||||
self.out_port = None
|
||||
#: Pipe: The pipe for receiving signals.
|
||||
self.in_port = None
|
||||
#: float: The time cost for generating a message.
|
||||
self.timecost = timecost
|
||||
#: bool: The flag for stopping the device.
|
||||
self.stop_flag = False
|
||||
|
||||
def setup(self):
|
||||
"""Set up the pipes.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Pipe
|
||||
The pipe for sending signals.
|
||||
Pipe
|
||||
The pipe for receiving signals.
|
||||
|
||||
Example
|
||||
-------
|
||||
>>> device.setup()
|
||||
"""
|
||||
|
||||
signalPort, self.in_port = mp.Pipe()
|
||||
dataPort, self.out_port = mp.Pipe()
|
||||
in_process = mp.Process(target=self.listen)
|
||||
out_process = mp.Process(target=self.sendout)
|
||||
in_process.start()
|
||||
out_process.start()
|
||||
|
||||
self.sendout_msg_count = 0
|
||||
self.msg_count.value = 0
|
||||
self.stop_flag = False
|
||||
|
||||
return signalPort, dataPort
|
||||
|
||||
def generate_message(self):
|
||||
"""Generate a message.
|
||||
|
||||
Example
|
||||
-------
|
||||
>>> device.generate_message()
|
||||
"""
|
||||
|
||||
time.sleep(self.timecost)
|
||||
self.msg_count.value += 1
|
||||
|
||||
def clear(self):
|
||||
"""Clear the pipes.
|
||||
|
||||
Example
|
||||
-------
|
||||
>>> device.clear()
|
||||
"""
|
||||
self.msg_count.value = 0
|
||||
|
||||
def listen(self):
|
||||
"""Listen to the pipe.
|
||||
|
||||
Example
|
||||
-------
|
||||
>>> device.listen()
|
||||
"""
|
||||
while not self.stop_flag:
|
||||
signal = self.in_port.recv()
|
||||
if signal == "shutdown":
|
||||
self.stop_flag = True
|
||||
self.in_port.close()
|
||||
break
|
||||
self.generate_message()
|
||||
self.in_port.send("done")
|
||||
|
||||
def sendout(self, timeout=100):
|
||||
"""Send out the message.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
timeout : int
|
||||
The timeout for sending out the message.
|
||||
|
||||
Example
|
||||
-------
|
||||
>>> device.sendout()
|
||||
"""
|
||||
while not self.stop_flag:
|
||||
msg = self.out_port.recv()
|
||||
if msg == "shutdown":
|
||||
self.out_port.close()
|
||||
break
|
||||
c = 0
|
||||
while self.msg_count.value == self.sendout_msg_count and c < timeout:
|
||||
time.sleep(0.01)
|
||||
c += 1
|
||||
self.out_port.send(
|
||||
list(range(self.sendout_msg_count, self.msg_count.value))
|
||||
)
|
||||
self.sendout_msg_count = self.msg_count.value
|
||||
|
||||
|
||||
class DummyMicroscope:
|
||||
"""Dummy Microscope - Class is used to test the controller and view."""
|
||||
|
||||
def __init__(self, name, configuration, devices_dict, is_synthetic=False):
|
||||
"""Initialize the Dummy microscope.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name : str
|
||||
The microscope name.
|
||||
configuration : dict
|
||||
The configuration dictionary.
|
||||
devices_dict : dict
|
||||
The dictionary of devices.
|
||||
is_synthetic : bool
|
||||
The flag for using a synthetic microscope.
|
||||
"""
|
||||
#: str: The microscope name.
|
||||
self.microscope_name = name
|
||||
#: dict: The configuration dictionary.
|
||||
self.configuration = configuration
|
||||
#: np.ndarray: The data buffer.
|
||||
self.data_buffer = None
|
||||
#: dict: The stage dictionary.
|
||||
self.stages = {}
|
||||
#: dict: The lasers dictionary.
|
||||
self.lasers = {}
|
||||
#: dict: The galvo dictionary.
|
||||
self.galvo = {}
|
||||
#: dict: The DAQ dictionary.
|
||||
self.daq = devices_dict.get("daq", None)
|
||||
#: int: The current channel.
|
||||
self.current_channel = 0
|
||||
self.camera = SyntheticCamera(
|
||||
self.configuration["experiment"]["MicroscopeState"]["microscope_name"],
|
||||
SyntheticCameraController(),
|
||||
self.configuration,
|
||||
)
|
||||
|
||||
def calculate_exposure_sweep_times(self):
|
||||
"""Get the exposure and sweep times for all channels.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
The dictionary of exposure times.
|
||||
dict
|
||||
The dictionary of sweep times.
|
||||
"""
|
||||
exposure_times = {}
|
||||
sweep_times = {}
|
||||
microscope_state = self.configuration["experiment"]["MicroscopeState"]
|
||||
waveform_constants = self.configuration["waveform_constants"]
|
||||
camera_delay = (
|
||||
self.configuration["configuration"]["microscopes"][self.microscope_name][
|
||||
"camera"
|
||||
]["delay"]
|
||||
/ 1000
|
||||
)
|
||||
camera_settle_duration = (
|
||||
self.configuration["configuration"]["microscopes"][self.microscope_name][
|
||||
"camera"
|
||||
].get("settle_duration", 0)
|
||||
/ 1000
|
||||
)
|
||||
remote_focus_ramp_falling = (
|
||||
float(waveform_constants["other_constants"]["remote_focus_ramp_falling"])
|
||||
/ 1000
|
||||
)
|
||||
|
||||
duty_cycle_wait_duration = (
|
||||
float(waveform_constants["other_constants"]["remote_focus_settle_duration"])
|
||||
/ 1000
|
||||
)
|
||||
ps = float(waveform_constants["other_constants"].get("percent_smoothing", 0.0))
|
||||
|
||||
readout_time = 0
|
||||
readout_mode = self.configuration["experiment"]["CameraParameters"][
|
||||
"sensor_mode"
|
||||
]
|
||||
if readout_mode == "Normal":
|
||||
readout_time = self.camera.calculate_readout_time()
|
||||
elif self.configuration["experiment"]["CameraParameters"][
|
||||
"readout_direction"
|
||||
] in ["Bidirectional", "Rev. Bidirectional"]:
|
||||
remote_focus_ramp_falling = 0
|
||||
# set readout out time
|
||||
self.configuration["experiment"]["CameraParameters"]["readout_time"] = (
|
||||
readout_time * 1000
|
||||
)
|
||||
for channel_key in microscope_state["channels"].keys():
|
||||
channel = microscope_state["channels"][channel_key]
|
||||
if channel["is_selected"] is True:
|
||||
exposure_time = channel["camera_exposure_time"] / 1000
|
||||
|
||||
if readout_mode == "Light-Sheet":
|
||||
(
|
||||
_,
|
||||
_,
|
||||
updated_exposure_time,
|
||||
) = self.camera.calculate_light_sheet_exposure_time(
|
||||
exposure_time,
|
||||
int(
|
||||
self.configuration["experiment"]["CameraParameters"][
|
||||
"number_of_pixels"
|
||||
]
|
||||
),
|
||||
)
|
||||
if updated_exposure_time != exposure_time:
|
||||
print(
|
||||
f"*** Notice: The actual exposure time of the camera for "
|
||||
f"{channel_key} is {round(updated_exposure_time*1000, 1)}"
|
||||
f"ms, not {exposure_time*1000}ms!"
|
||||
)
|
||||
exposure_time = round(updated_exposure_time, 4)
|
||||
# update the experiment file
|
||||
channel["camera_exposure_time"] = round(
|
||||
updated_exposure_time * 1000, 1
|
||||
)
|
||||
self.output_event_queue.put(
|
||||
(
|
||||
"exposure_time",
|
||||
(channel_key, channel["camera_exposure_time"]),
|
||||
)
|
||||
)
|
||||
|
||||
sweep_time = (
|
||||
exposure_time
|
||||
+ readout_time
|
||||
+ camera_delay
|
||||
+ max(
|
||||
remote_focus_ramp_falling + duty_cycle_wait_duration,
|
||||
camera_settle_duration,
|
||||
camera_delay,
|
||||
)
|
||||
- camera_delay
|
||||
)
|
||||
# TODO: should we keep the percent_smoothing?
|
||||
if ps > 0:
|
||||
sweep_time = (1 + ps / 100) * sweep_time
|
||||
|
||||
exposure_times[channel_key] = exposure_time + readout_time
|
||||
sweep_times[channel_key] = sweep_time
|
||||
|
||||
return exposure_times, sweep_times
|
||||
Reference in New Issue
Block a user