raster: now do auto_centre_line_scan based off of target_y instead of just above and below beam cente, if target avaialble, now submit iamges to databse, reduced number of significant figures inr aster message to db, grid_to_image and iamge_to_grid fucntions

This commit is contained in:
2026-04-24 14:19:36 +02:00
parent d856b77b7d
commit 3b39bb2144
2 changed files with 244 additions and 15 deletions
+49
View File
@@ -88,3 +88,52 @@ class RasterPayloadModel(BaseModel):
beam_mark_pxl: tuple[float, float]
beam_size_mm: Annotated[Coordinate, AfterValidator(positive_coords)]
def grid_to_image_id(grid_x: int, grid_y: int, number_of_cols: int) -> int:
"""
Get the associated image id for a given grid position (grid_x, grid_y)
using serpentine row ordering.
grid_x: 1-based column index
grid_y: 1-based row index
number_of_cols: total number of columns in the grid
"""
if number_of_cols < 1:
raise ValueError("number_of_cols must be >= 1")
if grid_x < 1:
raise ValueError("grid_x must be >= 1")
if grid_y < 1:
raise ValueError("grid_y must be >= 1")
if grid_x > number_of_cols:
raise ValueError("grid_x cannot be greater than number_of_cols")
col = grid_x - 1
row = grid_y - 1
if row % 2 == 0: # left -> right
return row * number_of_cols + col
else: # right -> left
return row * number_of_cols + (number_of_cols - 1 - col)
def image_id_to_grid(image_id: int, number_of_cols: int) -> tuple[int, int]:
"""
Get the associated grid position (grid_x, grid_y) for a given image id
using serpentine row ordering.
"""
if number_of_cols < 1:
raise ValueError("number_of_cols must be >= 1")
if image_id < 0:
raise ValueError("image_id must be >= 0")
row = image_id // number_of_cols
pos_in_row = image_id % number_of_cols
if row % 2 == 0: # left -> right
col = pos_in_row
else: # right -> left
col = number_of_cols - 1 - pos_in_row
grid_x = col + 1
grid_y = row + 1
return grid_x, grid_y
+195 -15
View File
@@ -4,7 +4,7 @@ import time
import traceback
from datetime import datetime
from math import ceil
from math import ceil, floor
from pathlib import Path
from typing import List, Tuple, Optional, Callable
@@ -39,7 +39,7 @@ from aare.common.automation_models import (
StepStatus,
WorkflowStateKind,
)
from aare.common.raster_grid import RasterGridRequest, CompletedRasterGrid, CompletedRasterGridElem
from aare.common.raster_grid import RasterGridRequest, CompletedRasterGrid, CompletedRasterGridElem, grid_to_image_id
from aare.common.rotation_scan import RotationScanRequest, CompletedRotationScan
from aare.common.sample_geometry import SampleGeometryModel
from aare.daq.spreadsheetupdater import beamline
@@ -1090,26 +1090,33 @@ class AareDAQ:
self.__set_state(BeamlineStateEnum.DataCollection)
grid.n_x = 1
# TODO generate y scan rather than had code for 1x50
grid.n_y = 50
grid.file_prefix = f"{old_prefix}_{grid.omega_deg}deg"
grid.grid_size_mm = Coordinate(x=geom.beam_size_mm.x, y=geom.beam_size_mm.y * 0.25)
offset = Coordinate(x=-grid.grid_size_mm.x / 2.0, y=-(grid.n_y + 0.5) * grid.grid_size_mm.y / 2.0)
geom = self.sample_geometry
logger.debug(f"set offset y scan {offset}")
grid.smargon_top_left = geom.translate_smargon(offset)
grid.grid_size_mm = Coordinate(x=geom.beam_size_mm.x, y=geom.beam_size_mm.y * 0.25)
grid.smargon_top_left, grid.n_y = self._auto_center_line_scan_top_left(
omega_deg=grid.omega_deg,
file_prefix=grid.file_prefix,
grid_size_mm=grid.grid_size_mm,
default_n_y=50,
y_retarget_threshold_mm=max(
geom.beam_size_mm.y * 2.0,
grid.grid_size_mm.y * 4.0,
),
y_padding_fraction_each_side=0.10,
)
logger.info(
"Prepared second auto-center raster",
extra={
"file_prefix": grid.file_prefix,
"omega_deg": grid.omega_deg,
"offset_x_mm": offset.x,
"offset_y_mm": offset.y,
"offset_z_mm": offset.z,
"top_left_x_mm": getattr(grid.smargon_top_left.sh_mm, "x", None),
"top_left_y_mm": getattr(grid.smargon_top_left.sh_mm, "y", None),
"top_left_z_mm": getattr(grid.smargon_top_left.sh_mm, "z", None),
"n_x": grid.n_x,
"n_y": grid.n_y,
"grid_size_x_mm": getattr(grid.grid_size_mm, "x", None),
"grid_size_y_mm": getattr(grid.grid_size_mm, "y", None),
},
)
@@ -1241,6 +1248,30 @@ class AareDAQ:
centre_of_mass=None,
)
@staticmethod
def _grid_image_id_from_centre_offset(
*,
x_mm: float,
y_mm: float,
request: RasterGridRequest,
) -> int:
if request.grid_size_mm.x <= 0 or request.grid_size_mm.y <= 0:
raise ValueError("grid_size_mm must be positive")
if request.n_x < 1 or request.n_y < 1:
raise ValueError("Raster grid dimensions must be >= 1")
grid_x = floor(x_mm / request.grid_size_mm.x) + 1
grid_y = floor(y_mm / request.grid_size_mm.y) + 1
grid_x = min(max(1, grid_x), request.n_x)
grid_y = min(max(1, grid_y), request.n_y)
return grid_to_image_id(
grid_x=grid_x,
grid_y=grid_y,
number_of_cols=request.n_x,
)
def __raster(self, request: RasterGridRequest, wait_for_screenshot: float | None = 0.2) -> CompletedRasterGridElem:
smargon_top_left =request.smargon_top_left
self.__devs.set_smargon_pos(
@@ -1273,7 +1304,7 @@ class AareDAQ:
try:
if self.sample is not None and self.sample.db_id is not None:
self.__aare.send_sample_event(self.sample, SampleEventType.RASTERING,
comment=f"Raster at {request.omega_deg} deg")
comment=f"Raster at {request.omega_deg:.1f} deg")
self.__aare.create_gridscan_run(self.sample, request, status)
if not self.__cfg.simulated_detector:
@@ -1291,7 +1322,6 @@ class AareDAQ:
time_sec=request.exp_time_s,
run_async=True,
)
self.__devs.aerotech.wait_till_done(timeout=int(round(total_time * 2, 0)))
# go back to aerotech x,y,z home not U home (0 degrees).
if isinstance(self.__cfg.abr_meas_pos, Coordinate):
@@ -1352,8 +1382,10 @@ class AareDAQ:
file_prefix=request.file_prefix,
image_count=request.n_x * request.n_y,
)
com = None
else:
scan_result = self.__jfjoch.wait_till_done(60)
com = None
if scan_result is None:
logger.error(
"JFJoch returned no ScanResult for raster",
@@ -1374,7 +1406,7 @@ class AareDAQ:
self.__set_state(BeamlineStateEnum.XtalSnapshot)
if wait_for_screenshot and wait_for_screenshot > 0:
time.sleep(wait_for_screenshot)
self.save_screenshot_db(sample_id, f"{sample_id}_post_raster_{request.omega_deg}deg")
self.save_screenshot_db(sample_id, f"{sample_id}_post_raster_{int(request.omega_deg)}deg")
self.__aare.ingest_gridscan(
sample=self.sample,
raster_result=scan_result,
@@ -1383,10 +1415,22 @@ class AareDAQ:
com=None,
beam_mark_pxl=self.__cfg.get_beam_mark(self.zoom),
)
if com is not None and com.max_image is not None:
diffraction_image_filename = f"{self.sample.db_id}_best_diffraction_from_raster_image_{com.max_image}"
diffraction_image_id = com.max_image
else:
diffraction_image_filename = f"{self.sample.db_id}_diffraction_image_near_grid_scan"
diffraction_image_id = self._grid_image_id_from_centre_offset(
x_mm=x,
y_mm=y,
request=request,
)
diffraction_image = self.__jfjoch.get_diffraction_image(diffraction_image_id)
self.__aare.upload_jpg(self.sample.db_id, diffraction_image_filename, diffraction_image)
self.__aare.send_sample_event(
self.sample,
event_type=SampleEventType.RASTERED,
comment=f"Raster completed at {request.omega_deg} deg"
comment=f"Raster completed at {request.omega_deg:.1f} deg"
)
logger.info(
@@ -2048,6 +2092,142 @@ class AareDAQ:
)
return calculated_target
def _auto_center_line_scan_top_left(
self,
*,
omega_deg: float,
file_prefix: str | None,
grid_size_mm: Coordinate,
default_n_y: int = 50,
y_retarget_threshold_mm: float | None = None,
y_padding_fraction_each_side: float = 0.10,
) -> tuple[SmargonCoordinate, int]:
geom = self.sample_geometry
beam_x_pxl = geom.beam_location_pxl.x
beam_y_pxl = geom.beam_location_pxl.y
line_scan_centre = geom.picture_to_smargon(
Coordinate(x=beam_x_pxl, y=beam_y_pxl)
)
n_y = default_n_y
prediction_result: MLBoxPredictionResult = self.__mlbox.predict(
preferred_class=(3, 0),
return_image=False,
return_bundle_meta=True,
)
self._log_ml_bundle_meta(
f"auto_center_line_scan_{omega_deg:.2f}deg",
target_point=prediction_result.target_point,
focus=prediction_result.focus,
)
target_point = prediction_result.target_point
prediction_box = prediction_result.box
if target_point is not None:
target_y_pxl = target_point[1]
y_delta_mm = abs(target_y_pxl - beam_y_pxl) * geom.pixel_in_mm
if y_retarget_threshold_mm is None:
y_retarget_threshold_mm = geom.beam_size_mm.y * 2.0
if y_delta_mm > y_retarget_threshold_mm:
line_scan_centre = geom.picture_to_smargon(
Coordinate(x=beam_x_pxl, y=target_y_pxl)
)
logger.info(
"Using ML target y for second auto-center raster",
extra={
"file_prefix": file_prefix,
"omega_deg": omega_deg,
"beam_y_pxl": beam_y_pxl,
"target_y_pxl": target_y_pxl,
"y_delta_mm": y_delta_mm,
"threshold_mm": y_retarget_threshold_mm,
"target_sh_x_mm": line_scan_centre.x,
"target_sh_y_mm": line_scan_centre.y,
"target_sh_z_mm": line_scan_centre.z,
},
)
else:
logger.info(
"Keeping beam-centred y line scan because ML target y shift is small",
extra={
"file_prefix": file_prefix,
"omega_deg": omega_deg,
"beam_y_pxl": beam_y_pxl,
"target_y_pxl": target_y_pxl,
"y_delta_mm": y_delta_mm,
"threshold_mm": y_retarget_threshold_mm,
},
)
else:
logger.info(
"No ML target point for second auto-center raster; using beam-centred line scan",
extra={
"file_prefix": file_prefix,
"omega_deg": omega_deg,
"beam_x_pxl": beam_x_pxl,
"beam_y_pxl": beam_y_pxl,
},
)
if prediction_box is not None and prediction_box.box is not None and grid_size_mm.y > 0:
box_height_pxl = abs(prediction_box.box.bottom_y - prediction_box.box.top_y)
padded_height_mm = box_height_pxl * geom.pixel_in_mm * (1.0 + 2.0 * y_padding_fraction_each_side)
n_y = max(1, int(ceil(padded_height_mm / grid_size_mm.y)))
logger.info(
"Computed second auto-center raster y size from ML box height",
extra={
"file_prefix": file_prefix,
"omega_deg": omega_deg,
"box_height_pxl": box_height_pxl,
"pixel_in_mm": geom.pixel_in_mm,
"grid_size_y_mm": grid_size_mm.y,
"padding_fraction_each_side": y_padding_fraction_each_side,
"padded_height_mm": padded_height_mm,
"computed_n_y": n_y,
},
)
else:
logger.info(
"Using default y size for second auto-center raster",
extra={
"file_prefix": file_prefix,
"omega_deg": omega_deg,
"default_n_y": default_n_y,
"has_prediction_box": prediction_box is not None and prediction_box.box is not None,
},
)
offset = Coordinate(
x=-grid_size_mm.x / 2.0,
y=-(n_y - 1) * grid_size_mm.y / 2.0,
)
top_left = SmargonCoordinate(
sh_mm=line_scan_centre + geom.smargon_nudge(offset),
phi_deg=geom.smargon.phi_deg,
chi_deg=geom.smargon.chi_deg,
)
logger.info(
"Prepared second auto-center raster top-left from helper",
extra={
"file_prefix": file_prefix,
"omega_deg": omega_deg,
"n_y": n_y,
"offset_x_mm": offset.x,
"offset_y_mm": offset.y,
"offset_z_mm": offset.z,
"top_left_x_mm": getattr(top_left.sh_mm, "x", None),
"top_left_y_mm": getattr(top_left.sh_mm, "y", None),
"top_left_z_mm": getattr(top_left.sh_mm, "z", None),
},
)
return top_left, n_y
def __loop_center_sequence(
self,
sample_id: Optional[int] = None,