AnalysisSettings had begun collecting per-method parameters - the calibrant was already in it, and the grid thresholds were about to be. That makes the structure every method reads grow whenever any one method gains a knob, and it puts a field in front of readers for whom it means nothing. So: AnalysisSettings keeps what all methods share, which for now is the mode, and each method gets a class of its own bound the same way. GridScanAnalysisSettings holds the protein-score threshold, the minimum cells per crystal, the decisive single-cell score, the maximum crystals reported and the indexing switch. CalibrationSettings holds the calibrant and the ring source. Both sit on DiffractionExperiment outside the per-run dataset member, both have an Import/Get pair, and both have their own endpoint - /config/grid_scan_analysis and /config/calibration - which is how every other settings group in this API is already reached. Grid indexing is no longer fixed in the stages table. It was turned off there on cost grounds, and that reasoning does not hold: a raster runs at up to 100 Hz, which the FFT indexer keeps up with, and a fixed-target serial experiment with a known cell wants ffbidx on every cell, where a raster that indexes is most of the measurement. So it is a setting, and DEFAULTS ON. It is additive rather than a change of answer - blobs are still found on the protein score, so indexing alters nothing about which cells are called crystals and only adds what was found in them, including the per-cell lattice count, which is the cheapest multi-lattice or cracked-crystal signal there is. That makes indexing the one stage a mode does not decide. AnalysisModeStages still carries a value for it, but only as the setting's default, and DiffractionExperiment::GetAnalysisStages - which is what every gate reads - substitutes the configured one. The table row is marked so nobody reads it as the mode's answer. The calibration knobs stay coupled to the mode but the rule now lives with them: CalibrationSettings::ApplyToAzimuthalIntegration moves azimuthal integration onto the CPU and supplies sectors where fewer than four were asked for, carrying the reason with it - the FPGA integration core holds 2048 bins in total, so 32 sectors leave 64 q bins, which cannot locate a ring. Stated there because it will otherwise read as an FPGA defect to be fixed back onto that path, and it is not one: the core is sized for a detector at full rate, and a calibration exposure is a few images at a few Hz. Both imports apply it, so the order the mode and the calibration settings are set in does not matter. CalibrationMethod moves from image_analysis/geom_refinement/PowderCalibration.h into common/CalibrationSettings.h, which that header now includes. One enum, so the setting and the code consuming it are not two vocabularies; every existing user sees it unchanged. The grid thresholds have one home and it is this class. The raster work owns AnalyzeGridScan's parameter surface and carries PROTEIN_SCORE_THRESHOLD_DEFAULT / MIN_BLOB_CELLS_DEFAULT beside that header today; the header here states the signature that replaces them, so the two do not become competing defaults. The beam size deliberately stays a separate argument to AnalyzeGridScan: it is measured, not configured. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N
80 lines
4.0 KiB
C++
80 lines
4.0 KiB
C++
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#pragma once
|
|
|
|
#include <optional>
|
|
#include <string>
|
|
#include <string_view>
|
|
|
|
// What analysis runs over the images - the one place the question is asked, shared by jfjoch_broker,
|
|
// rugnux and jfjoch_viewer. Before this, the answer was composed at each site out of a detector type,
|
|
// two independent "spot finding off" switches and a rotation flag, so what was configured and what
|
|
// actually ran were different things.
|
|
//
|
|
// There is deliberately no Auto value. GetIndexingAlgorithm() resolves Auto at read time against the
|
|
// GPU count and the unit cell, which is exactly why an indexing setting cannot be read back off the
|
|
// configuration; the mode getter is a plain accessor and stays one.
|
|
//
|
|
// MXStills is the default: None would silently switch analysis off on every deployment whose
|
|
// configuration predates this field.
|
|
//
|
|
// MXRotation is not offered by the REST API - jfjoch_broker has no rotation analysis path, so
|
|
// broker/jfjoch_api.yaml simply cannot express it and JFJochStateMachine refuses it if it arrives by
|
|
// any other route. Rotation data is processed offline with rugnux.
|
|
enum class AnalysisMode {
|
|
None, // images are received, written and streamed; nothing is analysed
|
|
MXRotation, // spot finding + rotation (two-pass) indexing + refinement + integration
|
|
MXStills, // spot finding + per-image indexing + refinement + integration
|
|
Azint, // azimuthal integration only
|
|
Grid, // grid scan: per-image scoring and crystal selection
|
|
PowderCalibration // detector geometry from a calibrant's powder rings
|
|
};
|
|
|
|
// Which stages of the pipeline a mode runs. This is the whole point of the mode: it does not label a
|
|
// run, it decides what happens in it, and every gate in the pipeline reads this rather than testing
|
|
// the mode itself. The table is in AnalysisSettings.cpp - modes are the rows, stages the columns.
|
|
struct AnalysisStages {
|
|
bool spot_finding;
|
|
// Fixed by the mode everywhere except Grid, where it is a setting
|
|
// (GridScanAnalysisSettings::indexing). AnalysisModeStages carries the grid DEFAULT;
|
|
// DiffractionExperiment::GetAnalysisStages is where the configured value takes over, and that is
|
|
// what every gate reads.
|
|
bool indexing;
|
|
// Bragg prediction and integration. It follows indexing in every engine - nothing is predicted
|
|
// without a lattice - so this never says yes where indexing says no.
|
|
bool bragg_integration;
|
|
bool scale_merge;
|
|
// Per-image protein / ice scoring: what a grid scan ranks its grid points on.
|
|
bool scoring;
|
|
bool azimuthal_integration;
|
|
};
|
|
|
|
AnalysisStages AnalysisModeStages(AnalysisMode mode);
|
|
|
|
// True for the two MX modes, i.e. the modes that index and integrate Bragg reflections.
|
|
bool AnalysisModeIsMX(AnalysisMode mode);
|
|
|
|
// The wire spelling of a mode: the same token in the CBOR stream, the HDF5 master and the rugnux
|
|
// --mode option, so a value can be carried between them without a translation table per hop. (The
|
|
// OpenAPI enum spells its values the way its neighbouring settings enums do, and OpenAPIConvert
|
|
// translates, as it does for every other enum in the API.)
|
|
std::string AnalysisModeName(AnalysisMode mode);
|
|
std::optional<AnalysisMode> AnalysisModeFromName(std::string_view name);
|
|
|
|
// What every analysis method shares, and nothing else. Each method's own knobs live in its own class
|
|
// beside this one - GridScanAnalysisSettings, CalibrationSettings - so that adding a parameter to one
|
|
// method cannot widen the structure every method reads.
|
|
//
|
|
// Persistent, not per-dataset: it sits on DiffractionExperiment outside the DatasetSettings member,
|
|
// which is the one thing a /start replaces wholesale. The per-method classes are bound the same way.
|
|
class AnalysisSettings {
|
|
AnalysisMode mode = AnalysisMode::MXStills;
|
|
public:
|
|
AnalysisSettings& Mode(AnalysisMode input);
|
|
|
|
[[nodiscard]] AnalysisMode GetMode() const;
|
|
|
|
[[nodiscard]] bool IsMX() const;
|
|
};
|