diff --git a/src/aarecommon/math/find_xtal.py b/src/aarecommon/math/find_xtal.py index 18211e3..f26f2a6 100644 --- a/src/aarecommon/math/find_xtal.py +++ b/src/aarecommon/math/find_xtal.py @@ -318,29 +318,38 @@ def raster_centre_of_mass(result_array, images) -> CenterOfMassModel | None: return _to_com_model(com, images, "Center of mass") -def raster_highest_score(images, min_low_res_spots: float = 10.0) -> CenterOfMassModel | None: +def center_of_grid(n_x: int, n_y: int) -> CenterOfMassModel: + # get_com_mm maps index i -> (i+0.5)*step, so index (N-1)/2 is the true + # geometric centre of the grid for both odd and even N (and N==1). + y_pos = (n_y - 1) / 2.0 + x_pos = (n_x - 1) / 2.0 + return CenterOfMassModel(n_x=x_pos, n_y=y_pos, max_image=int(y_pos * n_x + x_pos)) + + +def raster_highest_score(images, min_low_res_spots: float = 10.0) -> CenterOfMassModel: """Target the grid cell with the highest crystal score. If the whole grid is too weak to hold a crystal — max spots_low_res below min_low_res_spots — target the geometric centre of the grid instead of collecting at a noisy cell, so a 'nothing here' result is centred and deliberate rather than random noise. - """ + score_array = compute_crystal_score_array(images) max_low_res = max((getattr(img, "spots_low_res", 0) or 0 for img in images), default=0) if max_low_res < min_low_res_spots: - n_nx, n_ny = score_array.shape logger.info( - f"No crystal in loop (max spots_low_res={max_low_res} < {min_low_res_spots}); " - f"targeting grid centre of {n_nx}x{n_ny} grid" + f"No crystal in loop (max spots_low_res={max_low_res} < {min_low_res_spots}); targeting grid centre" ) - # get_com_mm maps index i -> (i+0.5)*step, so index (N-1)/2 is the true - # geometric centre of the grid for both odd and even N (and N==1). - y_pos = (n_ny - 1) / 2.0 - x_pos = (n_nx - 1) / 2.0 - return CenterOfMassModel(n_x=x_pos, n_y=y_pos, max_image=int(y_pos * n_nx + x_pos)) - return _to_com_model(_max_cell(score_array), images, "Highest score") + return center_of_grid(*score_array.shape) + com = _to_com_model(_max_cell(score_array), images, "Highest score") + if com is None: + logger.error( + "Error in computing centre of mass for gridscan result, images contained invalid data. Returning center image of grid." + ) + return center_of_grid(*score_array.shape) + else: + return com def has_sufficient_low_res_spots(result_array: np.ndarray, min_spots_low_res: float) -> bool: diff --git a/src/aarecommon/math/jfjoch_gridscan_union.py b/src/aarecommon/math/jfjoch_gridscan_union.py index 4ea2b6b..55efcb4 100644 --- a/src/aarecommon/math/jfjoch_gridscan_union.py +++ b/src/aarecommon/math/jfjoch_gridscan_union.py @@ -51,24 +51,13 @@ from pydantic import BaseModel, Field # The structures this exchanges with AareDAQ and Jungfraujoch. Verified against # jfjoch-client 1.0.0-rc.165. The fallbacks let the module import and its self-test # run outside AareDAQ; inside it the real classes are always the ones used. -try: - from jfjoch_client.models.grid_scan import GridScan - from jfjoch_client.models.scan_result import ScanResult - from jfjoch_client.models.scan_result_images_inner import ScanResultImagesInner - from jfjoch_client.models.unit_cell import UnitCell -except ImportError: # pragma: no cover - GridScan = ScanResult = ScanResultImagesInner = UnitCell = Any +from jfjoch_client.models.grid_scan import GridScan +from jfjoch_client.models.scan_result import ScanResult +from jfjoch_client.models.scan_result_images_inner import ScanResultImagesInner +from jfjoch_client.models.unit_cell import UnitCell -try: - from aarecommon.math.coordinate import Coordinate - from aarecommon.math.sample_geometry import SampleGeometryModel -except ImportError: # pragma: no cover - SampleGeometryModel = Any - - class Coordinate(BaseModel): # same shape as aarecommon's - x: float = 0.0 - y: float = 0.0 - z: float = 0.0 +from aarecommon.math.coordinate import Coordinate +from aarecommon.math.sample_geometry import SampleGeometryModel __all__ = ["Centre", "Counts", "GridScanResult", "Size", "Thresholds", "analyse"]