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
32 lines
1.8 KiB
C
32 lines
1.8 KiB
C
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#pragma once
|
|
|
|
#include "../../common/GridScanSettings.h"
|
|
#include "../../common/ScanResult.h"
|
|
#include "../../common/GridScanResult.h"
|
|
#include "../../common/GridScanAnalysisSettings.h"
|
|
|
|
// Finds the crystals in a completed grid scan: the per-image protein score is scattered back onto
|
|
// the raster, the map is thresholded and labelled, and each blob is reported as one crystal.
|
|
// Pure - no I/O, no state, no detector.
|
|
//
|
|
// The sizes reported are MEASURED, and the beam is still in them: what the raster sees is the
|
|
// crystal convolved with the beam, and nothing here takes the beam back out. That is deliberate -
|
|
// the beam size is in the file as incident_beam_size, so a downstream consumer can do the
|
|
// deconvolution itself, reproducibly and reversibly, and is not stuck with ours.
|
|
//
|
|
// If it does: removing an anisotropic beam is a subtraction of the two 2x2 covariance matrices
|
|
// followed by re-diagonalising the difference, NOT a per-axis quadrature removal of beam_x from
|
|
// major_um and beam_y from minor_um. Per-axis is silently wrong the moment the crystal is not
|
|
// aligned with the grid axes - a needle at 45 deg has both beam widths mixed into both of its own
|
|
// axes - and a needle at an arbitrary angle is exactly the case this whole design exists for.
|
|
// beam_size_x_um/beam_size_y_um are only copied into the result, so it says what the sizes contain.
|
|
|
|
GridScanResult AnalyzeGridScan(const ScanResult &scan,
|
|
const GridScanSettings &grid,
|
|
float beam_size_x_um,
|
|
float beam_size_y_um,
|
|
const GridScanAnalysisSettings &settings = {});
|