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
71 lines
4.3 KiB
C++
71 lines
4.3 KiB
C++
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#pragma once
|
|
|
|
#include <cstdint>
|
|
#include <optional>
|
|
|
|
// Settings for AnalysisMode::Grid and nothing else. Per-method by the same rule as
|
|
// CalibrationSettings: AnalysisSettings carries what every method shares, each method carries its
|
|
// own knobs.
|
|
//
|
|
// This is the single home for these values - AnalyzeGridScan
|
|
// (image_analysis/grid_scan_analysis/AnalyzeGridScan.h) takes this object rather than loose
|
|
// parameters, so a knob has one default and one place where it is documented.
|
|
//
|
|
// The beam size is deliberately NOT here: it is measured (from the file, or --beam-size), not
|
|
// configured, and rugnux reports where it came from beside the crystals it sized.
|
|
class GridScanAnalysisSettings {
|
|
// A cell counts as protein above this. The per-image protein score saturates, so this only has
|
|
// to separate "something diffracted here" from "nothing did".
|
|
float protein_score_threshold = 0.5f;
|
|
// A patch is GROWN out to this score once it has started, so a crystal is not broken in two by a
|
|
// single cell that fell just under the seed threshold - a real split in the corpus had its
|
|
// bridging cell at 0.498, two thousandths under it, which is a threshold artefact and not a gap
|
|
// in a crystal. Growth can never start on its own: a patch that reaches only this level and
|
|
// never the seed level is discarded, so lowering this cannot turn weak background into a crystal.
|
|
float grow_score_threshold = 0.35f;
|
|
// How many cells above the seed threshold make a shape rather than a coincidence. Two cells can
|
|
// be the two ends of a single hit lying on a cell boundary; three is the smallest patch that is
|
|
// not - unless the diffraction in it is decisive, below.
|
|
int64_t min_blob_cells = 3;
|
|
// ...unless one cell on its own is decisive. A crystal smaller than the grid step lights exactly
|
|
// one cell, and refusing it would lose precisely the samples a fine raster is run to find. The
|
|
// bar is the patch PEAK, not its mean: a two-cell patch with one strong cell and one marginal
|
|
// one is the case this exists for, and a mean averages that evidence away.
|
|
//
|
|
// 0.6 is measured, not chosen. Over 67 labelled rasters the peak-score populations do not
|
|
// overlap at all - no water raster reaches 0.15, no ice raster reaches 0.50, and the weakest
|
|
// confirmed-protein raster peaks at 0.67 - so 0.6 is the middle of the gap. Raising it to 0.9
|
|
// drops a real two-cell crystal peaking at 0.751 and costs a loop on this corpus.
|
|
float decisive_single_cell_score = 0.6f;
|
|
// Most crystals reported, best first. Unset means no cap, which is the default: a crystal that
|
|
// was found and then dropped is information the caller cannot get back. Set it where a loop full
|
|
// of shards would otherwise label dozens of blobs that nobody acts on.
|
|
std::optional<int64_t> max_crystals;
|
|
// Whether each raster cell is indexed as well as scored.
|
|
//
|
|
// On by default. It is affordable - a raster runs at up to 100 Hz, which the FFT indexer keeps up
|
|
// with - and it is additive: the blobs are still found on the protein score, so indexing changes
|
|
// nothing about which cells are called crystals and only adds what was found in them. The lattice
|
|
// count per cell is the cheapest multi-lattice or cracked-crystal signal there is, and on a
|
|
// fixed-target serial experiment with a known cell (-C, ffbidx) a raster that indexes is most of
|
|
// the measurement. Turn it off for a very large raster where the GPU is the constraint.
|
|
bool indexing = true;
|
|
public:
|
|
GridScanAnalysisSettings& ProteinScoreThreshold(float input);
|
|
GridScanAnalysisSettings& GrowScoreThreshold(float input);
|
|
GridScanAnalysisSettings& MinBlobCells(int64_t input);
|
|
GridScanAnalysisSettings& DecisiveSingleCellScore(float input);
|
|
GridScanAnalysisSettings& MaxCrystals(std::optional<int64_t> input);
|
|
GridScanAnalysisSettings& Indexing(bool input);
|
|
|
|
[[nodiscard]] float GetProteinScoreThreshold() const;
|
|
[[nodiscard]] float GetGrowScoreThreshold() const;
|
|
[[nodiscard]] int64_t GetMinBlobCells() const;
|
|
[[nodiscard]] float GetDecisiveSingleCellScore() const;
|
|
[[nodiscard]] std::optional<int64_t> GetMaxCrystals() const;
|
|
[[nodiscard]] bool IsIndexing() const;
|
|
};
|