fix: drop spots_indexed from no-crystal guard
CI / lint (push) Successful in 17s
CI / lint (pull_request) Successful in 16s
CI / test (3.11) (push) Successful in 23s
CI / test (3.13) (push) Successful in 23s
CI / test (3.12) (pull_request) Successful in 22s
CI / test (3.11) (pull_request) Successful in 24s
CI / test (3.12) (push) Successful in 29s
CI / test (3.13) (pull_request) Successful in 24s

Indexing was removed from the crystal score (w_indexed=0.00), so it must
not act as a hard veto either: a crystal that diffracts but fails to
index should still be targeted, not routed to grid centre. Guard now
checks max spots_low_res only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-28 13:59:38 +02:00
co-authored by Claude Fable 5
parent 0dcfe15b7c
commit 2969af8764
+16 -13
View File
@@ -323,24 +323,24 @@ 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, min_spots_indexed: int = 1
) -> CenterOfMassModel | None:
def raster_highest_score(images, min_low_res_spots: float = 10.0) -> CenterOfMassModel | None:
"""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 OR max spots_indexed below min_spots_indexed — 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.
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.
spots_indexed is deliberately not part of the guard: indexing was dropped
from the crystal score (w_indexed=0.00), so a crystal that diffracts but
fails to index must still be targeted, not routed to centre.
"""
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)
max_indexed = max((getattr(img, "spots_indexed", 0) or 0 for img in images), default=0)
if max_low_res < min_low_res_spots or max_indexed < min_spots_indexed:
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"or max spots_indexed={max_indexed} < {min_spots_indexed}); "
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"
)
# get_com_mm maps index i -> (i+0.5)*step, so index (N-1)/2 is the true
@@ -689,9 +689,12 @@ if __name__ == "__main__":
com = raster_highest_score(noise)
assert (com.n_x, com.n_y) == (1.0, 1.0), com
# Strong low-res but nothing indexed anywhere -> OR rule routes to centre.
no_index = [_cell(x, y, 40, 0) for x in range(3) for y in range(3)]
# Strong low-res but nothing indexed anywhere -> still targets the best
# cell: indexing is dropped from the score (w_indexed=0.00) and must not
# veto a diffracting crystal.
no_index = [_cell(x, y, 12, 0) for x in range(3) for y in range(3)]
no_index[6] = _cell(2, 0, 40, 0)
com = raster_highest_score(no_index)
assert (com.n_x, com.n_y) == (1.0, 1.0), com
assert (round(com.n_x), round(com.n_y)) == (2, 0), com
print("find_xtal self-check passed")