feat: init
This commit is contained in:
379
test/model/devices/camera/test_hamamatsu.py
Normal file
379
test/model/devices/camera/test_hamamatsu.py
Normal file
@@ -0,0 +1,379 @@
|
||||
# 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.
|
||||
#
|
||||
|
||||
# Third Party Imports
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.hardware
|
||||
@pytest.fixture(scope="module")
|
||||
def prepare_cameras(dummy_model):
|
||||
from navigate.model.devices.APIs.hamamatsu.HamamatsuAPI import DCAM, camReg
|
||||
from navigate.model.devices.camera.hamamatsu import HamamatsuOrca
|
||||
|
||||
def start_camera(idx=0):
|
||||
# open camera
|
||||
for i in range(10):
|
||||
assert camReg.numCameras == idx
|
||||
try:
|
||||
camera = DCAM(idx)
|
||||
if camera.get_camera_handler() != 0:
|
||||
break
|
||||
camera.dev_close()
|
||||
except Exception:
|
||||
continue
|
||||
camera = None
|
||||
return camera
|
||||
|
||||
model = dummy_model
|
||||
|
||||
temp = {}
|
||||
for microscope_name in model.configuration["configuration"]["microscopes"].keys():
|
||||
serial_number = model.configuration["configuration"]["microscopes"][
|
||||
microscope_name
|
||||
]["camera"]["hardware"]["serial_number"]
|
||||
temp[str(serial_number)] = microscope_name
|
||||
|
||||
camera_connections = {}
|
||||
|
||||
camera = start_camera()
|
||||
for i in range(camReg.maxCameras):
|
||||
if i > 0:
|
||||
camera = start_camera(i)
|
||||
if str(camera._serial_number) in temp:
|
||||
microscope_name = temp[str(camera._serial_number)]
|
||||
camera = HamamatsuOrca(microscope_name, camera, model.configuration)
|
||||
camera_connections[microscope_name] = camera
|
||||
|
||||
yield camera_connections
|
||||
|
||||
# close all the cameras
|
||||
for k in camera_connections:
|
||||
camera_connections[k].camera_controller.dev_close()
|
||||
|
||||
|
||||
@pytest.mark.hardware
|
||||
class TestHamamatsuOrca:
|
||||
"""Unit Test for HamamamatsuOrca Class"""
|
||||
|
||||
model = None
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _prepare_test(self, dummy_model, prepare_cameras):
|
||||
self.num_of_tests = 10
|
||||
self.model = dummy_model
|
||||
self.cameras = prepare_cameras
|
||||
|
||||
self.microscope_name = self.model.configuration["experiment"][
|
||||
"MicroscopeState"
|
||||
]["microscope_name"]
|
||||
self.camera = self.cameras[self.microscope_name]
|
||||
|
||||
def is_in_range(self, value, target, precision=100):
|
||||
target_min = target - target / precision
|
||||
target_max = target + target / precision
|
||||
return value > target_min and value < target_max
|
||||
|
||||
def test_hamamatsu_camera_attributes(self):
|
||||
from navigate.model.devices.camera.hamamatsu import HamamatsuOrca
|
||||
|
||||
attributes = dir(HamamatsuOrca)
|
||||
desired_attributes = [
|
||||
"serial_number",
|
||||
"report_settings",
|
||||
"close_camera",
|
||||
"set_sensor_mode",
|
||||
"set_readout_direction",
|
||||
"calculate_light_sheet_exposure_time",
|
||||
"calculate_readout_time",
|
||||
"set_exposure_time",
|
||||
"set_line_interval",
|
||||
"set_binning",
|
||||
"set_ROI",
|
||||
"initialize_image_series",
|
||||
"close_image_series",
|
||||
"get_new_frame",
|
||||
]
|
||||
|
||||
for da in desired_attributes:
|
||||
assert da in attributes
|
||||
|
||||
def test_init_camera(self):
|
||||
for microscope_name in self.model.configuration["configuration"][
|
||||
"microscopes"
|
||||
].keys():
|
||||
|
||||
camera = self.cameras[microscope_name]
|
||||
|
||||
assert camera is not None, f"Should start the camera {microscope_name}"
|
||||
|
||||
camera_controller = camera.camera_controller
|
||||
camera_configs = self.model.configuration["configuration"]["microscopes"][
|
||||
microscope_name
|
||||
]["camera"]
|
||||
|
||||
# serial number
|
||||
assert str(camera_controller._serial_number) == str(
|
||||
camera_configs["hardware"]["serial_number"]
|
||||
), f"the camera serial number isn't right for {microscope_name}!"
|
||||
assert str(camera.serial_number) == str(
|
||||
camera_configs["hardware"]["serial_number"]
|
||||
), f"the camera serial number isn't right for {microscope_name}!"
|
||||
|
||||
# verify camera is initialized with the attributes from configuration.yaml
|
||||
parameters = [
|
||||
"defect_correct_mode",
|
||||
"readout_speed",
|
||||
"trigger_active",
|
||||
"trigger_mode",
|
||||
"trigger_polarity",
|
||||
"trigger_source",
|
||||
]
|
||||
for parameter in parameters:
|
||||
value = camera_controller.get_property_value(parameter)
|
||||
assert value == camera_configs[parameter]
|
||||
|
||||
# sensor mode
|
||||
sensor_mode = camera_controller.get_property_value("sensor_mode")
|
||||
expected_value = 1 if camera_configs["sensor_mode"] == "Normal" else 12
|
||||
assert sensor_mode == expected_value, "Sensor mode isn't right!"
|
||||
|
||||
# exposure time
|
||||
exposure_time = camera_controller.get_property_value("exposure_time")
|
||||
assert self.is_in_range(
|
||||
exposure_time, camera_configs["exposure_time"] / 1000, 10
|
||||
), "Exposure time isn't right!"
|
||||
|
||||
# binning
|
||||
binning = camera_controller.get_property_value("binning")
|
||||
assert int(binning) == int(
|
||||
camera_configs["binning"][0]
|
||||
), "Binning isn't right!"
|
||||
|
||||
# image width and height
|
||||
width = camera_controller.get_property_value("image_width")
|
||||
assert width == camera_configs["x_pixels"], "image width isn't right"
|
||||
height = camera_controller.get_property_value("image_height")
|
||||
assert height == camera_configs["y_pixels"], "image height isn't right"
|
||||
|
||||
def test_set_sensor_mode(self):
|
||||
modes = {"Normal": 1, "Light-Sheet": 12, "RandomMode": None}
|
||||
for mode in modes:
|
||||
pre_value = self.camera.camera_controller.get_property_value("sensor_mode")
|
||||
self.camera.set_sensor_mode(mode)
|
||||
value = self.camera.camera_controller.get_property_value("sensor_mode")
|
||||
if modes[mode] is not None:
|
||||
assert value == modes[mode], f"sensor mode {mode} isn't right!"
|
||||
else:
|
||||
assert value == pre_value, "sensor mode shouldn't be set!"
|
||||
|
||||
def test_set_readout_direction(self):
|
||||
readout_directions = {"Top-to-Bottom": 1, "Bottom-to-Top": 2}
|
||||
for direction in readout_directions:
|
||||
self.camera.set_readout_direction(direction)
|
||||
value = self.camera.camera_controller.get_property_value(
|
||||
"readout_direction"
|
||||
)
|
||||
assert (
|
||||
value == readout_directions[direction]
|
||||
), f"readout direction setting isn't right for {direction}"
|
||||
|
||||
# def test_calculate_readout_time(self):
|
||||
# pass
|
||||
|
||||
def test_set_exposure_time(self):
|
||||
import random
|
||||
|
||||
modes_dict = {
|
||||
"Normal": 10000,
|
||||
"Light-Sheet": 20,
|
||||
}
|
||||
for mode in modes_dict:
|
||||
self.camera.set_sensor_mode(mode)
|
||||
for i in range(self.num_of_tests):
|
||||
exposure_time = random.randint(1, modes_dict[mode])
|
||||
self.camera.set_exposure_time(exposure_time / 1000)
|
||||
value = self.camera.camera_controller.get_property_value(
|
||||
"exposure_time"
|
||||
)
|
||||
assert self.is_in_range(
|
||||
value, exposure_time / 1000, 10
|
||||
), f"exposure time({exposure_time}) isn't right!"
|
||||
self.camera.set_sensor_mode("Normal")
|
||||
|
||||
def test_set_line_interval(self):
|
||||
import random
|
||||
|
||||
self.camera.set_sensor_mode("Light-Sheet")
|
||||
for i in range(self.num_of_tests):
|
||||
line_interval = random.random() / 10.0
|
||||
r = self.camera.set_line_interval(line_interval)
|
||||
if r is True:
|
||||
value = self.camera.camera_controller.get_property_value(
|
||||
"internal_line_interval"
|
||||
)
|
||||
assert self.is_in_range(
|
||||
value, line_interval
|
||||
), f"line interval {line_interval} isn't right! {value}"
|
||||
self.camera.set_sensor_mode("Normal")
|
||||
|
||||
def test_set_binning(self):
|
||||
import random
|
||||
|
||||
binning_dict = {
|
||||
"1x1": 1,
|
||||
"2x2": 2,
|
||||
"4x4": 4,
|
||||
# '8x8': 8,
|
||||
# '16x16': 16,
|
||||
# '1x2': 102,
|
||||
# '2x4': 204
|
||||
}
|
||||
for binning_string in binning_dict:
|
||||
self.camera.set_binning(binning_string)
|
||||
value = self.camera.camera_controller.get_property_value("binning")
|
||||
assert (
|
||||
int(value) == binning_dict[binning_string]
|
||||
), f"binning {binning_string} isn't right!"
|
||||
|
||||
for i in range(self.num_of_tests):
|
||||
x = random.randint(1, 20)
|
||||
y = random.randint(1, 20)
|
||||
binning_string = f"{x}x{y}"
|
||||
assert self.camera.set_binning(binning_string) == (
|
||||
binning_string in binning_dict
|
||||
)
|
||||
|
||||
def test_set_ROI(self):
|
||||
import random
|
||||
|
||||
self.camera.set_binning("1x1")
|
||||
width = self.camera.camera_parameters["x_pixels"]
|
||||
height = self.camera.camera_parameters["x_pixels"]
|
||||
w = self.camera.camera_controller.get_property_value("image_width")
|
||||
h = self.camera.camera_controller.get_property_value("image_height")
|
||||
assert width == w, f"maximum width should be the same {width} - {w}"
|
||||
assert height == h, f"maximum height should be the same {height} -{h}"
|
||||
|
||||
for i in range(self.num_of_tests):
|
||||
pre_x, pre_y = self.camera.x_pixels, self.camera.y_pixels
|
||||
x = random.randint(1, self.camera.camera_parameters["x_pixels"])
|
||||
y = random.randint(1, self.camera.camera_parameters["y_pixels"])
|
||||
r = self.camera.set_ROI(y, x)
|
||||
if x % 2 == 1 or y % 2 == 1:
|
||||
assert r is False
|
||||
assert self.camera.x_pixels == pre_x, "width shouldn't be chaged!"
|
||||
assert self.camera.y_pixels == pre_y, "height shouldn't be changed!"
|
||||
else:
|
||||
top = (height - y) / 2
|
||||
bottom = top + y - 1
|
||||
if top % 2 == 1 or bottom % 2 == 0:
|
||||
assert r is False
|
||||
else:
|
||||
assert r is True, (
|
||||
f"try to set{x}x{y}, but get "
|
||||
f"{self.camera.x_pixels}x{self.camera.y_pixels}"
|
||||
)
|
||||
assert (
|
||||
self.camera.x_pixels == x
|
||||
), f"trying to set {x}x{y}. width should be changed to {x}"
|
||||
assert self.camera.y_pixels == y, f"height should be chagned to {y}"
|
||||
|
||||
self.camera.set_ROI(512, 512)
|
||||
assert self.camera.x_pixels == 512
|
||||
assert self.camera.y_pixels == 512
|
||||
|
||||
self.camera.set_ROI(
|
||||
self.camera.camera_parameters["x_pixels"],
|
||||
self.camera.camera_parameters["y_pixels"],
|
||||
)
|
||||
assert self.camera.x_pixels == self.camera.camera_parameters["x_pixels"]
|
||||
assert self.camera.y_pixels == self.camera.camera_parameters["y_pixels"]
|
||||
|
||||
self.camera.set_ROI(
|
||||
self.camera.camera_parameters["x_pixels"] + 100,
|
||||
self.camera.camera_parameters["y_pixels"] + 100,
|
||||
)
|
||||
assert self.camera.x_pixels == self.camera.camera_parameters["x_pixels"]
|
||||
assert self.camera.y_pixels == self.camera.camera_parameters["y_pixels"]
|
||||
|
||||
def test_acquire_image(self):
|
||||
import random
|
||||
import time
|
||||
from navigate.model.concurrency.concurrency_tools import SharedNDArray
|
||||
|
||||
# set software trigger
|
||||
self.camera.camera_controller.set_property_value("trigger_source", 3)
|
||||
|
||||
assert self.camera.is_acquiring is False
|
||||
|
||||
number_of_frames = 100
|
||||
data_buffer = [
|
||||
SharedNDArray(shape=(2048, 2048), dtype="uint16")
|
||||
for i in range(number_of_frames)
|
||||
]
|
||||
|
||||
# initialize without release/close the camera
|
||||
self.camera.initialize_image_series(data_buffer, number_of_frames)
|
||||
assert self.camera.is_acquiring is True
|
||||
|
||||
self.camera.initialize_image_series(data_buffer, number_of_frames)
|
||||
assert self.camera.is_acquiring is True
|
||||
|
||||
exposure_time = self.camera.camera_controller.get_property_value(
|
||||
"exposure_time"
|
||||
)
|
||||
readout_time = self.camera.camera_controller.get_property_value("readout_time")
|
||||
|
||||
for i in range(self.num_of_tests):
|
||||
triggers = random.randint(1, 100)
|
||||
for j in range(triggers):
|
||||
self.camera.camera_controller.fire_software_trigger()
|
||||
time.sleep(exposure_time + readout_time)
|
||||
|
||||
time.sleep(0.01)
|
||||
frames = self.camera.get_new_frame()
|
||||
assert len(frames) == triggers
|
||||
|
||||
self.camera.close_image_series()
|
||||
assert self.camera.is_acquiring is False
|
||||
|
||||
for i in range(self.num_of_tests):
|
||||
self.camera.initialize_image_series(data_buffer, number_of_frames)
|
||||
assert self.camera.is_acquiring is True
|
||||
self.camera.close_image_series()
|
||||
assert self.camera.is_acquiring is False
|
||||
|
||||
# close a closed camera
|
||||
self.camera.close_image_series()
|
||||
self.camera.close_image_series()
|
||||
assert self.camera.is_acquiring is False
|
||||
Reference in New Issue
Block a user