Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ requires-python = ">=3.11"
license = {file = "LICENSE.txt"}
dependencies = [
"sci-agent@git+https://github.com/mdw771/sci-agent",
"opencv-contrib-python",
"matplotlib",
"numpy",
"scikit-image",
Expand Down
15 changes: 13 additions & 2 deletions src/eaa/task_manager/tuning/analytical_focusing.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def __init__(
line_scan_tool_y_coordinate_args: Tuple[str, ...] = ("y_center",),
image_acquisition_tool_x_coordinate_args: Tuple[str, ...] = ("x_center",),
image_acquisition_tool_y_coordinate_args: Tuple[str, ...] = ("y_center",),
use_feature_tracking_subtask: bool = True,
*args, **kwargs
):
"""Analytical scanning microscope focusing task manager driven
Expand Down Expand Up @@ -106,6 +107,9 @@ def __init__(
See `line_scan_tool_x_coordinate_args`.
image_acquisition_tool_y_coordinate_args: Tuple[str, ...]
See `line_scan_tool_y_coordinate_args`.
use_feature_tracking_subtask: bool
If True, feature tracking will be run if the line scan feature has drifted
out of the FOV.
"""
if acquisition_tool is None:
raise ValueError("`acquisition_tool` must be provided.")
Expand All @@ -127,6 +131,7 @@ def __init__(
self.last_acquisition_count_registered = 0
self.last_acquisition_count_stitched = 0

self.use_feature_tracking_subtask: bool = use_feature_tracking_subtask
self.feature_tracking_task_manager: Optional[AnalyticalFeatureTrackingTaskManager] = None

self.line_scan_tool_x_coordinate_args = line_scan_tool_x_coordinate_args
Expand Down Expand Up @@ -168,7 +173,7 @@ def create_image_registration_tool(self, acquisition_tool: AcquireImage):
reference_image=None,
reference_pixel_size=1.0,
image_coordinates_origin="top_left",
registration_method="sift",
registration_method="phase_correlation",
)
return image_registration_tool

Expand Down Expand Up @@ -292,6 +297,8 @@ def initialize_kwargs_buffers(
):
self.line_scan_kwargs = copy.deepcopy(initial_line_scan_kwargs)
self.image_acquisition_kwargs = copy.deepcopy(initial_2d_scan_kwargs)

self.line_scan_kwargs["view_scan_line_in_image"] = True

def run_line_scan(self) -> float:
"""Run a line scan and return the FWHM of the Gaussian fit.
Expand All @@ -301,6 +308,7 @@ def run_line_scan(self) -> float:
float
The FWHM of the Gaussian fit.
"""
self.record_system_message(f"Acquiring line scan with {self.line_scan_kwargs}.")
res = self.acquisition_tool.acquire_line_scan(**self.line_scan_kwargs)
try:
res = json.loads(res)
Expand All @@ -327,6 +335,7 @@ def update_optimization_model(self, fwhm: float):
self.optimization_tool.update(x, -np.array([[fwhm]]))

def run_2d_scan(self):
self.record_system_message(f"Acquiring 2D scan with {self.image_acquisition_kwargs}.")
image_path = self.acquisition_tool.acquire_image(**self.image_acquisition_kwargs)
content = f"Acquired 2D scan with kwargs: {self.image_acquisition_kwargs}"
if isinstance(image_path, str):
Expand Down Expand Up @@ -457,16 +466,18 @@ def run_tuning_iteration(self, x: np.ndarray):
f"but got {len(x)} and {len(self.parameter_names)}."
)
x = np.array(x)
self.record_system_message(f"Setting parameters to {x}.")
self.param_setting_tool.set_parameters(x)
self.run_2d_scan()
offset, is_present = self.find_offset_and_feature_presence()
if not is_present:
if not is_present and self.use_feature_tracking_subtask:
msg = "Feature is not present in the current image. Running feature tracking sub-task."
logger.info(msg)
self.record_system_message(msg)
offset = self.run_feature_tracking_subtask()
if np.any(np.isnan(offset)):
raise RuntimeError("Offset is NaN. Please set offset manually.")
self.record_system_message(f"Applying offset {offset}.")
self.apply_offset_to_kwargs_buffers(offset)
fwhm = self.run_line_scan()
if np.isnan(fwhm):
Expand Down
8 changes: 6 additions & 2 deletions src/eaa/tool/imaging/aps_mic/acquisition.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ def __init__(
show_colorbar_in_image: bool = False,
require_approval: bool = False,
line_scan_return_gaussian_fit: bool = False,
scan_samy: bool = False,
*args, **kwargs
):
"""Image acquisition tool with Bluesky.
Expand Down Expand Up @@ -86,7 +87,9 @@ def __init__(
line_scan_return_gaussian_fit: bool, optional
If True, the function returns a stringified JSON object containing the image path
and the Gaussian fit FWHM.

scan_samy: bool, optional
If True, the line_scan is generated by moving sample-y motor

Raises
------
ImportError
Expand All @@ -106,6 +109,7 @@ def __init__(
self.plot_image_in_log_scale = plot_image_in_log_scale
self.show_colorbar_in_image = show_colorbar_in_image
self.line_scan_return_gaussian_fit = line_scan_return_gaussian_fit
self.scan_samy = scan_samy
super().__init__(*args, require_approval=require_approval, **kwargs)


Expand Down Expand Up @@ -294,7 +298,7 @@ def acquire_line_scan(

img_path, [_, _, _, fwhm] = save_xrf_line_scan(
mda_file_path, png_output_dir, roi_num=self.xrf_roi_num,
return_line_array=True
return_line_array=True, scan_samy=self.scan_samy,
)
wait_for_file(img_path, duration=5)

Expand Down
1 change: 1 addition & 0 deletions src/eaa/tool/imaging/aps_mic/param_tuning.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ def set_parameters(
self.RE(self.bps.mv(self.zp_z_motor, parameters[0]))
msg = f"Moved Zone Plate z position to position: {parameters[0]}"
logger.info(msg)
self.update_parameter_history(parameters)
return msg
else:
raise ValueError("parameter_ranges is not set")
1 change: 1 addition & 0 deletions src/eaa/tool/imaging/aps_mic/scan_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def __init__(self, require_approval: bool = False, *args, **kwargs):
self.acquire_image_tool.scan2d_plan = fly2d_scanrecord
self.acquire_image_tool.scan1d_plan = step1d_scanrecord
self.acquire_image_tool.samy_motor = oregistry["samy"]
self.acquire_image_tool.scan_samy = True

self.param_tuning_tool = BlueskySetParameters()
self.param_tuning_tool.RE = RE
Expand Down
11 changes: 7 additions & 4 deletions src/eaa/tool/imaging/aps_mic/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ def process_xrfdata(
return None


def plot_xrf_line_scan(x, y, val_gauss, fwhm, scan_name, roi_num):
def plot_xrf_line_scan(x, y, val_gauss, fwhm, scan_name, roi_num, scan_samy=False):
"""
Plot the XRF line scan data.
"""
Expand All @@ -165,7 +165,7 @@ def plot_xrf_line_scan(x, y, val_gauss, fwhm, scan_name, roi_num):
)

ax.legend()
ax.set_xlabel("X-axis Position")
ax.set_xlabel("X-axis Position" if not scan_samy else "Y-axis Position")
ax.set_ylabel("Intensity")
ax.set_title(f"{scan_name}-{roi_num}")
ax.grid(True)
Expand All @@ -178,7 +178,8 @@ def save_xrf_line_scan(
output_dir: str,
roi_num: int,
y_threshold: float = 0.0,
return_line_array: bool = False
return_line_array: bool = False,
scan_samy: bool = False,
) -> str | None:

"""
Expand All @@ -196,6 +197,8 @@ def save_xrf_line_scan(
The threshold for the Gaussian fit.
return_line_array : bool
If True, the line array will be returned.
scan_samy : bool
If True, line profile is generated using sample-y motor, scanning sample-y

Returns
-------
Expand Down Expand Up @@ -233,7 +236,7 @@ def save_xrf_line_scan(

# Plot the data and the fit
scan_name = os.path.basename(mda_path)
fig = plot_xrf_line_scan(x, y, val_gauss, fwhm, scan_name, roi_num)
fig = plot_xrf_line_scan(x, y, val_gauss, fwhm, scan_name, roi_num, scan_samy=scan_samy)
sc = scan_name.replace(".mda", "")
fname = f"{output_dir}/{sc}_ROI{roi_num}.png"
fig.savefig(fname)
Expand Down
49 changes: 29 additions & 20 deletions src/eaa/tool/imaging/registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
import logging
import json

import cv2
import numpy as np
import scipy.ndimage as ndi
from skimage import feature
from sciagent.tool.base import BaseTool, check, ToolReturnType, tool

from eaa.tool.imaging.acquisition import AcquireImage
Expand Down Expand Up @@ -261,7 +261,7 @@ def prepare_image_for_feature_matching(self, image: np.ndarray) -> np.ndarray:
image = (image - min_val) / (max_val - min_val)
else:
image = np.zeros_like(image, dtype=np.float32)
return (image * 255).astype(np.uint8)
return image

def adjust_points_for_origin(
self,
Expand All @@ -282,30 +282,39 @@ def feature_based_registration(
image_t: np.ndarray,
image_r: np.ndarray,
) -> np.ndarray:
image_t_uint8 = self.prepare_image_for_feature_matching(image_t)
image_r_uint8 = self.prepare_image_for_feature_matching(image_r)
sift = cv2.SIFT_create()
keypoints_t, descriptors_t = sift.detectAndCompute(image_t_uint8, None)
keypoints_r, descriptors_r = sift.detectAndCompute(image_r_uint8, None)
image_t_float = self.prepare_image_for_feature_matching(image_t)
image_r_float = self.prepare_image_for_feature_matching(image_r)
sift_t = feature.SIFT()
sift_r = feature.SIFT()
sift_t.detect_and_extract(image_t_float)
sift_r.detect_and_extract(image_r_float)

descriptors_t = sift_t.descriptors
descriptors_r = sift_r.descriptors
keypoints_t = sift_t.keypoints
keypoints_r = sift_r.keypoints

if descriptors_t is None or descriptors_r is None:
if (
descriptors_t is None
or descriptors_r is None
or descriptors_t.size == 0
or descriptors_r.size == 0
):
raise RuntimeError("SIFT feature detection failed to find descriptors.")

matcher = cv2.BFMatcher(cv2.NORM_L2)
raw_matches = matcher.knnMatch(descriptors_t, descriptors_r, k=2)
good_matches = []
for match_pair in raw_matches:
if len(match_pair) != 2:
continue
m, n = match_pair
if m.distance < 0.75 * n.distance:
good_matches.append(m)
matches = feature.match_descriptors(
descriptors_t,
descriptors_r,
metric="euclidean",
cross_check=True,
max_ratio=0.75,
)

if len(good_matches) < 3:
if matches.shape[0] < 3:
raise RuntimeError("Not enough SIFT matches to estimate translation.")

pts_t = np.array([keypoints_t[m.queryIdx].pt for m in good_matches])
pts_r = np.array([keypoints_r[m.trainIdx].pt for m in good_matches])
pts_t = keypoints_t[matches[:, 0]][:, ::-1]
pts_r = keypoints_r[matches[:, 1]][:, ::-1]
pts_t = self.adjust_points_for_origin(pts_t, image_t.shape)
pts_r = self.adjust_points_for_origin(pts_r, image_r.shape)
deltas = pts_r - pts_t
Expand Down
25 changes: 2 additions & 23 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.