Files
leonarski_fandClaude Opus 5 5fe8a967cd grid scan: the five lanes become one, with a single home for the crystal type and its settings
Integration of the per-image detection scores, the analysis mode, the grid-scan
crystal search, its rugnux entry point and the viewer display.

GridScanCrystal/GridScanResult had two definitions - a placeholder in common/ and
the real one in image_analysis/ - which is a redefinition in any translation unit
reaching both, and tests/RasterReportTest.cpp reaches both. Unified into
common/GridScanResult.h, beside ScanResult where the data type belongs, leaving the
algorithm in image_analysis/. Same reason UnitCell lives in common while the
indexers do not.

GridScanAnalysisSettings is now the only home for the search parameters, replacing
the loose GridScanAnalysisParameters struct the raster lane carried while the class
did not yet exist. Three values changed with the move:

- decisive_single_cell_score 0.9 -> 0.6. 0.9 drops a real two-cell crystal peaking
  at 0.751 and costs a loop on the labelled corpus. 0.6 is the middle of a measured
  gap: over 67 rasters no water raster peaks above 0.15 and no ice raster above
  0.50, while the weakest confirmed-protein raster peaks at 0.67.
- max_crystals is std::optional, unset meaning no cap. 0 as a sentinel for
  "unlimited" reads as "find nothing", which is the opposite of what it did.
- grow_score_threshold was missing from the class entirely.

The viewer reads protein_score, ice_score and the crystal list from the reader
rather than a local stub, and asks the broker for ice_ring_ratio rather than the
retired ice_ring_score spelling.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N
2026-09-08 07:59:04 +02:00

43 lines
2.5 KiB
C++

// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#pragma once
#include <cmath>
#include <cstdint>
#include <vector>
// One crystal found in a grid scan. Positions are in the display grid of GridScanSettings -
// column 0 is the lowest x, row 0 the lowest y, whatever direction the stage actually moved in -
// so they match the per-image positions the scan writes with GetXContainer_m/GetYContainer_m.
//
// The shape is an ORIENTED extent, not a bounding box: a needle lying at 45 degrees has a bounding
// box of its own length in both directions and would read as a compact blob, which is the one case
// the axes exist to expose. major_um >= minor_um always, and angle_deg points along major_um.
struct GridScanCrystal {
float nx = 0, ny = 0; // centre, grid coords, fractional, 0-based
float x_um = 0, y_um = 0; // centre, signed offset from centre of cell (0,0), along grid axes
int64_t image_number = -1; // nearest COLLECTED image, for the DAQ to address
float major_um = 0, minor_um = 0; // extent along the crystal's own principal axes
// Angle of the major axis from the +x grid axis, counter-clockwise. This is an AXIS, so it lives
// in [0,180) and 179 is adjacent to 0 - anything averaging or comparing angles must know that.
// Arbitrary when major_um and minor_um are close; let that ratio say so rather than trusting it.
float angle_deg = 0;
float score = 0, ice_score = 0; // 0-1, the patch MEAN
float peak_score = 0; // 0-1, the patch peak - what a one-cell admission was decided on
float res_A = NAN; // robust best resolution in the blob, NaN if none was measured
int64_t n_images = 0; // grid points that fell in this crystal
};
// Sorted by score, strongest first, so crystals[0] is the one to collect. Today the list usually
// holds nothing or one entry; more than one is the point of the design, not an error.
//
// The extents are MEASURED - the beam is not removed. It is reported here so a consumer can do that
// itself, and reversibly: removing an anisotropic beam is a subtraction of covariance matrices
// followed by re-diagonalisation, NOT a per-axis quadrature removal, which is silently wrong for any
// crystal not aligned with the grid.
struct GridScanResult {
std::vector<GridScanCrystal> crystals;
float beam_size_x_um = 0, beam_size_y_um = 0;
};