feat: init
This commit is contained in:
239
test/model/features/test_volume_search.py
Normal file
239
test/model/features/test_volume_search.py
Normal file
@@ -0,0 +1,239 @@
|
||||
# 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 queue
|
||||
|
||||
import pytest
|
||||
import numpy as np
|
||||
|
||||
from navigate.model.features.volume_search import VolumeSearch
|
||||
|
||||
|
||||
class TestVolumeSearch:
|
||||
@pytest.fixture(
|
||||
autouse=True,
|
||||
params=[[False, False], [True, False], [False, True], [True, True]],
|
||||
)
|
||||
def _prepare_test(self, request, dummy_model_to_test_features):
|
||||
flipx, flipy = request.param
|
||||
self.model = dummy_model_to_test_features
|
||||
self.config = self.model.configuration["experiment"]["MicroscopeState"]
|
||||
self.record_num = 0
|
||||
self.overlap = 0.0 # TODO: Make this consistently work for
|
||||
# overlap > 0.0 (add fudge factor)
|
||||
self.feature_list = [
|
||||
[
|
||||
{
|
||||
"name": VolumeSearch,
|
||||
"args": ("Nanoscale", "N/A", flipx, flipy, self.overlap),
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
self.sinx = 1 if flipx else -1
|
||||
self.siny = 1 if flipy else -1
|
||||
|
||||
self.model.active_microscope_name = self.config["microscope_name"]
|
||||
curr_zoom = self.model.configuration["experiment"]["MicroscopeState"]["zoom"]
|
||||
self.curr_pixel_size = float(
|
||||
self.model.configuration["configuration"]["microscopes"][
|
||||
self.model.active_microscope_name
|
||||
]["zoom"]["pixel_size"][curr_zoom]
|
||||
)
|
||||
self.target_pixel_size = float(
|
||||
self.model.configuration["configuration"]["microscopes"]["Nanoscale"][
|
||||
"zoom"
|
||||
]["pixel_size"]["N/A"]
|
||||
)
|
||||
|
||||
self.N = 128
|
||||
# The target image size in pixels
|
||||
self.mag_ratio = int(self.curr_pixel_size / self.target_pixel_size)
|
||||
self.target_grid_pixels = int(self.N // self.mag_ratio)
|
||||
# The target image size in microns
|
||||
self.target_grid_width = self.N * self.target_pixel_size * (1 - self.overlap)
|
||||
|
||||
self.model.event_queue = queue.Queue(10)
|
||||
|
||||
self.model.configuration["experiment"]["StageParameters"]["z"] = 100
|
||||
self.model.configuration["experiment"]["StageParameters"]["f"] = 100
|
||||
|
||||
self.config["start_position"] = np.random.randint(-200, 0)
|
||||
self.config["end_position"] = self.config["start_position"] + 200
|
||||
self.config["number_z_steps"] = np.random.randint(5, 10)
|
||||
self.config["step_size"] = (
|
||||
self.config["end_position"] - self.config["start_position"]
|
||||
) / self.config["number_z_steps"]
|
||||
self.config["start_focus"] = -10
|
||||
self.config["end_focus"] = 10
|
||||
|
||||
self.focus_step = (
|
||||
self.config["end_focus"] - self.config["start_focus"]
|
||||
) / self.config["number_z_steps"]
|
||||
|
||||
def get_next_record(self, record_prefix, idx):
|
||||
idx += 1
|
||||
while self.model.signal_records[idx][0] != record_prefix:
|
||||
idx += 1
|
||||
if idx >= self.record_num:
|
||||
break
|
||||
return idx
|
||||
|
||||
def verify_volume_search(self):
|
||||
self.record_num = len(self.model.signal_records)
|
||||
|
||||
idx = -1
|
||||
|
||||
z_pos = (
|
||||
self.model.configuration["experiment"]["StageParameters"]["z"]
|
||||
+ self.config["number_z_steps"] // 2 * self.config["step_size"]
|
||||
)
|
||||
f_pos = (
|
||||
self.model.configuration["experiment"]["StageParameters"]["f"]
|
||||
+ self.config["number_z_steps"] // 2 * self.focus_step
|
||||
)
|
||||
|
||||
for j in range(self.config["number_z_steps"]):
|
||||
idx = self.get_next_record("move_stage", idx)
|
||||
if idx >= self.record_num:
|
||||
# volume search ended early
|
||||
break
|
||||
pos_moved = self.model.signal_records[idx][1][0]
|
||||
|
||||
fact = (
|
||||
j
|
||||
if j < (self.config["number_z_steps"] + 1) // 2
|
||||
else (self.config["number_z_steps"] + 1) // 2 - j - 1
|
||||
)
|
||||
|
||||
# z, f
|
||||
assert (
|
||||
pos_moved["z_abs"] - (z_pos + fact * self.config["step_size"])
|
||||
) < 1e-6, (
|
||||
f"should move to z: {z_pos + fact * self.config['step_size']}, "
|
||||
f"but moved to {pos_moved['z_abs']}"
|
||||
)
|
||||
assert (pos_moved["f_abs"] - (f_pos + fact * self.focus_step)) < 1e-6, (
|
||||
f"should move to f: {f_pos + fact * self.focus_step}, "
|
||||
f"but moved to {pos_moved['f_abs']}"
|
||||
)
|
||||
|
||||
for _ in range(100):
|
||||
event, value = self.model.event_queue.get()
|
||||
if event == "multiposition":
|
||||
break
|
||||
|
||||
positions = np.vstack(value) # Columns: X, Y, Z, Theta, F
|
||||
|
||||
# Check the bounding box. TODO: Make exact.
|
||||
min_x = (
|
||||
self.model.configuration["experiment"]["StageParameters"]["x"]
|
||||
- self.sinx * self.lxy
|
||||
)
|
||||
max_x = self.model.configuration["experiment"]["StageParameters"][
|
||||
"x"
|
||||
] + self.sinx * (self.lxy + self.N // 2 * self.curr_pixel_size) * (
|
||||
1 - self.overlap
|
||||
)
|
||||
min_y = (
|
||||
self.model.configuration["experiment"]["StageParameters"]["y"]
|
||||
- self.siny * self.lxy
|
||||
)
|
||||
max_y = self.model.configuration["experiment"]["StageParameters"][
|
||||
"y"
|
||||
] + self.siny * (self.lxy + self.N // 2 * self.curr_pixel_size) * (
|
||||
1 - self.overlap
|
||||
)
|
||||
|
||||
if self.sinx == -1:
|
||||
tmp = max_x
|
||||
max_x = min_x
|
||||
min_x = tmp
|
||||
|
||||
if self.siny == -1:
|
||||
tmp = max_y
|
||||
max_y = min_y
|
||||
min_y = tmp
|
||||
|
||||
min_z = self.model.configuration["experiment"]["StageParameters"]["z"] - self.lz
|
||||
max_z = self.model.configuration["experiment"]["StageParameters"]["z"] + (
|
||||
self.lz + self.N // 2 * self.curr_pixel_size
|
||||
) * (1 - self.overlap)
|
||||
|
||||
assert np.min(positions[:, 0]) >= min_x
|
||||
assert np.max(positions[:, 0]) <= max_x
|
||||
assert np.min(positions[:, 1]) >= min_y
|
||||
assert np.max(positions[:, 1]) <= max_y
|
||||
assert np.min(positions[:, 2]) >= min_z
|
||||
assert np.max(positions[:, 2]) <= max_z
|
||||
|
||||
def test_box_volume_search(self):
|
||||
from navigate.tools.sdf import volume_from_sdf, box
|
||||
|
||||
M = int(self.config["number_z_steps"])
|
||||
microscope_name = self.model.configuration["experiment"]["MicroscopeState"][
|
||||
"microscope_name"
|
||||
]
|
||||
self.model.configuration["experiment"]["CameraParameters"][microscope_name][
|
||||
"x_pixels"
|
||||
] = self.N
|
||||
self.lxy = (
|
||||
np.random.randint(int(0.1 * self.N), int(0.4 * self.N))
|
||||
* self.curr_pixel_size
|
||||
)
|
||||
self.lz = (
|
||||
np.random.randint(int(0.1 * self.N), int(0.4 * self.N))
|
||||
* self.curr_pixel_size
|
||||
)
|
||||
|
||||
vol = volume_from_sdf(
|
||||
lambda p: box(
|
||||
p,
|
||||
(
|
||||
self.lxy,
|
||||
self.lxy,
|
||||
self.lz,
|
||||
),
|
||||
),
|
||||
self.N,
|
||||
pixel_size=self.curr_pixel_size,
|
||||
subsample_z=self.N // M,
|
||||
)
|
||||
vol = (vol <= 0) * 100
|
||||
vol = vol[np.r_[(M // 2) : M, 0 : (M // 2)]]
|
||||
self.model.data_buffer = vol
|
||||
|
||||
def get_offset_variance_maps():
|
||||
return np.zeros((self.N, self.N)), np.ones((self.N, self.N))
|
||||
|
||||
self.model.get_offset_variance_maps = get_offset_variance_maps
|
||||
|
||||
self.model.start(self.feature_list)
|
||||
self.verify_volume_search()
|
||||
Reference in New Issue
Block a user