Build Packages / Create release (push) Successful in 16s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 8m27s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 9m15s
Build Packages / build:viewer-tgz:cpu (push) Successful in 10m11s
Build Packages / build:viewer-tgz:cuda (push) Successful in 12m6s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 15m44s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 16m1s
Build Packages / build:windows:nocuda (push) Successful in 17m29s
Build Packages / build:windows:cuda (push) Successful in 19m58s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 24m7s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 19m8s
Build Packages / build:rugnux:windows (push) Successful in 10m58s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 20m46s
Build Packages / Generate python client (push) Successful in 53s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 20m13s
Build Packages / Build documentation (push) Successful in 1m36s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 19m57s
Build Packages / build:rpm (rocky8) (push) Successful in 18m7s
Build Packages / build:rpm (rocky9) (push) Successful in 18m54s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 19m32s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 17m30s
Build Packages / Unit tests (push) Successful in 1h39m2s
* Fixed `jfjoch_broker` cancelling every data collection with a CUDA "out of memory" error after long operation: GPU memory no longer leaks with each collection. * Rugnux scales a rotation sweep until the per-frame scales settle instead of for a fixed three rounds, and says so when they did not - merged intensities, and the space group, resolution cut and frame rejection read off them, change accordingly; `--scaling-iterations` is now the cap on that loop (default 100). * Rugnux places every frame of a marCCD, SMV or miniCBF series at the spindle angle its own header states, so a series with missing frames, or with angles written modulo 360, is no longer read at the wrong geometry or refused. * Every rotation run writes two diagnostic files beside its reflections: `<prefix>_detector.jpg`, the detector projection with the pixel mask and the detected beam-stop shadow drawn on it, and `<prefix>_plot.txt`, one row per image. Reviewed-on: #82 Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
64 lines
4.3 KiB
C++
64 lines
4.3 KiB
C++
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#pragma once
|
|
|
|
#include "../../common/CrystalLattice.h"
|
|
#include "../../common/DiffractionExperiment.h"
|
|
#include "../../common/JFJochMessages.h"
|
|
|
|
// Minimum fraction of a frame's in-resolution spots that must lie on a candidate lattice for the
|
|
// frame to be that crystal's. See the frame gate in AnalyzeIndexing, which is where it is applied.
|
|
constexpr float LATTICE_MIN_INDEXED_FRACTION = 0.20f;
|
|
|
|
// Tally one image's spots by their rank in its intensity-ordered spot list: how many images had a spot
|
|
// at that rank at all (`counted`) and on how many of them it lay on the lattice (`indexed`). Ice spots
|
|
// are skipped, as they are in the frame gate. Both are added to, and their length bounds the ranks
|
|
// considered. Counts rather than weights so that the tally is exact whatever order the images are
|
|
// summed in, which is what makes the budget below independent of the thread schedule.
|
|
void AddSpotBudgetEvidence(const std::vector<SpotToSave> &spots, bool index_ice_rings,
|
|
std::vector<int64_t> &indexed, std::vector<int64_t> &counted);
|
|
|
|
// How far the fall from the peak must exceed the counting noise of the spots for the peak to be one.
|
|
//
|
|
// Under the null - the spots lie on the lattice at the same rate at every depth - the running sum
|
|
// below is a driftless random walk in the counted spots: each is worth 1 - g with probability g and
|
|
// -g otherwise, so its step has mean zero and variance g(1-g). The MAXIMUM of such a walk is positive
|
|
// whatever the data, so an argmax taken on its own cuts every dataset, including one with nothing to
|
|
// cut. What is acted on is the FALL from the peak to the end of the list, which is the maximum of the
|
|
// same walk read backwards from the end, and the reflection principle gives that maximum's null law
|
|
// exactly: P(fall > z sqrt(g(1-g)T)) = 2(1 - Phi(z)) over T counted spots in all. The search over the
|
|
// ranks is therefore already paid for and no further multiple-comparison correction is due. z is set
|
|
// for one false cut in a thousand measurements, which over a corpus the size of a rotation test set
|
|
// (tens of crystals, a measurement per pass) expects none at all.
|
|
constexpr float SPOT_BUDGET_SIGNIFICANCE_Z = 3.29f; // 2(1 - Phi(z)) = 0.001
|
|
|
|
// The spot budget those tallies support: the rank at which indexed - LATTICE_MIN_INDEXED_FRACTION *
|
|
// counted, summed over the ranks down to it, peaks. Each spot that lies on the lattice is worth
|
|
// 1 - LATTICE_MIN_INDEXED_FRACTION and each one that does not costs LATTICE_MIN_INDEXED_FRACTION - the
|
|
// same weighing the frame gate applies to a spot list as a whole - so the sum rises exactly while the
|
|
// spots at that depth are on the lattice more often than the gate's floor. Deeper than the peak they
|
|
// are not: they are no longer this crystal's reflections, and they can only push a frame towards
|
|
// rejection while adding nothing the lattice recognises.
|
|
//
|
|
// Zero - keep the whole list - when the fall from that peak to the end of the list is no larger than
|
|
// the counting noise above, which is the case whenever the spots go on lying on the lattice at the
|
|
// same rate all the way down, and the case a bare argmax gets wrong.
|
|
int64_t SpotBudgetFromEvidence(const std::vector<int64_t> &indexed, const std::vector<int64_t> &counted);
|
|
|
|
// integrate_every_frame: on rotation, integrate the frame from the sweep's lattice even where fewer than
|
|
// LATTICE_MIN_INDEXED_FRACTION of its spots lie on it - for a sweep whose lattice was accepted on the pooled
|
|
// spots although its frames, one at a time, cannot clear that floor (see Rugnux).
|
|
// spots_on_lattice, when given, receives how many of this frame's non-ice spots lie on latt - the
|
|
// numerator of the frame gate below, reported whatever the gate then decides. The rotation first
|
|
// pass's acceptance test pools that count over its validation frames, so it needs it from the sparse
|
|
// frames the gate refuses as much as from the ones it accepts.
|
|
bool AnalyzeIndexing(DataMessage &message,
|
|
const DiffractionExperiment &experiment,
|
|
const CrystalLattice &latt,
|
|
const std::vector<CrystalLattice> &extra_lattices = {},
|
|
int64_t *spots_on_lattice = nullptr,
|
|
bool integrate_every_frame = false);
|
|
|
|
|